diff --git a/.codebuddy/plans/修正_typings_中_GetApp_类型参数的约束缺失_TS2344__684f50fc.md b/.codebuddy/plans/修正_typings_中_GetApp_类型参数的约束缺失_TS2344__684f50fc.md new file mode 100644 index 0000000..936923d --- /dev/null +++ b/.codebuddy/plans/修正_typings_中_GetApp_类型参数的约束缺失_TS2344__684f50fc.md @@ -0,0 +1,67 @@ +--- +name: 修正 typings 中 GetApp 类型参数的约束缺失(TS2344) +overview: 修正 vendored 类型声明 typings/types/wx/lib.wx.app.d.ts 第 265 行:为 getApp 的泛型参数补上 extends IAnyObject 约束,消除全量 tsc 校验时唯一的既有 TS2344 报错,与 miniprogram-api-typings 上游新版本写法保持一致。 +todos: + - id: fix-getapp-constraint + content: 修正 lib.wx.app.d.ts 第 265 行 GetApp 泛型约束为 T extends IAnyObject = IAnyObject + status: completed + - id: verify-typings-fix + content: 用 [skill:lsp-code-analysis] 核查 GetApp 引用面,运行 tsc --noEmit 确认全项目 0 报错 + status: completed + dependencies: + - fix-getapp-constraint +--- + +## 需求概述 + +修正前端仓库 `typings` 内置类型声明中的既有 TypeScript 报错,使全量类型校验(含 typings)通过,为后续接入 CI 类型检查扫清障碍。用户已确认采用"单行修正"方案。 + +## 报错与根因 + +- 报错(全项目唯一一条,位于 typings、非业务代码):`typings/types/wx/lib.wx.app.d.ts(265,57): error TS2344: Type 'T' does not satisfy the constraint 'IAnyObject'`。 +- 根因:`Instance` 要求类型参数受约束,而 `GetApp` 泛型只写了默认值 ``;TypeScript 中默认值不等于约束,故 `Instance` 触发 TS2344。上游新版 typings 已修正为 ``。 +- 该文件自仓库首次提交引入后从未改动(`git diff -- typings` 为空),报错仅在全量 `tsc --noEmit` 时暴露,不影响微信开发者工具的编译、预览与上传。 + +## 核心内容 + +- 仅修正该声明文件中的一行泛型签名:补齐约束并保留默认值,即 `T extends IAnyObject = IAnyObject`。 +- 不升级整套 typings、不改动任何业务代码;修正后对外类型行为保持不变。 +- 验收标准:`tsc --noEmit` 全项目(含 typings)0 报错,IDE 诊断无新增告警。 + +## 技术方案 + +### 修改点(单文件单行) + +- 文件:`typings/types/wx/lib.wx.app.d.ts` 第 265 行 +- 修改前:`(opts?: GetAppOption): Instance` +- 修改后:`(opts?: GetAppOption): Instance` + +### 关键决策与兼容性 + +- 选择"补约束"而非"升级整套 typings":一行修正即与上游新版本写法一致,改动面最小、零运行时影响;整套升级会引入大量无关声明变更,收益低、风险高。 +- 影响面核查(已确认):业务代码中不存在 `getApp` 调用,仅有 `miniprogram/app.ts` 的 `App(...)` 注册(属于 `App.Constructor`,与 `GetApp` 接口无关);`getApp()` 若被调用仍走默认值 `IAnyObject`,类型不变。该修正只影响声明本身。 + +### 验证方式 + +- 运行 `npx --yes -p typescript@5.4.5 tsc --noEmit -p tsconfig.json`,预期 0 报错。 +- 用 [skill:lsp-code-analysis] 核查 `GetApp`/`Instance` 相关符号引用面,确认修正无其他消费点被波及。 +- `git diff` 复核为单行 diff。 + +### 目录结构(仅列出改动文件) + +``` +zt-closet-wxmini-frontend/ +└── typings/ + └── types/wx/lib.wx.app.d.ts # [MODIFY] 第 265 行 GetApp 泛型补充约束 T extends IAnyObject = IAnyObject,修复 TS2344 报错;仅改此一行,不格式化文件、不动其他声明 +``` + +### 实现说明 + +- 严格控制改动范围:单行修改,不改缩进风格与文件其余内容,保证 `git diff` 只有一行变更。 +- 不做额外的 typings 升级或业务代码调整;若未来升级 miniprogram-api-typings,此行与上游新写法一致,可被覆盖而无冲突。 + +### Skill + +- **lsp-code-analysis** +- Purpose: 用语义化引用分析核查 `GetApp`、`Instance` 等符号的引用面,确认修正仅影响该声明、无其他消费点被波及(业务代码无 `getApp` 调用)。 +- Expected outcome: 输出相关符号的引用清单与类型解析结果,确认修正后无新增错误、无遗漏消费方。 \ No newline at end of file diff --git a/.codebuddy/plans/前端对齐后端退款流程重构_订单状态6__0c76212f.md b/.codebuddy/plans/前端对齐后端退款流程重构_订单状态6__0c76212f.md new file mode 100644 index 0000000..a2a08cd --- /dev/null +++ b/.codebuddy/plans/前端对齐后端退款流程重构_订单状态6__0c76212f.md @@ -0,0 +1,96 @@ +--- +name: 前端对齐后端退款流程重构(订单状态6) +overview: 前端唯一未同步的后端变更是最新提交 b94dd6d(退款全流程重构 + 新增订单状态 6「申请退款中」)。需在订单页补齐状态 6 的筛选、文案、样式与提示,并按设计文档补充退款状态轮询;其余近期后端改动前端均已同步。 +todos: + - id: sync-order-status-def + content: 补充 config.ts OrderStatus 枚举与 models.ts 注释的状态 6「申请退款中」 + status: completed + - id: order-page-status-6 + content: order.ts 增加状态 6 文案、胶囊、筛选 tab 与 iOS 退款判定;order.wxml 增加审核中提示 + status: completed + dependencies: + - sync-order-status-def + - id: refund-status-polling + content: order.ts 实现退款状态轮询:6→5/6→2 提示并刷新,页面隐藏即停止 + status: completed + dependencies: + - order-page-status-6 + - id: verify-order-sync + content: 用 [skill:lsp-code-analysis] 核查订单状态引用覆盖,运行 npx tsc --noEmit 并手工验证退款全流程 + status: completed + dependencies: + - refund-status-polling +--- + +## 需求概述 + +用户修改了衣橱小程序的后端代码(退款流程重构),需要检查配套小程序前端是否需要同步修改,并给出具体修改方案。 + +补充业务约束(用户强调):**处于「6 申请退款中」的订单,用户不可再次发起退款,只能等待审核/退款结果**。 + +## 排查结论 + +- 经逐提交核对,前端此前已同步后端近期绝大多数改动:通知「全部已读」接口、通过社区公开穿搭只读查看衣物、衣物处理失败原因与处理状态字段等,均已在现有前端代码中落地。 +- 唯一尚未同步的是后端最新的退款流程重构:订单新增「6 申请退款中」状态(申请退款后进入待审核、退款失败回到「已支付」可重新申请、退款成功变为「已退款」)。前端订单页目前缺少该状态的展示与退款结果跟踪能力,需要补齐。 + +## 核心功能 + +- 订单列表状态徽标新增「申请退款中」(进行中语义),并在「已支付」与「已取消」之间新增对应筛选标签 +- 申请退款提交后,订单显示「申请退款中」及审核中提示;前端周期性跟踪退款结果:成功显示「已退款」并提示、失败回到「已支付」并提示可重新申请 +- **「申请退款中」订单为纯等待态:不展示任何退款/取消等操作入口,用户不可再次发起退款,只能等待结果**(后端同样拒绝:`RefundApply` 仅接受订单状态 `2 已支付`,状态 6 再次申请返回 71001「当前状态不允许退款」) +- iOS 虚拟支付订单不展示「申请退款」入口(按后端规则需走 App Store 退款) +- 其余交互(退款原因填写与长度限制、提交成功提示、退款相关错误码文案)已满足新契约,无需调整 + +## 排查范围与结论 + +对比前端最新提交(98c45f2)与后端仓库 /root/zt-closet-backend 最新提交(b94dd6d): + +- 已同步:`89b59c7` 通知全部已读(前端 notification.ts 已调用 `POST /notification/read-all`,并处理返回的 unread_count)、`c2519c4` 社区公开穿搭只读查看衣物(clothes-detail/outfit-detail 已携带 `outfit_uuid`)、`e9b9754` 衣物处理失败原因(upload/clothes-detail 已使用 `image_process_status`/`fail_reason`)。 +- 未同步:`b94dd6d` 退款全流程重构 —— 订单状态枚举扩展为 `oneof=1 2 3 4 5 6`,`OrderStatusRefunding = 6`,退款申请原子流转 2→6 并落待审核流水,退款成功→5、失败→回2。前端需对齐。 + +## 技术栈 + +- 微信小程序原生(TypeScript + WXML + WXSS),沿用现有 `utils/request` 封装、`utils/logger` 日志、`STATUS_*` 常量映射模式与全局 pill 胶囊样式体系,无新增依赖。 +- 类型校验:`npx tsc --noEmit`(仓库无 npm scripts)。 + +## 实现方案 + +1. **状态契约对齐**:`utils/config.ts` 的 `OrderStatus` 枚举新增 `REFUNDING = 6 // 申请退款中`;`types/models.ts` 中 `order_status` 注释补充 `6=申请退款中`。 +2. **订单页展示**(对齐 Stitch.md 设计约定): + +- `STATUS_TEXT` 增加 `6: '申请退款中'`;`STATUS_CLS` 增加 `6: 'pill--ink'`(遵循 app.wxss「进行中/强调 = pill--ink」约定);`STATUS_FILTERS` 在「已支付」之后插入 `{ name: '申请退款中', value: 6 }`(索引与数组同步,筛选逻辑不受影响)。 +- wxml 操作区新增 `wx:elif="{{item.order_status === 6}}"` 分支:**仅渲染灰字等待提示(如「退款审核中,审核结果将通过站内通知告知」),不提供任何可点击操作项**——不显示「申请退款」,也不显示「取消订单」,用户只能等待轮询/通知给出结果。优先复用 `.od-countdown` 同类灰字样式,必要时新增等价小类。 + +3. **iOS 虚拟支付退款入口收敛**:`RefundApply` 对 iOS 虚拟订单返回 71004(错误码文案前端已具备兜底)。订单响应无平台字段,页面用 `wx.getSystemInfoSync().platform` 判定(与 mall.ts 的 normalizePlatform 同一数据源,需 try/catch 兜底),新增 `_canRefund` = 状态 `2` 且非(`pay_channel === 2` 且 iOS),wxml 按钮改用该标记(状态 6 天然不在 `_canRefund` 范围,不会再出现退款入口)。 +4. **退款状态轮询**(设计文档明确约定「前端轮询 GET /order」):列表存在状态 6 订单时,每约 10 秒静默请求 `GET /order?status=6&page=1&page_size=50`(`showError: false`,独立于 fetchList 的 reqSeq 机制,避免干扰列表与闪烁);被跟踪订单从响应中消失即表示已离开退款中 → 触发一次 `refresh()`,并按刷新结果提示:`5`→「退款成功」、`2`→「退款未通过,可重新申请」(找不到时用通用文案);仍为 6 继续等待。无状态 6 订单不发请求;`onHide/onUnload` 清理定时器(与既有倒计时 timer 分开管理,避免互相覆盖)。 +5. **不改动项(经核实)**:退款提交交互与文案已满足新契约;状态 6 期间前端无退款入口、后端也拒绝重复申请,天然不会触发 71001;`mall.ts` 支付轮询终态判断(3/4/5)在支付场景不会遇到状态 6;错误码 71000-71004 文案已齐备。 + +## 实现说明(执行要点) + +- 性能与请求量:轮询为单用户轻量查询(page_size 50 封顶),仅在存在状态 6 订单时触发;失败静默降级(`logger.warn`)等待下一轮重试,不阻塞 UI。 +- 日志:复用 `logger`(模块名 `order`),仅状态流转与请求失败时记录,成功轮询走 debug,避免日志刷屏。 +- 影响面控制:改动仅限订单页与类型/枚举注释,其余页面与接口无波及;若后端未上线新版本(不返回状态 6),前端行为与现状完全一致,可安全先行发布。 + +## 目录结构(仅列出改动文件) + +``` +zt-closet-wxmini-frontend/ +└── miniprogram/ + ├── utils/ + │ └── config.ts # [MODIFY] OrderStatus 枚举新增 REFUNDING = 6;错误码区已含 71000-71004,无需改动 + ├── types/ + │ └── models.ts # [MODIFY] OrderItem.order_status 注释补充「6 申请退款中」 + └── pages/ + └── order/ + ├── order.ts # [MODIFY] STATUS_TEXT/STATUS_CLS/STATUS_FILTERS 增加状态 6;新增 _canRefund(含 iOS 判定);实现退款状态轮询与生命周期清理 + ├── order.wxml # [MODIFY] 状态 6 操作区仅渲染等待提示(无任何操作按钮);「申请退款」按钮改用 _canRefund + └── order.wxss # [MODIFY/可选] 仅在需要新提示样式时补充,优先复用既有灰字样式 +``` + +## Agent Extensions + +### Skill + +- **lsp-code-analysis** +- Purpose: 利用语义化引用分析核查 `order_status`、`OrderStatus`、`STATUS_TEXT/STATUS_CLS` 等符号的全部引用,确认状态 6 同步无遗漏(区别于文本搜索的补充验证) +- Expected outcome: 输出完整的引用清单,确认所有订单状态消费点均已覆盖,无遗漏文件 \ No newline at end of file diff --git a/miniprogram/pages/order/order.ts b/miniprogram/pages/order/order.ts index a866f9e..ed627a1 100644 --- a/miniprogram/pages/order/order.ts +++ b/miniprogram/pages/order/order.ts @@ -12,21 +12,35 @@ const STATUS_FILTERS = [ { name: '全部', value: 0 }, { name: '待支付', value: 1 }, { name: '已支付', value: 2 }, + { name: '申请退款中', value: 6 }, { name: '已取消', value: 3 }, { name: '已过期', value: 4 }, { name: '已退款', value: 5 }, ] -const STATUS_TEXT: Record = { 1: '待支付', 2: '已支付', 3: '已取消', 4: '已过期', 5: '已退款' } -// 零彩色状态语义:待支付 = 实心黑;已支付/已退款 = 描边;已取消/已过期 = 降透明 +const STATUS_TEXT: Record = { 1: '待支付', 2: '已支付', 3: '已取消', 4: '已过期', 5: '已退款', 6: '申请退款中' } +// 零彩色状态语义:待支付/申请退款中 = 实心黑(进行中);已支付/已退款 = 描边;已取消/已过期 = 降透明 const STATUS_CLS: Record = { 1: 'pill--ink', 2: 'pill--line', 3: 'pill--dim', 4: 'pill--dim', 5: 'pill--line pill--dim', + 6: 'pill--ink', } +// 退款结果轮询间隔:仅在列表存在「申请退款中」订单时才发请求 +const REFUND_POLL_MS = 10 * 1000 + +// 设备是否 iOS:iOS 虚拟支付订单退款需走 App Store(后端返回 71004),前端不展示退款入口 +const IS_IOS = (() => { + try { + return wx.getSystemInfoSync().platform === 'ios' + } catch (e) { + return false + } +})() + Page({ data: { filters: STATUS_FILTERS, @@ -62,19 +76,22 @@ Page({ onShow() { this.refresh() this.startCountdown() + this.startRefundWatch() }, onHide() { this.stopCountdown() + this.stopRefundWatch() }, onUnload() { this.stopCountdown() + this.stopRefundWatch() }, - refresh() { + refresh(): Promise { this.setData({ page: 1, hasMore: true, loading: this.data.list.length === 0 }) - this.fetchList(1) + return this.fetchList(1) }, onReachBottom() { @@ -112,7 +129,67 @@ Page({ if (changed) this.setData({ list: next }) }, - fetchList(page: number) { + // ===== 退款结果跟踪:申请退款中的订单只等待结果(不可再次发起退款),定期核对是否已出结果 ===== + startRefundWatch() { + this.stopRefundWatch() + ;(this as any).refundTimer = setInterval(() => this.pollRefunding(), REFUND_POLL_MS) + }, + + stopRefundWatch() { + const timer = (this as any).refundTimer + if (timer) { + clearInterval(timer) + ;(this as any).refundTimer = null + } + }, + + // 仅当列表存在「申请退款中」订单时才发请求:命中说明仍未出结果,缺失即视为状态已流转 + pollRefunding() { + const tracked = this.data.list.filter((it) => it.order_status === 6).map((it) => it.order_no) + if (!tracked.length) return + request>({ + url: '/order', + data: { status: 6, page: 1, page_size: 50 }, + showError: false, + }) + .then((res) => { + const still = new Set(normalizePaged(res).list.map((it) => it.order_no)) + const changed = tracked.filter((no) => !still.has(no)) + if (changed.length) this.settleRefundResult(changed) + }) + .catch((e: any) => logger.warn(LOG, '退款结果轮询失败(静默降级), code=', e && e.code)) + }, + + // 核对流转后的最新状态并刷新列表:5=退款成功(终态)、2=退款未通过可重新申请;仍为 6 则继续等待 + settleRefundResult(orderNos: string[]) { + request>({ url: '/order', data: { page: 1, page_size: 50 }, showError: false }) + .then((res) => { + const list = normalizePaged(res).list + orderNos.forEach((no) => { + const latest = list.find((it) => it.order_no === no) + if (!latest) { + logger.warn(LOG, '退款结果核对:未定位到订单最新状态,仅刷新列表, orderNo=', no) + return + } + if (latest.order_status === 6) return + if (latest.order_status === 5) { + logger.info(LOG, '退款成功, orderNo=', no) + wx.showToast({ title: '退款成功', icon: 'success' }) + } else if (latest.order_status === 2) { + logger.info(LOG, '退款未通过,可重新申请, orderNo=', no) + wx.showToast({ title: '退款未通过,可重新申请', icon: 'none' }) + } else { + logger.info(LOG, '退款状态已更新, orderNo=', no, 'status=', latest.order_status) + wx.showToast({ title: '退款状态已更新', icon: 'none' }) + } + }) + this.refresh() + }) + .catch((e: any) => logger.warn(LOG, '退款结果核对失败(静默降级), code=', e && e.code)) + }, + + // 返回列表请求的 Promise:退款结果核对后需要等列表刷新完成 + fetchList(page: number): Promise { const params: Record = { page, page_size: this.data.pageSize } const filter = this.data.activeFilter if (filter > 0) params.status = filter @@ -122,7 +199,7 @@ Page({ ;(this as any).reqSeq = seq if (page > 1) this.setData({ loadingMore: true }) - request>({ url: '/order', data: params }) + return request>({ url: '/order', data: params }) .then((res) => { if ((this as any).reqSeq !== seq) return const { list: raw, total } = normalizePaged(res) @@ -138,6 +215,8 @@ Page({ _countdown: it.order_status === 1 && it.expire_time > now ? formatCountdown(it.expire_time - now) : '', _benefit: it.member_days > 0 ? `${it.member_days} 天会员` : '', _canCancel: it.order_status === 1 && it.pay_channel !== 2, + // 退款入口:仅「已支付」可申请;iOS 虚拟支付订单需走 App Store(后端 71004),不展示入口 + _canRefund: it.order_status === 2 && !(it.pay_channel === 2 && IS_IOS), _payChannelText: it.pay_channel === 2 ? '虚拟支付' : '', })) const merged = mergePaged(this.data.list, list, page === 1) diff --git a/miniprogram/pages/order/order.wxml b/miniprogram/pages/order/order.wxml index 52a6e90..b75e468 100644 --- a/miniprogram/pages/order/order.wxml +++ b/miniprogram/pages/order/order.wxml @@ -57,10 +57,18 @@ 去支付 - 申请退款 + 申请退款 + + + 退款审核中,审核结果将通过站内通知告知 diff --git a/miniprogram/pages/order/order.wxss b/miniprogram/pages/order/order.wxss index be6d65f..879e02f 100644 --- a/miniprogram/pages/order/order.wxss +++ b/miniprogram/pages/order/order.wxss @@ -90,6 +90,13 @@ font-family: var(--serif); } +/* 状态 6「申请退款中」纯等待提示(无任何操作入口) */ +.od-note { + margin-top: 18rpx; + font-size: 23rpx; + color: var(--ink-3); +} + .od-refund-input { margin: 0 var(--pad); width: auto; diff --git a/miniprogram/types/models.ts b/miniprogram/types/models.ts index 1158c86..9087cef 100644 --- a/miniprogram/types/models.ts +++ b/miniprogram/types/models.ts @@ -92,7 +92,7 @@ export interface OrderItem { unit_price: number // 分 pay_amount: number // 分 pay_channel: number // 1 微信支付 2 虚拟支付 - order_status: number // 1 待支付 2 已支付 3 已取消 4 已过期 5 已退款 + order_status: number // 1 待支付 2 已支付 3 已取消 4 已过期 5 已退款 6 申请退款中 member_days: number expire_time: number // 秒 pay_time: number diff --git a/miniprogram/utils/config.ts b/miniprogram/utils/config.ts index ddce1f3..f0cc57e 100644 --- a/miniprogram/utils/config.ts +++ b/miniprogram/utils/config.ts @@ -38,6 +38,7 @@ export enum OrderStatus { CANCELLED = 3, // 已取消 EXPIRED = 4, // 已过期 REFUNDED = 5, // 已退款 + REFUNDING = 6, // 申请退款中(纯等待态,不可再次申请退款) } // 额度类型 diff --git a/typings/types/wx/lib.wx.app.d.ts b/typings/types/wx/lib.wx.app.d.ts index 36a7b92..d36c71b 100644 --- a/typings/types/wx/lib.wx.app.d.ts +++ b/typings/types/wx/lib.wx.app.d.ts @@ -262,7 +262,7 @@ declare namespace WechatMiniprogram.App { } interface GetApp { - (opts?: GetAppOption): Instance + (opts?: GetAppOption): Instance } }