|
@@ -0,0 +1,430 @@
|
|
|
|
|
+# SKU 规格价格统一分单位 + 详情页规格按钮修复 实现计划
|
|
|
|
|
+
|
|
|
|
|
+> **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:** 修复规格价格显示缩小100倍与选规格后按钮不更新两个缺陷:SKU 价格统一为"分"(Integer),管理端录入元→内部×100/÷100,详情页按钮始终可点并按规格价购买。
|
|
|
|
|
+
|
|
|
|
|
+**Architecture:** 以 `product_skus.price` 列已有注释"分"为准绳,实体层 `BigDecimal`(元) → `Integer`(分);管理端和详情页的元↔分转换只在 UI 边界执行;数据迁移通过 `sys_config` 表做幂等标记(可重复执行)。
|
|
|
|
|
+
|
|
|
|
|
+**Tech Stack:** Spring Boot 2.7.18 (Java 8, MyBatis-Plus) / Vue 2 + Element UI (管理端) / uni-app Vue 2 Options API (小程序)
|
|
|
|
|
+
|
|
|
|
|
+## Global Constraints
|
|
|
|
|
+
|
|
|
|
|
+- 后端接口统一 `@PostMapping`;DI 用 `@Resource` 且字段名=Bean Name
|
|
|
|
|
+- 数据库迁移唯一入口 `DatabaseInitializer.runMigrations()`(L114),最新编号244,新增迁移编号245,幂等(可重复执行不报错)
|
|
|
|
|
+- 迁移后必须同步 `schema.sql`(product_skus 表注释已是"分",无需 DDL 变更)
|
|
|
|
|
+- 管理端录入价格单位统一为**元**(与 ProductEdit.vue 的 `price/100` 显示、`price*100` 提交模式一致)
|
|
|
|
|
+- 小程序禁止可选链 `?.`、禁止 `:key` 表达式、禁止 `new Date(string)`;打包必须用 HBuilderX(Agent 只做语法校验)
|
|
|
|
|
+- 所有修改文件需运行编译/语法校验通过才视为完成
|
|
|
|
|
+- 禁止 `as any`/`@ts-ignore`、禁止空 catch、禁止删除测试
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 1: 实体与 DTO 价格字段 BigDecimal → Integer
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/entity/ProductSku.java` (L21, L26)
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/dto/ProductSkuDTO.java` (L16-17)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: 无
|
|
|
|
|
+- Produces: `ProductSku.price`/`originalPrice` 为 `Integer`(分);`ProductSkuDTO.price`/`originalPrice` 为 `Integer`(分) — Task 2/3 依赖此类型
|
|
|
|
|
+
|
|
|
|
|
+**说明**:`purchasePrice`(进价) 保持 `BigDecimal` 不动(schema 为 DECIMAL,且管理端不录入/不展示,不受影响)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 ProductSku.java**
|
|
|
|
|
+
|
|
|
|
|
+将 L8 无用的 `import java.math.BigDecimal;` 保留(L22 `purchasePrice` 仍用 BigDecimal),把 L21/L26 类型改为 Integer:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ private Integer price;
|
|
|
|
|
+ private BigDecimal purchasePrice; // 进价(分)
|
|
|
|
|
+ private Integer stock;
|
|
|
|
|
+ private Integer minStockAlert;
|
|
|
|
|
+ private Integer minQuantity; // 最小购买数量限制
|
|
|
|
|
+ private Integer originalPrice;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 修改 ProductSkuDTO.java**
|
|
|
|
|
+
|
|
|
|
|
+把 L16-17 类型改为:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ private Integer price;
|
|
|
|
|
+ private Integer originalPrice;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+(ProductSkuDTO 无 import 变更——BigDecimal import 如不再被使用需移除,若编译报 unused 警告可忽略;若 L1-10 有 `import java.math.BigDecimal;` 且全文件不再使用,删掉它。)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `cd cfc-backend && mvn clean compile -q`
|
|
|
|
|
+Expected: BUILD SUCCESS(若有 ProductSkuList/from 等转换代码引用 BigDecimal 方法报错,说明存在其他转换点,需在本任务内一并修正为直接赋值)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-backend/src/main/java/com/etotem/cfc/entity/ProductSku.java cfc-backend/src/main/java/com/etotem/cfc/dto/ProductSkuDTO.java
|
|
|
|
|
+git commit -m "fix(backend): ProductSku/ProductSkuDTO 价格字段 BigDecimal(元)→Integer(分),统一价格单位"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 2: 订单单价去掉 ×100 转换(已是分)
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java` (L463-465)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: Task 1 的 `ProductSku.getPrice()` 返回 `Integer`(分)
|
|
|
|
|
+- Produces: `createMultiItem` 中 `unitPrice` 直接为规格价(分),`totalAmount += unitPrice * quantity` 金额正确
|
|
|
|
|
+
|
|
|
|
|
+**说明**:原代码 `sku.getPrice().multiply(new BigDecimal(100)).intValue()` 是把"元"转"分";实体已变 Integer(分),此转换必须删除,否则规格价再×100 会放大100倍。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 ProductOrderService.java**
|
|
|
|
|
+
|
|
|
|
|
+将 L463-465 替换为:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ if (sku.getPrice() != null) {
|
|
|
|
|
+ unitPrice = sku.getPrice();
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `cd cfc-backend && mvn clean compile -q`
|
|
|
|
|
+Expected: BUILD SUCCESS
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java
|
|
|
|
|
+git commit -m "fix(backend): 订单规格单价去掉×100转换(sku.getPrice()已是分)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 3: 购物车单价使用规格价
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/CartService.java` (L146-151, L152-187)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: Task 1 的 `ProductSku.getPrice()` 返回 `Integer`(分)
|
|
|
|
|
+- Produces: `CartItemDTO.unitPrice` 在有 skuId 时为规格价(分),无 skuId 时为商品价(分) — 购物车/结算页正确显示
|
|
|
|
|
+
|
|
|
|
|
+**说明**:`buildCartItemDTO` L152 已查询 `ProductSku sku`,在同一代码块内把 `unitPrice` 覆盖为 SKU 价即可,不重复查询。当前 `dto.setUnitPrice(product.getPrice())`(L149)只用了商品价,导致"选了规格,购物车仍显示商品价"。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 CartService.java**
|
|
|
|
|
+
|
|
|
|
|
+L146-151 保持不动(商品价兜底)。在 L152-153 的 `if (cart.getSkuId() != null) { ProductSku sku = ... }` 块内,紧跟 `if (sku != null` 判断追加价格为 SKU 价:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ if (cart.getSkuId() != null) {
|
|
|
|
|
+ ProductSku sku = productSkuService.getById(cart.getSkuId());
|
|
|
|
|
+ if (sku != null) {
|
|
|
|
|
+ if (sku.getPrice() != null) {
|
|
|
|
|
+ dto.setUnitPrice(sku.getPrice());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (sku.getSpecs() != null) {
|
|
|
|
|
+ // ... 现有 specs 解析逻辑保持不动 ...
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+注意:只新增 `if (sku.getPrice() != null) { dto.setUnitPrice(sku.getPrice()); }` 并调整外层判断结构(原为 `if (sku != null && sku.getSpecs() != null)`),保持原 specs 解析逻辑不变。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `cd cfc-backend && mvn clean compile -q`
|
|
|
|
|
+Expected: BUILD SUCCESS
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-backend/src/main/java/com/etotem/cfc/service/CartService.java
|
|
|
|
|
+git commit -m "fix(backend): 购物车项单价有SKU时用规格价,修复购物车显示商品价而非规格价"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 4: 存量 SKU 价格数据迁移 ×100(幂等)
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` (runMigrations() 末尾 L9127 之前)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: 无(独立迁移)
|
|
|
|
|
+- Produces: 所有 `product_skus.price`/`original_price` 存量值 ×100(元→分);`sys_config` 写入 `sku_price_to_cents_done=1` 标记防重
|
|
|
|
|
+
|
|
|
|
|
+**说明**:
|
|
|
|
|
+- 迁移编号:现有最大编号 244,本迁移用 **245**
|
|
|
|
|
+- 幂等策略:先查 `sys_config` 有无 `sku_price_to_cents_done` 键;无则执行 UPDATE×100 后 INSERT 标记;每次重启检查一次,保证只执行一遍
|
|
|
|
|
+- `original_price` 同时 ×100(保持比例)
|
|
|
|
|
+- `purchase_price`(DECIMAL 进价)不动
|
|
|
|
|
+- schema.sql 无需变更(注释本就为"分"),但按 AGENTS.md 同步原则确认注释正确即可
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 在 runMigrations() 末尾追加迁移245**
|
|
|
|
|
+
|
|
|
|
|
+定位 `runMigrations()` 方法体内最后一个 `}`(L9127 `log.warn("初始化sys_menu种子数据失败...")` 块之后、方法结束 `}` 之前,即 L9128 前),插入:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ // 迁移245: product_skus 价格单位 元→分(×100),幂等(sys_config 标记防重)
|
|
|
|
|
+ try {
|
|
|
|
|
+ Integer migrated = jdbcTemplate.queryForObject(
|
|
|
|
|
+ "SELECT COUNT(*) FROM sys_config WHERE config_key = 'sku_price_to_cents_done'",
|
|
|
|
|
+ Integer.class);
|
|
|
|
|
+ if (migrated == null || migrated == 0) {
|
|
|
|
|
+ jdbcTemplate.update("UPDATE product_skus SET price = IFNULL(price, 0) * 100, original_price = IFNULL(original_price, 0) * 100");
|
|
|
|
|
+ jdbcTemplate.update("INSERT INTO sys_config (config_key, config_value, description) VALUES ('sku_price_to_cents_done', '1', 'SKU价格已迁移为分单位')");
|
|
|
|
|
+ log.info("迁移245: product_skus 价格已×100(元→分)");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.info("迁移245: product_skus 价格已迁移过,跳过");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("迁移245失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+注意:`sys_config` 表结构为 `config_key VARCHAR(64) UNIQUE` / `config_value TEXT` / `description TEXT`(schema.sql L1328),INSERT 需匹配实际列名(无 `config_type` 时可省略)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 核对 sys_config 实际列名**
|
|
|
|
|
+
|
|
|
|
|
+Run: `Select-String -Path "cfc-backend/src/main/resources/schema.sql" -Pattern "CREATE TABLE IF NOT EXISTS sys_config" -Context 0,8`
|
|
|
|
|
+Expected: 确认列名为 `id/config_key/config_value/description/updated_at`;若列名不同,调整 Step 1 的 INSERT 语句列名。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `cd cfc-backend && mvn clean compile -q`
|
|
|
|
|
+Expected: BUILD SUCCESS
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
|
|
|
|
|
+git commit -m "fix(backend): 迁移245 product_skus存量价格×100(元→分),sys_config幂等标记"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 5: 管理端 SKU 编辑器元↔分转换
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-web/src/views/admin/components/ProductSkuEditor.vue`
|
|
|
|
|
+ - L175-176(showDialog 回显)
|
|
|
|
|
+ - L289-292(formatPrice 表格显示)
|
|
|
|
|
+ - L301-302(handleSave 提交)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: 后端接口返回 `price`/`originalPrice` 为分(Integer);`createProductSku/updateProductSku` 接收分
|
|
|
|
|
+- Produces: 管理端表单始终保持**元**语义(`skuForm.price` 为元),边界转换:回显 `分/100`、提交 `元×100`、表格 `分/100`
|
|
|
|
|
+
|
|
|
|
|
+**说明**:与 ProductEdit.vue 模式一致(`price/100` 显示、`priceYuan*100` 提交)。`pendingSkus` 暂存路径(新建商品)存的是提交后的分,`flushPendingSkus` 直传不分叉,天然一致。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 showDialog 回显(L175-176)**
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ this.skuForm = {
|
|
|
|
|
+ specs: this.formatSpecsForInput(sku.specs),
|
|
|
|
|
+ price: sku.price ? (sku.price / 100) : 0,
|
|
|
|
|
+ originalPrice: sku.originalPrice ? (sku.originalPrice / 100) : 0,
|
|
|
|
|
+ stock: sku.stock || 0,
|
|
|
|
|
+ ...
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+(原为 `parseFloat(sku.price)` 按元读取 → 改为分÷100得元)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 修改 formatPrice 表格显示(L289-292)**
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ formatPrice(val) {
|
|
|
|
|
+ if (!val) return '¥0.00'
|
|
|
|
|
+ return '¥' + (parseFloat(val) / 100).toFixed(2)
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 修改 handleSave 提交(L301-302)**
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ productId: this.productId,
|
|
|
|
|
+ specs: this.normalizeSpecsToJson(this.skuForm.specs),
|
|
|
|
|
+ price: this.skuForm.price ? Math.round(parseFloat(this.skuForm.price) * 100) : 0,
|
|
|
|
|
+ originalPrice: this.skuForm.originalPrice ? Math.round(parseFloat(this.skuForm.originalPrice) * 100) : null,
|
|
|
|
|
+ stock: this.skuForm.stock,
|
|
|
|
|
+ ...
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+(元→分 ×100,`Math.round` 处理 10.505 类浮点)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 语法校验**
|
|
|
|
|
+
|
|
|
|
|
+Run: `node --check` 提取的该文件 script 块,或 `npx eslint src/views/admin/components/ProductSkuEditor.vue`(如项目配置了 eslint)
|
|
|
|
|
+Expected: 无语法错误
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 5: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-web/src/views/admin/components/ProductSkuEditor.vue
|
|
|
|
|
+git commit -m "fix(web): SKU编辑器价格按分存储,回显/表格/提交边界做元↔分转换"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 6: 小程序详情页——$set/$delete 修复响应式 + 按钮始终可点
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-frontend/pages/discover-detail/product-detail/product-detail.vue`
|
|
|
|
|
+ - L147(`selectedSpecs: {}` 保持)
|
|
|
|
|
+ - L260-266(buyButtonDisabled/buyButtonText computed)
|
|
|
|
|
+ - L393-401(selectSpec 方法)
|
|
|
|
|
+ - L488-492 + L521-525(onBuy/onAddToCart 拦截移除)
|
|
|
|
|
+ - 模板 L117/L119(按钮文字简化)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: specMap 返回 `option.price`(分)/`option.id`/`skuId`;`product.price`(分)
|
|
|
|
|
+- Produces: 选中规格后 `selectedSpecs` 响应式更新 → 按钮直接变为"加入购物车/立即购买";未选规格也可点击(无 skuId,后端按商品价结算)
|
|
|
|
|
+
|
|
|
|
|
+**小程序限制**:不能用可选链;`this.$set`/`this.$delete` 为 Vue 2 全局方法可用;不改打包,只做语法校验。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 selectSpec 方法(L393-401)**
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ selectSpec: function(groupId, option) {
|
|
|
|
|
+ if (this.selectedSpecs[groupId] === option.id) {
|
|
|
|
|
+ this.$delete(this.selectedSpecs, groupId)
|
|
|
|
|
+ this.selectedSku = null
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$set(this.selectedSpecs, groupId, option.id)
|
|
|
|
|
+ this.matchSku()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+(删除 `this.$forceUpdate()`——$set/$delete 已触发响应式依赖重算,且 $forceUpdate 无法让已缓存 computed 失效)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 修改 buyButtonDisabled/buyButtonText(L260-266)**
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ buyButtonDisabled: function() {
|
|
|
|
|
+ return false
|
|
|
|
|
+ },
|
|
|
|
|
+ buyButtonText: function() {
|
|
|
|
|
+ return '立即购买'
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+(用户已确认:不选规格也可直接购买,未选按商品价、选了按规格价;后端 createMultiItem L444-448 对实物商品统一校验商品级库存,与是否选规格无关,无副作用)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 修改模板按钮文案(L117, L119)**
|
|
|
|
|
+
|
|
|
|
|
+L117:`<button class="btn-cart" :class="buyButtonDisabled ? 'btn-disabled' : ''" :disabled="buyButtonDisabled" @click="onAddToCart">{{ buyButtonDisabled ? '请选规格' : '加入购物车' }}</button>` → 移除禁用分支:
|
|
|
|
|
+
|
|
|
|
|
+```html
|
|
|
|
|
+<button class="btn-cart" :disabled="buyButtonDisabled" @click="onAddToCart">加入购物车</button>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+L119:`<button class="btn-buy" :class="buyButtonDisabled ? 'btn-disabled' : ''" :disabled="buyButtonDisabled" @click="onBuy">{{ buyButtonText }}</button>` → 移除禁用样式分支:
|
|
|
|
|
+
|
|
|
|
|
+```html
|
|
|
|
|
+<button class="btn-buy" :disabled="buyButtonDisabled" @click="onBuy">{{ buyButtonText }}</button>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+(`buyButtonDisabled` 恒 false,`:disabled` 保留无害;移除了灰色禁用视觉)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 移除 onBuy/onAddToCart 的规格拦截**
|
|
|
|
|
+
|
|
|
|
|
+L488-492 删除:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ onBuy: function() {
|
|
|
|
|
+ if (this.buyButtonDisabled) {
|
|
|
|
|
+ uni.showToast({ title: '请选择完整规格', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+L521-525 删除:
|
|
|
|
|
+
|
|
|
|
|
+```js
|
|
|
|
|
+ onAddToCart: function() {
|
|
|
|
|
+ if (this.buyButtonDisabled) {
|
|
|
|
|
+ uni.showToast({ title: '请选择完整规格', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+(`buyButtonDisabled` 恒 false 后即使不删也无害,但删除更干净——保留方法体其余部分)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 5: 语法校验**
|
|
|
|
|
+
|
|
|
|
|
+Run: 提取该文件 `<script>` 块内容到临时文件后 `node --check`(小程序禁止 Agent 自行打包,按 AGENTS.md 只做语法校验)
|
|
|
|
|
+Expected: 无语法错误
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 6: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add cfc-frontend/pages/discover-detail/product-detail/product-detail.vue
|
|
|
|
|
+git commit -m "fix(miniprogram): 规格选择用\$set/\$delete修复响应式+按钮始终可点按商品价购买"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 7: 端到端验证
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- 无代码改动(验证任务)
|
|
|
|
|
+
|
|
|
|
|
+**Interfaces:**
|
|
|
|
|
+- Consumes: Task 1-6 全部产出
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 后端全量编译 + 迁移幂等检查**
|
|
|
|
|
+
|
|
|
|
|
+Run: `cd cfc-backend && mvn clean compile`
|
|
|
|
|
+Expected: BUILD SUCCESS;重启两次(或检查日志),确认"迁移245: product_skus 价格已迁移过,跳过"只出现一次 ×100 执行
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 抽查存量数据**
|
|
|
|
|
+
|
|
|
|
|
+Run: `mysql -h192.168.16.251 -ucfc -pcfc@123 cfc -e "SELECT id, product_id, price, original_price FROM product_skus LIMIT 10"`
|
|
|
|
|
+Expected: 迁移后 price 已 ×100(如原 10 → 1000 分)。若有值仍 <100 说明迁移未跑或该 SKU 为 0
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 管理端验证清单(人工)**
|
|
|
|
|
+
|
|
|
|
|
+- 新建/编辑 SKU:售价输入 10.50 → 保存 → 列表显示 ¥10.50 → 重新编辑回显 ¥10.50
|
|
|
|
|
+- 数据库确认存的是 1050
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 小程序验证清单(人工,HBuilderX 重新打包后)**
|
|
|
|
|
+
|
|
|
|
|
+- 详情页选中规格 → 价格显示 ¥10.50(原来是 ¥0.10)
|
|
|
|
|
+- 选完所有规格 → 底部按钮立即变"加入购物车/立即购买"(原来卡"请选规格")
|
|
|
|
|
+- 不选规格 → 按钮可点,加入购物车后购物车/结算按商品价
|
|
|
|
|
+- 选规格 → 购物车单价显示规格价(不再显示商品价)
|
|
|
|
|
+- 下单支付 → 订单金额 = 规格价 × 数量
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 5: 收尾提交(如有遗漏)**
|
|
|
|
|
+
|
|
|
|
|
+若验证发现遗漏改动,补提交后复查 `git status` 干净。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 验证矩阵(对应设计文档)
|
|
|
|
|
+
|
|
|
|
|
+| 设计要求 | Task | 验证方式 |
|
|
|
|
|
+|----------|:----:|----------|
|
|
|
|
|
+| 实体 price/originalPrice → Integer(分) | 1 | mvn compile + 代码审查 |
|
|
|
|
|
+| 订单单价不再 ×100 | 2 | mvn compile |
|
|
|
|
|
+| 购物车单价用规格价 | 3 | mvn compile + 人工 |
|
|
|
|
|
+| 存量数据 ×100 + sys_config 幂等 | 4 | 双启动日志 + SQL 抽查 |
|
|
|
|
|
+| 管理端元↔分边界转换 | 5 | 语法校验 + 人工 |
|
|
|
|
|
+| $set/$delete 修复响应式 | 6 | 语法校验 + 人工 |
|
|
|
|
|
+| 按钮始终可点 | 6 | 人工 |
|
|
|
|
|
+| 详情页规格价正确显示 | 1+6 | 人工 |
|
|
|
|
|
+
|
|
|
|
|
+## 风险注意事项
|
|
|
|
|
+
|
|
|
|
|
+- **存量数据语义**:迁移假设现有 INT 值全部是"元"(唯一写入入口是管理端,元语义)。迁移前可先 `SELECT price FROM product_skus WHERE price > 0` 抽查确认无已为分的异常值。
|
|
|
|
|
+- **sys_config 表结构**:INSERT 前核对列名(`config_key/config_value/description`),不要用不存在的 `config_type` 列。
|
|
|
|
|
+- **小程序打包**:改完代码必须 HBuilderX 重新打包才生效(AGENTS.md 强制)。
|
|
|
|
|
+- **purchase_price 不动**:DECIMAL 进价,管理端不展示,保持 BigDecimal。
|