# Phase 0: 骨架 — 角色模型 + TabBar + 服务商入驻 > **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:** Extend User entity with vendor/service provider fields, add VendorController for vendor application flow, change TabBar from 首页/任务/心愿单/我的 to 首页/发现/商城/我的, create placeholder pages, add membership banner to homepage, and add vendor review page to Web admin. **Architecture:** Phase 0 is the foundation — all subsequent phases depend on the role model extension and TabBar skeleton. Backend: add 4 fields to User + DatabaseInitializer migration + new VendorController/VendorService. Frontend: change pages.json TabBar + 3 new placeholder pages + update profile + update index. Web: add vendor review route + page. **Tech Stack:** Java 1.8 + Spring Boot 2.7 + MyBatis-Plus + uni-app Vue 2 + Element UI **Spec reference:** `docs/superpowers/specs/2026-06-02-care-family-club-design.md` (溪艾福 Care Family Club / CFC) **Key constraints:** - All entities use `java.util.Date` and `implements Serializable` - All controllers use `@RequestMapping("/api/...")` + `@PostMapping` - DI via `@Resource` - No indexes, no constraints, no custom SQL (DDL in DatabaseInitializer only) - Vue 2 Options API, uni-app tags, rpx units, NO optional chaining `?.` --- ### Task 1: User entity — add vendor/service provider fields **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/entity/User.java` - [ ] **Step 1: Add 4 fields to User.java** Add after the existing `familyRole` field (before `createdAt`): ```java // 服务商类型: null=普通用户 / planner / activity_provider / product_supplier private String vendorType; // 服务商审核状态: null=未申请 / pending / approved / rejected private String vendorStatus; // 服务商审核拒绝原因 private String vendorRejectReason; // 家庭管理员标记 private Boolean isFamilyAdmin; ``` - [ ] **Step 2: Verify compilation** Run: `cd cfc-backend && mvn clean compile -q` Expected: BUILD SUCCESS --- ### Task 2: DatabaseInitializer — add vendor columns migration **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` - [ ] **Step 1: Find the `runMigrations()` method and add ALTER TABLE statements** Search for `private void runMigrations()` in DatabaseInitializer.java. Add at the end of the migration SQL list: ```java // Phase 0: 溪福俱乐部 — 服务商字段 "ALTER TABLE users ADD COLUMN IF NOT EXISTS vendor_type VARCHAR(32) DEFAULT NULL COMMENT '服务商类型'", "ALTER TABLE users ADD COLUMN IF NOT EXISTS vendor_status VARCHAR(16) DEFAULT NULL COMMENT '服务商审核状态'", "ALTER TABLE users ADD COLUMN IF NOT EXISTS vendor_reject_reason VARCHAR(500) DEFAULT NULL COMMENT '审核拒绝原因'", "ALTER TABLE users ADD COLUMN IF NOT EXISTS is_family_admin TINYINT DEFAULT 0 COMMENT '家庭管理员'", ``` If `ADD COLUMN IF NOT EXISTS` is not supported by the MySQL version (8.0 should support it), use the try-catch pattern that already exists in the file. Inspect existing migrations to see the pattern used. - [ ] **Step 2: Add teacher→vendor data migration** At the end of `runMigrations()`, add data migration for existing teachers: ```java // Phase 0: 迁移已有规划师到服务商体系 jdbcTemplate.update( "UPDATE users SET vendor_type = 'planner', vendor_status = 'approved' WHERE teacher_status = 'approved' AND vendor_type IS NULL" ); ``` - [ ] **Step 3: Verify compilation** Run: `cd cfc-backend && mvn clean compile -q` Expected: BUILD SUCCESS --- ### Task 3: VendorController + VendorService (backend) **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/vendor/VendorController.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/service/VendorService.java` - Modify: `cfc-backend/src/main/java/com/etotem/cfc/mapper/UserMapper.java` (only if methods are missing, likely already has basic CRUD) - [ ] **Step 1: Create VendorService** ```java package com.etotem.cfc.service; import com.etotem.cfc.common.Result; import com.etotem.cfc.entity.User; import com.etotem.cfc.mapper.UserMapper; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Date; @Service public class VendorService { @Resource private UserMapper userMapper; /** * 提交服务商入驻申请 */ public Result apply(Long userId, String vendorType, String vendorInfo) { User user = userMapper.selectById(userId); if (user == null) { return Result.error("用户不存在"); } if (user.getVendorStatus() != null && !"rejected".equals(user.getVendorStatus())) { return Result.error("已有入驻申请,请勿重复提交"); } user.setVendorType(vendorType); user.setVendorStatus("pending"); user.setVendorRejectReason(null); user.setUpdatedAt(new Date()); userMapper.updateById(user); return Result.success("入驻申请已提交,请等待审核"); } /** * 查询入驻审核状态 */ public Result getStatus(Long userId) { User user = userMapper.selectById(userId); if (user == null) { return Result.error("用户不存在"); } java.util.Map data = new java.util.HashMap<>(); data.put("vendorType", user.getVendorType()); data.put("vendorStatus", user.getVendorStatus()); data.put("vendorRejectReason", user.getVendorRejectReason()); return Result.success(data); } /** * 获取/编辑服务商信息(仅供已通过服务商使用) */ public Result getVendorInfo(Long userId) { User user = userMapper.selectById(userId); if (user == null) { return Result.error("用户不存在"); } if (!"approved".equals(user.getVendorStatus())) { return Result.error("服务商资格未通过审核"); } java.util.Map data = new java.util.HashMap<>(); data.put("vendorType", user.getVendorType()); data.put("vendorInfo", user.getVendorInfo()); data.put("realName", user.getRealName()); data.put("phone", user.getPhone()); return Result.success(data); } } ``` - [ ] **Step 2: Create VendorController** ```java package com.etotem.cfc.controller.vendor; import com.etotem.cfc.common.Result; import com.etotem.cfc.service.VendorService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Map; @RestController @RequestMapping("/api/vendor") public class VendorController { @Resource private VendorService vendorService; /** * 提交服务商入驻申请 * POST /api/vendor/apply * Body: { "vendorType": "planner", "vendorInfo": "..." } */ @PostMapping("/apply") public Result apply(@RequestBody Map params) { Long userId = getCurrentUserId(); String vendorType = (String) params.get("vendorType"); String vendorInfo = (String) params.getOrDefault("vendorInfo", ""); return vendorService.apply(userId, vendorType, vendorInfo); } /** * 查询入驻审核状态 * POST /api/vendor/status */ @PostMapping("/status") public Result getStatus() { Long userId = getCurrentUserId(); return vendorService.getStatus(userId); } /** * 获取服务商信息 * POST /api/vendor/info */ @PostMapping("/info") public Result getVendorInfo() { Long userId = getCurrentUserId(); return vendorService.getVendorInfo(userId); } /** * 从请求头获取当前用户ID * 与现有AuthController中模式一致 */ private Long getCurrentUserId() { String userIdStr = org.springframework.web.context.request.RequestContextHolder .currentRequestAttributes() .getRequest() .getHeader("X-User-Id"); return userIdStr != null ? Long.parseLong(userIdStr) : null; } } ``` - [ ] **Step 3: Verify compilation** Run: `cd cfc-backend && mvn clean compile -q` Check: The new directory `controller/vendor/` is picked up by Spring component scan. Verify no bean name conflicts. --- ### Task 4: Admin vendor API — Web端审核服务商入驻 **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java` - [ ] **Step 1: Add admin vendor review endpoints to AdminController** Find the AdminController file. Add the following methods: ```java /** * 服务商入驻申请列表 * POST /api/admin/vendor/list */ @PostMapping("/vendor/list") public Result vendorList(@RequestBody Map params) { // 查询所有vendor_status非空的用户 Integer page = (Integer) params.getOrDefault("page", 1); Integer size = (Integer) params.getOrDefault("size", 20); String status = (String) params.get("status"); // pending / approved / rejected / null=全部 // 使用MyBatis-Plus QueryWrapper com.baomidou.mybatisplus.core.conditions.query.QueryWrapper wrapper = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>(); wrapper.isNotNull("vendor_type"); if (status != null && !status.isEmpty()) { wrapper.eq("vendor_status", status); } wrapper.orderByDesc("updated_at"); com.baomidou.mybatisplus.extension.plugins.pagination.Page pageObj = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, size); com.baomidou.mybatisplus.core.metadata.IPage result = userMapper.selectPage(pageObj, wrapper); java.util.Map data = new java.util.HashMap<>(); data.put("list", result.getRecords()); data.put("total", result.getTotal()); data.put("page", page); data.put("size", size); return Result.success(data); } /** * 审核服务商入驻 * POST /api/admin/vendor/review * Body: { "userId": 1, "action": "approve" / "reject", "reason": "拒绝原因" } */ @PostMapping("/vendor/review") public Result vendorReview(@RequestBody Map params) { Long userId = Long.valueOf(params.get("userId").toString()); String action = (String) params.get("action"); String reason = (String) params.getOrDefault("reason", ""); User user = userMapper.selectById(userId); if (user == null) { return Result.error("用户不存在"); } if ("approve".equals(action)) { user.setVendorStatus("approved"); user.setVendorRejectReason(null); } else if ("reject".equals(action)) { user.setVendorStatus("rejected"); user.setVendorRejectReason(reason); } else { return Result.error("无效的审核操作"); } user.setUpdatedAt(new Date()); userMapper.updateById(user); return Result.success("审核完成"); } ``` Add the required imports at the top: ```java import com.etotem.cfc.entity.User; import com.etotem.cfc.mapper.UserMapper; import java.util.Date; ``` And confirm `@Resource private UserMapper userMapper;` is present in the class. - [ ] **Step 2: Verify compilation** Run: `cd cfc-backend && mvn clean compile -q` Expected: BUILD SUCCESS --- ### Task 5: Frontend API additions — vendor module **Files:** - Modify: `cfc-frontend/utils/api.js` - [ ] **Step 1: Add vendor API calls to api.js** Find the end of file. Add: ```javascript // ===================== 溪福俱乐部 - 服务商模块 ===================== // 提交服务商入驻申请 export const vendorApply = (data) => { return request('/api/vendor/apply', 'POST', data) } // 查询入驻审核状态 export const vendorStatus = () => { return request('/api/vendor/status', 'POST', {}) } // 获取服务商信息 export const vendorInfo = () => { return request('/api/vendor/info', 'POST', {}) } ``` --- ### Task 6: TabBar change — pages.json **Files:** - Modify: `cfc-frontend/pages.json` - [ ] **Step 1: Change TabBar list and add new page registrations** Replace the `tabBar.list` with: ```json "tabBar": { "color": "#7A7E83", "selectedColor": "#FF6B6B", "borderStyle": "black", "backgroundColor": "#FFFFFF", "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/discover/index", "text": "发现" }, { "pagePath": "pages/shop/index", "text": "商城" }, { "pagePath": "pages/profile/profile", "text": "我的" } ] } ``` Add new page registrations in the `pages` array (before the `globalStyle` section). Place after the last existing entry: ```json // ===== 溪福俱乐部 Phase 0 ===== { "path": "pages/discover/index", "style": { "navigationBarTitleText": "发现" } }, { "path": "pages/shop/index", "style": { "navigationBarTitleText": "商城" } }, { "path": "pages/vendor/apply", "style": { "navigationBarTitleText": "服务商入驻" } }, { "path": "pages/vendor/center", "style": { "navigationBarTitleText": "服务商中心" } } ``` --- ### Task 7: Discover placeholder page **Files:** - Create: `cfc-frontend/pages/discover/index.vue` - [ ] **Step 1: Create discover placeholder page** ```vue ``` --- ### Task 8: Shop placeholder page **Files:** - Create: `cfc-frontend/pages/shop/index.vue` - [ ] **Step 1: Create shop placeholder page** ```vue ``` --- ### Task 9: Vendor apply page (frontend) **Files:** - Create: `cfc-frontend/pages/vendor/apply.vue` - [ ] **Step 1: Create vendor application form page** ```vue