# 服务角色统一申请 — 实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在"我的"页面增加统一的服务角色申请入口,支持多选申请规划师/营养师/管家/服务商/文章管理员/活动提供商,上传身份证和资质证书,填写联系地址,提交后台审核。 **Architecture:** 新建统一 `service_role_applications` 表,一张表涵盖所有角色申请。前端统一申请页一次性提交,后端审核通过后按角色分别同步到现有各角色系统(Guide/User.butlerStatus/User.nutritionistStatus/User.vendorStatus 等)。 **Tech Stack:** Spring Boot 2.7.18 + MyBatis-Plus / uni-app Vue 2 小程序 / Vue 2 + Element UI Web管理端 ## Global Constraints - 所有接口使用 `@PostMapping`,禁止 `@GetMapping/@PutMapping/@DeleteMapping` - 响应统一使用 `Result` (code/message/data) - DI 使用 `@Resource` 代替 `@Autowired` - 小程序禁止可选链 `?.`(用 `&&` 替代)、禁止 CSS Grid(用 flexbox) - DI 字段名必须与类型默认 Bean Name 一致 - 新表名使用 `service_role_applications` - 迁移编号从 111 开始(当前最新迁移 110) - 所有迁移必须幂等(try-catch 忽略已存在) --- ## 文件结构 ### 新建文件 | 文件 | 用途 | |------|------| | `cfc-backend/.../entity/ServiceRoleApplication.java` | 实体类 | | `cfc-backend/.../mapper/ServiceRoleApplicationMapper.java` | Mapper 接口 | | `cfc-backend/.../dto/ServiceRoleApplyDTO.java` | 申请提交 DTO | | `cfc-backend/.../service/ServiceRoleApplicationService.java` | Service 接口 | | `cfc-backend/.../service/impl/ServiceRoleApplicationServiceImpl.java` | Service 实现 | | `cfc-backend/.../controller/ServiceRoleApplicationController.java` | Controller | | `cfc-frontend/pages/profile/service-role-apply.vue` | 统一申请页 | | `cfc-web/src/views/service/ServiceRoleApplications.vue` | 管理端审核页 | ### 修改文件 | 文件 | 修改内容 | |------|----------| | `cfc-backend/.../entity/User.java` | 添加 `articleAdminStatus`、`activityProviderStatus` 字段 | | `cfc-backend/.../config/DatabaseInitializer.java` | 添加迁移 111-113 | | `cfc-backend/src/main/resources/schema.sql` | 添加 `service_role_applications` 表 + User 表新字段 | | `cfc-frontend/pages/profile/components/ProfileMenu.vue` | 添加"服务角色申请"菜单项 | | `cfc-frontend/utils/api.js` | 新增 API 函数 | | `cfc-frontend/pages.json` | 注册新页面到 profile 子包 | | `cfc-web/src/router/index.js` | 添加审核页面路由 | --- ### Task 1: 数据库迁移 — 建表 & User 加字段 **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` (追加迁移 111-113) - Modify: `cfc-backend/src/main/resources/schema.sql` **Interfaces:** - Consumes: (none) - Produces: `service_role_applications` 表存在,User 表有 `article_admin_status` 和 `activity_provider_status` 列 - [ ] **Step 1: 在 schema.sql 追加新表定义** 在 `schema.sql` 末尾追加: ```sql -- ============================================================ -- 服务角色申请表 -- ============================================================ CREATE TABLE IF NOT EXISTS `service_role_applications` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '申请人ID', `roles` varchar(255) NOT NULL COMMENT '申请角色,JSON数组 e.g. ["planner","nutritionist"]', `id_card` varchar(18) DEFAULT '' COMMENT '身份证号', `id_card_images` text COMMENT '身份证照片,JSON数组[正面URL,反面URL]', `certificate_images` text COMMENT '资质证书照片,JSON数组', `province` varchar(50) DEFAULT '' COMMENT '省', `city` varchar(50) DEFAULT '' COMMENT '市', `district` varchar(50) DEFAULT '' COMMENT '区', `street` varchar(50) DEFAULT '' COMMENT '街道', `address_detail` varchar(200) DEFAULT '' COMMENT '详细地址', `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-pending, 1-approved, 2-rejected', `reject_reason` varchar(500) DEFAULT '' COMMENT '驳回原因', `reviewed_by` int(11) DEFAULT NULL COMMENT '审核人ID', `reviewed_at` datetime DEFAULT NULL COMMENT '审核时间', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务角色申请表'; ``` 在 `users` 表的 `CREATE TABLE` 语句中(或表定义注释区)添加字段注释。schema.sql 中 users 表定义在 `-- nutritionist_status` 注释附近追加: ```sql -- ALTER TABLE users ADD COLUMN article_admin_status VARCHAR(20) DEFAULT NULL COMMENT '文章管理员状态: pending/approved/rejected'; -- ALTER TABLE users ADD COLUMN activity_provider_status VARCHAR(20) DEFAULT NULL COMMENT '活动提供商状态: pending/approved/rejected'; ``` - [ ] **Step 2: 在 DatabaseInitializer 添加迁移 111-113** 在 `runMigrations()` 方法末尾(最后一个 `// 迁移` 之后)追加: ```java // 迁移111: 创建 service_role_applications 表(服务角色统一申请) try { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS service_role_applications (" + "id int(11) NOT NULL AUTO_INCREMENT, " + "user_id int(11) NOT NULL COMMENT '申请人ID', " + "roles varchar(255) NOT NULL COMMENT '申请角色,JSON数组', " + "id_card varchar(18) DEFAULT '' COMMENT '身份证号', " + "id_card_images text COMMENT '身份证照片,JSON数组', " + "certificate_images text COMMENT '资质证书照片,JSON数组', " + "province varchar(50) DEFAULT '' COMMENT '省', " + "city varchar(50) DEFAULT '' COMMENT '市', " + "district varchar(50) DEFAULT '' COMMENT '区', " + "street varchar(50) DEFAULT '' COMMENT '街道', " + "address_detail varchar(200) DEFAULT '' COMMENT '详细地址', " + "status tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-pending 1-approved 2-rejected', " + "reject_reason varchar(500) DEFAULT '' COMMENT '驳回原因', " + "reviewed_by int(11) DEFAULT NULL COMMENT '审核人ID', " + "reviewed_at datetime DEFAULT NULL COMMENT '审核时间', " + "created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, " + "updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " + "PRIMARY KEY (id), " + "KEY idx_user_id (user_id), " + "KEY idx_status (status)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务角色申请表'"); log.info("已创建service_role_applications表"); } catch (Exception e) { // 表已存在,忽略错误 } // 迁移112: users表添加article_admin_status字段(文章管理员状态) try { jdbcTemplate.execute("ALTER TABLE users ADD COLUMN article_admin_status VARCHAR(20) DEFAULT NULL COMMENT '文章管理员状态: pending/approved/rejected'"); log.info("已添加article_admin_status列到users表"); } catch (Exception e) { // 列已存在,忽略错误 } // 迁移113: users表添加activity_provider_status字段(活动提供商状态) try { jdbcTemplate.execute("ALTER TABLE users ADD COLUMN activity_provider_status VARCHAR(20) DEFAULT NULL COMMENT '活动提供商状态: pending/approved/rejected'"); log.info("已添加activity_provider_status列到users表"); } catch (Exception e) { // 列已存在,忽略错误 } ``` - [ ] **Step 3: 编译验证** ```bash cd cfc-backend && mvn clean compile ``` Expected: BUILD SUCCESS - [ ] **Step 4: 提交** ```bash git add cfc-backend/src/main/resources/schema.sql cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java git commit -m "feat(db): add service_role_applications table and User status fields" ``` --- ### Task 2: 后端 Entity + Mapper + DTO **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/ServiceRoleApplication.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/ServiceRoleApplicationMapper.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/dto/ServiceRoleApplyDTO.java` - Modify: `cfc-backend/src/main/java/com/etotem/cfc/entity/User.java` **Interfaces:** - Consumes: `service_role_applications` 表 (Task 1) - Produces: `ServiceRoleApplication` entity, `ServiceRoleApplicationMapper`, `ServiceRoleApplyDTO`, updated `User` entity - [ ] **Step 1: 创建 ServiceRoleApplication 实体** `cfc-backend/src/main/java/com/etotem/cfc/entity/ServiceRoleApplication.java`: ```java package com.etotem.cfc.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("service_role_applications") public class ServiceRoleApplication { @TableId(type = IdType.AUTO) private Long id; private Long userId; private String roles; private String idCard; private String idCardImages; private String certificateImages; private String province; private String city; private String district; private String street; private String addressDetail; private Integer status; private String rejectReason; private Long reviewedBy; private LocalDateTime reviewedAt; private LocalDateTime createdAt; private LocalDateTime updatedAt; } ``` - [ ] **Step 2: 创建 Mapper 接口** `cfc-backend/src/main/java/com/etotem/cfc/mapper/ServiceRoleApplicationMapper.java`: ```java package com.etotem.cfc.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.etotem.cfc.entity.ServiceRoleApplication; import org.apache.ibatis.annotations.Mapper; @Mapper public interface ServiceRoleApplicationMapper extends BaseMapper { } ``` - [ ] **Step 3: 创建 DTO** `cfc-backend/src/main/java/com/etotem/cfc/dto/ServiceRoleApplyDTO.java`: ```java package com.etotem.cfc.dto; import lombok.Data; import javax.validation.constraints.NotEmpty; import java.util.List; @Data public class ServiceRoleApplyDTO { @NotEmpty(message = "至少选择一个角色") private List roles; private String idCard; private List idCardImages; private List certificateImages; private String province; private String city; private String district; private String street; private String addressDetail; } ``` - [ ] **Step 4: 修改 User 实体添加新字段** 在 `User.java` 中添加两个新字段(放在 `nutritionistStatus` 附近): ```java /** 文章管理员状态: pending/approved/rejected */ private String articleAdminStatus; /** 活动提供商状态: pending/approved/rejected */ private String activityProviderStatus; ``` - [ ] **Step 5: 编译验证** ```bash cd cfc-backend && mvn clean compile ``` Expected: BUILD SUCCESS - [ ] **Step 6: 提交** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/entity/ServiceRoleApplication.java \ cfc-backend/src/main/java/com/etotem/cfc/mapper/ServiceRoleApplicationMapper.java \ cfc-backend/src/main/java/com/etotem/cfc/dto/ServiceRoleApplyDTO.java \ cfc-backend/src/main/java/com/etotem/cfc/entity/User.java git commit -m "feat(backend): add ServiceRoleApplication entity, mapper, DTO + User fields" ``` --- ### Task 3: 后端 Service 接口 + 实现 **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/service/ServiceRoleApplicationService.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/service/impl/ServiceRoleApplicationServiceImpl.java` **Interfaces:** - Consumes: `ServiceRoleApplication` entity, `ServiceRoleApplicationMapper`, `ServiceRoleApplyDTO` (Task 2) - Produces: `ServiceRoleApplicationService` with `submit()`, `getMyApplication()`, `approve()`, `reject()` methods - [ ] **Step 1: 创建 Service 接口** `cfc-backend/src/main/java/com/etotem/cfc/service/ServiceRoleApplicationService.java`: ```java package com.etotem.cfc.service; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.ServiceRoleApplyDTO; public interface ServiceRoleApplicationService { Result submit(Long userId, ServiceRoleApplyDTO dto); Result getMyApplication(Long userId); Result approve(Long id, Long reviewerId); Result reject(Long id, String reason, Long reviewerId); } ``` - [ ] **Step 2: 创建 Service 实现** `cfc-backend/src/main/java/com/etotem/cfc/service/impl/ServiceRoleApplicationServiceImpl.java`: ```java package com.etotem.cfc.service.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.ServiceRoleApplyDTO; import com.etotem.cfc.entity.*; import com.etotem.cfc.mapper.*; import com.etotem.cfc.service.ServiceRoleApplicationService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.List; @Service public class ServiceRoleApplicationServiceImpl extends ServiceImpl implements ServiceRoleApplicationService { @Resource private UserMapper userMapper; @Resource private GuideMapper guideMapper; @Resource private ButlerProfileMapper butlerProfileMapper; @Override public Result submit(Long userId, ServiceRoleApplyDTO dto) { // 检查是否已有 pending 申请 ServiceRoleApplication existing = this.getOne( new LambdaQueryWrapper() .eq(ServiceRoleApplication::getUserId, userId) .eq(ServiceRoleApplication::getStatus, 0)); if (existing != null) { return Result.error("您已提交过申请,请等待审核"); } ServiceRoleApplication app = new ServiceRoleApplication(); app.setUserId(userId); app.setRoles(JSON.toJSONString(dto.getRoles())); app.setIdCard(dto.getIdCard()); app.setIdCardImages(dto.getIdCardImages() != null ? JSON.toJSONString(dto.getIdCardImages()) : null); app.setCertificateImages(dto.getCertificateImages() != null ? JSON.toJSONString(dto.getCertificateImages()) : null); app.setProvince(dto.getProvince()); app.setCity(dto.getCity()); app.setDistrict(dto.getDistrict()); app.setStreet(dto.getStreet()); app.setAddressDetail(dto.getAddressDetail()); app.setStatus(0); // pending this.save(app); return Result.success("申请提交成功,请等待审核"); } @Override public Result getMyApplication(Long userId) { ServiceRoleApplication app = this.getOne( new LambdaQueryWrapper() .eq(ServiceRoleApplication::getUserId, userId) .orderByDesc(ServiceRoleApplication::getCreatedAt) .last("LIMIT 1")); return Result.success(app); } @Override @Transactional public Result approve(Long id, Long reviewerId) { ServiceRoleApplication app = this.getById(id); if (app == null) { return Result.error("申请记录不存在"); } if (app.getStatus() != 0) { return Result.error("该申请已处理"); } app.setStatus(1); // approved app.setReviewedBy(reviewerId); app.setReviewedAt(LocalDateTime.now()); this.updateById(app); // 同步各角色状态 List roles = JSON.parseArray(app.getRoles(), String.class); User user = userMapper.selectById(app.getUserId()); for (String role : roles) { switch (role) { case "planner": // 创建 Guide 记录 Guide guide = new Guide(); guide.setUserId(app.getUserId()); guide.setStatus("active"); guide.setProvince(app.getProvince()); guide.setCity(app.getCity()); guide.setDistrict(app.getDistrict()); guide.setStreet(app.getStreet()); guideMapper.insert(guide); break; case "nutritionist": user.setNutritionistStatus("approved"); break; case "butler": user.setButlerStatus("approved"); // 创建 ButlerProfile ButlerProfile bp = new ButlerProfile(); bp.setUserId(app.getUserId()); bp.setStatus("active"); butlerProfileMapper.insert(bp); break; case "vendor": user.setVendorStatus("approved"); break; case "article_admin": user.setArticleAdminStatus("approved"); break; case "activity_provider": user.setActivityProviderStatus("approved"); break; } } userMapper.updateById(user); return Result.success("审核通过"); } @Override @Transactional public Result reject(Long id, String reason, Long reviewerId) { ServiceRoleApplication app = this.getById(id); if (app == null) { return Result.error("申请记录不存在"); } if (app.getStatus() != 0) { return Result.error("该申请已处理"); } app.setStatus(2); // rejected app.setRejectReason(reason); app.setReviewedBy(reviewerId); app.setReviewedAt(LocalDateTime.now()); this.updateById(app); return Result.success("已驳回"); } } ``` - [ ] **Step 3: 编译验证** ```bash cd cfc-backend && mvn clean compile ``` Expected: BUILD SUCCESS - [ ] **Step 4: 提交** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/ServiceRoleApplicationService.java \ cfc-backend/src/main/java/com/etotem/cfc/service/impl/ServiceRoleApplicationServiceImpl.java git commit -m "feat(backend): add ServiceRoleApplicationService with submit/approve/reject" ``` --- ### Task 4: 后端 Controller + 图片上传 **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/ServiceRoleApplicationController.java` **Interfaces:** - Consumes: `ServiceRoleApplicationService` (Task 3) - Produces: `POST /api/service-role/apply`, `POST /api/service-role/my-apply`, `POST /api/service-role/upload-image` - [ ] **Step 1: 创建 Controller** `cfc-backend/src/main/java/com/etotem/cfc/controller/ServiceRoleApplicationController.java`: ```java package com.etotem.cfc.controller; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.ServiceRoleApplyDTO; import com.etotem.cfc.service.ServiceRoleApplicationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.validation.Valid; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.UUID; @Tag(name = "服务角色申请", description = "统一服务角色申请入口") @RestController @RequestMapping("/api/service-role") public class ServiceRoleApplicationController { @Resource private ServiceRoleApplicationService serviceRoleApplicationService; @Value("${upload.path:/data/uploads}") private String uploadPath; @PostMapping("/apply") @Operation(summary = "提交服务角色申请") public Result apply(@RequestAttribute("userId") Long userId, @Valid @RequestBody ServiceRoleApplyDTO dto) { return serviceRoleApplicationService.submit(userId, dto); } @PostMapping("/my-apply") @Operation(summary = "查询我的申请记录") public Result myApply(@RequestAttribute("userId") Long userId) { return serviceRoleApplicationService.getMyApplication(userId); } @PostMapping("/upload-image") @Operation(summary = "上传图片(身份证/资质证书)") public Result uploadImage(@RequestAttribute("userId") Long userId, @RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("请选择文件"); } try { String dir = uploadPath + "/service-role/" + userId; File dirFile = new File(dir); if (!dirFile.exists()) { dirFile.mkdirs(); } String ext = file.getOriginalFilename(); ext = ext != null && ext.contains(".") ? ext.substring(ext.lastIndexOf(".")) : ".jpg"; String fileName = UUID.randomUUID().toString() + ext; File dest = new File(dirFile, fileName); file.transferTo(dest); String url = "/uploads/service-role/" + userId + "/" + fileName; return Result.success(Map.of("url", url)); } catch (IOException e) { return Result.error("上传失败: " + e.getMessage()); } } } ``` Note: 检查路由重复: ```bash grep -rn '@PostMapping\|@GetMapping\|@PutMapping\|@DeleteMapping' src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u | grep service-role ``` Expected: only `/api/service-role/*` routes, no duplicates. - [ ] **Step 2: 编译验证** ```bash cd cfc-backend && mvn clean compile ``` Expected: BUILD SUCCESS - [ ] **Step 3: 提交** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/controller/ServiceRoleApplicationController.java git commit -m "feat(backend): add ServiceRoleApplicationController with apply/upload endpoints" ``` --- ### Task 5: 前端 API 函数 + 页面注册 **Files:** - Modify: `cfc-frontend/utils/api.js` - Modify: `cfc-frontend/pages.json` **Interfaces:** - Consumes: Backend endpoints (Task 4) - Produces: API functions `submitServiceRoleApply`, `getMyServiceRoleApply`, `uploadServiceRoleImage`; page registered in `pages.json` - [ ] **Step 1: 在 api.js 添加 API 函数** 在 `cfc-frontend/utils/api.js` 末尾(与其他 apply 函数放在一起)追加: ```javascript // 服务角色统一申请 export const submitServiceRoleApply = (data) => { return request('/api/service-role/apply', 'POST', data) } export const getMyServiceRoleApply = () => { return request('/api/service-role/my-apply', 'POST', {}) } export const uploadServiceRoleImage = (filePath) => { return new Promise((resolve, reject) => { uni.uploadFile({ url: BASE_URL + '/api/service-role/upload-image', filePath: filePath, name: 'file', header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') }, success: (res) => { if (res.statusCode === 200) { const data = JSON.parse(res.data) resolve(data) } else { reject(new Error('上传失败')) } }, fail: reject }) }) } ``` - [ ] **Step 2: 在 pages.json 注册新页面** 在 `pages.json` 的 profile 子包 pages 数组中追加: ```json { "path": "service-role-apply", "style": { "navigationBarTitleText": "服务角色申请" } } ``` - [ ] **Step 3: 提交** ```bash git add cfc-frontend/utils/api.js cfc-frontend/pages.json git commit -m "feat(frontend): add API functions and page registration for service role apply" ``` --- ### Task 6: 前端统一申请页 **Files:** - Create: `cfc-frontend/pages/profile/service-role-apply.vue` **Interfaces:** - Consumes: API functions from Task 5, `` component from cfc-frontend - Produces: Full apply page with role multi-select, ID card upload, certificate upload, address picker, submit - [ ] **Step 1: 创建 service-role-apply.vue** ```vue ``` - [ ] **Step 2: 提交** ```bash git add cfc-frontend/pages/profile/service-role-apply.vue git commit -m "feat(frontend): add service role apply page with multi-select, upload, address" ``` --- ### Task 7: ProfileMenu 入口 **Files:** - Modify: `cfc-frontend/pages/profile/components/ProfileMenu.vue` **Interfaces:** - Consumes: Page created in Task 6 - Produces: Menu item visible in "个人" group - [ ] **Step 1: 添加菜单项** 在 `ProfileMenu.vue` 的"个人"分组中,在"报告管理"之后追加: ```vue 📋 服务角色申请 › ``` - [ ] **Step 2: 添加 methods 方法** 在 `methods` 中添加: ```js goToServiceRoleApply() { uni.navigateTo({ url: '/pages/profile/service-role-apply' }) } ``` - [ ] **Step 3: 提交** ```bash git add cfc-frontend/pages/profile/components/ProfileMenu.vue git commit -m "feat(frontend): add service role apply entry in ProfileMenu" ``` --- ### Task 8: Web 管理端审核页面 **Files:** - Create: `cfc-web/src/views/service/ServiceRoleApplications.vue` - Modify: `cfc-web/src/router/index.js` **Interfaces:** - Consumes: `POST /api/service-role/apply` (approve/reject through admin endpoints) - Produces: Web admin review page for pending applications - [ ] **Step 1: 创建审核页面** `cfc-web/src/views/service/ServiceRoleApplications.vue`: ```vue ``` - [ ] **Step 2: 添加后端管理端接口** 在 `ServiceRoleApplicationController` 中添加管理端接口(追加到同一个 Controller 中): ```java @PostMapping("/admin/list") @Operation(summary = "管理端: 获取申请列表") public Result list(@RequestParam(required = false, defaultValue = "0") Integer status) { List list = serviceRoleApplicationService.list( new LambdaQueryWrapper() .eq(status != null, ServiceRoleApplication::getStatus, status) .orderByDesc(ServiceRoleApplication::getCreatedAt)); return Result.success(list); } @PostMapping("/admin/approve") @Operation(summary = "管理端: 批准申请") public Result adminApprove(@RequestBody Map params, @RequestAttribute("userId") Long adminId) { Long id = Long.valueOf(params.get("id").toString()); return serviceRoleApplicationService.approve(id, adminId); } @PostMapping("/admin/reject") @Operation(summary = "管理端: 驳回申请") public Result adminReject(@RequestBody Map params, @RequestAttribute("userId") Long adminId) { Long id = Long.valueOf(params.get("id").toString()); String reason = params.get("reason") != null ? params.get("reason").toString() : ""; return serviceRoleApplicationService.reject(id, reason, adminId); } ``` - [ ] **Step 3: 添加管理端路由** 在 `cfc-web/src/router/index.js` 中添加路由: ```javascript { path: '/service/service-role-applications', name: 'ServiceRoleApplications', component: () => import('@/views/service/ServiceRoleApplications.vue'), meta: { title: '服务角色申请审核', roles: ['admin'] } } ``` - [ ] **Step 4: 提交** ```bash git add cfc-web/src/views/service/ServiceRoleApplications.vue \ cfc-web/src/router/index.js \ cfc-backend/src/main/java/com/etotem/cfc/controller/ServiceRoleApplicationController.java git commit -m "feat(admin): add service role application review page and admin endpoints" ``` --- ### Task 9: 替换旧申请页(可选) **Files:** - Modify: `cfc-frontend/pages/butler/apply.vue` - Modify: `cfc-frontend/pages/nutritionist/apply.vue` - Modify: `cfc-frontend/pages/vendor/apply.vue` - Modify: `cfc-frontend/pages/guide/register/index.vue` - [ ] **Step 1: 将旧申请页重定向到统一入口** 在每个旧页面的 `onLoad` 或页面顶部添加重定向逻辑: ```javascript // 例:在 butler/apply.vue 的 onLoad 中添加 onLoad() { uni.redirectTo({ url: '/pages/profile/service-role-apply' }) } ``` - [ ] **Step 2: 提交** ```bash git add cfc-frontend/pages/butler/apply.vue \ cfc-frontend/pages/nutritionist/apply.vue \ cfc-frontend/pages/vendor/apply.vue \ cfc-frontend/pages/guide/register/index.vue git commit -m "refactor: redirect old apply pages to unified service role apply page" ```