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 (小程序)
@PostMapping;DI 用 @Resource 且字段名=Bean NameDatabaseInitializer.runMigrations()(L114),最新编号244,新增迁移编号245,幂等(可重复执行不报错)schema.sql(product_skus 表注释已是"分",无需 DDL 变更)price/100 显示、price*100 提交模式一致)?.、禁止 :key 表达式、禁止 new Date(string);打包必须用 HBuilderX(Agent 只做语法校验)as any/@ts-ignore、禁止空 catch、禁止删除测试Files:
cfc-backend/src/main/java/com/etotem/cfc/entity/ProductSku.java (L21, L26)cfc-backend/src/main/java/com/etotem/cfc/dto/ProductSkuDTO.java (L16-17)Interfaces:
ProductSku.price/originalPrice 为 Integer(分);ProductSkuDTO.price/originalPrice 为 Integer(分) — Task 2/3 依赖此类型说明:purchasePrice(进价) 保持 BigDecimal 不动(schema 为 DECIMAL,且管理端不录入/不展示,不受影响)。
将 L8 无用的 import java.math.BigDecimal; 保留(L22 purchasePrice 仍用 BigDecimal),把 L21/L26 类型改为 Integer:
private Integer price;
private BigDecimal purchasePrice; // 进价(分)
private Integer stock;
private Integer minStockAlert;
private Integer minQuantity; // 最小购买数量限制
private Integer originalPrice;
把 L16-17 类型改为:
private Integer price;
private Integer originalPrice;
(ProductSkuDTO 无 import 变更——BigDecimal import 如不再被使用需移除,若编译报 unused 警告可忽略;若 L1-10 有 import java.math.BigDecimal; 且全文件不再使用,删掉它。)
Run: cd cfc-backend && mvn clean compile -q
Expected: BUILD SUCCESS(若有 ProductSkuList/from 等转换代码引用 BigDecimal 方法报错,说明存在其他转换点,需在本任务内一并修正为直接赋值)
[ ] Step 4: 提交
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(分),统一价格单位"
Files:
cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java (L463-465)Interfaces:
ProductSku.getPrice() 返回 Integer(分)createMultiItem 中 unitPrice 直接为规格价(分),totalAmount += unitPrice * quantity 金额正确说明:原代码 sku.getPrice().multiply(new BigDecimal(100)).intValue() 是把"元"转"分";实体已变 Integer(分),此转换必须删除,否则规格价再×100 会放大100倍。
将 L463-465 替换为:
if (sku.getPrice() != null) {
unitPrice = sku.getPrice();
}
Run: cd cfc-backend && mvn clean compile -q
Expected: BUILD SUCCESS
[ ] Step 3: 提交
git add cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java
git commit -m "fix(backend): 订单规格单价去掉×100转换(sku.getPrice()已是分)"
Files:
cfc-backend/src/main/java/com/etotem/cfc/service/CartService.java (L146-151, L152-187)Interfaces:
ProductSku.getPrice() 返回 Integer(分)CartItemDTO.unitPrice 在有 skuId 时为规格价(分),无 skuId 时为商品价(分) — 购物车/结算页正确显示说明:buildCartItemDTO L152 已查询 ProductSku sku,在同一代码块内把 unitPrice 覆盖为 SKU 价即可,不重复查询。当前 dto.setUnitPrice(product.getPrice())(L149)只用了商品价,导致"选了规格,购物车仍显示商品价"。
L146-151 保持不动(商品价兜底)。在 L152-153 的 if (cart.getSkuId() != null) { ProductSku sku = ... } 块内,紧跟 if (sku != null 判断追加价格为 SKU 价:
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 解析逻辑不变。
Run: cd cfc-backend && mvn clean compile -q
Expected: BUILD SUCCESS
[ ] Step 3: 提交
git add cfc-backend/src/main/java/com/etotem/cfc/service/CartService.java
git commit -m "fix(backend): 购物车项单价有SKU时用规格价,修复购物车显示商品价而非规格价"
Files:
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java (runMigrations() 末尾 L9127 之前)Interfaces:
product_skus.price/original_price 存量值 ×100(元→分);sys_config 写入 sku_price_to_cents_done=1 标记防重说明:
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 前),插入:
// 迁移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 时可省略)。
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 语句列名。
Run: cd cfc-backend && mvn clean compile -q
Expected: BUILD SUCCESS
[ ] Step 4: 提交
git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
git commit -m "fix(backend): 迁移245 product_skus存量价格×100(元→分),sys_config幂等标记"
Files:
cfc-web/src/views/admin/components/ProductSkuEditor.vue
Interfaces:
price/originalPrice 为分(Integer);createProductSku/updateProductSku 接收分skuForm.price 为元),边界转换:回显 分/100、提交 元×100、表格 分/100说明:与 ProductEdit.vue 模式一致(price/100 显示、priceYuan*100 提交)。pendingSkus 暂存路径(新建商品)存的是提交后的分,flushPendingSkus 直传不分叉,天然一致。
[ ] Step 1: 修改 showDialog 回显(L175-176)
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)
formatPrice(val) {
if (!val) return '¥0.00'
return '¥' + (parseFloat(val) / 100).toFixed(2)
},
[ ] Step 3: 修改 handleSave 提交(L301-302)
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 类浮点)
Run: node --check 提取的该文件 script 块,或 npx eslint src/views/admin/components/ProductSkuEditor.vue(如项目配置了 eslint)
Expected: 无语法错误
[ ] Step 5: 提交
git add cfc-web/src/views/admin/components/ProductSkuEditor.vue
git commit -m "fix(web): SKU编辑器价格按分存储,回显/表格/提交边界做元↔分转换"
Files:
cfc-frontend/pages/discover-detail/product-detail/product-detail.vue
selectedSpecs: {} 保持)Interfaces:
option.price(分)/option.id/skuId;product.price(分)selectedSpecs 响应式更新 → 按钮直接变为"加入购物车/立即购买";未选规格也可点击(无 skuId,后端按商品价结算)小程序限制:不能用可选链;this.$set/this.$delete 为 Vue 2 全局方法可用;不改打包,只做语法校验。
[ ] Step 1: 修改 selectSpec 方法(L393-401)
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)
buyButtonDisabled: function() {
return false
},
buyButtonText: function() {
return '立即购买'
}
(用户已确认:不选规格也可直接购买,未选按商品价、选了按规格价;后端 createMultiItem L444-448 对实物商品统一校验商品级库存,与是否选规格无关,无副作用)
L117:<button class="btn-cart" :class="buyButtonDisabled ? 'btn-disabled' : ''" :disabled="buyButtonDisabled" @click="onAddToCart">{{ buyButtonDisabled ? '请选规格' : '加入购物车' }}</button> → 移除禁用分支:
<button class="btn-cart" :disabled="buyButtonDisabled" @click="onAddToCart">加入购物车</button>
L119:<button class="btn-buy" :class="buyButtonDisabled ? 'btn-disabled' : ''" :disabled="buyButtonDisabled" @click="onBuy">{{ buyButtonText }}</button> → 移除禁用样式分支:
<button class="btn-buy" :disabled="buyButtonDisabled" @click="onBuy">{{ buyButtonText }}</button>
(buyButtonDisabled 恒 false,:disabled 保留无害;移除了灰色禁用视觉)
L488-492 删除:
onBuy: function() {
if (this.buyButtonDisabled) {
uni.showToast({ title: '请选择完整规格', icon: 'none' })
return
}
L521-525 删除:
onAddToCart: function() {
if (this.buyButtonDisabled) {
uni.showToast({ title: '请选择完整规格', icon: 'none' })
return
}
(buyButtonDisabled 恒 false 后即使不删也无害,但删除更干净——保留方法体其余部分)
Run: 提取该文件 <script> 块内容到临时文件后 node --check(小程序禁止 Agent 自行打包,按 AGENTS.md 只做语法校验)
Expected: 无语法错误
[ ] Step 6: 提交
git add cfc-frontend/pages/discover-detail/product-detail/product-detail.vue
git commit -m "fix(miniprogram): 规格选择用\$set/\$delete修复响应式+按钮始终可点按商品价购买"
Files:
Interfaces:
Consumes: Task 1-6 全部产出
[ ] Step 1: 后端全量编译 + 迁移幂等检查
Run: cd cfc-backend && mvn clean compile
Expected: BUILD SUCCESS;重启两次(或检查日志),确认"迁移245: product_skus 价格已迁移过,跳过"只出现一次 ×100 执行
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 | 人工 |
SELECT price FROM product_skus WHERE price > 0 抽查确认无已为分的异常值。config_key/config_value/description),不要用不存在的 config_type 列。