日期:2026-08-15 状态:待评审
用户报告两个相互关联的缺陷:
| 层 | 现状 | 问题 |
|---|---|---|
DB product_skus.price |
INT COMMENT 'SKU价格(分)' |
列本身语义是分 |
实体 ProductSku.price |
BigDecimal(元语义) |
与列注释冲突 |
| 管理端 ProductSkuEditor | el-input-number :precision="2" 输入元 |
元值直接写入 INT 列被截断 |
| specMap 接口(ProductController L132) | option.put("price", sku.getPrice())(BigDecimal 元) |
前端拿到元 |
| 小程序详情页 | formatPriceWithSymbol(displayPrice) 一律 ÷100 |
元 ÷100 → 显示缩小 100 倍 |
| 订单 ProductOrderService L464 | sku.getPrice().multiply(100)(元→分转换) |
订单金额反而是对的 |
结论:specMap 返回"元"、前端当"分"÷100 → 用户看到"后台价值÷100"。同时管理端录入 10.50 元会被 INT 列截断为 10(分),小数价格静默丢失。Product.price(商品价)是 Integer 分,与 SKU 不一致。
product-detail.vue 的 selectSpec 方法:
selectSpec(groupId, option) {
if (this.selectedSpecs[groupId] === option.id) {
delete this.selectedSpecs[groupId] // 删除属性 — Vue2 不可侦测
this.selectedSku = null
} else {
this.selectedSpecs[groupId] = option.id // 新增属性 — Vue2 不可侦测
this.matchSku()
}
this.$forceUpdate()
}
selectedSpecs: {} 初始为空对象,this.selectedSpecs[groupId] = option.id 是对象新增属性,Vue 2(Object.defineProperty)无法侦测 → 依赖它的 computed(allSpecsSelected、buyButtonDisabled、buyButtonText)不失效 → $forceUpdate() 只强制重渲染但不重算已缓存 computed → 按钮文案永远停留在"请选规格"。ProductSku:price/originalPrice 从 BigDecimal → Integer(分)。purchasePrice(进价)保持 DECIMAL 不动(管理端不进价,无显示问题)。ProductSkuDTO:price/originalPrice 同步 BigDecimal → Integer。ProductController.specMap:option.put("price", sku.getPrice()) 无需改代码——实体变 Integer 后天然返回分。前端 formatPriceWithSymbol(分) → 正确显示。ProductOrderService.createMultiItem L463-465:删除 ×100 转换,直接 unitPrice = sku.getPrice()(已是分)。CartService.buildCartItemDTO L149:有 skuId 时 unitPrice 用 sku.getPrice()(分),无 skuId 用商品价——修复"购物车显示商品价而非规格价"。product_skus.price 列里存的是"元语义"(10 = 10 元,因 BigDecimal 写入 INT 截断)。统一为分后需 price = price * 100, original_price = original_price * 100。sys_config 表(已有,config_key UNIQUE)写入 sku_price_to_cents_done = 1;迁移执行前先查标记,已迁移则跳过。price INT COMMENT 'SKU价格(分)'、original_price INT COMMENT '原价(分)' 注释已是分 ✓ 无需改 DDL;仅追加注释说明数据语义(可选)。// 迁移N 递增。与 ProductEdit.vue(ActivityEdit/CouponManagement 同款模式)对齐——录入保持元,边界 ×100/÷100:
handleSave:price: Math.round(parseFloat(this.skuForm.price) * 100);originalPrice 同。注意 pendingSkus 暂存路径(新建商品时)同样 ×100。showDialog:price: sku.price ? (sku.price / 100) : 0(现在接口返回分)。formatPrice:'¥' + (parseFloat(val) / 100).toFixed(2)。skuRules 不变(仍输入元)。selectSpec 改用 this.$set(this.selectedSpecs, groupId, option.id) / this.$delete(this.selectedSpecs, groupId),删除 $forceUpdate() → computed 自动更新,按钮实时变化。buyButtonDisabled 移除禁用逻辑(或恒 false)——但保留库存为 0 的禁用?见下。buyButtonText 固定"立即购买",不再显示"请选择完整规格"。product.price,分);选了规格 → 规格价(selectedSku.price,分)。已由 originalPriceVal/actualPrice computed 覆盖(SKU 优先)。onBuy/onAddToCart:去掉 buyButtonDisabled 拦截;selectedSku 存在才传 skuId。价格显示逻辑不变(actualPrice 已正确取规格价)。displayStock 已显示规格库存/商品库存;未选规格时按钮可点但若商品库存为 0 后端会拒绝——保持现状,不额外加禁用。| 文件 | 改动 |
|---|---|
cfc-backend/.../entity/ProductSku.java |
price/originalPrice: BigDecimal→Integer |
cfc-backend/.../dto/ProductSkuDTO.java |
同步类型 |
cfc-backend/.../controller/product/ProductController.java |
specMap 无代码改动(类型传导) |
cfc-backend/.../service/ProductOrderService.java |
L464 删 ×100 |
cfc-backend/.../service/CartService.java |
buildCartItemDTO 用 SKU 价 |
cfc-backend/.../config/DatabaseInitializer.java |
迁移:存量 ×100 + sys_config 标记 |
cfc-backend/src/main/resources/schema.sql |
注释微调(可选) |
cfc-web/src/views/admin/components/ProductSkuEditor.vue |
元↔分转换 |
cfc-frontend/pages/discover-detail/product-detail/product-detail.vue |
$set/$delete + 按钮逻辑 |
不改动:Product.price(已是分)、商品管理端录入(已是元→×100 模式)、其他价格显示页面。
cd cfc-backend && mvn clean compileSELECT id, product_id, price FROM product_skus 抽查。pendingSkus 里 price 已 ×100,flushPendingSkus 直传——保持一致。hasSku=false 时按钮行为不变(始终可点、商品价)。