|
@@ -0,0 +1,245 @@
|
|
|
|
|
+# 限时特价(扫码闪购)设计
|
|
|
|
|
+
|
|
|
|
|
+- 日期:2026-10-20
|
|
|
|
|
+- 状态:待批准(已按用户确认的交互模型细化)
|
|
|
|
|
+- 涉及端:cfc-backend、cfc-frontend(小程序)、cfc-web(管理端)、生产库迁移
|
|
|
|
|
+
|
|
|
|
|
+## 1. 背景
|
|
|
|
|
+
|
|
|
|
|
+运营需要一种「扫码限时特价」玩法:给某个商品配置一个 **限时特价活动**,生成专属小程序码(二维码),用户扫码进入商品详情页后,可以按特价购买该商品。目的是线下渠道(海报、物料、社群)精准引流转化。
|
|
|
|
|
+
|
|
|
|
|
+与普通促销的区别:
|
|
|
|
|
+
|
|
|
|
|
+- **特价只对扫码进入的用户生效**(已确认的交互模型:仅限扫码进入,从商品列表/搜索/分享进入都是原价);
|
|
|
|
|
+- 特价活动可配置:**时限、销售数量、每人购买数量、是否可以使用优惠券**;
|
|
|
|
|
+- 需要一个管理端界面配置活动、查看进度、生成二维码。
|
|
|
|
|
+
|
|
|
|
|
+## 2. 目标
|
|
|
|
|
+
|
|
|
|
|
+1. 单个商品可配置多个限时特价活动(串行),每个活动独立二维码。
|
|
|
|
|
+2. 扫码进入商品详情页,活动进行中时按特价展示并可特价下单;未开始/已结束/已售罄时回落原价展示与购买。
|
|
|
|
|
+3. 下单时服务端强制校验:时间窗口、剩余供应量(原子扣减)、每人限购、优惠券开关。
|
|
|
|
|
+4. 取消 / 超时自动取消 / 退款完成后回滚供应量与限购额度。
|
|
|
|
|
+5. 管理端提供活动列表(进度/状态)、新建/编辑/启停/删除、二维码生成。
|
|
|
|
|
+
|
|
|
|
|
+## 3. 非目标
|
|
|
|
|
+
|
|
|
|
|
+- 不做手机端点直接创建活动(配置仅管理端)。
|
|
|
|
|
+- 不做多商品合并购物车特价(特价仅单商品单 SKU 直购路径)。
|
|
|
|
|
+- 不改变现有的会员价体系:特价直接覆盖会员价(扫码用户统一按特价结算)。
|
|
|
|
|
+- 不排除积分抵扣与赠品规则:特价订单**保留**积分抵扣与买赠(与普通订单一致),仅优惠券作为独立开关。
|
|
|
|
|
+- 不改动生产数据库既有表结构之外的内容(仅新增表 + `product_orders` 一列)。
|
|
|
|
|
+
|
|
|
|
|
+## 4. 现状对照(关键链路)
|
|
|
|
|
+
|
|
|
|
|
+| 环节 | 现状 | 对本功能的影响 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 商品计价 | `products.price`(分)存储,下单时 `getEligibleMemberPrice()` 计算会员价 | 特价分支需绕过会员价,直接按 `special_price` 结算 |
|
|
|
|
|
+| 下单入口 | `ProductOrderService.create(CreateProductOrderDTO, userId)`(POST `/api/product/order/create`),无 `@Transactional` | 特价分支需要独立 `@Transactional` 事务入口,原子完成「扣量 + 插单」 |
|
|
|
|
|
+| 优惠券 | `couponService.apply/reserve/markUsed/revert`,`family_coupon.status` AVAILABLE/RESERVED/USED | 特价订单同样走这套;`allow_coupon=0` 时下单直接拒绝 couponId |
|
|
|
|
|
+| 库存扣减 | 支付成功时 `productService.decreaseStock`(不是下单时扣) | **特价供应量独立于商品库存**:活动总量 `sold_quantity` 下单即扣(占坑),商品库存保持现有支付时扣减不干预 |
|
|
|
|
|
+| 超时取消 | `OrderTimeoutCancelTask` 每分钟扫描 pending 超时订单,直接置 `cancelled` + 还券 + 恢复库存,**不经过 `cancel()`** | 回滚特价扣量必须在**该任务内新增**,不能只挂在 `cancel()` |
|
|
|
|
|
+| 订单状态 | `pending / paid / cancelled / refunding / refund_pending / refunded / completed / closed` 等 | 回滚挂 `cancelled` 与 `refunded` 两个终态 |
|
|
|
|
|
+| 供应商改价 | `reviewUpdate()` 允许供应商修改 pending 订单单价 | 特价订单必须禁止改价,否则可绕过特价校验 |
|
|
|
|
|
+| 小程序码 | `WechatService.generateWxacode(scene, page)`,scene≤32字符;产品详情页 `onLoad` 已解析 `scene` 中 `id=(\d+)` | 复用;scene 扩展为 `id={商品id}&sp={活动id}`,前端扩展解析 `sp=` |
|
|
|
|
|
+| 产品详情页 | `pages/discover-detail/product-detail/product-detail.vue`,扫码/分享进入,`checkoutItems` 存 storage → 跳 checkout | 特价状态经 `checkoutSpecialDealId` storage 透传给结算页 |
|
|
|
|
|
+| 管理端商品 | `ProductManage.vue` + `ProductEdit.vue`(已近 1300 行) | 特价做成**独立页面**,嵌入会继续膨胀商品编辑页 |
|
|
|
|
|
+
|
|
|
|
|
+## 5. 方案
|
|
|
|
|
+
|
|
|
|
|
+### 5.1 数据模型
|
|
|
|
|
+
|
|
|
|
|
+新表 `special_price_deals`(DatabaseInitializer 迁移 + schema.sql 同步):
|
|
|
|
|
+
|
|
|
|
|
+```sql
|
|
|
|
|
+CREATE TABLE IF NOT EXISTS special_price_deals (
|
|
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
+ product_id BIGINT NOT NULL COMMENT '商品ID',
|
|
|
|
|
+ name VARCHAR(100) NOT NULL COMMENT '活动名称',
|
|
|
|
|
+ special_price INT NOT NULL COMMENT '特价(分)',
|
|
|
|
|
+ start_time DATETIME COMMENT '开始时间',
|
|
|
|
|
+ end_time DATETIME COMMENT '结束时间',
|
|
|
|
|
+ total_quantity INT NOT NULL DEFAULT 0 COMMENT '特价总供应量',
|
|
|
|
|
+ sold_quantity INT NOT NULL DEFAULT 0 COMMENT '已售数量(下单即扣)',
|
|
|
|
|
+ per_user_limit INT NOT NULL DEFAULT 1 COMMENT '每人限购件数',
|
|
|
|
|
+ allow_coupon TINYINT NOT NULL DEFAULT 0 COMMENT '是否允许优惠券 1=允许 0=禁止',
|
|
|
|
|
+ status VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT 'draft/active/disabled',
|
|
|
|
|
+ created_at DATETIME,
|
|
|
|
|
+ updated_at DATETIME,
|
|
|
|
|
+ KEY idx_product (product_id),
|
|
|
|
|
+ KEY idx_status (status, start_time, end_time)
|
|
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='限时特价活动'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`product_orders` 增加一列:
|
|
|
|
|
+
|
|
|
|
|
+```sql
|
|
|
|
|
+ALTER TABLE product_orders ADD COLUMN special_deal_id BIGINT NULL COMMENT '限时特价活动ID(非空=特价单)'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**状态机说明**:存储状态仅 `draft / active / disabled`;「未开始 / 进行中 / 已结束 / 已售罄」是**推断的运行状态**(`runStatus`),由 `info` / 列表 / 下单校验按当前时间与 `sold_quantity` 实时计算,不做定时任务改状态:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+runStatus 取值(集中在一个函数 computeRunStatus(deal, now)):
|
|
|
|
|
+ deal == null → INVALID (已被删除,前端回落原价)
|
|
|
|
|
+ status = draft → DRAFT (未启用,前端不展示特价)
|
|
|
|
|
+ status = disabled → DISABLED (已停用,前端不展示特价)
|
|
|
|
|
+ status = active 且 now < start_time → NOT_STARTED
|
|
|
|
|
+ status = active 且 start_time <= now <= end_time:
|
|
|
|
|
+ sold_quantity < total_quantity → ACTIVE
|
|
|
|
|
+ sold_quantity >= total_quantity → SOLD_OUT
|
|
|
|
|
+ status = active 且 now > end_time → ENDED
|
|
|
|
|
+前端特价展示仅对 ACTIVE / NOT_STARTED 生效;其余一律回落原价。
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**单商品并发活动约束**:同一商品同时只允许一个 `status=active`(新建/启用时后端校验拦截并给出提示;非 active 的活动不参加互斥)。
|
|
|
|
|
+
|
|
|
|
|
+### 5.2 后端接口
|
|
|
|
|
+
|
|
|
|
|
+#### 管理端(新 `AdminSpecialPriceController`,`@RequestMapping("/api/admin/special-price")`,随 `/api/admin/**` 走既有管理员鉴权)
|
|
|
|
|
+
|
|
|
|
|
+| 接口 | 请求 | 返回 | 说明 |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| `POST /list` | `{page,size,keyword?,status?}` | `Result<{records,total}>`,每条含 `productName/price/runStatus/remainingQuantity` | 分页;`runStatus` 为推断状态 |
|
|
|
|
|
+| `POST /create` | `{productId,name,specialPrice,startTime,endTime,totalQuantity,perUserLimit,allowCoupon}` | `Result<SpecialPriceDeal>` | 校验必填与互斥;创建为 `draft` |
|
|
|
|
|
+| `POST /update` | `{id,...同上}` | `Result<SpecialPriceDeal>` | 仅 `draft/disabled` 可改(active 中不允许改价/量/时间,避免与已售数据不一致) |
|
|
|
|
|
+| `POST /status` | `{id, action:enable/disable}` | `Result<String>` | enable 校验时间窗口、商品在架、同商品互斥 |
|
|
|
|
|
+| `POST /delete` | `{id}` | `Result<String>` | 仅无特价订单(`product_orders.special_deal_id` 无记录)可删除;有订单仅允许 disable |
|
|
|
|
|
+| `POST /qrcode` | `{id}` | `Result<{qrCodeBase64}>` | 调 `wechatService.generateWxacode(sceene, page)`,scene=`id={productId}&sp={id}`,page=`pages/discover-detail/product-detail/product-detail`;测试模式(返回空串)时管理端显示占位提示 |
|
|
|
|
|
+
|
|
|
|
|
+#### 小程序端(新 `SpecialPriceController`,`@RequestMapping("/api/special-price")`,`info` 加入 JwtInterceptor / WebConfig 公开白名单)
|
|
|
|
|
+
|
|
|
|
|
+| 接口 | 请求 | 返回 | 说明 |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| `POST /info` | `{dealId}` | `Result<{id,productId,specialPrice,startTime,endTime,runStatus,totalQuantity,soldQuantity,remainingQuantity,perUserLimit,allowCoupon,userPurchased,userRemaining}>` | 公开(匿名可查活动展示信息);登录时附加 `userPurchased`(该用户已购件数)与 `userRemaining`(还剩可购件数);活动不存在、已被删除返回 `runStatus=INVALID` |
|
|
|
|
|
+
|
|
|
|
|
+#### 下单改动
|
|
|
|
|
+
|
|
|
|
|
+- `CreateProductOrderDTO` 增加 `specialDealId`(`Long`)字段。
|
|
|
|
|
+- `ProductOrder` 实体增加 `specialDealId` 字段(`product_orders.special_deal_id`)。
|
|
|
|
|
+- `ProductOrderService.create()`:`dto.specialDealId != null` 时委托给新增的独立事务入口 `createSpecialDealOrder(dto, buyerId)`(`@Transactional(rollbackFor = Exception.class)`),**保持普通订单创建的事务语义不变**(当前 `create` 无事务,不改造)。
|
|
|
|
|
+
|
|
|
|
|
+`createSpecialDealOrder` 校验与结算流程:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+1. items 非空(多商品购物车路径)→ 拒绝:"特价商品请单独购买"
|
|
|
|
|
+2. 查询特价活动,校验:
|
|
|
|
|
+ - 活动存在且 status=active
|
|
|
|
|
+ - now 在 [start_time, end_time] 内,否则按 runStatus 给出提示(未开始/已结束)
|
|
|
|
|
+ - 商品匹配(deal.product_id == productId)
|
|
|
|
|
+ - dto.quantity >= 1
|
|
|
|
|
+3. 原子扣量(防并发超卖,核心):
|
|
|
|
|
+ UPDATE special_price_deals SET sold_quantity = sold_quantity + #{qty}, updated_at = now()
|
|
|
|
|
+ WHERE id = #{dealId} AND status = 'active'
|
|
|
|
|
+ AND start_time <= now() AND now() <= end_time
|
|
|
|
|
+ AND sold_quantity + #{qty} <= total_quantity
|
|
|
|
|
+ 影响行数 = 0 → 重查活动判定原因(已售罄 / 已结束 / 非活动期),返回明确错误
|
|
|
|
|
+4. 每人限购(先查再扣,与扣量同事务):
|
|
|
|
|
+ SELECT COALESCE(SUM(quantity),0) FROM product_orders
|
|
|
|
|
+ WHERE special_deal_id = #{dealId} AND buyer_id = #{buyerId}
|
|
|
|
|
+ AND status NOT IN ('cancelled','refunded')
|
|
|
|
|
+ 已购 + dto.quantity > per_user_limit → 拒绝:"每人限购N件,您已购买X件"
|
|
|
|
|
+5. 优惠券开关:allow_coupon = 0 且 dto.couponId != null → 拒绝:"该特价商品不可使用优惠券"
|
|
|
|
|
+6. 复用 create() 单商品构建逻辑(商品在架/库存检查、validateFulfill、地址、赠品、购买信息等),
|
|
|
|
|
+ 定价替换:
|
|
|
|
|
+ unitPrice = specialPrice(覆盖会员价)
|
|
|
|
|
+ totalAmount = specialPrice * quantity
|
|
|
|
|
+ order.specialDealId = dealId
|
|
|
|
|
+ order.setStatus("pending"), coupon(如允许) 按原逻辑 apply+reserve
|
|
|
|
|
+7. 同一事务内:订单 insert + 扣量 UPDATE 原子完成
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**回滚(释放供应量与限购额度)** —— 集中封装 `SpecialPriceService.releaseQuota(dealId, quantity)`:
|
|
|
|
|
+
|
|
|
|
|
+```sql
|
|
|
|
|
+UPDATE special_price_deals SET sold_quantity = GREATEST(sold_quantity - #{qty}, 0), updated_at = now()
|
|
|
|
|
+ WHERE id = #{dealId}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+挂载点(三处,**全部**必须加,否则数量泄漏):
|
|
|
|
|
+
|
|
|
|
|
+| 挂载点 | 位置 | 触发 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 用户取消订单 | `ProductOrderService.cancel()` 置 `cancelled` 处(约 L1020,与恢复商品库存并列) | 用户取消 |
|
|
|
|
|
+| 退款完成 | `ProductOrderService.approveRefund()` 置 `refunded` 处(约 L1295,与恢复库存并列) | 退款审核通过 |
|
|
|
|
|
+| 超时自动取消 | `OrderTimeoutCancelTask.cancelTimeoutOrders()` 置 `cancelled` 处(约 L72,与还券/恢复库存并列) | pending 超 30 分钟 |
|
|
|
|
|
+
|
|
|
|
|
+> 回滚只在订单进入 `cancelled / refunded` 终态时执行;`refunding/refund_pending` 中间态保留占坑,与商品库存回滚时机完全对齐。
|
|
|
|
|
+
|
|
|
|
|
+**供应商改价防护**:`reviewUpdate()` 对 `order.specialDealId != null` 的订单直接返回错误("特价订单不允许修改价格"),只放行配送方式等非价格项(实现时按需裁剪)。
|
|
|
|
|
+
|
|
|
|
|
+### 5.3 小程序前端(cfc-frontend)
|
|
|
|
|
+
|
|
|
|
|
+**`pages/discover-detail/product-detail/product-detail.vue`**
|
|
|
|
|
+- `onLoad` 扩展 scene 解析:现有 `id=(\d+)`、`ref=([^&]+)` 之外,新增 `sp=(\d+)` → `this.specialDealId`;
|
|
|
|
|
+- 有 `sp` 时调用新 API `getSpecialPriceInfo(dealId)`(`utils/api.js` 封装 `POST /api/special-price/info`);
|
|
|
|
|
+- 特价区块展示(按 `runStatus`):
|
|
|
|
|
+ - `ACTIVE`:显示特价(划线原价)、「限每人N件 · 剩余X件」进度、购买按钮按特价走;
|
|
|
|
|
+ - `NOT_STARTED`:展示特价与「距开始」倒计时,购买按钮禁用;
|
|
|
|
|
+ - `SOLD_OUT`:「已抢完」标签,回落原价展示与购买;
|
|
|
|
|
+ - `ENDED / INVALID`:「特价已结束」,回落原价展示与购买;
|
|
|
|
|
+- 进行中点击购买 → 原直购逻辑(`checkoutItems` storage),并额外 `uni.setStorageSync('checkoutSpecialDealId', dealId)`;
|
|
|
|
|
+- 倒计时等时间展示遵循 `utils/format.js` 的 `parseDate()` 规范,**不用** `new Date(string)`(iOS 兼容);
|
|
|
|
|
+- 遵守 API 去重规范:新增接口是独立 URL,不与 `loadDetail` 冲突;`onLoad/onShow` 双发防护沿用现有 `_onLoadFired` 模式。
|
|
|
|
|
+
|
|
|
|
|
+**`pages/shop/checkout/checkout.vue`**
|
|
|
|
|
+- `onLoad` 读取 `checkoutSpecialDealId`(无则清空);
|
|
|
|
|
+- 下单 payload 增加 `specialDealId`;
|
|
|
|
|
+- `allow_coupon=0`(特价信息带过来)时**隐藏优惠券选择区**;
|
|
|
|
|
+- 成功下单后清理 `checkoutSpecialDealId`(与 `checkoutItems` 等一同 `removeStorageSync`)。
|
|
|
|
|
+
|
|
|
|
|
+**`utils/api.js`**:新增唯一入口 `getSpecialPriceInfo = (dealId) => request('/api/special-price/info', 'POST', { dealId })`。
|
|
|
|
|
+
|
|
|
|
|
+### 5.4 Web 管理端(cfc-web)
|
|
|
|
|
+
|
|
|
|
|
+新建 `src/views/admin/SpecialPriceManage.vue`(视觉/交互风格对齐 `ProductManage.vue`):
|
|
|
|
|
+
|
|
|
|
|
+- **列表**:活动名 / 商品(名+ID)/ 特价 vs 原价 / 时间窗口 / 供应量(已售+剩余)/ 每人限购 / 券开关 / 运行状态 / 操作(编辑、启停、删除、二维码);
|
|
|
|
|
+- **新建/编辑弹窗**:选商品(复用 `getProductList` 搜索下拉)、活动名、特价、起止时间(`el-date-picker type="datetime"`)、供应量、每人限购、券开关;
|
|
|
|
|
+- **二维码弹窗**:调 `/api/admin/special-price/qrcode` 生成小程序码图片(download 保存);测试模式空串时显示「测试模式无法生成,请在生产环境操作」;
|
|
|
|
|
+- 路由 `src/router/index.js` 新增 `/special-price`(商城分组,`perm: 'commerce:products'`);
|
|
|
|
|
+- `src/api/admin.js` 增加封装:`getSpecialPriceList/createSpecialPrice/updateSpecialPrice/setSpecialPriceStatus/deleteSpecialPrice/getSpecialPriceQrcode`。
|
|
|
|
|
+
|
|
|
|
|
+### 5.5 JwtInterceptor / WebConfig 白名单
|
|
|
|
|
+
|
|
|
|
|
+`/api/special-price/info` 加入公开白名单(与 `/api/product/detail` 同级),匿名可查看活动展示信息;下单、购买仍走既有登录校验。
|
|
|
|
|
+
|
|
|
|
|
+## 6. 边界与错误处理
|
|
|
|
|
+
|
|
|
|
|
+| 场景 | 处理 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 匿名用户扫码 | `info` 公开可看;点购买触发既有登录流程 |
|
|
|
|
|
+| 活动已停用/删除后旧二维码扫码 | 页面回落原价;`info` 返回 `INVALID`;下单(若带 dealId)拒绝 |
|
|
|
|
|
+| 特价订单并发下单 | 原子 UPDATE 条件扣量,超卖被数据库层拦截 |
|
|
|
|
|
+| 用户挂多个 pending 单 | 每人限购按「非取消/退款」订单累计,pending 同样占额度(与 30 分钟占坑一致) |
|
|
|
|
|
+| 退款进行中 | 额度保留至 `refunded` 才释放(防「买了退、退了买」刷量) |
|
|
|
|
|
+| 商品库存不足 | 复用 create 既有库存检查;支付时 `decreaseStock` 失败走既有失败路径 |
|
|
|
|
|
+| 特价 > 原价 | 创建时校验 `specialPrice < product.price`(允许等于?不允许,必须低于原价) |
|
|
|
|
|
+| 同商品并发 active 活动 | 新建/启用互斥校验 |
|
|
|
|
|
+| 供应商改价 | 特价订单禁止 |
|
|
|
|
|
+| 时区 | 与全系统一致(服务器本地时间),管理端输入按 `yyyy-MM-dd HH:mm:ss` |
|
|
|
|
|
+
|
|
|
|
|
+## 7. 验收标准
|
|
|
|
|
+
|
|
|
|
|
+- [ ] `mvn clean compile` 通过;DatabaseInitializer 新增迁移(建表 + `product_orders.special_deal_id`)幂等,schema.sql 同步。
|
|
|
|
|
+- [ ] 管理端可创建/编辑/启停/删除活动;同一商品仅一个 active;特价必须低于原价。
|
|
|
|
|
+- [ ] 管理端生成的小程序码 scene 为 `id={productId}&sp={dealId}`(≤32 字符),页面路径正确。
|
|
|
|
|
+- [ ] 小程序扫码进入产品详情页:未开始显示倒计时且按钮禁用;进行中显示特价并可下单;已售罄/已结束回落原价。
|
|
|
|
|
+- [ ] 特价下单校验全通过:时间窗口、原子扣量(并发不超卖)、每人限购累计、券开关。
|
|
|
|
|
+- [ ] 取消 / 超时自动取消 / 退款完成后 `sold_quantity` 回滚(`GREATEST(x-? ,0)`),额度释放可再购。
|
|
|
|
|
+- [ ] 特价订单 `unitPrice = specialPrice`(覆盖会员价),保留积分抵扣与赠品;`allow_coupon=0` 时禁用优惠券。
|
|
|
|
|
+- [ ] 特价订单供应商改价被拒绝。
|
|
|
|
|
+- [ ] 文档更新:`docs/superpowers/api/API_REFERENCE.md` 记录新接口;`PROJECT-OVERVIEW.md` 登记本文档。
|
|
|
|
|
+
|
|
|
|
|
+## 8. 涉及文件清单
|
|
|
|
|
+
|
|
|
|
|
+**cfc-backend**
|
|
|
|
|
+- 新增:`entity/SpecialPriceDeal.java`、`mapper/SpecialPriceDealMapper.java`、`service/SpecialPriceService.java`、`controller/special/SpecialPriceController.java`、`controller/admin/AdminSpecialPriceController.java`
|
|
|
|
|
+- 修改:`config/DatabaseInitializer.java`(迁移)、`resources/schema.sql`、`service/ProductOrderService.java`(特价分支+回滚+改价防护)、`task/OrderTimeoutCancelTask.java`(回滚)、`entity/ProductOrder.java`、`dto/CreateProductOrderDTO.java`、`config/JwtInterceptor.java` + `config/WebConfig.java`(白名单)、`docs/superpowers/api/API_REFERENCE.md`
|
|
|
|
|
+
|
|
|
|
|
+**cfc-frontend(小程序)**
|
|
|
|
|
+- 修改:`pages/discover-detail/product-detail/product-detail.vue`、`pages/shop/checkout/checkout.vue`、`utils/api.js`
|
|
|
|
|
+
|
|
|
|
|
+**cfc-web(管理端)**
|
|
|
|
|
+- 新增:`src/views/admin/SpecialPriceManage.vue`
|
|
|
|
|
+- 修改:`src/router/index.js`、`src/api/admin.js`
|