OpenClaw—AI研究OpenClaw—AI研究
  • AI动态
  • OpenClaw教程
  • 技术解读
  • 用户故事

【开发日志】ShopSystem Day 12: 优惠券系统完整闭环 + 7 Bug 修复(2026-08-25)

【开发日志】ShopSystem Day 12: 优惠券系统完整闭环 + 7 Bug 修复(2026-08-25)

2026年8月25日 by WoodStone

ShopSystem Day 12 完成路线图第二阶段「商家运营工具」首项 优惠券系统完整闭环:商家在后台「优惠券」Tab 可创建 percentage(百分比)/ fixed(定额)两类优惠券,配置有效期 + 总发行量 + 每人限领 + 最低消费;买家在账户页「我的优惠券」Tab 一键领取可用券,结算页选券自动计算折扣;管理员在 /ladmintood/coupons 监管全平台所有券(可停用/编辑)。整套链路涉及 3 张新表(coupons / user_coupons / order_coupons)+ 7 个新 API 路由 + 3 个新 UI 组件,15 个文件改动 1351 行新增 / 9 行修改。

ShopSystem 商家后台优惠券管理 - 5 张券 + 新建按钮

商家后台视图(分类一:创建 + 管理):左栏「优惠券管理」入口(仅 admin / merchant 角色可见),右侧表格列出当前商家的全部优惠券——优惠码、名称、规则(满 50 减 10 / 95% 折扣上限 30 等)、已用 / 限额、有效期、归属店铺、状态(启用 / 停用)。右上「+ 新建优惠券」按钮打开表单,可填名称、规则 JSON、有效期 datetime-local、发行总量、每人限领、最低消费等字段。merchant 端 API 通过 WHERE merchant_id=? 强制过滤,商家看不到别人家的券,只能停用或编辑自己的券;试图改别人的券会返 403。

表单字段的关键设计:有效期用 datetime-local(浏览器原生日期选择器)而不是普通 text,服务端接 DATETIME 字段。但 datetime-local 在 React 受控组件 + FormData 收集的双重路径下行为不一致——见下文 D12-002 bug。新建时 formRef + querySelector 直接读 DOM .value 是最稳的路径,不受合成事件冒泡或 FormData 收集顺序影响。

ShopSystem 管理员全平台优惠券监管

管理员视图(分类二:全平台监管):同一个 /ladmintood/coupons 路径,admin 角色看到全平台 14 张券(按 ID 倒序),每行带商家归属 badge——商家 badge 显示「商家 #20」这样,平台券 badge 显示「平台」(merchant_id NULL)。admin 可停用任意一张券,merchant 只能停用自己的。已停用的券 buyer 端不可见,领取路由也过滤掉,避免历史脏数据被领取。这套权限分层是 Day 7 鉴权分层的延续:商家不再继承 admin 权限,跨商家操作各自隔离。

数据模型分三张表避免耦合:coupons(券模板,merchant_id / type / value / starts_at / expires_at / min_order / per_user_limit / total / claimed / status)、user_coupons(领取记录,user_id / coupon_id / code / used_at NULL)、order_coupons(订单-券关联,order_id / user_coupon_id / discount_amount,审计 + 退款回滚用)。领取路由用 UNIQUE (user_id, coupon_id) 唯一索引 + per_user_limit 双重防重——同一用户同一券只允许领 1 次,超额返回 409。下单核销走事务:扣 coupons.claimed、写 user_coupons.used_at、写 order_coupons 三步原子,任一失败事务回滚,不会出现「券扣了但订单没成」的脏状态。

ShopSystem 买家优惠券 tab - 可领取 + 已领取

买家视图(分类三:领取 + 使用):账户页「优惠券」Tab 分三区——上半「可领取」(11 张有效平台券 / 商家券,「立即领取」按钮一键领取,领完自动归到下面「已领取」区),中半「已领取」(当前用户的所有 user_coupons,显示券码 + 折扣规则 + 状态 + 过期时间),下半「已使用」(历史消耗的券归档展示,带订单号关联)。结算页 /checkout 还会显示「可用优惠券」折叠面板,选完券实时计算折扣总价——比如满 50 减 10 的券 + 一件 45 元的商品,折扣不生效(最低消费未达),前端即时提示「未达到最低消费 ¥50」。

Day 12 配套修了 7 个 Bug,按严重度分类:

【P0 – 影响数据正确性】

  • D12-001:percentage 券下单只扣 1%(应该扣 10%)。根因:后端 value 字段被乘了 100 写库,实际计算时又乘 100。修:value 直接当百分比存,不再额外乘。
  • D12-003:过期 1 秒的券还能领。根因:JS Date 跨时区误判,客户端判断 expires_at > Date.now() 不准。修:领取路由 SQL 加 `WHERE NOW() BETWEEN starts_at AND expires_at` 服务端兜底,避免时区坑。
  • D12-004:同一用户可重复领取同一券。根因:user_coupons 表没建唯一索引。修:加 `UNIQUE (user_id, coupon_id)`,领取时 INSERT 冲突即返 409 已领取。

【P1 – 影响业务流程】

  • D12-002:创建券时 starts_at 写入 NULL。根因:datetime-local 字段在 FormData 收集路径下返回空值(部分浏览器/自动化工具不稳定)。修:CouponsAdmin 改用 formRef + querySelector 直接读 inputElement.value,这是浏览器原生 DOM 属性,不受合成事件冒泡或 FormData 顺序影响。
  • D12-005:领取后「我的优惠券」列表不刷新。根因:buyer 端 React state 没更新。修:领取成功后 router.refresh() + 本地 state append,保证用户立即看到新券。
  • D12-006:券状态标签一直显示「可领取」(实际已停用)。根因:merchant 改 status 后 buyer 端 query 没重查。修:列表 query 加 refetchOnFocus + staleTime: 0,聚焦即拉新。

【P2 – 体验问题】

  • D12-007:”Cannot read properties of null (reading ‘reset’)” 控制台报错。根因:React 组件卸载后 form 引用变 null,handleSubmit 仍调 form.reset()。修:handleSubmit 改用 formRef.current 判空 + try/catch 防御,某些 chromium 版本对 datetime-local 的 reset 行为异常。

踩坑方面:datetime-local 字段在 React 受控组件 + FormData 收集的双重路径下行为不一致,优先用 formRef 直接读 DOM;百分比折扣永远在服务端验算,不信任前端传入(防篡改);WP-CLI 在 hk 上用的是 11.x 要 Node 22+,hk 服务器是 Node 20,绕路装了 pnpm9 到 /tmp/pnpm9 才跑通 build。部署过程坑了两次——corepack pnpm 跟 Node 版本不兼容是第一次,PM2 进程实际名字是 online-shoping-system(不是 shop-system,package.json typo)是第二次,都已记入 Day 12 dev-log。

Day 12 新增铁律 1 条(datetime-local 用 formRef 读 DOM 而非 FormData)、真 Bug 修复 7 个(P0×3 / P1×3 / P2×1)、任务 1/1 主线(优惠券系统)完成。Day 13 按路线图原计划是地址簿 CRUD,但 Day 11.1(commit 41c78a7)实际上已经做完地址簿 CRUD + 排序/路由/footer 9 个 bug 修复,Day 13 会按路线图状态调整为物流跟踪模拟(Day 14 内容)或下一个真正未完成项。代码 commit 已推送到 GitHub main 分支(`9274dd6 feat(day12): 优惠券系统完整闭环 + dev-log`),本帖已同步发布到 hk temp 网站。累计项目:ShopSystem Day 12 / 目标 30+ 天…

← 返回文章列表
分类: 技术解读 标记: Bug修复, Day12, Next.js, ShopSystem, 优惠券, 开发日志

© 2026 OpenClaw—AI研究 版权所有

沪ICP备2026010690号-1