Просмотр исходного кода

fix(vendor): 修复服务商申请闭环流程

后端:
- AdminVendorController: 兼容旧前端 action 字段,修复拒绝操作永远失败
- VendorService: pending 状态允许重新提交(之前被永久阻止)

管理端 (VendorAudit.vue):
- handleReview: 改用 vendorStatus 字段传参(之前误传 action)
- confirmReject: 补传 rejectReason(之前漏传)

小程序:
- service-role-apply.vue: 提交后展示审核状态(审核中/已通过/已驳回)
  - 驳回原因红色高亮,支持一键重新申请
  - 通过自动跳转服务商中心
  - UI 美化:渐变背景、状态卡片、角色卡片选中态
- vendor/center.vue: 新增待审核 banner、团队管理入口、类型徽章
  - UI 美化:渐变背景、菜单图标布局
Sisyphus 5 дней назад
Родитель
Сommit
8f15b475bb

+ 7 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminVendorController.java

@@ -65,6 +65,13 @@ public class AdminVendorController {
         String vendorStatus = (String) params.get("vendorStatus");
         String rejectReason = (String) params.getOrDefault("rejectReason", "");
 
+        // 兼容旧前端(VendorAudit)传 action='approve'/'reject' 的调用方式
+        if (vendorStatus == null) {
+            String action = (String) params.get("action");
+            if ("approve".equals(action)) vendorStatus = "approved";
+            else if ("reject".equals(action)) vendorStatus = "rejected";
+        }
+
         if (userId == null) {
             return Result.error("userId不能为空");
         }

+ 3 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/VendorService.java

@@ -21,8 +21,9 @@ public class VendorService {
         if (user == null) {
             return Result.error("用户不存在");
         }
-        if (user.getVendorStatus() != null && !"rejected".equals(user.getVendorStatus())) {
-            return Result.error("已有入驻申请,请勿重复提交");
+        // 已审核通过不允许重复提交;pending 状态下允许重新提交(覆盖旧申请)
+        if ("approved".equals(user.getVendorStatus())) {
+            return Result.error("您已是服务商,无需重复申请");
         }
         // 验证服务商类型
         if (vendorType != null) {

+ 333 - 108
cfc-frontend/pages/profile/service-role-apply.vue

@@ -1,66 +1,102 @@
 <template>
   <view class="page">
-    <view class="section">
-      <view class="section-title">选择申请角色 <text class="required">*</text></view>
-      <view class="role-grid">
-        <view
-          v-for="role in roleList"
-          :key="role.key"
-          class="role-card"
-          :class="{ selected: selectedRoles.indexOf(role.key) > -1 }"
-          @click="toggleRole(role.key)"
-        >
-          <text class="role-icon">{{ role.icon }}</text>
-          <text class="role-name">{{ role.name }}</text>
-          <text v-if="selectedRoles.indexOf(role.key) > -1" class="check-mark">✓</text>
-        </view>
+    <!-- 审核状态展示 -->
+    <view v-if="showStatusView" class="status-view">
+      <view class="status-card" :class="'status-' + applyStatus">
+        <view class="status-icon">{{ statusIcon }}</view>
+        <text class="status-title">{{ statusTitle }}</text>
+        <text class="status-desc">{{ statusDesc }}</text>
+      </view>
+      <view v-if="applyStatus === 'pending'" class="status-tip">
+        <text>预计 1-3 个工作日内完成审核,请耐心等待</text>
+      </view>
+      <view v-if="applyStatus === 'rejected'" class="reject-info">
+        <text class="reject-label">拒绝原因</text>
+        <text class="reject-reason">{{ applyRecord && applyRecord.rejectReason }}</text>
+      </view>
+      <view class="action-buttons">
+        <button v-if="applyStatus === 'rejected'" class="btn-reapply" @click="handleReapply">
+          重新申请
+        </button>
+        <button v-if="applyStatus === 'approved'" class="btn-center" @click="goToCenter">
+          进入服务商中心
+        </button>
+        <button class="btn-back" @click="uni.navigateBack">返回首页</button>
       </view>
     </view>
 
-    <view class="section">
-      <view class="section-title">身份证信息</view>
-      <input class="input" v-model="form.idCard" placeholder="请输入身份证号码" maxlength="18" />
-      <view class="upload-row">
-        <view class="upload-item" @click="chooseIdCardImage('front')">
-          <image v-if="form.idCardFront" :src="form.idCardFront" mode="aspectFill" class="upload-preview" />
-          <view v-else class="upload-placeholder">
-            <text class="upload-icon">+</text>
-            <text class="upload-label">身份证正面</text>
+    <!-- 申请表单 -->
+    <view v-else class="form-wrap">
+      <view class="header">
+        <text class="title">服务角色申请</text>
+        <text class="subtitle">选择申请角色,提交资质信息</text>
+      </view>
+
+      <view class="section">
+        <view class="section-title">选择申请角色 <text class="required">*</text></view>
+        <view class="role-grid">
+          <view
+            v-for="role in roleList"
+            :key="role.key"
+            class="role-card"
+            :class="{ selected: selectedRoles.indexOf(role.key) > -1 }"
+            @click="toggleRole(role.key)"
+          >
+            <text class="role-icon">{{ role.icon }}</text>
+            <text class="role-name">{{ role.name }}</text>
+            <view v-if="selectedRoles.indexOf(role.key) > -1" class="check-mark">✓</view>
           </view>
         </view>
-        <view class="upload-item" @click="chooseIdCardImage('back')">
-          <image v-if="form.idCardBack" :src="form.idCardBack" mode="aspectFill" class="upload-preview" />
-          <view v-else class="upload-placeholder">
-            <text class="upload-icon">+</text>
-            <text class="upload-label">身份证反面</text>
+      </view>
+
+      <view class="section">
+        <view class="section-title">身份证信息</view>
+        <input class="input" v-model="form.idCard" placeholder="请输入身份证号码" maxlength="18" />
+        <view class="upload-row">
+          <view class="upload-item" @click="chooseIdCardImage('front')">
+            <image v-if="form.idCardFront" :src="form.idCardFront" mode="aspectFill" class="upload-preview" />
+            <view v-else class="upload-placeholder">
+              <text class="upload-icon">+</text>
+              <text class="upload-label">身份证正面</text>
+            </view>
+          </view>
+          <view class="upload-item" @click="chooseIdCardImage('back')">
+            <image v-if="form.idCardBack" :src="form.idCardBack" mode="aspectFill" class="upload-preview" />
+            <view v-else class="upload-placeholder">
+              <text class="upload-icon">+</text>
+              <text class="upload-label">身份证反面</text>
+            </view>
           </view>
         </view>
       </view>
-    </view>
 
-    <view class="section">
-      <view class="section-title">资质证书</view>
-      <view class="cert-list">
-        <view v-for="(img, idx) in form.certificateImages" :key="getCertKey(idx)" class="cert-item">
-          <image :src="img" mode="aspectFill" class="cert-preview" />
-          <text class="cert-del" @click="removeCert(idx)">×</text>
-        </view>
-        <view class="cert-add" @click="chooseCertificate">
-          <text class="upload-icon">+</text>
-          <text class="upload-label">添加证书</text>
+      <view class="section">
+        <view class="section-title">资质证书</view>
+        <view class="cert-list">
+          <view v-for="(img, idx) in form.certificateImages" :key="getCertKey(idx)" class="cert-item">
+            <image :src="img" mode="aspectFill" class="cert-preview" />
+            <text class="cert-del" @click="removeCert(idx)">×</text>
+          </view>
+          <view class="cert-add" @click="chooseCertificate">
+            <text class="upload-icon">+</text>
+            <text class="upload-label">添加证书</text>
+          </view>
         </view>
       </view>
-    </view>
 
-    <view class="section">
-      <view class="section-title">联系地址 <text class="required">*</text></view>
-      <AddressPicker ref="addressPicker" @change="onAddressChange" />
-      <input class="input" v-model="form.addressDetail" placeholder="详细地址(门牌号等)" />
-    </view>
+      <view class="section">
+        <view class="section-title">联系地址 <text class="required">*</text></view>
+        <AddressPicker ref="addressPicker" @change="onAddressChange" />
+        <input class="input" v-model="form.addressDetail" placeholder="详细地址(门牌号等)" />
+      </view>
 
-    <button class="submit-btn" :disabled="submitting" @click="handleSubmit">
-      {{ submitting ? '提交中...' : '提交申请' }}
-    </button>
+      <button class="submit-btn" :disabled="submitting" @click="handleSubmit">
+        {{ submitting ? '提交中...' : '提交申请' }}
+      </button>
+      <view class="footer-hint">
+        <text>提交后将在 1-3 个工作日内完成审核</text>
+      </view>
+    </view>
   </view>
 </template>
 
@@ -92,7 +128,29 @@ export default {
         street: '',
         addressDetail: ''
       },
-      submitting: false
+      submitting: false,
+      applyRecord: null,
+      showStatusView: false
+    }
+  },
+  computed: {
+    applyStatus() {
+      if (!this.applyRecord) return 'none'
+      const s = this.applyRecord.status
+      return s === 0 ? 'pending' : s === 1 ? 'approved' : 'rejected'
+    },
+    statusIcon() {
+      return { pending: '⏳', approved: '✅', rejected: '❌' }[this.applyStatus] || '📋'
+    },
+    statusTitle() {
+      return { pending: '审核中', approved: '申请已通过', rejected: '申请被驳回' }[this.applyStatus] || ''
+    },
+    statusDesc() {
+      return {
+        pending: '您的申请正在审核中,请留意消息通知',
+        approved: '恭喜!您已成功通过审核,可以开始使用服务商功能',
+        rejected: '您的申请未通过审核,可查看原因后重新提交'
+      }[this.applyStatus] || ''
     }
   },
   onLoad() {
@@ -102,16 +160,21 @@ export default {
     async checkExistingApply() {
       try {
         const res = await getMyServiceRoleApply()
-        if (res.code === 200 && res.data && res.data.status === 0) {
-          uni.showModal({
-            title: '提示',
-            content: '您已提交过申请,请等待审核',
-            showCancel: false,
-            success: () => uni.navigateBack()
-          })
+        if (res.code === 200 && res.data) {
+          this.applyRecord = res.data
+          const status = res.data.status
+          if (status === 0) {
+            this.showStatusView = true
+          } else if (status === 1) {
+            // 已通过,跳转服务商中心
+            setTimeout(() => uni.redirectTo({ url: '/pages/vendor/center' }), 800)
+          } else if (status === 2) {
+            // 已拒绝,显示原因
+            this.showStatusView = true
+          }
         }
       } catch (e) {
-        // 忽略错误
+        // 无申请记录,显示表单
       }
     },
     toggleRole(key) {
@@ -151,7 +214,7 @@ export default {
     },
     async chooseCertificate() {
       uni.chooseImage({
-        count: 9,
+        count: 9 - this.form.certificateImages.length,
         sizeType: ['compressed'],
         sourceType: ['album', 'camera'],
         success: async (res) => {
@@ -214,12 +277,9 @@ export default {
         })
         uni.hideLoading()
         if (res.code === 200) {
-          uni.showModal({
-            title: '提交成功',
-            content: '申请已提交,请等待后台审核',
-            showCancel: false,
-            success: () => uni.navigateBack()
-          })
+          // 提交成功,刷新状态
+          await this.checkExistingApply()
+          if (!this.showStatusView) this.showStatusView = true
         } else {
           uni.showToast({ title: res.message || '提交失败', icon: 'none' })
         }
@@ -229,6 +289,25 @@ export default {
       } finally {
         this.submitting = false
       }
+    },
+    handleReapply() {
+      this.applyRecord = null
+      this.showStatusView = false
+      this.selectedRoles = []
+      this.form = {
+        idCard: '',
+        idCardFront: '',
+        idCardBack: '',
+        certificateImages: [],
+        province: '',
+        city: '',
+        district: '',
+        street: '',
+        addressDetail: ''
+      }
+    },
+    goToCenter() {
+      uni.redirectTo({ url: '/pages/vendor/center' })
     }
   }
 }
@@ -237,24 +316,150 @@ export default {
 <style>
 .page {
   min-height: 100vh;
-  background: #f5f5f5;
-  padding: 20rpx 30rpx 100rpx;
+  background: linear-gradient(180deg, #FFF7ED 0%, #F8F8F8 30%);
+  padding: 0 30rpx 120rpx;
+}
+
+/* ===== 状态展示区 ===== */
+.status-view {
+  padding-top: 60rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.status-card {
+  width: 100%;
+  border-radius: 24rpx;
+  padding: 60rpx 40rpx;
+  text-align: center;
+  margin-bottom: 30rpx;
+}
+.status-card.status-pending {
+  background: linear-gradient(135deg, #FFFBEB, #FEF3C7);
+  border: 2rpx solid #FDE68A;
+}
+.status-card.status-approved {
+  background: linear-gradient(135deg, #F0FDF4, #DCFCE7);
+  border: 2rpx solid #86EFAC;
+}
+.status-card.status-rejected {
+  background: linear-gradient(135deg, #FEF2F2, #FEE2E2);
+  border: 2rpx solid #FCA5A5;
+}
+.status-icon {
+  font-size: 80rpx;
+  display: block;
+  margin-bottom: 20rpx;
+}
+.status-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #1F2937;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.status-desc {
+  font-size: 26rpx;
+  color: #6B7280;
+  line-height: 1.6;
+  display: block;
+}
+.status-tip {
+  background: #fff;
+  border-radius: 12rpx;
+  padding: 20rpx 28rpx;
+  margin-bottom: 40rpx;
+  width: 100%;
+  box-sizing: border-box;
+}
+.status-tip text {
+  font-size: 24rpx;
+  color: #92400E;
+}
+.reject-info {
+  background: #fff;
+  border-radius: 12rpx;
+  padding: 28rpx;
+  margin-bottom: 40rpx;
+  width: 100%;
+  box-sizing: border-box;
+}
+.reject-label {
+  font-size: 24rpx;
+  color: #9CA3AF;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.reject-reason {
+  font-size: 28rpx;
+  color: #DC2626;
+  line-height: 1.6;
+  display: block;
+}
+.action-buttons {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+.btn-reapply, .btn-center, .btn-back {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  border-radius: 44rpx;
+  font-size: 30rpx;
+  text-align: center;
+  border: none;
+}
+.btn-reapply {
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  color: #fff;
+}
+.btn-center {
+  background: linear-gradient(135deg, #10B981, #34D399);
+  color: #fff;
+}
+.btn-back {
+  background: #F3F4F6;
+  color: #6B7280;
+}
+
+/* ===== 表单区 ===== */
+.header {
+  text-align: center;
+  padding: 50rpx 0 30rpx;
+}
+.title {
+  font-size: 42rpx;
+  font-weight: bold;
+  color: #1F2937;
+  display: block;
+}
+.subtitle {
+  font-size: 26rpx;
+  color: #9CA3AF;
+  margin-top: 10rpx;
+  display: block;
 }
 .section {
   background: #fff;
-  border-radius: 16rpx;
+  border-radius: 20rpx;
   padding: 30rpx;
-  margin-bottom: 20rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
 }
 .section-title {
   font-size: 28rpx;
   font-weight: bold;
-  margin-bottom: 20rpx;
-  color: #333;
+  margin-bottom: 24rpx;
+  color: #374151;
 }
 .required {
-  color: #ff4444;
+  color: #EF4444;
+  margin-left: 4rpx;
 }
+
+/* 角色网格 */
 .role-grid {
   display: flex;
   flex-wrap: wrap;
@@ -262,45 +467,52 @@ export default {
 }
 .role-card {
   width: calc((100% - 40rpx) / 3);
-  background: #f8f8f8;
-  border-radius: 12rpx;
-  padding: 24rpx 0;
+  background: #F9FAFB;
+  border-radius: 16rpx;
+  padding: 30rpx 0;
   display: flex;
   flex-direction: column;
   align-items: center;
   position: relative;
-  border: 2rpx solid transparent;
+  border: 3rpx solid transparent;
+  transition: all 0.2s;
 }
 .role-card.selected {
-  background: #fff7ed;
-  border-color: #f97316;
+  background: #FFF7ED;
+  border-color: #F97316;
 }
 .role-icon {
-  font-size: 40rpx;
-  margin-bottom: 8rpx;
+  font-size: 44rpx;
+  margin-bottom: 10rpx;
 }
 .role-name {
   font-size: 24rpx;
-  color: #333;
+  color: #374151;
+  font-weight: 500;
 }
 .check-mark {
   position: absolute;
   top: 8rpx;
   right: 12rpx;
-  color: #f97316;
-  font-size: 28rpx;
+  color: #F97316;
+  font-size: 32rpx;
   font-weight: bold;
 }
+
+/* 输入框 */
 .input {
   width: 100%;
   height: 72rpx;
-  border: 2rpx solid #eee;
-  border-radius: 8rpx;
-  padding: 0 20rpx;
+  border: 2rpx solid #E5E7EB;
+  border-radius: 12rpx;
+  padding: 0 24rpx;
   font-size: 26rpx;
   box-sizing: border-box;
   margin-bottom: 16rpx;
+  background: #FAFAFA;
 }
+
+/* 图片上传 */
 .upload-row {
   display: flex;
   gap: 20rpx;
@@ -308,12 +520,13 @@ export default {
 .upload-item {
   flex: 1;
   height: 200rpx;
-  border: 2rpx dashed #ddd;
-  border-radius: 12rpx;
+  border: 3rpx dashed #D1D5DB;
+  border-radius: 16rpx;
   display: flex;
   align-items: center;
   justify-content: center;
   overflow: hidden;
+  background: #FAFAFA;
 }
 .upload-placeholder {
   display: flex;
@@ -322,17 +535,19 @@ export default {
 }
 .upload-icon {
   font-size: 48rpx;
-  color: #ccc;
+  color: #9CA3AF;
 }
 .upload-label {
   font-size: 22rpx;
-  color: #999;
+  color: #9CA3AF;
   margin-top: 8rpx;
 }
 .upload-preview {
   width: 100%;
   height: 100%;
 }
+
+/* 证书列表 */
 .cert-list {
   display: flex;
   flex-wrap: wrap;
@@ -342,7 +557,7 @@ export default {
   width: 160rpx;
   height: 160rpx;
   position: relative;
-  border-radius: 8rpx;
+  border-radius: 12rpx;
   overflow: hidden;
 }
 .cert-preview {
@@ -353,41 +568,51 @@ export default {
   position: absolute;
   top: 4rpx;
   right: 4rpx;
-  width: 32rpx;
-  height: 32rpx;
-  background: rgba(0,0,0,0.5);
+  width: 36rpx;
+  height: 36rpx;
+  background: rgba(0,0,0,0.55);
   color: #fff;
   border-radius: 50%;
   text-align: center;
-  line-height: 32rpx;
-  font-size: 24rpx;
+  line-height: 36rpx;
+  font-size: 28rpx;
 }
 .cert-add {
   width: 160rpx;
   height: 160rpx;
-  border: 2rpx dashed #ddd;
-  border-radius: 8rpx;
+  border: 3rpx dashed #D1D5DB;
+  border-radius: 12rpx;
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
+  background: #FAFAFA;
 }
+
+/* 提交按钮 */
 .submit-btn {
   width: 100%;
-  height: 88rpx;
-  background: linear-gradient(135deg, #f97316, #fb923c);
+  height: 96rpx;
+  line-height: 96rpx;
+  background: linear-gradient(135deg, #F97316, #FB923C);
   color: #fff;
+  border-radius: 48rpx;
   font-size: 32rpx;
-  border-radius: 44rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  position: fixed;
-  bottom: 40rpx;
-  left: 30rpx;
-  width: calc(100% - 60rpx);
+  font-weight: 600;
+  margin-top: 16rpx;
+  border: none;
+  box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
 }
 .submit-btn[disabled] {
   opacity: 0.6;
+  box-shadow: none;
+}
+.footer-hint {
+  text-align: center;
+  margin-top: 24rpx;
+}
+.footer-hint text {
+  font-size: 24rpx;
+  color: #9CA3AF;
 }
-</style>
+</style>

+ 140 - 48
cfc-frontend/pages/vendor/center.vue

@@ -1,40 +1,64 @@
 <template>
   <view class="container">
-    <view class="header">
-      <text class="icon">🏪</text>
-      <text class="title">服务商中心</text>
+    <!-- 待审核提示 -->
+    <view v-if="isPending" class="pending-banner">
+      <text class="pending-icon">⏳</text>
+      <view class="pending-info">
+        <text class="pending-title">服务商申请审核中</text>
+        <text class="pending-desc">请等待管理员审核,预计 1-3 个工作日完成</text>
+      </view>
     </view>
 
-    <view class="info-card" v-if="vendorInfoData">
-      <view class="info-row">
-        <text class="info-label">服务商类型</text>
-        <text class="info-value">{{ getTypeLabel(vendorInfoData.vendorType) }}</text>
-      </view>
-      <view class="info-row">
-        <text class="info-label">真实姓名</text>
-        <text class="info-value">{{ vendorInfoData.realName || '未设置' }}</text>
-      </view>
-      <view class="info-row">
-        <text class="info-label">联系电话</text>
-        <text class="info-value">{{ vendorInfoData.phone || '未设置' }}</text>
+    <!-- 未审核通过时隐藏信息卡片和菜单 -->
+    <view v-else>
+      <view class="header">
+        <text class="icon">🏪</text>
+        <text class="title">服务商中心</text>
+        <text class="status-badge" :class="'badge-' + (vendorInfoData.vendorType || '')">{{ getTypeLabel(vendorInfoData.vendorType) }}</text>
       </view>
-    </view>
 
-    <view class="menu-list">
-      <view class="menu-item" @click="goToProducts">
-        <text>📦 商品管理</text>
-        <text class="arrow">›</text>
+      <view class="info-card" v-if="vendorInfoData">
+        <view class="info-row">
+          <text class="info-label">服务商类型</text>
+          <text class="info-value">{{ getTypeLabel(vendorInfoData.vendorType) }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">真实姓名</text>
+          <text class="info-value">{{ vendorInfoData.realName || '未设置' }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">联系电话</text>
+          <text class="info-value">{{ vendorInfoData.phone || '未设置' }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">联系方式</text>
+          <text class="info-value">{{ vendorInfoData.vendorInfo || '暂无' }}</text>
+        </view>
       </view>
-      <view class="menu-item" @click="goToOrders">
-        <text>📋 订单管理</text>
-        <text class="arrow">›</text>
+
+      <view class="menu-list">
+        <view class="menu-item" @click="goToProducts">
+          <view class="menu-icon">📦</view>
+          <text class="menu-label">商品管理</text>
+          <text class="arrow">›</text>
+        </view>
+        <view class="menu-item" @click="goToOrders">
+          <view class="menu-icon">📋</view>
+          <text class="menu-label">订单管理</text>
+          <text class="arrow">›</text>
+        </view>
+        <view class="menu-item" @click="goToTeamSystem">
+          <view class="menu-icon">👥</view>
+          <text class="menu-label">团队管理</text>
+          <text class="arrow">›</text>
+        </view>
       </view>
     </view>
   </view>
 </template>
 
 <script>
-import { vendorInfo } from '../../utils/api'
+import { vendorInfo, vendorStatus } from '../../utils/api'
 
 export default {
   data() {
@@ -44,23 +68,33 @@ export default {
         { value: 'activity_provider', label: '活动提供商' },
         { value: 'product_supplier', label: '商品供应商' }
       ],
-      vendorInfoData: null
+      vendorInfoData: null,
+      isPending: false
     }
   },
   onLoad() {
+    this.loadStatus()
     this.loadVendorInfo()
   },
   methods: {
     getTypeLabel(value) {
       const item = this.vendorTypes.find(t => t.value === value)
-      return item ? item.label : value
+      return item ? item.label : (value || '服务商')
+    },
+    async loadStatus() {
+      try {
+        const res = await vendorStatus()
+        this.isPending = !!(res && res.data && res.data.vendorStatus === 'pending')
+      } catch (e) {
+        this.isPending = false
+      }
     },
     async loadVendorInfo() {
       try {
         const res = await vendorInfo()
         this.vendorInfoData = res.data
       } catch (e) {
-        // handled by api.js
+        // vendor 未审核通过时会报错,忽略
       }
     },
     goToProducts() {
@@ -68,6 +102,9 @@ export default {
     },
     goToOrders() {
       uni.navigateTo({ url: '/pages/vendor/orders/orders' })
+    },
+    goToTeamSystem() {
+      uni.navigateTo({ url: '/pages/vendor/team-system/team-system' })
     }
   }
 }
@@ -76,71 +113,126 @@ export default {
 <style>
 .container {
   padding: 30rpx;
-  background: #f8f8f8;
+  background: linear-gradient(180deg, #FFF7ED 0%, #F8F8F8 25%);
   min-height: 100vh;
 }
+
+/* 待审核提示 */
+.pending-banner {
+  background: linear-gradient(135deg, #FEF3C7, #FFFBEB);
+  border: 2rpx solid #FDE68A;
+  border-radius: 20rpx;
+  padding: 40rpx 30rpx;
+  display: flex;
+  align-items: center;
+  gap: 24rpx;
+  margin-top: 30rpx;
+}
+.pending-icon {
+  font-size: 72rpx;
+}
+.pending-info {
+  display: flex;
+  flex-direction: column;
+}
+.pending-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #92400E;
+}
+.pending-desc {
+  font-size: 24rpx;
+  color: #B45309;
+  margin-top: 8rpx;
+}
+
+/* 头部 */
 .header {
   text-align: center;
-  padding: 40rpx 0;
+  padding: 50rpx 0 40rpx;
   display: flex;
   flex-direction: column;
   align-items: center;
 }
 .icon {
   font-size: 100rpx;
-  margin-bottom: 20rpx;
+  margin-bottom: 16rpx;
 }
 .title {
-  font-size: 36rpx;
+  font-size: 40rpx;
   font-weight: bold;
-  color: #333;
+  color: #1F2937;
+}
+.status-badge {
+  margin-top: 12rpx;
+  padding: 6rpx 20rpx;
+  border-radius: 20rpx;
+  font-size: 24rpx;
+  background: #F3F4F6;
+  color: #6B7280;
 }
+.status-badge.badge-planner { background: #ECFDF5; color: #059669; }
+.status-badge.badge-activity_provider { background: #EFF6FF; color: #2563EB; }
+.status-badge.badge-product_supplier { background: #FFF7ED; color: #EA580C; }
+
+/* 信息卡片 */
 .info-card {
   background: #fff;
   border-radius: 20rpx;
   padding: 30rpx;
   margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
 }
 .info-row {
   display: flex;
   justify-content: space-between;
-  padding: 15rpx 0;
-  border-bottom: 2rpx solid #f5f5f5;
+  padding: 18rpx 0;
+  border-bottom: 1rpx solid #F3F4F6;
 }
 .info-row:last-child {
   border-bottom: none;
 }
 .info-label {
-  font-size: 28rpx;
-  color: #999;
+  font-size: 26rpx;
+  color: #9CA3AF;
 }
 .info-value {
-  font-size: 28rpx;
-  color: #333;
+  font-size: 26rpx;
+  color: #374151;
+  font-weight: 500;
+  max-width: 60%;
+  text-align: right;
+  word-break: break-all;
 }
+
+/* 菜单列表 */
 .menu-list {
   background: #fff;
   border-radius: 20rpx;
+  overflow: hidden;
+  box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
 }
 .menu-item {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 30rpx;
-  border-bottom: 2rpx solid #f5f5f5;
-  font-size: 30rpx;
+  padding: 32rpx 30rpx;
+  border-bottom: 1rpx solid #F3F4F6;
 }
 .menu-item:last-child {
   border-bottom: none;
 }
-.arrow {
-  color: #ccc;
+.menu-icon {
   font-size: 36rpx;
+  margin-right: 20rpx;
 }
-.coming-soon {
-  text-align: center;
-  padding: 60rpx;
-  color: #ccc;
-  font-size: 26rpx;
+.menu-label {
+  font-size: 30rpx;
+  color: #374151;
+  flex: 1;
+}
+.arrow {
+  color: #D1D5DB;
+  font-size: 36rpx;
 }
 </style>

+ 2 - 2
cfc-web/src/views/admin/review/VendorAudit.vue

@@ -100,7 +100,7 @@ export default {
     },
     async handleReview(row, action) {
       try {
-        await reviewVendor({ userId: row.id, action })
+        await reviewVendor({ userId: row.id, vendorStatus: action === 'approve' ? 'approved' : 'rejected' })
         this.$message.success('审核完成')
         this.loadData()
       } catch (e) { this.$message.error('操作失败') }
@@ -109,7 +109,7 @@ export default {
     async confirmReject() {
       if (!this.rejectReason) return this.$message.warning('请填写拒绝原因')
       try {
-        await reviewVendor({ userId: this.rejectTarget.id, action: 'reject' })
+        await reviewVendor({ userId: this.rejectTarget.id, vendorStatus: 'rejected', rejectReason: this.rejectReason })
         this.$message.success('已拒绝')
         this.rejectDialogVisible = false
         this.loadData()