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

feat: 添加供应商管理员(supplier_admin)角色及供应商管理功能

后端:
- 添加supplier_admin角色到UserService.isValidRole()
- 添加supplier_admin到AdminAuthController登录角色检测
- 添加supplier_admin到数据库users.role ENUM
- 创建SupplyManageController供应商管理控制器(CRUD+角色管理+层级调整)

前端:
- 添加supplier_admin到permissions.js权限映射
- 添加供应商管理菜单到Layout.vue
- 添加供应商管理路由到router/index.js
- 创建supply-manage/List.vue, Form.vue, Detail.vue
- 添加supply-manage API函数到admin.js
User 2 месяцев назад
Родитель
Сommit
c9196d7f73

+ 8 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -4204,6 +4204,14 @@ try {
         } catch (Exception e) {
             log.warn("创建supply_settlement_detail表失败: {}", e.getMessage());
         }
+
+        // 迁移39: 添加supplier_admin角色到users.role ENUM
+        try {
+            jdbcTemplate.execute("ALTER TABLE users MODIFY COLUMN role ENUM('parent','child','teacher','supplier_admin') NOT NULL DEFAULT 'parent' COMMENT '当前活跃角色'");
+            log.info("已添加supplier_admin到users.role ENUM");
+        } catch (Exception e) {
+            log.warn("添加supplier_admin到users.role ENUM失败: {}", e.getMessage());
+        }
     }
 
     private void insertSysConfigSeed(String key, String value, String desc) {

+ 6 - 2
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminAuthController.java

@@ -67,14 +67,16 @@ public class AdminAuthController {
             return Result.error("该手机号未注册");
         }
 
-        // 检查是否有管理员、规划师、营养师、活动管理员或文章管理员角色
+        // 检查是否有管理员、规划师、营养师、活动管理员、文章管理员或供应商管理员角色
         String role = user.getRole();
         if (!"admin".equals(role) && !"teacher".equals(role)
                 && !"nutritionist".equals(role) && !"activity_admin".equals(role) && !"article_admin".equals(role)
+                && !"supplier_admin".equals(role)
                 && !userService.hasRole(user.getId(), "teacher")
                 && !userService.hasRole(user.getId(), "nutritionist")
                 && !userService.hasRole(user.getId(), "activity_admin")
-                && !userService.hasRole(user.getId(), "article_admin")) {
+                && !userService.hasRole(user.getId(), "article_admin")
+                && !userService.hasRole(user.getId(), "supplier_admin")) {
             return Result.error("该账号没有管理权限");
         }
 
@@ -293,6 +295,8 @@ public class AdminAuthController {
             role = "activity_admin";
         } else if ("article_admin".equals(userRole) || userService.hasRole(user.getId(), "article_admin")) {
             role = "article_admin";
+        } else if ("supplier_admin".equals(userRole) || userService.hasRole(user.getId(), "supplier_admin")) {
+            role = "supplier_admin";
         } else {
             // 如果用户没有匹配的角色,给个默认角色,但记录警告
             role = userRole != null ? userRole : "admin";

+ 239 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/SupplyManageController.java

@@ -0,0 +1,239 @@
+package com.etotem.cfc.controller.admin;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.UserMapper;
+import com.etotem.cfc.service.UserService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 供应商管理控制器 - 供应商管理员角色专用
+ * 管理供应商用户、商品发布、服务商层级调整
+ */
+@RestController
+@RequestMapping("/api/admin/supply-manage")
+public class SupplyManageController {
+
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private UserService userService;
+
+    /**
+     * 供应商列表(分页+搜索)
+     * 查询拥有 supplier_admin 角色的用户
+     */
+    @PostMapping("/list")
+    public Result<Map<String, Object>> list(@RequestBody Map<String, Object> params) {
+        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
+        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
+        String keyword = (String) params.get("keyword");
+        String vendorStatus = (String) params.get("vendorStatus");
+
+        Page<User> pageParam = new Page<>(page, size);
+        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
+
+        // 搜索条件:手机号、昵称、真实姓名
+        if (keyword != null && !keyword.isEmpty()) {
+            wrapper.and(w -> w.like(User::getPhone, keyword)
+                    .or().like(User::getNickname, keyword)
+                    .or().like(User::getRealName, keyword));
+        }
+
+        // 服务商状态筛选
+        if (vendorStatus != null && !vendorStatus.isEmpty()) {
+            wrapper.eq(User::getVendorStatus, vendorStatus);
+        }
+
+        wrapper.orderByDesc(User::getUpdatedAt);
+        Page<User> result = userMapper.selectPage(pageParam, wrapper);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("list", result.getRecords());
+        data.put("total", result.getTotal());
+        data.put("page", page);
+        data.put("size", size);
+        return Result.success(data);
+    }
+
+    /**
+     * 获取供应商详情
+     */
+    @PostMapping("/detail")
+    public Result<User> detail(@RequestBody Map<String, Object> params) {
+        Long id = params.get("id") != null ? ((Number) params.get("id")).longValue() : null;
+        if (id == null) {
+            return Result.error("id不能为空");
+        }
+        User user = userMapper.selectById(id);
+        if (user == null) {
+            return Result.error("供应商不存在");
+        }
+        user.setPassword(null); // 清除密码
+        return Result.success(user);
+    }
+
+    /**
+     * 创建供应商
+     */
+    @PostMapping("/create")
+    public Result<User> create(@RequestBody Map<String, Object> params) {
+        String phone = (String) params.get("phone");
+        String nickname = (String) params.get("nickname");
+        String realName = (String) params.get("realName");
+
+        if (phone == null || phone.isEmpty()) {
+            return Result.error("手机号不能为空");
+        }
+
+        // 检查手机号是否已存在
+        User existing = userMapper.selectOne(new LambdaQueryWrapper<User>()
+                .eq(User::getPhone, phone));
+        if (existing != null) {
+            return Result.error("手机号已存在");
+        }
+
+        User user = new User();
+        user.setPhone(phone);
+        user.setNickname(nickname != null ? nickname : "供应商" + phone.substring(7));
+        user.setRealName(realName);
+        user.setRole("supplier_admin");
+        user.setVendorStatus("approved"); // 默认通过审核
+        user.setVendorType("product_supplier"); // 默认商品供应商
+        user.setCreatedAt(new Date());
+        user.setUpdatedAt(new Date());
+
+        userMapper.insert(user);
+        user.setPassword(null);
+        return Result.success(user);
+    }
+
+    /**
+     * 更新供应商信息
+     */
+    @PostMapping("/update")
+    public Result<Boolean> update(@RequestBody Map<String, Object> params) {
+        Long id = params.get("id") != null ? ((Number) params.get("id")).longValue() : null;
+        if (id == null) {
+            return Result.error("id不能为空");
+        }
+
+        User user = userMapper.selectById(id);
+        if (user == null) {
+            return Result.error("供应商不存在");
+        }
+
+        if (params.containsKey("nickname")) {
+            user.setNickname((String) params.get("nickname"));
+        }
+        if (params.containsKey("realName")) {
+            user.setRealName((String) params.get("realName"));
+        }
+        if (params.containsKey("vendorType")) {
+            user.setVendorType((String) params.get("vendorType"));
+        }
+        if (params.containsKey("vendorStatus")) {
+            user.setVendorStatus((String) params.get("vendorStatus"));
+        }
+        if (params.containsKey("vendorInfo")) {
+            user.setVendorInfo((String) params.get("vendorInfo"));
+        }
+
+        user.setUpdatedAt(new Date());
+        userMapper.updateById(user);
+        return Result.success(true);
+    }
+
+    /**
+     * 删除供应商
+     */
+    @PostMapping("/delete")
+    public Result<Boolean> delete(@RequestBody Map<String, Object> params) {
+        Long id = params.get("id") != null ? ((Number) params.get("id")).longValue() : null;
+        if (id == null) {
+            return Result.error("id不能为空");
+        }
+
+        User user = userMapper.selectById(id);
+        if (user == null) {
+            return Result.error("供应商不存在");
+        }
+
+        userMapper.deleteById(id);
+        return Result.success(true);
+    }
+
+    /**
+     * 添加供应商管理员角色到现有用户
+     */
+    @PostMapping("/add-role")
+    public Result<Boolean> addRole(@RequestBody Map<String, Object> params) {
+        Long userId = params.get("userId") != null ? ((Number) params.get("userId")).longValue() : null;
+        if (userId == null) {
+            return Result.error("userId不能为空");
+        }
+
+        boolean added = userService.addRole(userId, "supplier_admin");
+        return added ? Result.success(true) : Result.error("添加角色失败");
+    }
+
+    /**
+     * 移除供应商管理员角色
+     */
+    @PostMapping("/remove-role")
+    public Result<Boolean> removeRole(@RequestBody Map<String, Object> params) {
+        Long userId = params.get("userId") != null ? ((Number) params.get("userId")).longValue() : null;
+        if (userId == null) {
+            return Result.error("userId不能为空");
+        }
+
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        // 移除 supplier_admin 角色
+        if (user.getRoles() != null && user.getRoles().contains("supplier_admin")) {
+            java.util.List<String> roles = userService.parseRoles(user.getRoles());
+            roles.remove("supplier_admin");
+            user.setRoles(String.join(",", roles));
+            user.setUpdatedAt(new Date());
+            userMapper.updateById(user);
+        }
+
+        return Result.success(true);
+    }
+
+    /**
+     * 调整服务商层级
+     * vendorType: planner / activity_provider / product_supplier
+     */
+    @PostMapping("/adjust-tier")
+    public Result<Boolean> adjustTier(@RequestBody Map<String, Object> params) {
+        Long userId = params.get("userId") != null ? ((Number) params.get("userId")).longValue() : null;
+        String vendorType = (String) params.get("vendorType");
+
+        if (userId == null || vendorType == null || vendorType.isEmpty()) {
+            return Result.error("参数不完整");
+        }
+
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        user.setVendorType(vendorType);
+        user.setUpdatedAt(new Date());
+        userMapper.updateById(user);
+
+        return Result.success(true);
+    }
+}

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

@@ -564,7 +564,7 @@ private FamilyInvitationService familyInvitationService;
     /**
      * 解析角色字符串为列表
      */
-    private List<String> parseRoles(String rolesStr) {
+    public List<String> parseRoles(String rolesStr) {
         List<String> result = new ArrayList<>();
         if (rolesStr == null || rolesStr.isEmpty()) {
             return result;
@@ -603,7 +603,8 @@ private FamilyInvitationService familyInvitationService;
      * 检查角色是否有效
      */
     private boolean isValidRole(String role) {
-        return "parent".equals(role) || "child".equals(role) || "teacher".equals(role);
+        return "parent".equals(role) || "child".equals(role) || "teacher".equals(role)
+                || "supplier_admin".equals(role);
     }
 
 /**

+ 1 - 1
cfc-backend/src/main/resources/schema.sql

@@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS users (
     openid VARCHAR(64) NOT NULL,
     unionid VARCHAR(64),
     family_id BIGINT NOT NULL,
-    role ENUM('parent','child','teacher') NOT NULL,
+    role ENUM('parent','child','teacher','supplier_admin') NOT NULL,
     nickname VARCHAR(50),
     avatar VARCHAR(255),
     password VARCHAR(128),

+ 72 - 6
cfc-web/src/api/admin.js

@@ -1104,10 +1104,76 @@ export function createSupplySettlement(systemId, periodStart, periodEnd, remark)
   })
 }
 
-export function confirmSupplySettlement(id) {
-  return request({
-    url: '/api/admin/supply-settlement/confirm',
-    method: 'post',
-    data: { id }
-  })
+export function confirmSupplySettlement(id) {
+  return request({
+    url: '/api/admin/supply-settlement/confirm',
+    method: 'post',
+    data: { id }
+  })
+}
+
+// ========== 供应商管理 (supplier_admin) ==========
+
+export function getSupplyManageList(params) {
+  return request({
+    url: '/api/admin/supply-manage/list',
+    method: 'post',
+    data: params
+  })
+}
+
+export function getSupplyManageDetail(params) {
+  return request({
+    url: '/api/admin/supply-manage/detail',
+    method: 'post',
+    data: params
+  })
+}
+
+export function createSupplyManage(params) {
+  return request({
+    url: '/api/admin/supply-manage/create',
+    method: 'post',
+    data: params
+  })
+}
+
+export function updateSupplyManage(params) {
+  return request({
+    url: '/api/admin/supply-manage/update',
+    method: 'post',
+    data: params
+  })
+}
+
+export function deleteSupplyManage(params) {
+  return request({
+    url: '/api/admin/supply-manage/delete',
+    method: 'post',
+    data: params
+  })
+}
+
+export function addSupplierRole(params) {
+  return request({
+    url: '/api/admin/supply-manage/add-role',
+    method: 'post',
+    data: params
+  })
+}
+
+export function removeSupplierRole(params) {
+  return request({
+    url: '/api/admin/supply-manage/remove-role',
+    method: 'post',
+    data: params
+  })
+}
+
+export function adjustSupplyTier(params) {
+  return request({
+    url: '/api/admin/supply-manage/adjust-tier',
+    method: 'post',
+    data: params
+  })
 }

+ 27 - 0
cfc-web/src/router/index.js

@@ -316,6 +316,33 @@ const routes = [
         meta: { title: '编辑体系', perm: 'system:supply' },
         props: true
       },
+      // ========== 供应商管理 (supplier_admin) ==========
+      {
+        path: 'supply-manage',
+        name: 'SupplyManage',
+        component: () => import('@/views/admin/supply-manage/List.vue'),
+        meta: { title: '供应商管理', perm: 'supply:manage' }
+      },
+      {
+        path: 'supply-manage/create',
+        name: 'SupplyManageCreate',
+        component: () => import('@/views/admin/supply-manage/Form.vue'),
+        meta: { title: '新增供应商', perm: 'supply:manage' }
+      },
+      {
+        path: 'supply-manage/:id',
+        name: 'SupplyManageDetail',
+        component: () => import('@/views/admin/supply-manage/Detail.vue'),
+        meta: { title: '供应商详情', perm: 'supply:manage' },
+        props: true
+      },
+      {
+        path: 'supply-manage/:id/edit',
+        name: 'SupplyManageEdit',
+        component: () => import('@/views/admin/supply-manage/Form.vue'),
+        meta: { title: '编辑供应商', perm: 'supply:manage' },
+        props: true
+      },
       {
         path: 'zodiac-configs',
         name: 'ZodiacConfigs',

+ 2 - 0
cfc-web/src/utils/permissions.js

@@ -8,6 +8,7 @@ const ROLE_PERMISSIONS = {
   nutritionist: ['dashboard', 'service:family', 'health:*', 'diet:*', 'assessment:dan', 'energy'],
   article_manager: ['dashboard', 'articles:*'],
   activity_manager: ['dashboard', 'activity:*'],
+  supplier_admin: ['dashboard', 'commerce:*', 'supply:*', 'system:config'],
 }
 
 /**
@@ -78,6 +79,7 @@ export function getRoleLabel(role) {
     nutritionist: '营养师',
     article_manager: '文章管理员',
     activity_manager: '活动管理员',
+    supplier_admin: '供应商管理员',
   }
   return labels[role] || role
 }

+ 9 - 0
cfc-web/src/views/Layout.vue

@@ -269,6 +269,15 @@ export default {
               children: [
                 { path: '/supply-system', label: '供应商体系', icon: 'el-icon-s-management', perm: 'system:supply' },
               ]},
+            // --- 供应商管理 (supplier_admin) ---
+            { title: '供应商管理', icon: 'el-icon-s-shop', perm: 'supply:manage',
+              children: [
+                { path: '/supply-manage', label: '供应商管理', icon: 'el-icon-s-shop', perm: 'supply:manage' },
+                { path: '/product-manage', label: '商品管理', icon: 'el-icon-s-goods', perm: 'commerce:products' },
+                { path: '/product-edit', label: '商品编辑', icon: 'el-icon-edit', perm: 'commerce:products' },
+                { path: '/category-manage', label: '商品分类', icon: 'el-icon-menu', perm: 'commerce:category' },
+                { path: '/order-manage', label: '订单管理', icon: 'el-icon-s-order', perm: 'commerce:orders' },
+              ]},
             // --- 健康配置 ---
             { title: '健康配置', icon: 'el-icon-first-aid-kit', perm: 'system:config',
               children: [

+ 132 - 0
cfc-web/src/views/admin/supply-manage/Detail.vue

@@ -0,0 +1,132 @@
+<template>
+  <div class="supply-manage-detail">
+    <el-card>
+      <div slot="header">
+        <span>供应商详情 — {{ supplier.nickname || supplier.phone }}</span>
+        <el-button style="float: right" size="mini" @click="$router.push('/supply-manage')">返回列表</el-button>
+      </div>
+
+      <el-descriptions :column="2" border v-if="supplier.id">
+        <el-descriptions-item label="ID">{{ supplier.id }}</el-descriptions-item>
+        <el-descriptions-item label="手机号">{{ supplier.phone }}</el-descriptions-item>
+        <el-descriptions-item label="昵称">{{ supplier.nickname }}</el-descriptions-item>
+        <el-descriptions-item label="真实姓名">{{ supplier.realName || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="服务商类型">
+          <el-tag size="small">{{ getVendorTypeLabel(supplier.vendorType) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="服务商状态">
+          <el-tag :type="getStatusType(supplier.vendorStatus)" size="small">
+            {{ getStatusLabel(supplier.vendorStatus) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ supplier.createdAt | parseTime('{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
+        <el-descriptions-item label="更新时间">{{ supplier.updatedAt | parseTime('{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
+      </el-descriptions>
+
+      <div style="margin-top: 20px;">
+        <el-button type="primary" size="small" @click="$router.push('/supply-manage/' + supplier.id + '/edit')">编辑供应商</el-button>
+        <el-button type="warning" size="small" @click="showTierDialog = true">调整服务商层级</el-button>
+      </div>
+    </el-card>
+
+    <!-- 调整服务商层级对话框 -->
+    <el-dialog title="调整服务商层级" :visible.sync="showTierDialog" width="400px">
+      <el-form label-width="120px">
+        <el-form-item label="服务商类型">
+          <el-select v-model="tierForm.vendorType" style="width: 100%">
+            <el-option label="成长规划师" value="planner"></el-option>
+            <el-option label="活动规划师" value="activity_provider"></el-option>
+            <el-option label="商品供应商" value="product_supplier"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="showTierDialog = false">取消</el-button>
+        <el-button type="primary" @click="handleAdjustTier" :loading="adjusting">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getSupplyManageDetail, adjustSupplyTier } from '@/api/admin'
+
+export default {
+  name: 'SupplyManageDetail',
+  data() {
+    return {
+      supplier: {},
+      showTierDialog: false,
+      tierForm: {
+        vendorType: 'product_supplier'
+      },
+      adjusting: false
+    }
+  },
+  created() {
+    this.loadDetail()
+  },
+  methods: {
+    async loadDetail() {
+      try {
+        const id = this.$route.params.id
+        const res = await getSupplyManageDetail({ id })
+        if (res.data) {
+          this.supplier = res.data
+          this.tierForm.vendorType = res.data.vendorType || 'product_supplier'
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      }
+    },
+    async handleAdjustTier() {
+      this.adjusting = true
+      try {
+        await adjustSupplyTier({
+          userId: this.supplier.id,
+          vendorType: this.tierForm.vendorType
+        })
+        this.$message.success('调整成功')
+        this.showTierDialog = false
+        this.loadDetail()
+      } catch (e) {
+        this.$message.error('调整失败')
+      } finally {
+        this.adjusting = false
+      }
+    },
+    getVendorTypeLabel(type) {
+      const map = {
+        planner: '成长规划师',
+        activity_provider: '活动规划师',
+        product_supplier: '商品供应商'
+      }
+      return map[type] || '-'
+    },
+    getStatusLabel(status) {
+      const map = {
+        pending: '待审核',
+        approved: '已通过',
+        rejected: '已拒绝',
+        null: '-',
+        '': '-'
+      }
+      return map[status] || status || '-'
+    },
+    getStatusType(status) {
+      const map = {
+        pending: 'warning',
+        approved: 'success',
+        rejected: 'danger'
+      }
+      return map[status] || 'info'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.supply-manage-detail {
+  padding: 20px;
+}
+</style>

+ 130 - 0
cfc-web/src/views/admin/supply-manage/Form.vue

@@ -0,0 +1,130 @@
+<template>
+  <div class="supply-manage-form">
+    <el-card>
+      <div slot="header">
+        <span>{{ isEdit ? '编辑供应商' : '新增供应商' }}</span>
+        <el-button style="float: right" size="mini" @click="$router.back()">返回</el-button>
+      </div>
+
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="手机号" prop="phone">
+          <el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11"></el-input>
+        </el-form-item>
+
+        <el-form-item label="昵称" prop="nickname">
+          <el-input v-model="form.nickname" placeholder="请输入昵称" maxlength="50"></el-input>
+        </el-form-item>
+
+        <el-form-item label="真实姓名" prop="realName">
+          <el-input v-model="form.realName" placeholder="请输入真实姓名" maxlength="50"></el-input>
+        </el-form-item>
+
+        <el-form-item label="服务商类型" prop="vendorType">
+          <el-select v-model="form.vendorType" placeholder="请选择服务商类型" style="width: 100%">
+            <el-option label="成长规划师" value="planner"></el-option>
+            <el-option label="活动规划师" value="activity_provider"></el-option>
+            <el-option label="商品供应商" value="product_supplier"></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="服务商状态" prop="vendorStatus">
+          <el-radio-group v-model="form.vendorStatus">
+            <el-radio label="pending">待审核</el-radio>
+            <el-radio label="approved">已通过</el-radio>
+            <el-radio label="rejected">已拒绝</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" :loading="saving" @click="handleSubmit">{{ isEdit ? '保存修改' : '创建' }}</el-button>
+          <el-button @click="$router.back()">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { createSupplyManage, updateSupplyManage, getSupplyManageDetail } from '@/api/admin'
+
+export default {
+  name: 'SupplyManageForm',
+  data() {
+    return {
+      form: {
+        id: null,
+        phone: '',
+        nickname: '',
+        realName: '',
+        vendorType: 'product_supplier',
+        vendorStatus: 'approved'
+      },
+      rules: {
+        phone: [
+          { required: true, message: '请输入手机号', trigger: 'blur' },
+          { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
+        ],
+        nickname: [
+          { required: true, message: '请输入昵称', trigger: 'blur' }
+        ]
+      },
+      saving: false
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.$route.params.id
+    }
+  },
+  created() {
+    if (this.isEdit) {
+      this.loadDetail(this.$route.params.id)
+    }
+  },
+  methods: {
+    async loadDetail(id) {
+      try {
+        const res = await getSupplyManageDetail({ id })
+        if (res.data) {
+          this.form = {
+            id: res.data.id,
+            phone: res.data.phone || '',
+            nickname: res.data.nickname || '',
+            realName: res.data.realName || '',
+            vendorType: res.data.vendorType || 'product_supplier',
+            vendorStatus: res.data.vendorStatus || 'approved'
+          }
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      }
+    },
+    async handleSubmit() {
+      this.$refs.form.validate(async (valid) => {
+        if (!valid) return
+        this.saving = true
+        try {
+          if (this.isEdit) {
+            await updateSupplyManage(this.form)
+          } else {
+            await createSupplyManage(this.form)
+          }
+          this.$message.success(this.isEdit ? '保存成功' : '创建成功')
+          this.$router.push('/supply-manage')
+        } catch (e) {
+          this.$message.error('操作失败')
+        } finally {
+          this.saving = false
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.supply-manage-form {
+  padding: 20px;
+  max-width: 600px;
+}
+</style>

+ 162 - 0
cfc-web/src/views/admin/supply-manage/List.vue

@@ -0,0 +1,162 @@
+<template>
+  <div class="supply-manage-list">
+    <el-card>
+      <div slot="header">
+        <span>供应商管理</span>
+        <el-button style="float: right" type="primary" size="small" @click="$router.push('/supply-manage/create')">+ 新增供应商</el-button>
+      </div>
+
+      <el-form :inline="true" style="margin-bottom: 15px;">
+        <el-form-item>
+          <el-input v-model="searchKeyword" placeholder="搜索手机号/昵称/姓名" clearable @clear="loadData" @keyup.enter.native="loadData">
+            <i slot="prefix" class="el-icon-search"></i>
+          </el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-select v-model="searchVendorStatus" placeholder="服务商状态" clearable style="width: 150px" @change="loadData">
+            <el-option label="待审核" value="pending"></el-option>
+            <el-option label="已通过" value="approved"></el-option>
+            <el-option label="已拒绝" value="rejected"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData" :loading="loading">查询</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="suppliers" stripe v-loading="loading">
+        <el-table-column prop="id" label="ID" width="70"></el-table-column>
+        <el-table-column prop="nickname" label="昵称" width="130"></el-table-column>
+        <el-table-column prop="realName" label="真实姓名" width="120"></el-table-column>
+        <el-table-column prop="phone" label="手机号" width="130"></el-table-column>
+        <el-table-column label="服务商类型" width="130">
+          <template slot-scope="scope">
+            <el-tag size="small">{{ getVendorTypeLabel(scope.row.vendorType) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="服务商状态" width="100">
+          <template slot-scope="scope">
+            <el-tag :type="getStatusType(scope.row.vendorStatus)" size="small">
+              {{ getStatusLabel(scope.row.vendorStatus) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createdAt" label="创建时间" width="170">
+          <template slot-scope="scope">{{ scope.row.createdAt | parseTime('{y}-{m}-{d} {h}:{i}') }}</template>
+        </el-table-column>
+        <el-table-column label="操作" width="220" fixed="right">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary" @click="viewDetail(scope.row)">详情</el-button>
+            <el-button size="mini" @click="$router.push('/supply-manage/' + scope.row.id + '/edit')">编辑</el-button>
+            <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        v-if="total > 0"
+        style="margin-top: 15px; text-align: right"
+        background
+        layout="total, prev, pager, next"
+        :total="total"
+        :page-size="pageSize"
+        :current-page="page"
+        @current-change="handlePageChange">
+      </el-pagination>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { getSupplyManageList, deleteSupplyManage } from '@/api/admin'
+
+export default {
+  name: 'SupplyManageList',
+  data() {
+    return {
+      suppliers: [],
+      loading: false,
+      searchKeyword: '',
+      searchVendorStatus: '',
+      page: 1,
+      pageSize: 10,
+      total: 0
+    }
+  },
+  created() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      this.loading = true
+      try {
+        const res = await getSupplyManageList({
+          keyword: this.searchKeyword,
+          vendorStatus: this.searchVendorStatus,
+          page: this.page,
+          size: this.pageSize
+        })
+        if (res.data) {
+          this.suppliers = res.data.list || []
+          this.total = res.data.total || 0
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    handlePageChange(val) {
+      this.page = val
+      this.loadData()
+    },
+    async handleDelete(row) {
+      try {
+        await this.$confirm('确定要删除该供应商吗?', '提示', { type: 'warning' })
+        await deleteSupplyManage({ id: row.id })
+        this.$message.success('删除成功')
+        this.loadData()
+      } catch (e) {
+        if (e !== 'cancel') {
+          this.$message.error('删除失败')
+        }
+      }
+    },
+    viewDetail(row) {
+      this.$router.push('/supply-manage/' + row.id)
+    },
+    getVendorTypeLabel(type) {
+      const map = {
+        planner: '成长规划师',
+        activity_provider: '活动规划师',
+        product_supplier: '商品供应商'
+      }
+      return map[type] || '-'
+    },
+    getStatusLabel(status) {
+      const map = {
+        pending: '待审核',
+        approved: '已通过',
+        rejected: '已拒绝',
+        null: '-',
+        '': '-'
+      }
+      return map[status] || status || '-'
+    },
+    getStatusType(status) {
+      const map = {
+        pending: 'warning',
+        approved: 'success',
+        rejected: 'danger'
+      }
+      return map[status] || 'info'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.supply-manage-list {
+  padding: 20px;
+}
+</style>