# Phase 1: 商品系统 — 实现计划 > **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:** 在 Phase 0 角色模型基础上,搭建完整的商品发布、浏览、购买、订单管理、系统配置能力,为 Phase 2 会员体系提供商品基础。 **Architecture:** 3 个新实体(Product/ProductOrder/SysConfig)+ 3 个新 Controller 包(product/ + config/)+ 5 个新小程序页面 + 3 个新 Web 管理页面。服务商可发布商品(需审核),用户可在发现/商城浏览并下单,管理员可在 Web 端审核/管理商品和订单。商品价格支持原价+会员价,等值商品选择留给 Phase 2。 **Tech Stack:** Java 1.8 + Spring Boot 2.7.18 + MyBatis-Plus + uni-app Vue 2 + Element UI **Spec reference:** `docs/superpowers/specs/2026-06-02-care-family-club-design.md` (溪福俱乐部 §3.2-3.6, §9.2, §10.1, §11.1) **依赖关系:** - 依赖 Phase 0 的 User.vendorType / vendorStatus(服务商必须为 approved 才能发布商品) - 依赖 Phase 0 的 TabBar 改版(商城/发现 Tab 已就位) - 为 Phase 2 提供 Product 基础(等值商品选择依赖此 Phase) **Key constraints:** - 所有实体使用 `java.util.Date` 和 `implements Serializable` - 所有 Controller 使用 `@RequestMapping("/api/...")` + `@PostMapping` - DI 通过 `@Resource` - 不建索引、不建约束、不写自定义 SQL(DDL 全部在 DatabaseInitializer) - Vue 2 Options API,uni-app 标签,rpx 单位,**禁止可选链 `?.`** - 支付走测试模式:直接"已支付",无真实微信支付(Phase 3+ 再接入) - 商品发布需审核流程:draft → pending → approved → on_shelf/off_shelf --- ## 任务结构总览 | 任务组 | 范围 | 任务数 | |--------|------|--------| | **T1: 数据库初始化** | Product/Order/SysConfig 3 张表 DDL + 商品种子数据 | 1 | | **T2: Product 实体+Mapper+Service** | 商品 CRUD + 列表查询 | 4 | | **T3: ProductOrder 实体+Mapper+Service** | 订单创建/支付/状态流转 | 3 | | **T4: SysConfig 实体+Mapper+Service** | 系统配置 CRUD | 2 | | **T5: 后端 API Controller** | ProductController / ProductOrderController / SysConfigController / ProductVendorController | 4 | | **T6: 前端 API 封装** | cfc-frontend/utils/api.js 新增商品/订单/配置 API | 1 | | **T7: 商城页迭代** | shop/index.vue 从占位改为商品列表(含筛选) | 1 | | **T8: 发现页迭代** | discover/index.vue 从占位改为活动+课程列表 | 1 | | **T9: 商品详情页** | discover/product-detail.vue(可复用商城/发现) | 1 | | **T10: 订单列表/详情页** | shop/order-list.vue + shop/order-detail.vue | 2 | | **T11: 服务商中心扩展** | vendor/center.vue 添加商品管理/订单管理入口 + 新增 vendor/products.vue + vendor/orders.vue | 3 | | **T12: Web 商品管理** | admin/ProductManage.vue + api/product.js + router + Layout | 4 | | **T13: Web 订单管理** | admin/OrderManage.vue | 1 | | **T14: Web 系统配置** | admin/SysConfig.vue | 1 | | **T15: 验证** | 后端编译 + 前端 lint + 端到端测试 | 1 | | **总计** | | **30 任务** | --- ## T1: 数据库初始化 — Product/Order/SysConfig 三表 DDL + 商品种子数据 **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` - [ ] **Step 1: 在 DatabaseInitializer 末尾("数据库迁移完成" 日志前)追加 3 张表 DDL** 找到 `runMigrations()` 方法结束位置("数据库迁移完成" log 之前),添加: ```java // ==================== Phase 1: 溪福俱乐部商品系统 ==================== // 商品表 jdbcTemplate.execute( "CREATE TABLE IF NOT EXISTS products (" + " id BIGINT AUTO_INCREMENT PRIMARY KEY," + " name VARCHAR(200) NOT NULL," + " description VARCHAR(1000)," + " intro TEXT," + " cover_image VARCHAR(500)," + " images TEXT," + " price DECIMAL(10,2) NOT NULL DEFAULT 0," + " stock INT NOT NULL DEFAULT 0," + " sales_count INT NOT NULL DEFAULT 0," + " product_type VARCHAR(32) NOT NULL COMMENT 'activity/course/physical/digital/service'," + " vendor_id BIGINT," + " vendor_name VARCHAR(100)," + " status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT 'draft/pending/approved/rejected/on_shelf/off_shelf'," + " reject_reason VARCHAR(500)," + " member_price DECIMAL(10,2)," + " member_eligible VARCHAR(32) DEFAULT 'none' COMMENT 'all/budget/none'," + " domain VARCHAR(32) COMMENT 'action/mind/body/wisdom/wealth'," + " external_source VARCHAR(64)," + " external_id VARCHAR(128)," + " external_data TEXT," + " created_at DATETIME," + " updated_at DATETIME" + ")" ); // 订单表 jdbcTemplate.execute( "CREATE TABLE IF NOT EXISTS product_orders (" + " id BIGINT AUTO_INCREMENT PRIMARY KEY," + " order_no VARCHAR(64) NOT NULL," + " product_id BIGINT NOT NULL," + " product_name VARCHAR(200)," + " product_type VARCHAR(32)," + " buyer_id BIGINT NOT NULL," + " family_id BIGINT," + " quantity INT NOT NULL DEFAULT 1," + " unit_price DECIMAL(10,2) NOT NULL," + " total_amount DECIMAL(10,2) NOT NULL," + " status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/completed/cancelled/refunded'," + " payment_method VARCHAR(32) COMMENT 'wechat/points/membership'," + " remark VARCHAR(500)," + " paid_at DATETIME," + " created_at DATETIME," + " updated_at DATETIME" + ")" ); // 系统配置表 jdbcTemplate.execute( "CREATE TABLE IF NOT EXISTS sys_config (" + " id BIGINT AUTO_INCREMENT PRIMARY KEY," + " config_key VARCHAR(64) NOT NULL," + " config_value TEXT," + " description VARCHAR(500)," + " updated_at DATETIME" + ")" ); // SysConfig 种子数据 insertSysConfigSeed("points_exchange_rate", "0.01", "积分兑换汇率(1积分=0.01元)"); insertSysConfigSeed("points_deduct_cap", "0.5", "积分抵扣上限(50%)"); insertSysConfigSeed("member_fee_standard", "1314", "标准会员年费(元)"); insertSysConfigSeed("member_fee_premium", "3650", "高级会员年费(元)"); insertSysConfigSeed("member_eligible_budget", "1314", "等值商品选择预算上限(元)"); insertSysConfigSeed("renewal_discount_rate", "0.85", "续费折扣率"); insertSysConfigSeed("member_discount_standard", "0.9", "标准会员折扣率"); insertSysConfigSeed("member_discount_premium", "0.8", "高级会员折扣率"); // Product 种子数据(5 个示例商品,覆盖全部 5 种 productType) // 注意:vendor_id 设为 NULL(平台官方商品,无服务商归属) insertProductSeed("亲子关系工作坊", "线下半日工作坊,重建家庭对话模式", "activity", 388.00, 1L, "action", "all"); insertProductSeed("情绪管理21天训练营", "21天视频课程,识别并管理情绪", "course", 199.00, 1L, "mind", "all"); insertProductSeed("肠道菌群检测套装", "家用检测 + 报告解读", "physical", 599.00, 1L, "body", "budget"); insertProductSeed("儿童多模态认知测评", "在线测评 + 个性化建议", "digital", 99.00, 1L, "wisdom", "none"); insertProductSeed("成长规划师1对1咨询", "60分钟专业规划", "service", 500.00, 1L, "wealth", "budget"); ``` - [ ] **Step 2: 在 DatabaseInitializer 类中添加私有方法 insertSysConfigSeed 和 insertProductSeed** 在类的末尾(最后一个方法之后)添加: ```java private void insertSysConfigSeed(String key, String value, String desc) { Integer count = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM sys_config WHERE config_key = ?", Integer.class, key); if (count == null || count == 0) { jdbcTemplate.update( "INSERT INTO sys_config (config_key, config_value, description, updated_at) VALUES (?, ?, ?, NOW())", key, value, desc); } } private void insertProductSeed(String name, String desc, String type, java.math.BigDecimal price, Long vendorId, String domain, String memberEligible) { Integer count = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM products WHERE name = ? AND product_type = ?", Integer.class, name, type); if (count == null || count == 0) { jdbcTemplate.update( "INSERT INTO products (name, description, price, product_type, vendor_id, vendor_name, status, domain, member_eligible, created_at, updated_at) " + "VALUES (?, ?, ?, ?, ?, '平台官方', 'on_shelf', ?, ?, NOW(), NOW())", name, desc, price, type, vendorId, domain, memberEligible); } } ``` - [ ] **Step 3: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok`(无错误) - [ ] **Step 4: 启动后端验证表创建成功** ```bash # 查找现有后端进程 ps aux | grep "cfc-backend\|spring-boot" | grep -v grep # 如果进程存在,kill 后重启 pkill -f "cfc-backend" 2>/dev/null || true sleep 2 cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn spring-boot:run & sleep 30 # 等待启动 # 检查表是否创建(用 mysql 客户端或 REST) curl -s -X POST http://localhost:8080/api/product/list -H "Content-Type: application/json" -d '{"page":1,"size":10}' ``` Expected: 返回 JSON 含 `code: 200`,5 个商品数据(虽然 ProductController 还没实现,会报 404,这是正常的) - [ ] **Step 5: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java git commit -m "feat(phase1): 商品/订单/系统配置 3 张表 DDL + 种子数据" ``` --- ## T2: Product 实体 + Mapper + Service ### Task 2.1: Product 实体类 **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java` - [ ] **Step 1: 创建 Product.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.io.Serializable; import java.math.BigDecimal; import java.util.Date; @Data @TableName("products") public class Product implements Serializable { @TableId(type = IdType.AUTO) private Long id; private String name; private String description; private String intro; private String coverImage; private String images; private BigDecimal price; private Integer stock; private Integer salesCount; private String productType; // activity/course/physical/digital/service private Long vendorId; private String vendorName; private String status; // draft/pending/approved/rejected/on_shelf/off_shelf private String rejectReason; private BigDecimal memberPrice; private String memberEligible; // all/budget/none private String domain; // action/mind/body/wisdom/wealth private String externalSource; private String externalId; private String externalData; private Date createdAt; private Date updatedAt; } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` --- ### Task 2.2: Product Mapper **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/ProductMapper.java` - [ ] **Step 1: 创建 ProductMapper.java** ```java package com.etotem.cfc.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.etotem.cfc.entity.Product; import org.apache.ibatis.annotations.Mapper; @Mapper public interface ProductMapper extends BaseMapper { } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` --- ### Task 2.3: Product DTO 类 **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/dto/ProductDTO.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/dto/ProductListQueryDTO.java` - [ ] **Step 1: 创建 ProductDTO.java** ```java package com.etotem.cfc.dto; import com.etotem.cfc.entity.Product; import lombok.Data; import java.math.BigDecimal; import java.util.Date; @Data public class ProductDTO { private Long id; private String name; private String description; private String intro; private String coverImage; private String images; private BigDecimal price; private BigDecimal memberPrice; private Integer stock; private Integer salesCount; private String productType; private Long vendorId; private String vendorName; private String status; private String domain; private String memberEligible; private Date createdAt; private Date updatedAt; public static ProductDTO from(Product p) { if (p == null) return null; ProductDTO d = new ProductDTO(); d.id = p.getId(); d.name = p.getName(); d.description = p.getDescription(); d.intro = p.getIntro(); d.coverImage = p.getCoverImage(); d.images = p.getImages(); d.price = p.getPrice(); d.memberPrice = p.getMemberPrice(); d.stock = p.getStock(); d.salesCount = p.getSalesCount(); d.productType = p.getProductType(); d.vendorId = p.getVendorId(); d.vendorName = p.getVendorName(); d.status = p.getStatus(); d.domain = p.getDomain(); d.memberEligible = p.getMemberEligible(); d.createdAt = p.getCreatedAt(); d.updatedAt = p.getUpdatedAt(); return d; } } ``` - [ ] **Step 2: 创建 ProductListQueryDTO.java** ```java package com.etotem.cfc.dto; import lombok.Data; @Data public class ProductListQueryDTO { private Integer page = 1; private Integer size = 10; private String productType; // 可选筛选 private String domain; // 可选筛选 private Long vendorId; // 可选筛选(查询某服务商的商品) private String keyword; // 可选搜索关键字 } ``` - [ ] **Step 3: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` --- ### Task 2.4: ProductService **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/service/ProductService.java` - [ ] **Step 1: 创建 ProductService.java** ```java package com.etotem.cfc.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.ProductDTO; import com.etotem.cfc.dto.ProductListQueryDTO; import com.etotem.cfc.entity.Product; import com.etotem.cfc.entity.User; import com.etotem.cfc.mapper.ProductMapper; import com.etotem.cfc.mapper.UserMapper; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Service public class ProductService { @Resource private ProductMapper productMapper; @Resource private UserMapper userMapper; /** * 商品列表(公开 - 仅显示已上架商品) */ public Result> list(ProductListQueryDTO query) { Page page = new Page<>(query.getPage(), query.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(Product::getStatus, "on_shelf") .orderByDesc(Product::getCreatedAt); if (query.getProductType() != null && !query.getProductType().isEmpty()) { wrapper.eq(Product::getProductType, query.getProductType()); } if (query.getDomain() != null && !query.getDomain().isEmpty()) { wrapper.eq(Product::getDomain, query.getDomain()); } if (query.getKeyword() != null && !query.getKeyword().isEmpty()) { wrapper.like(Product::getName, query.getKeyword()); } IPage result = productMapper.selectPage(page, wrapper); List records = result.getRecords().stream() .map(ProductDTO::from) .collect(Collectors.toList()); Map data = new HashMap<>(); data.put("records", records); data.put("total", result.getTotal()); data.put("page", result.getCurrent()); data.put("size", result.getSize()); return Result.success(data); } /** * 商品详情(公开) */ public Result detail(Long id) { Product product = productMapper.selectById(id); if (product == null) { return Result.error("商品不存在"); } if (!"on_shelf".equals(product.getStatus()) && !"approved".equals(product.getStatus())) { // 非上架商品,仅创建者和管理员可见 return Result.error("商品未上架"); } return Result.success(ProductDTO.from(product)); } /** * 服务商发布商品 */ public Result create(Product product, Long vendorId) { if (vendorId == null) { return Result.error("请先登录"); } User vendor = userMapper.selectById(vendorId); if (vendor == null || !"approved".equals(vendor.getVendorStatus())) { return Result.error("仅审核通过的服务商可发布商品"); } product.setVendorId(vendorId); product.setVendorName(vendor.getNickname() != null ? vendor.getNickname() : vendor.getRealName()); product.setStatus("pending"); // 待管理员审核 product.setCreatedAt(new Date()); product.setUpdatedAt(new Date()); productMapper.insert(product); return Result.success(ProductDTO.from(product)); } /** * 服务商更新商品 */ public Result update(Product product, Long vendorId) { Product existing = productMapper.selectById(product.getId()); if (existing == null) { return Result.error("商品不存在"); } if (!existing.getVendorId().equals(vendorId)) { return Result.error("无权修改此商品"); } // 服务商更新后回到 pending 状态待审核 product.setStatus("pending"); product.setUpdatedAt(new Date()); productMapper.updateById(product); return Result.success(ProductDTO.from(productMapper.selectById(product.getId()))); } /** * 服务商查询自己的商品(含各状态) */ public Result> myProducts(Long vendorId) { List list = productMapper.selectList( new LambdaQueryWrapper() .eq(Product::getVendorId, vendorId) .orderByDesc(Product::getCreatedAt) ); List dtos = list.stream().map(ProductDTO::from).collect(Collectors.toList()); return Result.success(dtos); } /** * 服务商上架/下架商品 */ public Result shelve(Long productId, Long vendorId, String action) { Product product = productMapper.selectById(productId); if (product == null) { return Result.error("商品不存在"); } if (!product.getVendorId().equals(vendorId)) { return Result.error("无权操作"); } if (!"approved".equals(product.getStatus())) { return Result.error("商品未通过审核,无法上下架"); } if ("shelve".equals(action)) { product.setStatus("on_shelf"); } else if ("unshelve".equals(action)) { product.setStatus("off_shelf"); } else { return Result.error("操作类型错误"); } product.setUpdatedAt(new Date()); productMapper.updateById(product); return Result.success("操作成功"); } /** * 管理员审核商品 */ public Result review(Long productId, String action, String rejectReason) { Product product = productMapper.selectById(productId); if (product == null) { return Result.error("商品不存在"); } if (!"pending".equals(product.getStatus())) { return Result.error("仅待审核商品可审核"); } if ("approve".equals(action)) { product.setStatus("approved"); product.setRejectReason(null); } else if ("reject".equals(action)) { product.setStatus("rejected"); product.setRejectReason(rejectReason != null ? rejectReason : "不符合规范"); } else { return Result.error("操作类型错误"); } product.setUpdatedAt(new Date()); productMapper.updateById(product); return Result.success("操作成功"); } /** * 管理员查询待审核商品 */ public Result> pendingReview() { List list = productMapper.selectList( new LambdaQueryWrapper() .eq(Product::getStatus, "pending") .orderByAsc(Product::getCreatedAt) ); List dtos = list.stream().map(ProductDTO::from).collect(Collectors.toList()); return Result.success(dtos); } /** * 管理员查询所有商品 */ public Result> adminList(String status) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .orderByDesc(Product::getCreatedAt); if (status != null && !status.isEmpty()) { wrapper.eq(Product::getStatus, status); } List list = productMapper.selectList(wrapper); List dtos = list.stream().map(ProductDTO::from).collect(Collectors.toList()); return Result.success(dtos); } /** * 减少库存(订单创建时调用) */ public boolean decreaseStock(Long productId, Integer quantity) { Product product = productMapper.selectById(productId); if (product == null || product.getStock() < quantity) { return false; } product.setStock(product.getStock() - quantity); product.setSalesCount((product.getSalesCount() != null ? product.getSalesCount() : 0) + quantity); product.setUpdatedAt(new Date()); productMapper.updateById(product); return true; } } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java cfc-backend/src/main/java/com/etotem/cfc/mapper/ProductMapper.java cfc-backend/src/main/java/com/etotem/cfc/dto/ProductDTO.java cfc-backend/src/main/java/com/etotem/cfc/dto/ProductListQueryDTO.java cfc-backend/src/main/java/com/etotem/cfc/service/ProductService.java git commit -m "feat(phase1): Product 实体/Mapper/Service 实现" ``` --- ## T3: ProductOrder 实体 + Mapper + Service ### Task 3.1: ProductOrder 实体 + Mapper + DTO **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/ProductOrder.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/ProductOrderMapper.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/dto/ProductOrderDTO.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/dto/CreateProductOrderDTO.java` - [ ] **Step 1: 创建 ProductOrder.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.io.Serializable; import java.math.BigDecimal; import java.util.Date; @Data @TableName("product_orders") public class ProductOrder implements Serializable { @TableId(type = IdType.AUTO) private Long id; private String orderNo; private Long productId; private String productName; private String productType; private Long buyerId; private Long familyId; private Integer quantity; private BigDecimal unitPrice; private BigDecimal totalAmount; private String status; // pending/paid/completed/cancelled/refunded private String paymentMethod; // wechat/points/membership private String remark; private Date paidAt; private Date createdAt; private Date updatedAt; } ``` - [ ] **Step 2: 创建 ProductOrderMapper.java** ```java package com.etotem.cfc.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.etotem.cfc.entity.ProductOrder; import org.apache.ibatis.annotations.Mapper; @Mapper public interface ProductOrderMapper extends BaseMapper { } ``` - [ ] **Step 3: 创建 ProductOrderDTO.java** ```java package com.etotem.cfc.dto; import com.etotem.cfc.entity.ProductOrder; import lombok.Data; import java.math.BigDecimal; import java.util.Date; @Data public class ProductOrderDTO { private Long id; private String orderNo; private Long productId; private String productName; private String productType; private Long buyerId; private String buyerName; private Long familyId; private Integer quantity; private BigDecimal unitPrice; private BigDecimal totalAmount; private String status; private String paymentMethod; private String remark; private Date paidAt; private Date createdAt; public static ProductOrderDTO from(ProductOrder o) { if (o == null) return null; ProductOrderDTO d = new ProductOrderDTO(); d.id = o.getId(); d.orderNo = o.getOrderNo(); d.productId = o.getProductId(); d.productName = o.getProductName(); d.productType = o.getProductType(); d.buyerId = o.getBuyerId(); d.familyId = o.getFamilyId(); d.quantity = o.getQuantity(); d.unitPrice = o.getUnitPrice(); d.totalAmount = o.getTotalAmount(); d.status = o.getStatus(); d.paymentMethod = o.getPaymentMethod(); d.remark = o.getRemark(); d.paidAt = o.getPaidAt(); d.createdAt = o.getCreatedAt(); return d; } } ``` - [ ] **Step 4: 创建 CreateProductOrderDTO.java** ```java package com.etotem.cfc.dto; import lombok.Data; @Data public class CreateProductOrderDTO { private Long productId; private Integer quantity = 1; private String paymentMethod; // wechat/points/membership private String remark; } ``` - [ ] **Step 5: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` --- ### Task 3.2: ProductOrderService **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java` - [ ] **Step 1: 创建 ProductOrderService.java** ```java package com.etotem.cfc.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.CreateProductOrderDTO; import com.etotem.cfc.dto.ProductOrderDTO; import com.etotem.cfc.entity.Product; import com.etotem.cfc.entity.ProductOrder; import com.etotem.cfc.entity.User; import com.etotem.cfc.mapper.ProductMapper; import com.etotem.cfc.mapper.ProductOrderMapper; import com.etotem.cfc.mapper.UserMapper; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.util.Date; import java.util.List; import java.util.stream.Collectors; @Service public class ProductOrderService { @Resource private ProductOrderMapper orderMapper; @Resource private ProductMapper productMapper; @Resource private UserMapper userMapper; @Resource private ProductService productService; /** * 创建订单 */ public Result create(CreateProductOrderDTO dto, Long buyerId) { if (buyerId == null) { return Result.error("请先登录"); } Product product = productMapper.selectById(dto.getProductId()); if (product == null) { return Result.error("商品不存在"); } if (!"on_shelf".equals(product.getStatus())) { return Result.error("商品已下架"); } if (product.getStock() != null && product.getStock() < dto.getQuantity()) { return Result.error("库存不足"); } User buyer = userMapper.selectById(buyerId); ProductOrder order = new ProductOrder(); order.setOrderNo(generateOrderNo()); order.setProductId(product.getId()); order.setProductName(product.getName()); order.setProductType(product.getProductType()); order.setBuyerId(buyerId); order.setFamilyId(buyer != null ? buyer.getFamilyId() : null); order.setQuantity(dto.getQuantity()); order.setUnitPrice(product.getPrice()); order.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(dto.getQuantity()))); order.setStatus("pending"); order.setPaymentMethod(dto.getPaymentMethod() != null ? dto.getPaymentMethod() : "wechat"); order.setRemark(dto.getRemark()); order.setCreatedAt(new Date()); order.setUpdatedAt(new Date()); orderMapper.insert(order); return Result.success(ProductOrderDTO.from(order)); } /** * 支付订单(测试模式:直接标记为已支付) */ public Result pay(String orderNo, Long userId) { ProductOrder order = orderMapper.selectOne( new LambdaQueryWrapper().eq(ProductOrder::getOrderNo, orderNo)); if (order == null) { return Result.error("订单不存在"); } if (!order.getBuyerId().equals(userId)) { return Result.error("无权操作此订单"); } if (!"pending".equals(order.getStatus())) { return Result.error("订单状态异常"); } // 减少库存 boolean stockOk = productService.decreaseStock(order.getProductId(), order.getQuantity()); if (!stockOk) { return Result.error("库存不足"); } order.setStatus("paid"); order.setPaidAt(new Date()); order.setUpdatedAt(new Date()); orderMapper.updateById(order); return Result.success(ProductOrderDTO.from(order)); } /** * 取消订单 */ public Result cancel(String orderNo, Long userId) { ProductOrder order = orderMapper.selectOne( new LambdaQueryWrapper().eq(ProductOrder::getOrderNo, orderNo)); if (order == null) { return Result.error("订单不存在"); } if (!order.getBuyerId().equals(userId)) { return Result.error("无权操作此订单"); } if (!"pending".equals(order.getStatus())) { return Result.error("仅待支付订单可取消"); } order.setStatus("cancelled"); order.setUpdatedAt(new Date()); orderMapper.updateById(order); return Result.success("订单已取消"); } /** * 买家查询自己的订单 */ public Result> myOrders(Long buyerId, String status) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(ProductOrder::getBuyerId, buyerId) .orderByDesc(ProductOrder::getCreatedAt); if (status != null && !status.isEmpty()) { wrapper.eq(ProductOrder::getStatus, status); } List list = orderMapper.selectList(wrapper); return Result.success(list.stream().map(ProductOrderDTO::from).collect(Collectors.toList())); } /** * 订单详情 */ public Result detail(String orderNo, Long userId) { ProductOrder order = orderMapper.selectOne( new LambdaQueryWrapper().eq(ProductOrder::getOrderNo, orderNo)); if (order == null) { return Result.error("订单不存在"); } // 仅买家/卖家/管理员可见 Product product = productMapper.selectById(order.getProductId()); boolean isBuyer = order.getBuyerId().equals(userId); boolean isVendor = product != null && product.getVendorId() != null && product.getVendorId().equals(userId); if (!isBuyer && !isVendor) { return Result.error("无权查看"); } return Result.success(ProductOrderDTO.from(order)); } /** * 服务商查询收到的订单 */ public Result> vendorOrders(Long vendorId, String status) { // 找出该服务商的所有商品 ID List products = productMapper.selectList( new LambdaQueryWrapper().eq(Product::getVendorId, vendorId)); if (products.isEmpty()) { return Result.success(java.util.Collections.emptyList()); } List productIds = products.stream().map(Product::getId).collect(Collectors.toList()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .in(ProductOrder::getProductId, productIds) .orderByDesc(ProductOrder::getCreatedAt); if (status != null && !status.isEmpty()) { wrapper.eq(ProductOrder::getStatus, status); } List list = orderMapper.selectList(wrapper); return Result.success(list.stream().map(ProductOrderDTO::from).collect(Collectors.toList())); } /** * 管理员查询所有订单 */ public Result> adminList(String status) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .orderByDesc(ProductOrder::getCreatedAt); if (status != null && !status.isEmpty()) { wrapper.eq(ProductOrder::getStatus, status); } List list = orderMapper.selectList(wrapper); return Result.success(list.stream().map(ProductOrderDTO::from).collect(Collectors.toList())); } /** * 服务商确认订单完成 */ public Result confirm(String orderNo, Long vendorId) { ProductOrder order = orderMapper.selectOne( new LambdaQueryWrapper().eq(ProductOrder::getOrderNo, orderNo)); if (order == null) { return Result.error("订单不存在"); } Product product = productMapper.selectById(order.getProductId()); if (product == null || !product.getVendorId().equals(vendorId)) { return Result.error("无权操作"); } if (!"paid".equals(order.getStatus())) { return Result.error("仅已支付订单可确认完成"); } order.setStatus("completed"); order.setUpdatedAt(new Date()); orderMapper.updateById(order); return Result.success("订单已完成"); } private String generateOrderNo() { return "PO" + System.currentTimeMillis(); } } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-backend/src/main/java/com/etotem/cfc/entity/ProductOrder.java cfc-backend/src/main/java/com/etotem/cfc/mapper/ProductOrderMapper.java cfc-backend/src/main/java/com/etotem/cfc/dto/ProductOrderDTO.java cfc-backend/src/main/java/com/etotem/cfc/dto/CreateProductOrderDTO.java cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java git commit -m "feat(phase1): ProductOrder 实体/Mapper/Service 实现" ``` --- ## T4: SysConfig 实体 + Mapper + Service ### Task 4.1: SysConfig 实体 + Mapper + DTO **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/SysConfig.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/SysConfigMapper.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/dto/SysConfigDTO.java` - [ ] **Step 1: 创建 SysConfig.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.io.Serializable; import java.util.Date; @Data @TableName("sys_config") public class SysConfig implements Serializable { @TableId(type = IdType.AUTO) private Long id; private String configKey; private String configValue; private String description; private Date updatedAt; } ``` - [ ] **Step 2: 创建 SysConfigMapper.java** ```java package com.etotem.cfc.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.etotem.cfc.entity.SysConfig; import org.apache.ibatis.annotations.Mapper; @Mapper public interface SysConfigMapper extends BaseMapper { } ``` - [ ] **Step 3: 创建 SysConfigDTO.java** ```java package com.etotem.cfc.dto; import com.etotem.cfc.entity.SysConfig; import lombok.Data; import java.util.Date; @Data public class SysConfigDTO { private Long id; private String configKey; private String configValue; private String description; private Date updatedAt; public static SysConfigDTO from(SysConfig c) { if (c == null) return null; SysConfigDTO d = new SysConfigDTO(); d.id = c.getId(); d.configKey = c.getConfigKey(); d.configValue = c.getConfigValue(); d.description = c.getDescription(); d.updatedAt = c.getUpdatedAt(); return d; } } ``` - [ ] **Step 4: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` --- ### Task 4.2: SysConfigService **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/service/SysConfigService.java` - [ ] **Step 1: 创建 SysConfigService.java** ```java package com.etotem.cfc.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.SysConfigDTO; import com.etotem.cfc.entity.SysConfig; import com.etotem.cfc.mapper.SysConfigMapper; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Service public class SysConfigService { @Resource private SysConfigMapper configMapper; /** * 获取所有配置(key-value 形式) */ public Result> getAllAsMap() { List list = configMapper.selectList(null); Map map = new HashMap<>(); for (SysConfig c : list) { map.put(c.getConfigKey(), c.getConfigValue()); } return Result.success(map); } /** * 获取所有配置(详细列表) */ public Result> listAll() { List list = configMapper.selectList( new LambdaQueryWrapper().orderByAsc(SysConfig::getConfigKey)); return Result.success(list.stream().map(SysConfigDTO::from).collect(Collectors.toList())); } /** * 获取单个配置 */ public Result get(String key) { SysConfig config = configMapper.selectOne( new LambdaQueryWrapper().eq(SysConfig::getConfigKey, key)); if (config == null) { return Result.error("配置不存在"); } return Result.success(SysConfigDTO.from(config)); } /** * 更新配置 */ public Result update(String key, String value) { SysConfig config = configMapper.selectOne( new LambdaQueryWrapper().eq(SysConfig::getConfigKey, key)); if (config == null) { return Result.error("配置不存在"); } config.setConfigValue(value); config.setUpdatedAt(new Date()); configMapper.updateById(config); return Result.success("更新成功"); } /** * 内部方法:获取配置值(供其他 Service 调用) */ public String getValue(String key) { SysConfig config = configMapper.selectOne( new LambdaQueryWrapper().eq(SysConfig::getConfigKey, key)); return config != null ? config.getConfigValue() : null; } } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-backend/src/main/java/com/etotem/cfc/entity/SysConfig.java cfc-backend/src/main/java/com/etotem/cfc/mapper/SysConfigMapper.java cfc-backend/src/main/java/com/etotem/cfc/dto/SysConfigDTO.java cfc-backend/src/main/java/com/etotem/cfc/service/SysConfigService.java git commit -m "feat(phase1): SysConfig 实体/Mapper/Service 实现" ``` --- ## T5: 后端 API Controller ### Task 5.1: ProductController(公开 API + 服务商 API) **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/product/ProductController.java` - [ ] **Step 1: 创建 ProductController.java** ```java package com.etotem.cfc.controller.product; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.ProductDTO; import com.etotem.cfc.dto.ProductListQueryDTO; import com.etotem.cfc.entity.Product; import com.etotem.cfc.service.ProductService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/product") public class ProductController { @Resource private ProductService productService; /** * 商品列表(公开) */ @PostMapping("/list") public Result> list(@RequestBody ProductListQueryDTO query) { return productService.list(query); } /** * 商品详情(公开) */ @PostMapping("/detail") public Result detail(@RequestBody Map params) { Long id = Long.parseLong(params.get("id").toString()); return productService.detail(id); } /** * 服务商发布商品 */ @PostMapping("/create") public Result create(@RequestBody Product product, @RequestHeader(value = "X-User-Id", required = false) Long userId) { return productService.create(product, userId); } /** * 服务商更新商品 */ @PostMapping("/update") public Result update(@RequestBody Product product, @RequestHeader(value = "X-User-Id", required = false) Long userId) { return productService.update(product, userId); } /** * 服务商查询自己的商品 */ @PostMapping("/my") public Result> myProducts(@RequestHeader(value = "X-User-Id", required = false) Long userId) { return productService.myProducts(userId); } /** * 服务商上架/下架商品 */ @PostMapping("/shelve") public Result shelve(@RequestBody Map params, @RequestHeader(value = "X-User-Id", required = false) Long userId) { Long productId = Long.parseLong(params.get("productId").toString()); String action = (String) params.get("action"); // shelve / unshelve return productService.shelve(productId, userId, action); } } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` --- ### Task 5.2: ProductOrderController **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/product/ProductOrderController.java` - [ ] **Step 1: 创建 ProductOrderController.java** ```java package com.etotem.cfc.controller.product; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.CreateProductOrderDTO; import com.etotem.cfc.dto.ProductOrderDTO; import com.etotem.cfc.service.ProductOrderService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/product/order") public class ProductOrderController { @Resource private ProductOrderService orderService; /** * 创建订单 */ @PostMapping("/create") public Result create(@RequestBody CreateProductOrderDTO dto, @RequestHeader(value = "X-User-Id", required = false) Long userId) { return orderService.create(dto, userId); } /** * 支付订单(测试模式) */ @PostMapping("/pay") public Result pay(@RequestBody Map params, @RequestHeader(value = "X-User-Id", required = false) Long userId) { String orderNo = (String) params.get("orderNo"); return orderService.pay(orderNo, userId); } /** * 取消订单 */ @PostMapping("/cancel") public Result cancel(@RequestBody Map params, @RequestHeader(value = "X-User-Id", required = false) Long userId) { String orderNo = (String) params.get("orderNo"); return orderService.cancel(orderNo, userId); } /** * 我的订单 */ @PostMapping("/my") public Result> myOrders(@RequestBody Map params, @RequestHeader(value = "X-User-Id", required = false) Long userId) { String status = (String) params.get("status"); return orderService.myOrders(userId, status); } /** * 订单详情 */ @PostMapping("/detail") public Result detail(@RequestBody Map params, @RequestHeader(value = "X-User-Id", required = false) Long userId) { String orderNo = (String) params.get("orderNo"); return orderService.detail(orderNo, userId); } /** * 服务商查询收到的订单 */ @PostMapping("/vendor") public Result> vendorOrders(@RequestBody Map params, @RequestHeader(value = "X-User-Id", required = false) Long userId) { String status = (String) params.get("status"); return orderService.vendorOrders(userId, status); } /** * 服务商确认订单完成 */ @PostMapping("/confirm") public Result confirm(@RequestBody Map params, @RequestHeader(value = "X-User-Id", required = false) Long userId) { String orderNo = (String) params.get("orderNo"); return orderService.confirm(orderNo, userId); } } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` --- ### Task 5.3: SysConfigController(管理员 API) **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/config/SysConfigController.java` - [ ] **Step 1: 创建 SysConfigController.java** ```java package com.etotem.cfc.controller.config; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.SysConfigDTO; import com.etotem.cfc.service.SysConfigService; 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.List; import java.util.Map; @RestController @RequestMapping("/api/admin/config") public class SysConfigController { @Resource private SysConfigService configService; /** * 管理员获取所有配置 */ @PostMapping("/list") public Result> list() { return configService.listAll(); } /** * 管理员更新配置 */ @PostMapping("/update") public Result update(@RequestBody Map params) { String key = (String) params.get("configKey"); String value = (String) params.get("configValue"); return configService.update(key, value); } /** * 内部使用:获取所有配置(key-value 形式) */ @PostMapping("/all") public Result> getAllAsMap() { return configService.getAllAsMap(); } } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` --- ### Task 5.4: AdminController 扩展 — 商品审核端点 **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java` - [ ] **Step 1: 找到 AdminController 类,在最后添加商品/订单审核端点** 在类的最后一个方法之后(`}` 闭合大括号之前)添加: ```java @Resource private com.etotem.cfc.service.ProductService productService; @Resource private com.etotem.cfc.service.ProductOrderService productOrderService; @PostMapping("/product/pending") public Result> productPending() { return productService.pendingReview(); } @PostMapping("/product/list") public Result> productList(@RequestBody Map params) { String status = (String) params.get("status"); return productService.adminList(status); } @PostMapping("/product/review") public Result productReview(@RequestBody Map params) { Long productId = Long.parseLong(params.get("productId").toString()); String action = (String) params.get("action"); // approve / reject String rejectReason = (String) params.get("rejectReason"); return productService.review(productId, action, rejectReason); } @PostMapping("/order/list") public Result> orderList(@RequestBody Map params) { String status = (String) params.get("status"); return productOrderService.adminList(status); } ``` - [ ] **Step 2: 验证编译** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok` - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-backend/src/main/java/com/etotem/cfc/controller/product/ProductController.java cfc-backend/src/main/java/com/etotem/cfc/controller/product/ProductOrderController.java cfc-backend/src/main/java/com/etotem/cfc/controller/config/SysConfigController.java cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java git commit -m "feat(phase1): Product/ProductOrder/SysConfig/Admin 后端 Controller" ``` --- ## T6: 前端 API 封装 **Files:** - Modify: `cfc-frontend/utils/api.js` - [ ] **Step 1: 在 api.js 末尾追加商品/订单/系统配置 API** 打开 `cfc-frontend/utils/api.js`,在 `module.exports` 之前找到合适位置(推荐放在 `vendorApply` 等 vendor API 附近),添加: ```javascript // ==================== Phase 1: 商品系统 ==================== // 商品列表(公开) const productList = (params) => request('/api/product/list', 'POST', params); // 商品详情(公开) const productDetail = (params) => request('/api/product/detail', 'POST', params); // 服务商发布商品 const productCreate = (params) => request('/api/product/create', 'POST', params); // 服务商更新商品 const productUpdate = (params) => request('/api/product/update', 'POST', params); // 服务商查询自己的商品 const productMy = (params) => request('/api/product/my', 'POST', params); // 服务商上架/下架商品 const productShelve = (params) => request('/api/product/shelve', 'POST', params); // 创建订单 const productOrderCreate = (params) => request('/api/product/order/create', 'POST', params); // 支付订单 const productOrderPay = (params) => request('/api/product/order/pay', 'POST', params); // 取消订单 const productOrderCancel = (params) => request('/api/product/order/cancel', 'POST', params); // 我的订单 const productOrderMy = (params) => request('/api/product/order/my', 'POST', params); // 订单详情 const productOrderDetail = (params) => request('/api/product/order/detail', 'POST', params); // 服务商查询收到的订单 const productOrderVendor = (params) => request('/api/product/order/vendor', 'POST', params); // 服务商确认订单完成 const productOrderConfirm = (params) => request('/api/product/order/confirm', 'POST', params); // 管理员:商品待审核列表 const adminProductPending = (params) => request('/api/admin/product/pending', 'POST', params); // 管理员:商品列表 const adminProductList = (params) => request('/api/admin/product/list', 'POST', params); // 管理员:商品审核 const adminProductReview = (params) => request('/api/admin/product/review', 'POST', params); // 管理员:订单列表 const adminOrderList = (params) => request('/api/admin/order/list', 'POST', params); // 管理员:系统配置列表 const adminConfigList = (params) => request('/api/admin/config/list', 'POST', params); // 管理员:更新系统配置 const adminConfigUpdate = (params) => request('/api/admin/config/update', 'POST', params); ``` - [ ] **Step 2: 找到 `module.exports`,添加这些函数的导出** 在 `module.exports` 对象中追加: ```javascript productList, productDetail, productCreate, productUpdate, productMy, productShelve, productOrderCreate, productOrderPay, productOrderCancel, productOrderMy, productOrderDetail, productOrderVendor, productOrderConfirm, adminProductPending, adminProductList, adminProductReview, adminOrderList, adminConfigList, adminConfigUpdate, ``` - [ ] **Step 3: 验证前端能 import 这些 API** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-frontend && rtk grep "productList\|adminProductPending" -n 2>&1 ``` Expected: 输出显示这些函数已经在 api.js 中定义和导出 - [ ] **Step 4: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/utils/api.js git commit -m "feat(phase1): 前端 api.js 新增商品/订单/系统配置 API" ``` --- ## T7: 商城页迭代 — 从占位改为商品列表 **Files:** - Modify: `cfc-frontend/pages/shop/index.vue` - [ ] **Step 1: 完整重写 pages/shop/index.vue** 用以下代码完整覆盖 `cfc-frontend/pages/shop/index.vue`: ```vue ``` - [ ] **Step 2: 验证文件保存成功** ```bash rtk wc -l /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/shop/index.vue ``` Expected: 输出行数(应该 > 100) - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/pages/shop/index.vue git commit -m "feat(phase1): 商城页 - 从占位改为商品列表(五行筛选+类型筛选)" ``` --- ## T8: 发现页迭代 **Files:** - Modify: `cfc-frontend/pages/discover/index.vue` - [ ] **Step 1: 完整重写 pages/discover/index.vue** 用以下代码完整覆盖 `cfc-frontend/pages/discover/index.vue`: ```vue ``` - [ ] **Step 2: 验证文件保存成功** ```bash rtk wc -l /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/discover/index.vue ``` Expected: 输出行数(应该 > 100) - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/pages/discover/index.vue git commit -m "feat(phase1): 发现页 - 从占位改为活动+课程列表" ``` --- ## T9: 商品详情页(公共组件,商城和发现共用) **Files:** - Create: `cfc-frontend/pages/discover/product-detail.vue` - Modify: `cfc-frontend/pages.json`(注册新页面) - [ ] **Step 1: 创建 pages/discover/product-detail.vue** ```vue ``` - [ ] **Step 2: 在 pages.json 中注册新页面** 打开 `cfc-frontend/pages.json`,找到 `pages` 数组,在最后添加: ```json { "path": "pages/discover/product-detail", "style": { "navigationBarTitleText": "商品详情" } }, ``` - [ ] **Step 3: 验证文件存在** ```bash rtk test -f /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/discover/product-detail.vue rtk grep "product-detail" /mnt/e/workspace/dan/zxyj/cfc-frontend/pages.json ``` Expected: 第二个命令输出包含 `"pages/discover/product-detail"` - [ ] **Step 4: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/pages/discover/product-detail.vue cfc-frontend/pages.json git commit -m "feat(phase1): 商品详情页(商城/发现共用)" ``` --- ## T10: 订单列表 + 订单详情页 ### Task 10.1: 订单列表页 **Files:** - Create: `cfc-frontend/pages/shop/order-list.vue` - Modify: `cfc-frontend/pages.json` - [ ] **Step 1: 创建 pages/shop/order-list.vue** ```vue ``` - [ ] **Step 2: 在 pages.json 中注册新页面** 打开 `cfc-frontend/pages.json`,在 `pages` 数组最后添加: ```json { "path": "pages/shop/order-list", "style": { "navigationBarTitleText": "我的订单" } }, ``` - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/pages/shop/order-list.vue cfc-frontend/pages.json git commit -m "feat(phase1): 订单列表页" ``` --- ### Task 10.2: 订单详情页 **Files:** - Create: `cfc-frontend/pages/shop/order-detail.vue` - Modify: `cfc-frontend/pages.json` - [ ] **Step 1: 创建 pages/shop/order-detail.vue** ```vue ``` - [ ] **Step 2: 在 pages.json 中注册新页面** 打开 `cfc-frontend/pages.json`,在 `pages` 数组最后添加: ```json { "path": "pages/shop/order-detail", "style": { "navigationBarTitleText": "订单详情" } }, ``` - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/pages/shop/order-detail.vue cfc-frontend/pages.json git commit -m "feat(phase1): 订单详情页" ``` --- ## T11: 服务商中心扩展 ### Task 11.1: 服务商商品管理页 **Files:** - Create: `cfc-frontend/pages/vendor/products.vue` - Modify: `cfc-frontend/pages.json` - [ ] **Step 1: 创建 pages/vendor/products.vue** ```vue ``` - [ ] **Step 2: 在 pages.json 中注册新页面** 打开 `cfc-frontend/pages.json`,在 `pages` 数组最后添加: ```json { "path": "pages/vendor/products", "style": { "navigationBarTitleText": "商品管理" } }, ``` - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/pages/vendor/products.vue cfc-frontend/pages.json git commit -m "feat(phase1): 服务商商品管理页" ``` --- ### Task 11.2: 服务商订单管理页 **Files:** - Create: `cfc-frontend/pages/vendor/orders.vue` - Modify: `cfc-frontend/pages.json` - [ ] **Step 1: 创建 pages/vendor/orders.vue** ```vue ``` - [ ] **Step 2: 在 pages.json 中注册新页面** 打开 `cfc-frontend/pages.json`,在 `pages` 数组最后添加: ```json { "path": "pages/vendor/orders", "style": { "navigationBarTitleText": "订单管理" } }, ``` - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/pages/vendor/orders.vue cfc-frontend/pages.json git commit -m "feat(phase1): 服务商订单管理页" ``` --- ### Task 11.3: 服务商中心菜单扩展 **Files:** - Modify: `cfc-frontend/pages/vendor/center.vue` - [ ] **Step 1: 找到 vendor/center.vue 中"菜单项"区域** 打开 `cfc-frontend/pages/vendor/center.vue`,找到 menu-items 列表(如 `...` 或类似),在每个菜单项 `` 之后添加新菜单项。 如果菜单项使用 `methods.goXxx`,在 `methods` 中添加: ```javascript goProductManage() { uni.navigateTo({ url: '/pages/vendor/products' }); }, goOrderManage() { uni.navigateTo({ url: '/pages/vendor/orders' }); }, ``` 具体位置请根据现有 center.vue 的代码结构进行调整。参考现有代码(已在 Phase 0 实现)。 - [ ] **Step 2: 验证文件被修改** ```bash rtk grep -c "goProductManage\|goOrderManage" /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/vendor/center.vue ``` Expected: 输出 >= 1(说明有新增的菜单项处理函数) - [ ] **Step 3: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-frontend/pages/vendor/center.vue git commit -m "feat(phase1): 服务商中心添加商品管理/订单管理入口" ``` --- ## T12: Web 端 — 商品管理 ### Task 12.1: Web API 封装 **Files:** - Create: `cfc-web/src/api/product.js` - [ ] **Step 1: 创建 src/api/product.js** ```javascript import request from '@/utils/request'; // 管理员:商品待审核列表 export const adminProductPending = (params) => request({ url: '/api/admin/product/pending', method: 'post', data: params }); // 管理员:商品列表 export const adminProductList = (params) => request({ url: '/api/admin/product/list', method: 'post', data: params }); // 管理员:商品审核 export const adminProductReview = (params) => request({ url: '/api/admin/product/review', method: 'post', data: params }); // 商品列表(公开) export const productList = (params) => request({ url: '/api/product/list', method: 'post', data: params }); ``` - [ ] **Step 2: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-web/src/api/product.js git commit -m "feat(phase1): Web 端 product API 封装" ``` --- ### Task 12.2: Web 商品管理页面 **Files:** - Create: `cfc-web/src/views/admin/ProductManage.vue` - [ ] **Step 1: 创建 src/views/admin/ProductManage.vue** ```vue ``` - [ ] **Step 2: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-web/src/views/admin/ProductManage.vue git commit -m "feat(phase1): Web 端商品管理页面" ``` --- ### Task 12.3: Web 路由 + 侧边栏菜单 **Files:** - Modify: `cfc-web/src/router/index.js` - Modify: `cfc-web/src/views/Layout.vue` - [ ] **Step 1: 在 router/index.js 添加商品管理路由** 打开 `cfc-web/src/router/index.js`,找到路由数组(如 `adminRoutes` 或类似),添加: ```javascript { path: '/admin/product', component: () => import('@/views/admin/ProductManage.vue'), meta: { title: '商品管理', icon: 'goods' } } ``` - [ ] **Step 2: 在 Layout.vue 侧边栏添加菜单项** 打开 `cfc-web/src/views/Layout.vue`,找到菜单列表,在"系统"或"运营"子菜单下添加: ```vue 商品管理 ``` 具体位置请根据现有 Layout.vue 的菜单结构调整。 - [ ] **Step 3: 验证文件修改** ```bash rtk grep "ProductManage\|商品管理" /mnt/e/workspace/dan/zxyj/cfc-web/src/router/index.js rtk grep "商品管理" /mnt/e/workspace/dan/zxyj/cfc-web/src/views/Layout.vue ``` Expected: 两个 grep 都有输出 - [ ] **Step 4: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-web/src/router/index.js cfc-web/src/views/Layout.vue git commit -m "feat(phase1): Web 端商品管理路由 + 菜单" ``` --- ## T13: Web 端 — 订单管理 **Files:** - Create: `cfc-web/src/views/admin/OrderManage.vue` - Modify: `cfc-web/src/router/index.js` - Modify: `cfc-web/src/views/Layout.vue` - [ ] **Step 1: 创建 src/views/admin/OrderManage.vue** ```vue ``` - [ ] **Step 2: 添加路由** 在 `cfc-web/src/router/index.js` 中添加: ```javascript { path: '/admin/order', component: () => import('@/views/admin/OrderManage.vue'), meta: { title: '订单管理', icon: 'list' } } ``` - [ ] **Step 3: 添加菜单项** 在 `cfc-web/src/views/Layout.vue` 菜单中"商品管理"附近添加: ```vue 订单管理 ``` - [ ] **Step 4: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-web/src/views/admin/OrderManage.vue cfc-web/src/router/index.js cfc-web/src/views/Layout.vue git commit -m "feat(phase1): Web 端订单管理页面+路由+菜单" ``` --- ## T14: Web 端 — 系统配置 **Files:** - Create: `cfc-web/src/views/admin/SysConfig.vue` - Modify: `cfc-web/src/router/index.js` - Modify: `cfc-web/src/views/Layout.vue` - [ ] **Step 1: 创建 src/views/admin/SysConfig.vue** ```vue ``` - [ ] **Step 2: 添加路由** 在 `cfc-web/src/router/index.js` 中添加: ```javascript { path: '/admin/config', component: () => import('@/views/admin/SysConfig.vue'), meta: { title: '系统配置', icon: 'setting' } } ``` - [ ] **Step 3: 添加菜单项** 在 `cfc-web/src/views/Layout.vue` 菜单中添加: ```vue 系统配置 ``` - [ ] **Step 4: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add cfc-web/src/views/admin/SysConfig.vue cfc-web/src/router/index.js cfc-web/src/views/Layout.vue git commit -m "feat(phase1): Web 端系统配置页面+路由+菜单" ``` --- ## T15: 端到端验证 **Files:** 无(仅运行验证命令) - [ ] **Step 1: 后端编译验证** ```bash cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn clean compile -q ``` Expected: `mvn: ok`(无错误) - [ ] **Step 2: 启动后端** ```bash # 杀掉旧进程(如有) pkill -f "cfc-backend" 2>/dev/null || true sleep 2 # 启动新进程 cd /mnt/e/workspace/dan/zxyj/cfc-backend && rtk mvn spring-boot:run > /tmp/cfc-backend.log 2>&1 & echo "Started, PID: $!" # 等待启动完成 sleep 30 rtk grep "Started" /tmp/cfc-backend.log ``` Expected: 输出包含 "Started CfcApplication" - [ ] **Step 3: 测试商品列表 API** ```bash curl -s -X POST http://localhost:8080/api/product/list \ -H "Content-Type: application/json" \ -d '{"page":1,"size":10}' | rtk python3 -m json.tool ``` Expected: 返回 `code: 200`,data.records 含 5 个种子商品 - [ ] **Step 4: 测试系统配置 API** ```bash curl -s -X POST http://localhost:8080/api/admin/config/list \ -H "Content-Type: application/json" -d '{}' | rtk python3 -m json.tool ``` Expected: 返回 `code: 200`,data 含 8 条配置记录 - [ ] **Step 5: 验证前端文件可解析** ```bash # 检查所有新增的 .vue 文件能被读出 for f in \ /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/shop/index.vue \ /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/discover/index.vue \ /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/discover/product-detail.vue \ /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/shop/order-list.vue \ /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/shop/order-detail.vue \ /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/vendor/products.vue \ /mnt/e/workspace/dan/zxyj/cfc-frontend/pages/vendor/orders.vue do rtk head -1 "$f" > /dev/null && echo "OK: $f" || echo "FAIL: $f" done ``` Expected: 全部输出 "OK: ..." - [ ] **Step 6: 验证 pages.json 注册了所有新页面** ```bash for p in \ "pages/discover/product-detail" \ "pages/shop/order-list" \ "pages/shop/order-detail" \ "pages/vendor/products" \ "pages/vendor/orders" do rtk grep -q "\"$p\"" /mnt/e/workspace/dan/zxyj/cfc-frontend/pages.json && echo "OK: $p" || echo "MISSING: $p" done ``` Expected: 全部输出 "OK: ..." - [ ] **Step 7: 验证 Web 端路由** ```bash for p in "ProductManage" "OrderManage" "SysConfig"; do rtk grep -q "$p" /mnt/e/workspace/dan/zxyj/cfc-web/src/router/index.js && echo "OK: $p" || echo "MISSING: $p" done ``` Expected: 全部输出 "OK: ..." - [ ] **Step 8: 编写 Phase 1 完成报告** 在 `docs/superpowers/plans/2026-06-04-phase1-completion-report.md` 写入: ```markdown # Phase 1 完成报告 > **日期:** 2026-06-04 > **范围:** 溪福俱乐部 Phase 1 — 商品系统 > **依赖:** Phase 0 角色模型 + TabBar + 服务商入驻 ## 交付清单 ### 后端 (10 新建 + 2 修改) - [x] `entity/Product.java` — 商品实体 - [x] `entity/ProductOrder.java` — 订单实体 - [x] `entity/SysConfig.java` — 系统配置实体 - [x] `mapper/ProductMapper.java` / `ProductOrderMapper.java` / `SysConfigMapper.java` - [x] `dto/ProductDTO.java` / `ProductListQueryDTO.java` - [x] `dto/ProductOrderDTO.java` / `CreateProductOrderDTO.java` - [x] `dto/SysConfigDTO.java` - [x] `service/ProductService.java` — 商品 CRUD/审核/上下架/库存 - [x] `service/ProductOrderService.java` — 订单创建/支付/取消/完成 - [x] `service/SysConfigService.java` — 配置 CRUD - [x] `controller/product/ProductController.java` - [x] `controller/product/ProductOrderController.java` - [x] `controller/config/SysConfigController.java` - [x] `controller/admin/AdminController.java` — 新增 product/order 审核端点 - [x] `config/DatabaseInitializer.java` — 3 张表 DDL + 商品+配置种子 ### 前端 (7 新建 + 1 修改) - [x] `pages/shop/index.vue` — 商城页(从占位 → 商品列表) - [x] `pages/discover/index.vue` — 发现页(从占位 → 活动+课程) - [x] `pages/discover/product-detail.vue` — 商品详情 - [x] `pages/shop/order-list.vue` — 订单列表 - [x] `pages/shop/order-detail.vue` — 订单详情 - [x] `pages/vendor/products.vue` — 服务商商品管理 - [x] `pages/vendor/orders.vue` — 服务商订单管理 - [x] `pages/vendor/center.vue` — 添加商品/订单菜单项 - [x] `utils/api.js` — 新增 19 个商品/订单/配置 API - [x] `pages.json` — 注册 5 个新页面 ### Web 管理端 (3 新建 + 2 修改) - [x] `api/product.js` — Web API 封装 - [x] `views/admin/ProductManage.vue` — 商品管理 - [x] `views/admin/OrderManage.vue` — 订单管理 - [x] `views/admin/SysConfig.vue` — 系统配置 - [x] `router/index.js` — 3 个新路由 - [x] `views/Layout.vue` — 3 个菜单项 ## 测试结果 - [x] 后端编译通过 (`mvn clean compile`) - [x] 后端启动成功 (Started CfcApplication) - [x] 商品列表 API 返回 5 条种子数据 - [x] 系统配置 API 返回 8 条配置 - [x] 前端所有新增页面文件可读 - [x] pages.json 注册完整 - [x] Web 端路由注册完整 ## 已知限制 - 商品发布功能:Phase 1 仅提供 Web 端/管理端入口;小程序端 `onCreate` 为占位提示(待 Phase 2+ 完善编辑表单) - 真实微信支付:Phase 1+2 均使用测试模式(直接标记为已支付);Phase 3+ 接入真实支付 - SKU 多规格:未实现(设计 Phase 0-2 暂不支持) ## Phase 2 启动条件 - [x] Product 列表可访问 - [x] 商品已上架(on_shelf) - [x] 用户可购买(订单流程已通) - [ ] **Phase 2 计划待编写** ``` - [ ] **Step 9: 提交** ```bash cd /mnt/e/workspace/dan/zxyj && git add docs/superpowers/plans/2026-06-04-phase1-completion-report.md git commit -m "docs(phase1): Phase 1 商品系统完成报告" ``` --- ## 总结 Phase 1 完成后: 1. ✅ 用户可在小程序浏览/购买商品 2. ✅ 服务商可发布商品(需审核) 3. ✅ 管理员可在 Web 端审核商品/管理订单/配置系统参数 4. ✅ 5 个种子商品 + 8 个系统配置已就位 5. ✅ 30 个任务全部完成 6. ✅ Phase 2 启动条件满足 **下一步:编写 Phase 2 计划文档(在 Phase 1 验收通过后开始)**