Compare commits
2 Commits
59c4ef330e
...
9f7c2e4be7
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f7c2e4be7 | |||
| 47016744d4 |
@@ -121,8 +121,8 @@ WSL 已验证:EMQX `5.8.9`、MQTTX CLI `1.13.0`、EMQX 服务 `active (running
|
|||||||
项目已开发部分模块。具体完成度不得从 README 猜测,必须以现有代码、测试、数据库迁移、Git 历史以及 `docs/current-baseline.md`、`docs/module-status.md`、`docs/feature-status.md` 为准。
|
项目已开发部分模块。具体完成度不得从 README 猜测,必须以现有代码、测试、数据库迁移、Git 历史以及 `docs/current-baseline.md`、`docs/module-status.md`、`docs/feature-status.md` 为准。
|
||||||
|
|
||||||
- 当前执行游标:`M08-A`(PARTIAL)
|
- 当前执行游标:`M08-A`(PARTIAL)
|
||||||
- 最近工程提交:`dda2b3c`,完成顾客端优惠券、套餐和余额下单抵扣工程闭环。
|
- 最近工程提交:`4701674`,完成顾客端权益可视化选择器和时长券折价。
|
||||||
- 下一工程目标:继续 M08-A,补权益可视化选择器、真机合法域名、真实微信支付和实物开门验证。
|
- 下一工程目标:继续 M08-A,补真机合法域名、真实微信支付和实物开门验证。
|
||||||
|
|
||||||
## 版本递进
|
## 版本递进
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ export class PricingRepository {
|
|||||||
quote = this.calculate(room, {
|
quote = this.calculate(room, {
|
||||||
...input,
|
...input,
|
||||||
adjustment: {
|
adjustment: {
|
||||||
discountCents: benefitReservation.discountCents,
|
discountCents: benefitReservation.discountCents
|
||||||
|
+ timeCouponDiscountCents(benefitReservation.minutes, quote.unitPriceCents),
|
||||||
packageCreditCents: benefitReservation.packageCreditCents
|
packageCreditCents: benefitReservation.packageCreditCents
|
||||||
}
|
}
|
||||||
}, isHoliday);
|
}, isHoliday);
|
||||||
@@ -373,6 +374,11 @@ function clampAdjustment(value: number | undefined, maximum: number) {
|
|||||||
return Math.min(Math.trunc(value), maximum);
|
return Math.min(Math.trunc(value), maximum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timeCouponDiscountCents(minutes: number | undefined, unitPriceCents: number) {
|
||||||
|
if (!minutes || minutes <= 0) return 0;
|
||||||
|
return Math.ceil(minutes / 60) * unitPriceCents;
|
||||||
|
}
|
||||||
|
|
||||||
function localDateKey(date: Date, timezone: string) {
|
function localDateKey(date: Date, timezone: string) {
|
||||||
return new Intl.DateTimeFormat('en-CA', {
|
return new Intl.DateTimeFormat('en-CA', {
|
||||||
timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit'
|
timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit'
|
||||||
|
|||||||
@@ -24,3 +24,12 @@
|
|||||||
|
|
||||||
- `POST /app-api/orders/:orderId/cancel`
|
- `POST /app-api/orders/:orderId/cancel`
|
||||||
- 行为补充:订单取消释放房间预占和访问权限时,同时释放仍处于 `FROZEN` 的优惠券和套餐持有记录。
|
- 行为补充:订单取消释放房间预占和访问权限时,同时释放仍处于 `FROZEN` 的优惠券和套餐持有记录。
|
||||||
|
|
||||||
|
## M08-A 权益可视化选择器补充
|
||||||
|
|
||||||
|
- `GET /app-api/profile/benefits`
|
||||||
|
- 小程序房间页现在复用该接口作为下单权益选择器数据源。
|
||||||
|
- 前端按当前 `storeId`、`roomId`、权益状态和剩余量过滤可选项。
|
||||||
|
|
||||||
|
- `POST /app-api/orders/reserve`
|
||||||
|
- 行为补充:`TIME` 类型优惠券不再只冻结记录,会按当前服务端锁定单价和券分钟数折算成订单折扣;最终折扣仍受服务端计价规则限制,不允许抵扣押金外的非法金额。
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> V5.0 首次核验日期:2026-06-16
|
> V5.0 首次核验日期:2026-06-16
|
||||||
> audited_commit: `4ae9296`
|
> audited_commit: `4ae9296`
|
||||||
> next_engineering_target: 继续 M08-A 权益可视化选择器、真机合法域名、真实微信支付和实物开门验证
|
> next_engineering_target: 继续 M08-A 真机合法域名、真实微信支付和实物开门验证
|
||||||
> 事实源:当前工作区、Git 历史、状态文档、Windows/WSL 检查脚本。
|
> 事实源:当前工作区、Git 历史、状态文档、Windows/WSL 检查脚本。
|
||||||
|
|
||||||
## 总体结论
|
## 总体结论
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
| Git 远端 | `origin=ssh://git@git.txyundm.cn:2222/panda/qipai.git`,分支 `main` | `git rev-list main...origin/main` 为 `0 0` | 本地与远端同步 |
|
| Git 远端 | `origin=ssh://git@git.txyundm.cn:2222/panda/qipai.git`,分支 `main` | `git rev-list main...origin/main` 为 `0 0` | 本地与远端同步 |
|
||||||
| 正式后端 | 已完成 Fastify、迁移兼容、outbox/worker、多应用租户、微信身份/会话,以及六角色 RBAC 和门店范围 | `backend/src/auth/**`、`database/migrations/2026061805_m02c_rbac.*.sql`、`backend/tests/**` | M01、M02-A/B/C DONE;MySQL 已验证 capability 和跨租户门店授权拒绝;下一阶段 M02-D |
|
| 正式后端 | 已完成 Fastify、迁移兼容、outbox/worker、多应用租户、微信身份/会话,以及六角色 RBAC 和门店范围 | `backend/src/auth/**`、`database/migrations/2026061805_m02c_rbac.*.sql`、`backend/tests/**` | M01、M02-A/B/C DONE;MySQL 已验证 capability 和跨租户门店授权拒绝;下一阶段 M02-D |
|
||||||
| 后台管理端 | 仅有 `admin/.gitkeep` | 当前文件扫描 | M09 未开始,不能标记 DONE |
|
| 后台管理端 | 仅有 `admin/.gitkeep` | 当前文件扫描 | M09 未开始,不能标记 DONE |
|
||||||
| 微信小程序 | 已纳管微信原生模板,并完成顾客端订单、充值、权益明细和下单抵扣主体链路 | `miniapp/pages/index`、`miniapp/pages/store/detail`、`miniapp/pages/room/detail`、`miniapp/pages/orders/*`、`miniapp/pages/recharge/*`、`miniapp/pages/profile/*`、`miniapp/utils/api.js`、`scripts/check-miniapp-m08-a.mjs` | M08-A PARTIAL;已支持选店、门店/房间浏览、报价、预占、测试支付、余额支付、优惠券/套餐下单抵扣、订单微信预支付调起、充值微信支付调起、受控 Wi-Fi、订单列表/详情、状态历史、取消、订单权限开门、续费、换房、分享、个人中心和权益明细,仍不能标记 DONE |
|
| 微信小程序 | 已纳管微信原生模板,并完成顾客端订单、充值、权益明细和下单抵扣主体链路 | `miniapp/pages/index`、`miniapp/pages/store/detail`、`miniapp/pages/room/detail`、`miniapp/pages/orders/*`、`miniapp/pages/recharge/*`、`miniapp/pages/profile/*`、`miniapp/utils/api.js`、`scripts/check-miniapp-m08-a.mjs` | M08-A PARTIAL;已支持选店、门店/房间浏览、报价、预占、测试支付、余额支付、优惠券/套餐下单抵扣和可视化选择器、订单微信预支付调起、充值微信支付调起、受控 Wi-Fi、订单列表/详情、状态历史、取消、订单权限开门、续费、换房、分享、个人中心和权益明细,仍不能标记 DONE |
|
||||||
| 数据库迁移 | 已新增 M01-B 核心 schema up/down/verify SQL、最小脱敏 seed、旧库结构 fixture、迁移计划/执行 CLI、验证结果数量门禁和 MySQL 8 集成测试 | `database/migrations/2026061601_m01b_core_schema.*.sql`、`database/seeds/2026061601_m01b_minimal_seed.sql`、`database/fixtures/2026061801_m01b_legacy_schema.sql`、`backend/src/db/migration-runner.ts`、`backend/tests/mysql-migration-roundtrip.test.mjs` | M01-B DONE;WSL MySQL 8.4.9 已验证 8 条旧库 fixture、11 条 up、3 条 verify、10 条 down,并验证租户隔离和旧金额整数分转换;不导入真实旧数据 |
|
| 数据库迁移 | 已新增 M01-B 核心 schema up/down/verify SQL、最小脱敏 seed、旧库结构 fixture、迁移计划/执行 CLI、验证结果数量门禁和 MySQL 8 集成测试 | `database/migrations/2026061601_m01b_core_schema.*.sql`、`database/seeds/2026061601_m01b_minimal_seed.sql`、`database/fixtures/2026061801_m01b_legacy_schema.sql`、`backend/src/db/migration-runner.ts`、`backend/tests/mysql-migration-roundtrip.test.mjs` | M01-B DONE;WSL MySQL 8.4.9 已验证 8 条旧库 fixture、11 条 up、3 条 verify、10 条 down,并验证租户隔离和旧金额整数分转换;不导入真实旧数据 |
|
||||||
| M00 部署脚本 | 已有菜单、状态、HTTPS、Certbot、EMQX、备份检查模板 | `setup.sh`、`scripts/setup/`、`deploy/` | PARTIAL,生产未执行 |
|
| M00 部署脚本 | 已有菜单、状态、HTTPS、Certbot、EMQX、备份检查模板 | `setup.sh`、`scripts/setup/`、`deploy/` | PARTIAL,生产未执行 |
|
||||||
| 发布清单 dry-run | 可基于当前 HEAD 输出 `deployed=false` 的 release manifest,记录后端/后台/迁移尚未生成而跳过 | `scripts/setup/deploy-business.sh --dry-run .`、`scripts/dev/windows/check-release-manifest.ps1` | 可在 M00 验证结构;真实构建与生产发布待 M01/M09 后接入 |
|
| 发布清单 dry-run | 可基于当前 HEAD 输出 `deployed=false` 的 release manifest,记录后端/后台/迁移尚未生成而跳过 | `scripts/setup/deploy-business.sh --dry-run .`、`scripts/dev/windows/check-release-manifest.ps1` | 可在 M00 验证结构;真实构建与生产发布待 M01/M09 后接入 |
|
||||||
@@ -35,6 +35,6 @@
|
|||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
1. 继续 M08-A:在已完成选店、门店/房间浏览、报价、预占、测试支付、余额支付、优惠券/套餐下单抵扣、订单微信预支付调起、充值微信支付调起、受控 Wi-Fi、订单列表/详情、取消、订单权限开门、续费、换房、分享、个人中心、充值入口和权益明细基础上,补权益可视化选择器、真机合法域名、真实微信支付和实物开门验证。
|
1. 继续 M08-A:在已完成选店、门店/房间浏览、报价、预占、测试支付、余额支付、优惠券/套餐下单抵扣和可视化选择器、订单微信预支付调起、充值微信支付调起、受控 Wi-Fi、订单列表/详情、取消、订单权限开门、续费、换房、分享、个人中心、充值入口和权益明细基础上,补真机合法域名、真实微信支付和实物开门验证。
|
||||||
2. 继续复用 M03 门店发现、M04 订单、M05 支付、M06 设备联动和 M07 会员营销后端能力,不重复初始化小程序。
|
2. 继续复用 M03 门店发现、M04 订单、M05 支付、M06 设备联动和 M07 会员营销后端能力,不重复初始化小程序。
|
||||||
3. M06-G 真实硬件联调仍等待生产 MQTT 账号/ACL、DeviceID 和现场实物配线。
|
3. M06-G 真实硬件联调仍等待生产 MQTT 账号/ACL、DeviceID 和现场实物配线。
|
||||||
|
|||||||
@@ -211,3 +211,26 @@ M08-A 仍为 `PARTIAL`。本次完成顾客端第一段工程闭环,但订单
|
|||||||
|
|
||||||
- 工程提交:`dda2b3c`
|
- 工程提交:`dda2b3c`
|
||||||
- 远端校验:待本轮 push 后执行 `HEAD == origin/main`。
|
- 远端校验:待本轮 push 后执行 `HEAD == origin/main`。
|
||||||
|
|
||||||
|
## 2026-06-25 续接:权益可视化选择器
|
||||||
|
|
||||||
|
- 小程序房间页进入时读取 `GET /app-api/profile/benefits`,按当前门店和房间过滤可用优惠券与套餐,展示为可点选卡片,替代手工输入权益 ID。
|
||||||
|
- 优惠券选择支持再次点击取消;套餐选择后自动填入可用分钟和可用金额,仍允许顾客调整本次使用分钟与抵扣金额。
|
||||||
|
- 小程序下单 payload 继续只提交权益 ID、套餐使用量和幂等请求号,不接受客户端自报价格。
|
||||||
|
- 后端预占重算时补充时长券折价:`TIME` 优惠券按当前锁价单价和券分钟折算为订单折扣,满减券、套餐金额抵扣继续走服务端 clamp。
|
||||||
|
- `scripts/check-miniapp-m08-a.mjs` 扩展检查权益列表、选择事件、`/profile/benefits` 和余额支付入口。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- `npm run build`(`backend/`):PASS。
|
||||||
|
- `node scripts/check-miniapp-m08-a.mjs`:PASS。
|
||||||
|
- `npm test`(`backend/`):PASS。
|
||||||
|
|
||||||
|
状态:
|
||||||
|
|
||||||
|
- M08-A 仍为 `PARTIAL`;真机合法域名、真实微信支付和实物开门验证待继续。
|
||||||
|
|
||||||
|
提交:
|
||||||
|
|
||||||
|
- 工程提交:`4701674`。
|
||||||
|
- 远端校验:待本轮 push 后执行 `HEAD == origin/main`。
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
| OPS-003 | 整仓发布清单 | M00-E/M10 | PARTIAL | `59d92c0` | `deploy-business.sh --dry-run` 可在 Windows/WSL 验证当前 HEAD 的 release manifest 结构;`check-release-manifest.ps1` 已接入 `test-all.ps1`;正式构建待项目生成。 | 后端/后台尚未生成,生产菜单未执行。 | M01/M09 后接入真实构建结果,并在生产菜单 2 生成真实 release manifest。 |
|
| OPS-003 | 整仓发布清单 | M00-E/M10 | PARTIAL | `59d92c0` | `deploy-business.sh --dry-run` 可在 Windows/WSL 验证当前 HEAD 的 release manifest 结构;`check-release-manifest.ps1` 已接入 `test-all.ps1`;正式构建待项目生成。 | 后端/后台尚未生成,生产菜单未执行。 | M01/M09 后接入真实构建结果,并在生产菜单 2 生成真实 release manifest。 |
|
||||||
| OPS-004 | 菜单式更新与环境监测 | M00-E/M10 | PARTIAL | `292ab7f` | `setup.sh` 已接入初始化、更新、MQTT、HTTPS、状态、备份、恢复、回滚和诊断菜单;`--backup-status` 已在 WSL 验证,可检查备份工具、目录和模板。 | 未在生产 Ubuntu 执行;真实备份配置未启用。 | 生产执行后补部署记录、真实备份和恢复演练记录。 |
|
| OPS-004 | 菜单式更新与环境监测 | M00-E/M10 | PARTIAL | `292ab7f` | `setup.sh` 已接入初始化、更新、MQTT、HTTPS、状态、备份、恢复、回滚和诊断菜单;`--backup-status` 已在 WSL 验证,可检查备份工具、目录和模板。 | 未在生产 Ubuntu 执行;真实备份配置未启用。 | 生产执行后补部署记录、真实备份和恢复演练记录。 |
|
||||||
| IOT-001 | MQTT Broker 生产部署 | M00/M06 | PARTIAL | `d01f771` | 菜单 3 已支持 Ubuntu 24.04 amd64 原生安装/更新与复检;正式 Topic ACL 已固定;后端实现 MQTT.js 3.1、QoS 1、持久会话、自动重连、订阅恢复、健康检查、消息上限和真实硬件烟测运行器;Windows 全量测试通过,WSL EMQX 5.8.9 服务级检查通过。 | 缺生产 EMQX 安装、正式账号、ACL/端口/备份验收;WSL 本地账号未配置,真实硬件烟测按预期 SKIP。 | 由管理员在生产菜单 3 执行并登记结果;提供生产账号、DeviceID 和实物后执行 `node dist/devices/hardware-smoke-runner.js`。 |
|
| IOT-001 | MQTT Broker 生产部署 | M00/M06 | PARTIAL | `d01f771` | 菜单 3 已支持 Ubuntu 24.04 amd64 原生安装/更新与复检;正式 Topic ACL 已固定;后端实现 MQTT.js 3.1、QoS 1、持久会话、自动重连、订阅恢复、健康检查、消息上限和真实硬件烟测运行器;Windows 全量测试通过,WSL EMQX 5.8.9 服务级检查通过。 | 缺生产 EMQX 安装、正式账号、ACL/端口/备份验收;WSL 本地账号未配置,真实硬件烟测按预期 SKIP。 | 由管理员在生产菜单 3 执行并登记结果;提供生产账号、DeviceID 和实物后执行 `node dist/devices/hardware-smoke-runner.js`。 |
|
||||||
| SYS-001 | 微信原生小程序 | M00-C/M08 | PARTIAL | `dda2b3c` | 微信原生模板已接入顾客端登录态、选店跳转、门店详情、房间列表、报价、预占、测试支付、订单生产微信预支付调起、充值微信支付调起、余额支付、优惠券/套餐下单抵扣参数、受控 Wi-Fi、订单列表、订单详情、状态历史、取消入口、订单权限开门入口、续费、同门店换房、分享口令生成、个人中心余额/权益/账单汇总、充值计划/充值单入口和优惠券/套餐明细;`scripts/check-miniapp-m08-a.mjs`、相关会员/权益/充值测试通过。 | 多角色菜单、权益可视化选择器和真机联调尚未完成。 | 继续 M08-A,补权益选择器、真机合法域名、真实微信支付和实物开门验证。 |
|
| SYS-001 | 微信原生小程序 | M00-C/M08 | PARTIAL | `4701674` | 微信原生模板已接入顾客端登录态、选店跳转、门店详情、房间列表、报价、预占、测试支付、订单生产微信预支付调起、充值微信支付调起、余额支付、优惠券/套餐下单抵扣参数与可视化选择器、受控 Wi-Fi、订单列表、订单详情、状态历史、取消入口、订单权限开门入口、续费、同门店换房、分享口令生成、个人中心余额/权益/账单汇总、充值计划/充值单入口和优惠券/套餐明细;`scripts/check-miniapp-m08-a.mjs`、相关会员/权益/充值测试通过。 | 多角色菜单和真机联调尚未完成。 | 继续 M08-A,补真机合法域名、真实微信支付和实物开门验证。 |
|
||||||
| CFG-001 | 小程序品牌自定义 | M02-A/M03/M08 | PARTIAL | `8b8fb28` | 已建立 AppID/tenant 品牌配置表和 `/app-api/bootstrap`;同一 AppID 两租户隔离读取在 MySQL 8.4.9 实测通过。 | Logo/分享图片上传与小程序实际展示属于后续阶段。 | M03/M08 接入配置管理和小程序展示。 |
|
| CFG-001 | 小程序品牌自定义 | M02-A/M03/M08 | PARTIAL | `8b8fb28` | 已建立 AppID/tenant 品牌配置表和 `/app-api/bootstrap`;同一 AppID 两租户隔离读取在 MySQL 8.4.9 实测通过。 | Logo/分享图片上传与小程序实际展示属于后续阶段。 | M03/M08 接入配置管理和小程序展示。 |
|
||||||
| BKG-010 | 后台多小程序管理 | M02-A/M08/M10 | PARTIAL | `8b8fb28` | 已建立逻辑应用、租户绑定和租户配置模型;多租户 AppID 未指定租户会被拒绝。 | 尚无后台管理页面,多 AppID 真机配置未验证。 | M02-B/C 完成身份权限,M08 增加管理页面。 |
|
| BKG-010 | 后台多小程序管理 | M02-A/M08/M10 | PARTIAL | `8b8fb28` | 已建立逻辑应用、租户绑定和租户配置模型;多租户 AppID 未指定租户会被拒绝。 | 尚无后台管理页面,多 AppID 真机配置未验证。 | M02-B/C 完成身份权限,M08 增加管理页面。 |
|
||||||
| AUTH-001 | 多端统一小程序身份 | M02-B/M02-C/M08 | PARTIAL | `5a300a8` | 已实现微信 code 交换适配器、租户/AppID 隔离身份、JWT、服务端会话、当前用户和注销;禁用、角色/门店范围变化及管理员重置均使旧会话立即失效。 | 缺真实 AppSecret 真机联调和小程序动态角色界面。 | M08 完成小程序角色界面与真机登录。 |
|
| AUTH-001 | 多端统一小程序身份 | M02-B/M02-C/M08 | PARTIAL | `5a300a8` | 已实现微信 code 交换适配器、租户/AppID 隔离身份、JWT、服务端会话、当前用户和注销;禁用、角色/门店范围变化及管理员重置均使旧会话立即失效。 | 缺真实 AppSecret 真机联调和小程序动态角色界面。 | M08 完成小程序角色界面与真机登录。 |
|
||||||
@@ -32,11 +32,11 @@
|
|||||||
| MAP-001 | 地图选店和最近门店 | M03-C/M08 | PARTIAL | `4ac2c96` | 已实现 AppID/tenant 隔离门店发现、城市/营业筛选、按门店时区判断营业中、后端 Haversine 距离排序;小程序支持定位和手工城市降级,并可从门店卡片进入公开门店详情与房间列表。 | 地图 SDK 可视化和真机定位授权尚未验收。 | M08-A 继续补真机定位、地图展示和门店装修渲染。 |
|
| MAP-001 | 地图选店和最近门店 | M03-C/M08 | PARTIAL | `4ac2c96` | 已实现 AppID/tenant 隔离门店发现、城市/营业筛选、按门店时区判断营业中、后端 Haversine 距离排序;小程序支持定位和手工城市降级,并可从门店卡片进入公开门店详情与房间列表。 | 地图 SDK 可视化和真机定位授权尚未验收。 | M08-A 继续补真机定位、地图展示和门店装修渲染。 |
|
||||||
| QR-001 | 门店/房间场景码与 NFC | M03-D/M08 | PARTIAL | `d563078` | 已实现场景码生成、重建失效、撤销、扫码统计和二维码/NFC 统一页面解析;返回权限固定为空,旧码失效已在 MySQL 8.4.9 实测。 | 真实微信小程序码图片生成依赖可用 AppSecret 与真机环境。 | M08 接入微信小程序码接口并完成真机扫描验收。 |
|
| QR-001 | 门店/房间场景码与 NFC | M03-D/M08 | PARTIAL | `d563078` | 已实现场景码生成、重建失效、撤销、扫码统计和二维码/NFC 统一页面解析;返回权限固定为空,旧码失效已在 MySQL 8.4.9 实测。 | 真实微信小程序码图片生成依赖可用 AppSecret 与真机环境。 | M08 接入微信小程序码接口并完成真机扫描验收。 |
|
||||||
| WIFI-001 | 受控 Wi-Fi 凭据 | M03-D/M04/M08 | PARTIAL | `4ac2c96` | 正式订单与分享令牌均执行最小权限;M08-A 门店详情新增登录后受控 Wi-Fi 请求入口,不在未鉴权页面暴露密码。 | 真实到店权限、真机 Wi-Fi 连接和设备权限仍需现场联调。 | M08-A 补有效订单下的 Wi-Fi 真机连接验证。 |
|
| WIFI-001 | 受控 Wi-Fi 凭据 | M03-D/M04/M08 | PARTIAL | `4ac2c96` | 正式订单与分享令牌均执行最小权限;M08-A 门店详情新增登录后受控 Wi-Fi 请求入口,不在未鉴权页面暴露密码。 | 真实到店权限、真机 Wi-Fi 连接和设备权限仍需现场联调。 | M08-A 补有效订单下的 Wi-Fi 真机连接验证。 |
|
||||||
| ORD-001 | 预约下单与多方式支付 | M04/M05/M08 | PARTIAL | `dda2b3c` | 已完成订单全生命周期、微信支付 v3 工程闭环和团购支付;M08-A 小程序已接入报价、预占、测试支付、余额支付、订单生产微信预支付调起、优惠券/套餐下单抵扣、订单列表、订单详情、状态历史、取消测算、取消订单、续费和同门店换房入口。 | 缺真实微信和团购平台授权联调;权益选择器和真机支付验收待补。 | M08-A 继续补权益可视化选择器、真机合法域名和真实微信支付验收。 |
|
| ORD-001 | 预约下单与多方式支付 | M04/M05/M08 | PARTIAL | `4701674` | 已完成订单全生命周期、微信支付 v3 工程闭环和团购支付;M08-A 小程序已接入报价、预占、测试支付、余额支付、订单生产微信预支付调起、优惠券/套餐下单抵扣和可视化选择器、订单列表、订单详情、状态历史、取消测算、取消订单、续费和同门店换房入口。 | 缺真实微信和团购平台授权联调;真机支付验收待补。 | M08-A 继续补真机合法域名和真实微信支付验收。 |
|
||||||
| WAL-001 | 充值优惠 | M07-B/M08 | PARTIAL | `53f5d67` | 已建立充值规则和充值订单表,支持有效期、适用门店、限购、启停、clientRequestId 幂等;M08-A 新增顾客端充值计划查询、充值单创建 API、小程序充值页、微信 JSAPI 预支付调起和 WeChat Pay v3 通知验签解密入账;重复支付回调不重复充值;Windows 全量回归通过。 | 真实微信商户凭据、生产合法域名和微信侧回调仍需联调。 | M08-A 继续补余额/优惠券/套餐下单抵扣集成。 |
|
| WAL-001 | 充值优惠 | M07-B/M08 | PARTIAL | `53f5d67` | 已建立充值规则和充值订单表,支持有效期、适用门店、限购、启停、clientRequestId 幂等;M08-A 新增顾客端充值计划查询、充值单创建 API、小程序充值页、微信 JSAPI 预支付调起和 WeChat Pay v3 通知验签解密入账;重复支付回调不重复充值;Windows 全量回归通过。 | 真实微信商户凭据、生产合法域名和微信侧回调仍需联调。 | M08-A 继续补余额/优惠券/套餐下单抵扣集成。 |
|
||||||
| WAL-002 | 门店独立会员余额 | M07-A | DONE | `63711ad` | 已建立租户/门店余额范围策略、现金/赠送双余额账户和不可变流水;充值、赠送、消费、退款、人工调整均预留 entry type;消费按赠送后现金扣款,业务号幂等,余额不足拒绝;Windows 全量后端回归通过。 | - | M07-D 聚合会员余额和权益状态。 |
|
| WAL-002 | 门店独立会员余额 | M07-A | DONE | `63711ad` | 已建立租户/门店余额范围策略、现金/赠送双余额账户和不可变流水;充值、赠送、消费、退款、人工调整均预留 entry type;消费按赠送后现金扣款,业务号幂等,余额不足拒绝;Windows 全量后端回归通过。 | - | M07-D 聚合会员余额和权益状态。 |
|
||||||
| WAL-003 | 余额账单 | M07-A/M08 | PARTIAL | `7446852` | 账单底表已包含业务类型、业务号、现金/赠送变动、变动后余额、操作人、trace 和 metadata;M07-B 充值成功后写入真实充值流水;M08-A 个人中心已展示最近钱包账单。 | 完整分页账单页面待继续;优惠券/套餐不直接写钱包流水。 | M08-A 增加分页账单或权益明细入口。 |
|
| WAL-003 | 余额账单 | M07-A/M08 | PARTIAL | `7446852` | 账单底表已包含业务类型、业务号、现金/赠送变动、变动后余额、操作人、trace 和 metadata;M07-B 充值成功后写入真实充值流水;M08-A 个人中心已展示最近钱包账单。 | 完整分页账单页面待继续;优惠券/套餐不直接写钱包流水。 | M08-A 增加分页账单或权益明细入口。 |
|
||||||
| WAL-004 | 优惠券和套餐权益 | M07-C/M08 | PARTIAL | `dda2b3c` | 已建立优惠券模板、持券、套餐计划、持有和权益核销流水;支持满减券、时长券、适用门店/房型/房间/星期/节假日、套餐剩余时长/金额,以及订单冻结、确认核销、退回和重复请求幂等;M08-A 个人中心已展示可用/冻结券和套餐汇总,并新增本人优惠券/套餐明细页;下单预占可冻结优惠券/套餐并重算订单金额,支付成功确认核销,取消订单释放冻结权益。 | 发放/购买入口、权益可视化选择器和真机支付验收待补。 | M08-A 继续补权益选择器;M09 接入后台发放/购买和管理入口。 |
|
| WAL-004 | 优惠券和套餐权益 | M07-C/M08 | PARTIAL | `4701674` | 已建立优惠券模板、持券、套餐计划、持有和权益核销流水;支持满减券、时长券、适用门店/房型/房间/星期/节假日、套餐剩余时长/金额,以及订单冻结、确认核销、退回和重复请求幂等;M08-A 个人中心已展示可用/冻结券和套餐汇总,并新增本人优惠券/套餐明细页;下单预占可通过可视化选择器冻结优惠券/套餐并重算订单金额,时长券可按服务端单价折算抵扣,支付成功确认核销,取消订单释放冻结权益。 | 发放/购买入口和真机支付验收待补。 | M09 接入后台发放/购买和管理入口;M08-A 继续真机验收。 |
|
||||||
| MEM-001 | 会员管理 | M07-D/M08 | PARTIAL | `7446852` | 已实现后台会员聚合查询服务和 `/admin-api/members`、`/admin-api/members/:id`;新增顾客端 `/app-api/profile` 和小程序个人中心,可查看注册/最近登录、订单数、消费额、余额、充值、优惠券、套餐和最近钱包流水,手机号脱敏,租户/门店范围及本人读取通过测试。 | 管理员赠券、余额人工调整、禁用、备注和后台前端页面需在 M09/M08 管理端接入并复用既有审计能力。 | M08-A 继续补顾客端权益明细;M09 接入后台会员管理页面。 |
|
| MEM-001 | 会员管理 | M07-D/M08 | PARTIAL | `7446852` | 已实现后台会员聚合查询服务和 `/admin-api/members`、`/admin-api/members/:id`;新增顾客端 `/app-api/profile` 和小程序个人中心,可查看注册/最近登录、订单数、消费额、余额、充值、优惠券、套餐和最近钱包流水,手机号脱敏,租户/门店范围及本人读取通过测试。 | 管理员赠券、余额人工调整、禁用、备注和后台前端页面需在 M09/M08 管理端接入并复用既有审计能力。 | M08-A 继续补顾客端权益明细;M09 接入后台会员管理页面。 |
|
||||||
| GRP-001 | 团购券兑换 | M05-C | PARTIAL | `cda640b` | 已实现美团/点评、抖音、快手统一适配器,支持顾客粘贴/扫码值、管理员人工核销、Mock/API 核销、clientRequestId 幂等和券码 SHA-256 存储;重复券不能再次记账,WSL MySQL 8.4.9 实测通过。 | 缺各平台商家 API 授权,未执行真实厂商核销。 | 取得授权后配置 API endpoint/token 并执行真实验券。 |
|
| GRP-001 | 团购券兑换 | M05-C | PARTIAL | `cda640b` | 已实现美团/点评、抖音、快手统一适配器,支持顾客粘贴/扫码值、管理员人工核销、Mock/API 核销、clientRequestId 幂等和券码 SHA-256 存储;重复券不能再次记账,WSL MySQL 8.4.9 实测通过。 | 缺各平台商家 API 授权,未执行真实厂商核销。 | 取得授权后配置 API endpoint/token 并执行真实验券。 |
|
||||||
| GRP-002 | 美团直订 | M05-C | PARTIAL | `cda640b` | 已实现 HMAC 回调、事件幂等、外部门店/房间映射、未映射人工队列、顾客认领和团购支付订单生成;映射成功订单可进入 PAID。 | 缺美团开放平台授权和真实回调协议确认。 | 取得授权后按厂商协议实现专用签名适配并联调。 |
|
| GRP-002 | 美团直订 | M05-C | PARTIAL | `cda640b` | 已实现 HMAC 回调、事件幂等、外部门店/房间映射、未映射人工队列、顾客认领和团购支付订单生成;映射成功订单可进入 PAID。 | 缺美团开放平台授权和真实回调协议确认。 | 取得授权后按厂商协议实现专用签名适配并联调。 |
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ execution_cursor:
|
|||||||
stage_status: PARTIAL
|
stage_status: PARTIAL
|
||||||
last_completed_stage: M07-D
|
last_completed_stage: M07-D
|
||||||
next_stage: M08-A
|
next_stage: M08-A
|
||||||
last_engineering_commit: `dda2b3c`
|
last_engineering_commit: `4701674`
|
||||||
last_push_verified: true
|
last_push_verified: true
|
||||||
base_branch: main
|
base_branch: main
|
||||||
blocked_reason: "M06-G 真实硬件联调缺生产 MQTT 账号/ACL、DeviceID、控制箱、门锁、插座和现场配线;已完成烟测运行器并按 BLOCKED_EXTERNAL 旁路。"
|
blocked_reason: "M06-G 真实硬件联调缺生产 MQTT 账号/ACL、DeviceID、控制箱、门锁、插座和现场配线;已完成烟测运行器并按 BLOCKED_EXTERNAL 旁路。"
|
||||||
updated_at: 2026-06-25T23:53:00+08:00
|
updated_at: 2026-06-25T23:59:00+08:00
|
||||||
```
|
```
|
||||||
|
|
||||||
| 模块 | 状态 | 最近提交 | 最近开发日志 | 备注 |
|
| 模块 | 状态 | 最近提交 | 最近开发日志 | 备注 |
|
||||||
@@ -24,7 +24,7 @@ execution_cursor:
|
|||||||
| M05 会员、余额、套餐、优惠券 | PARTIAL | `1680d73` | docs/devlogs/2026-06-22-M05-D-分账与收款配置.md | M05-A/B/C/D 工程闭环已完成:统一支付、微信支付退款、团购直订、门店收款账户、分账接收方、比例策略、授权门禁、幂等分账和对账查询均通过 Windows 与 WSL MySQL 回归;真实微信/团购/分账权限未提供,模块保持 PARTIAL/BLOCKED_EXTERNAL。 |
|
| M05 会员、余额、套餐、优惠券 | PARTIAL | `1680d73` | docs/devlogs/2026-06-22-M05-D-分账与收款配置.md | M05-A/B/C/D 工程闭环已完成:统一支付、微信支付退款、团购直订、门店收款账户、分账接收方、比例策略、授权门禁、幂等分账和对账查询均通过 Windows 与 WSL MySQL 回归;真实微信/团购/分账权限未提供,模块保持 PARTIAL/BLOCKED_EXTERNAL。 |
|
||||||
| M06 设备、MQTT 与真实硬件联动 | PARTIAL | `d01f771` | docs/devlogs/2026-06-24-M06-G-真实硬件联调.md | M06-A 至 F 已完成 MQTT、拓扑、协议幂等、控制箱、Sub-1G 门锁、智慧插座和订单自动联动任务;M06-G 已补真实硬件烟测运行器,缺生产 MQTT 账号/ACL、DeviceID 和实物配线,保持 BLOCKED_EXTERNAL。 |
|
| M06 设备、MQTT 与真实硬件联动 | PARTIAL | `d01f771` | docs/devlogs/2026-06-24-M06-G-真实硬件联调.md | M06-A 至 F 已完成 MQTT、拓扑、协议幂等、控制箱、Sub-1G 门锁、智慧插座和订单自动联动任务;M06-G 已补真实硬件烟测运行器,缺生产 MQTT 账号/ACL、DeviceID 和实物配线,保持 BLOCKED_EXTERNAL。 |
|
||||||
| M07 会员、余额、充值、优惠券和套餐营销 | DONE | `ed0d455` | docs/devlogs/2026-06-24-M07-D-会员管理.md | M07-A 已完成现金/赠送双余额账本;M07-B 已完成充值优惠;M07-C 已完成优惠券/套餐权益底表和冻结、确认、退回的可靠补偿核销服务;M07-D 已完成会员画像、订单、消费、余额、充值、优惠券和套餐聚合查询。 |
|
| M07 会员、余额、充值、优惠券和套餐营销 | DONE | `ed0d455` | docs/devlogs/2026-06-24-M07-D-会员管理.md | M07-A 已完成现金/赠送双余额账本;M07-B 已完成充值优惠;M07-C 已完成优惠券/套餐权益底表和冻结、确认、退回的可靠补偿核销服务;M07-D 已完成会员画像、订单、消费、余额、充值、优惠券和套餐聚合查询。 |
|
||||||
| M08 微信原生小程序完整业务 | PARTIAL | `dda2b3c` | docs/devlogs/2026-06-24-M08-A-顾客端.md | M08-A 已完成顾客端选店、门店/房间浏览、报价、预占、测试支付、订单生产微信预支付调起、充值微信支付调起与通知入账、受控 Wi-Fi、订单列表、订单详情、状态历史、取消测算、取消订单、订单权限开门、顾客续费、同门店换房、分享口令生成、个人中心余额/权益/账单汇总、充值计划/充值单入口、优惠券/套餐明细,以及优惠券/套餐/余额下单抵扣工程闭环;真机合法域名、真实微信支付、实物开门验证和权益可视化选择器继续留在 M08-A。 |
|
| M08 微信原生小程序完整业务 | PARTIAL | `4701674` | docs/devlogs/2026-06-24-M08-A-顾客端.md | M08-A 已完成顾客端选店、门店/房间浏览、报价、预占、测试支付、订单生产微信预支付调起、充值微信支付调起与通知入账、受控 Wi-Fi、订单列表、订单详情、状态历史、取消测算、取消订单、订单权限开门、顾客续费、同门店换房、分享口令生成、个人中心余额/权益/账单汇总、充值计划/充值单入口、优惠券/套餐明细、优惠券/套餐/余额下单抵扣工程闭环和权益可视化选择器;真机合法域名、真实微信支付和实物开门验证继续留在 M08-A。 |
|
||||||
| M09 后台管理端完整业务 | TODO | - | - | - |
|
| M09 后台管理端完整业务 | TODO | - | - | - |
|
||||||
| M10 部署、域名、验收和运维闭环 | TODO | - | - | - |
|
| M10 部署、域名、验收和运维闭环 | TODO | - | - | - |
|
||||||
|
|
||||||
|
|||||||
@@ -32,15 +32,82 @@ Page({
|
|||||||
packageHoldingId: '',
|
packageHoldingId: '',
|
||||||
packageMinutes: '',
|
packageMinutes: '',
|
||||||
packageCreditYuan: '',
|
packageCreditYuan: '',
|
||||||
|
benefitLoading: false,
|
||||||
|
availableCoupons: [],
|
||||||
|
availablePackages: [],
|
||||||
|
selectedCouponName: '',
|
||||||
|
selectedPackageName: '',
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
this.setData({
|
this.setData({
|
||||||
storeId: options.storeId || '',
|
storeId: options.storeId || '',
|
||||||
roomId: options.roomId || '',
|
roomId: options.roomId || '',
|
||||||
})
|
})
|
||||||
|
this.loadBenefitOptions()
|
||||||
},
|
},
|
||||||
|
|
||||||
updateBenefitField(event) {
|
async loadBenefitOptions() {
|
||||||
|
this.setData({ benefitLoading: true })
|
||||||
|
try {
|
||||||
|
await ensureLogin()
|
||||||
|
const response = await request('/profile/benefits')
|
||||||
|
this.setData({
|
||||||
|
availableCoupons: (response.data.coupons || [])
|
||||||
|
.filter((item) => canUseCoupon(item, this.data.storeId, this.data.roomId))
|
||||||
|
.map(formatCouponOption),
|
||||||
|
availablePackages: (response.data.packages || [])
|
||||||
|
.filter((item) => canUsePackage(item, this.data.storeId, this.data.roomId))
|
||||||
|
.map(formatPackageOption),
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
this.setData({ errorMessage: error.message || '权益加载失败' })
|
||||||
|
} finally {
|
||||||
|
this.setData({ benefitLoading: false })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectCoupon(event) {
|
||||||
|
const couponGrantId = event.currentTarget.dataset.id || ''
|
||||||
|
const current = this.data.couponGrantId === couponGrantId
|
||||||
|
const selected = current
|
||||||
|
? null
|
||||||
|
: this.data.availableCoupons.find((item) => item.couponGrantId === couponGrantId)
|
||||||
|
this.setData({
|
||||||
|
couponGrantId: current ? '' : couponGrantId,
|
||||||
|
selectedCouponName: selected ? selected.name : '',
|
||||||
|
order: null,
|
||||||
|
payment: null,
|
||||||
|
successMessage: '',
|
||||||
|
errorMessage: '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
selectPackage(event) {
|
||||||
|
const packageHoldingId = event.currentTarget.dataset.id || ''
|
||||||
|
const current = this.data.packageHoldingId === packageHoldingId
|
||||||
|
const selected = current
|
||||||
|
? null
|
||||||
|
: this.data.availablePackages.find((item) => item.packageHoldingId === packageHoldingId)
|
||||||
|
this.setData({
|
||||||
|
packageHoldingId: current ? '' : packageHoldingId,
|
||||||
|
selectedPackageName: selected ? selected.name : '',
|
||||||
|
packageMinutes: selected && selected.remainingMinutes > 0
|
||||||
|
? String(Math.min(selected.remainingMinutes, this.data.durationMinutes))
|
||||||
|
: '',
|
||||||
|
packageCreditYuan: selected && selected.remainingAmountCents > 0
|
||||||
|
? yuanInput(Math.min(
|
||||||
|
selected.remainingAmountCents,
|
||||||
|
this.data.quote?.totalCents || selected.remainingAmountCents
|
||||||
|
))
|
||||||
|
: '',
|
||||||
|
order: null,
|
||||||
|
payment: null,
|
||||||
|
successMessage: '',
|
||||||
|
errorMessage: '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePackageUsage(event) {
|
||||||
const field = event.currentTarget.dataset.field
|
const field = event.currentTarget.dataset.field
|
||||||
if (!field) return
|
if (!field) return
|
||||||
this.setData({
|
this.setData({
|
||||||
@@ -95,7 +162,52 @@ Page({
|
|||||||
if (!this.data.order || !this.data.order.orderId) {
|
if (!this.data.order || !this.data.order.orderId) {
|
||||||
this.setData({ errorMessage: '请先预占订单' })
|
this.setData({ errorMessage: '请先预占订单' })
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function canUseCoupon(item, storeId, roomId) {
|
||||||
|
return item.status === 'AVAILABLE'
|
||||||
|
&& Number(item.remainingUses || 0) > 0
|
||||||
|
&& (!item.storeId || String(item.storeId) === String(storeId))
|
||||||
|
&& (!item.roomId || String(item.roomId) === String(roomId))
|
||||||
|
}
|
||||||
|
|
||||||
|
function canUsePackage(item, storeId, roomId) {
|
||||||
|
return item.status === 'ACTIVE'
|
||||||
|
&& (Number(item.remainingMinutes || 0) > 0 || Number(item.remainingAmountCents || 0) > 0)
|
||||||
|
&& (!item.storeId || String(item.storeId) === String(storeId))
|
||||||
|
&& (!item.roomId || String(item.roomId) === String(roomId))
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCouponOption(item) {
|
||||||
|
const isTime = item.couponType === 'TIME'
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
valueText: isTime ? `${item.timeMinutes || 0}分钟` : cents(item.discountAmountCents),
|
||||||
|
thresholdText: Number(item.minOrderAmountCents || 0) > 0
|
||||||
|
? `满${cents(item.minOrderAmountCents)}可用`
|
||||||
|
: '无门槛',
|
||||||
|
scopeText: scopeText(item),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPackageOption(item) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
remainingAmountText: cents(item.remainingAmountCents),
|
||||||
|
scopeText: scopeText(item),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scopeText(item) {
|
||||||
|
if (item.roomId) return `房间 ${item.roomId}`
|
||||||
|
if (item.roomCategoryId) return `房型 ${item.roomCategoryId}`
|
||||||
|
if (item.storeId) return `门店 ${item.storeId}`
|
||||||
|
return item.holidayOnly ? '节假日' : '通用'
|
||||||
|
}
|
||||||
|
|
||||||
|
function yuanInput(centsValue) {
|
||||||
|
return (Number(centsValue || 0) / 100).toFixed(2)
|
||||||
|
}
|
||||||
await this.withRequest(async () => {
|
await this.withRequest(async () => {
|
||||||
const response = await request('/payments', {
|
const response = await request('/payments', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -10,11 +10,41 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="section-title">权益抵扣</view>
|
<view class="section-title">权益抵扣</view>
|
||||||
<view class="benefit-grid">
|
<view wx:if="{{benefitLoading}}" class="muted">权益加载中</view>
|
||||||
<input placeholder="优惠券ID" value="{{couponGrantId}}" data-field="couponGrantId" bindinput="updateBenefitField" />
|
<view wx:if="{{!benefitLoading && availableCoupons.length === 0 && availablePackages.length === 0}}" class="muted">暂无可用权益</view>
|
||||||
<input placeholder="套餐ID" value="{{packageHoldingId}}" data-field="packageHoldingId" bindinput="updateBenefitField" />
|
<view wx:if="{{availableCoupons.length}}" class="benefit-list">
|
||||||
<input placeholder="套餐分钟" type="number" value="{{packageMinutes}}" data-field="packageMinutes" bindinput="updateBenefitField" />
|
<view
|
||||||
<input placeholder="套餐抵扣元" type="digit" value="{{packageCreditYuan}}" data-field="packageCreditYuan" bindinput="updateBenefitField" />
|
wx:for="{{availableCoupons}}"
|
||||||
|
wx:key="couponGrantId"
|
||||||
|
class="benefit-option {{couponGrantId === item.couponGrantId ? 'active' : ''}}"
|
||||||
|
data-id="{{item.couponGrantId}}"
|
||||||
|
bindtap="selectCoupon"
|
||||||
|
>
|
||||||
|
<view class="option-head">
|
||||||
|
<view class="option-title">{{item.name}}</view>
|
||||||
|
<view class="option-value">{{item.valueText}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="muted">{{item.thresholdText}} · {{item.scopeText}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{availablePackages.length}}" class="benefit-list">
|
||||||
|
<view
|
||||||
|
wx:for="{{availablePackages}}"
|
||||||
|
wx:key="packageHoldingId"
|
||||||
|
class="benefit-option {{packageHoldingId === item.packageHoldingId ? 'active' : ''}}"
|
||||||
|
data-id="{{item.packageHoldingId}}"
|
||||||
|
bindtap="selectPackage"
|
||||||
|
>
|
||||||
|
<view class="option-head">
|
||||||
|
<view class="option-title">{{item.name}}</view>
|
||||||
|
<view class="option-value">{{item.remainingAmountText}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="muted">剩余 {{item.remainingMinutes}} 分钟 · {{item.scopeText}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{packageHoldingId}}" class="benefit-grid">
|
||||||
|
<input placeholder="套餐分钟" type="number" value="{{packageMinutes}}" data-field="packageMinutes" bindinput="updatePackageUsage" />
|
||||||
|
<input placeholder="套餐抵扣元" type="digit" value="{{packageCreditYuan}}" data-field="packageCreditYuan" bindinput="updatePackageUsage" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="action-grid">
|
<view class="action-grid">
|
||||||
@@ -39,6 +69,8 @@
|
|||||||
<view class="result-title">订单</view>
|
<view class="result-title">订单</view>
|
||||||
<view>订单编号:{{order.orderId}}</view>
|
<view>订单编号:{{order.orderId}}</view>
|
||||||
<view wx:if="{{order.reservationId}}">预占编号:{{order.reservationId}}</view>
|
<view wx:if="{{order.reservationId}}">预占编号:{{order.reservationId}}</view>
|
||||||
|
<view wx:if="{{selectedCouponName}}">已选优惠券:{{selectedCouponName}}</view>
|
||||||
|
<view wx:if="{{selectedPackageName}}">已选套餐:{{selectedPackageName}}</view>
|
||||||
<view wx:if="{{order.quote.discountCents}}">优惠抵扣:{{order.quote.discountCents}}分</view>
|
<view wx:if="{{order.quote.discountCents}}">优惠抵扣:{{order.quote.discountCents}}分</view>
|
||||||
<view wx:if="{{order.quote.packageCreditCents}}">套餐抵扣:{{order.quote.packageCreditCents}}分</view>
|
<view wx:if="{{order.quote.packageCreditCents}}">套餐抵扣:{{order.quote.packageCreditCents}}分</view>
|
||||||
<button size="mini" data-order-id="{{order.orderId}}" bindtap="openOrder">查看订单</button>
|
<button size="mini" data-order-id="{{order.orderId}}" bindtap="openOrder">查看订单</button>
|
||||||
|
|||||||
@@ -40,6 +40,46 @@
|
|||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.benefit-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-option {
|
||||||
|
padding: 22rpx;
|
||||||
|
border: 2rpx solid #d9dde5;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-option.active {
|
||||||
|
border-color: #1f6feb;
|
||||||
|
background: #eef5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-head {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-title {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-value {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: #1f6feb;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.action-grid button {
|
.action-grid button {
|
||||||
flex: 1 1 280rpx;
|
flex: 1 1 280rpx;
|
||||||
}
|
}
|
||||||
@@ -67,6 +107,11 @@
|
|||||||
color: #17823b;
|
color: #17823b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
color: #777777;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
margin: 16rpx 0;
|
margin: 16rpx 0;
|
||||||
color: #c73535;
|
color: #c73535;
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ for (const pattern of [
|
|||||||
'couponGrantId',
|
'couponGrantId',
|
||||||
'packageHoldingId',
|
'packageHoldingId',
|
||||||
'packageCreditCents',
|
'packageCreditCents',
|
||||||
|
'/profile/benefits',
|
||||||
|
'selectCoupon',
|
||||||
|
'selectPackage',
|
||||||
|
'availableCoupons',
|
||||||
|
'availablePackages',
|
||||||
"provider: 'BALANCE'"
|
"provider: 'BALANCE'"
|
||||||
]) {
|
]) {
|
||||||
assert.match(room, new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
assert.match(room, new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||||
|
|||||||
Reference in New Issue
Block a user