diff --git a/apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts b/apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts index 82e519c..1e2998d 100644 --- a/apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts +++ b/apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts @@ -12,9 +12,23 @@ import { checkPageAccess, getVisibleBoardTabs } from '../../utils/auth-guard' import { getRandomAiColor } from '../../utils/ai-color' import { fetchBoardFinance, fetchAICache } from '../../services/api' +import { getBusinessClock } from '../../utils/runtime-clock' -function isCurrentMonthFilter(selectedTime: string): boolean { - return selectedTime === 'month' && new Date().getDate() <= 5 +/** + * 判断当前选择的时间筛选是否为"本月前 5 天"(用于显示"(预估)"提示)。 + * + * Wave 1 W1-T1 沙箱接入(2026-05-04): + * - 优先使用业务时钟 businessDate(YYYY-MM-DD),沙箱模式下回放虚拟日期 + * - businessDate 为空时降级到本地 new Date()(初次进入页面、业务时钟未就绪) + * - 后端 board_service 已通过 runtime_context 自处理时间窗口,本函数仅控制 UI 提示 + */ +function isCurrentMonthFilter(selectedTime: string, businessDate?: string): boolean { + if (selectedTime !== 'month') return false + if (businessDate) { + const day = parseInt(businessDate.slice(8, 10), 10) + return !Number.isNaN(day) && day <= 5 + } + return new Date().getDate() <= 5 } // 2026-04-22 小程序轻量 Markdown 内联解析:支持 **加粗** / *倾斜* / _倾斜_ / ***加粗倾斜*** @@ -106,6 +120,8 @@ Page({ compareEnabled: false, isCurrentMonth: true, + // Wave 1 W1-T1 沙箱接入:业务时钟(YYYY-MM-DD),沙箱模式下回放虚拟日期。空字符串表示尚未拉取,降级用本地 new Date() + businessDate: '', // 2026-04-22 改版:拆成 title/body 两段,title 在 wxml 渲染为 ai-insight-dim(灰色标题),body 为正文 aiInsights: [] as Array<{ title: string; body: string }>, // 2026-04-22 seq11/12 置顶:从 aiInsights 派生,供"本期总结"卡片渲染 @@ -231,6 +247,16 @@ Page({ }) const tabBar = this.getTabBar?.() if (tabBar) tabBar.setData({ active: 'board' }) + // Wave 1 W1-T1:刷新业务时钟,沙箱模式下回放虚拟日期 → 修正"本月前 5 天预估"提示 + getBusinessClock().then((clock) => { + const businessDate = clock.business_date || '' + this.setData({ + businessDate, + isCurrentMonth: isCurrentMonthFilter(this.data.selectedTime, businessDate), + }) + }).catch(() => { + // 降级:保留默认 isCurrentMonth(本地时钟) + }) this._loadData() }) }, @@ -656,7 +682,7 @@ Page({ onTimeChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) { const value = e.detail.value const option = this.data.timeOptions.find(o => o.value === value) - this.setData({ selectedTime: value, selectedTimeText: option?.text || '本月', isCurrentMonth: isCurrentMonthFilter(value) }) + this.setData({ selectedTime: value, selectedTimeText: option?.text || '本月', isCurrentMonth: isCurrentMonthFilter(value, this.data.businessDate) }) this._loadData() }, diff --git a/docs/audit/changes/2026-05-04__wave1_t1_board_sandbox_clock.md b/docs/audit/changes/2026-05-04__wave1_t1_board_sandbox_clock.md new file mode 100644 index 0000000..f31a7dc --- /dev/null +++ b/docs/audit/changes/2026-05-04__wave1_t1_board_sandbox_clock.md @@ -0,0 +1,150 @@ +# Wave 1 W1-T1 — 看板沙箱接入(P0-3) + +| 字段 | 值 | +|---|---| +| 日期 | 2026-05-04 | +| Wave | 1 / Day 2 | +| 范围 | P0-3 看板沙箱接入(实际改动量 < 原估算 5-7h,因调研发现后端已接入) | +| 文件改动 | 1 个小程序文件(board-finance.ts) | +| 编译验证 | tsc 对 board-finance.ts 零错误 ✅ | + +## 一、调研修正前期 P0-3 报告 + +**P0-3 原结论(Wave 0)**: +> Grep 实证:小程序三大看板 + xcx_board.py **零处引用** runtime-clock / runtime_context + +**Day 2 实地走查发现**: +- ✅ **xcx_board.py 是 router 层薄封装**,不直接引用 runtime_context 是正常设计 +- ✅ **board_service.py(服务层)已接入 runtime_context**: + - L264 `runtime_ctx = get_runtime_context(site_id)` + `_calc_date_range(time, ref_date=runtime_ctx.business_date)` + - L482-490 60 天消费窗口已用 `as_runtime_today_param` + - L712 同样接入业务日 +- ✅ **board-customer.ts / board-coach.ts 0 处使用 `new Date()`**,完全没问题 +- ❌ **board-finance.ts 唯一遗漏**:`isCurrentMonthFilter` 函数用 `new Date().getDate() <= 5` 判断"本月前 5 天",沙箱模式回放历史日期时会判错 + +**P20 SPEC §13 已知冲突表的标记是准确的**:`board-finance.ts isCurrentMonthFilter` 设计文档要求改但未在 grep 结果中确认 → 现已确认未改,本轮修复。 + +## 二、修复内容 + +文件:[apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts](../../../apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts) + +### 改动 1:import + isCurrentMonthFilter 改造 + +```typescript +import { getBusinessClock } from '../../utils/runtime-clock' + +function isCurrentMonthFilter(selectedTime: string, businessDate?: string): boolean { + if (selectedTime !== 'month') return false + if (businessDate) { + const day = parseInt(businessDate.slice(8, 10), 10) + return !Number.isNaN(day) && day <= 5 + } + return new Date().getDate() <= 5 // 降级:业务时钟未就绪时用本地 +} +``` + +### 改动 2:data 加 businessDate + +```typescript +data: { + ... + isCurrentMonth: true, + businessDate: '', // YYYY-MM-DD,沙箱模式下回放虚拟日期 +} +``` + +### 改动 3:onShow 拉业务时钟 + 修正 isCurrentMonth + +```typescript +onShow() { + checkPageAccess(...).then((allowed) => { + if (!allowed) return + ... + // Wave 1 W1-T1:刷新业务时钟,沙箱模式下回放虚拟日期 + getBusinessClock().then((clock) => { + const businessDate = clock.business_date || '' + this.setData({ + businessDate, + isCurrentMonth: isCurrentMonthFilter(this.data.selectedTime, businessDate), + }) + }).catch(() => { + // 降级:保留默认 isCurrentMonth(本地时钟) + }) + this._loadData() + }) +} +``` + +### 改动 4:onTimeChange 用 businessDate + +```typescript +onTimeChange(...) { + this.setData({ + selectedTime: value, + selectedTimeText: option?.text || '本月', + isCurrentMonth: isCurrentMonthFilter(value, this.data.businessDate), // 加 businessDate + }) + this._loadData() +} +``` + +## 三、未改动项(已是设计共识) + +- `board-finance.ts L243 `Date.now()`** — onPageScroll 节流计时,纯 UI 行为(100ms 节流),与业务时间无关,**保留**(对齐 P20 SPEC §11.1 "操作时间戳保留 new Date()" 设计共识) +- `board-customer.ts` — 0 处 `new Date()`,**无需改** +- `board-coach.ts` — 0 处 `new Date()`,**无需改** +- 后端 `xcx_board.py` + `board_service.py` — 服务层已接入 runtime_context,**无需改** + +## 四、行为对照(沙箱模式下) + +| 场景 | 修改前 | 修改后 | +|---|---|---| +| live 模式,真实今天 = 2026-05-04(月初 4 日) | `isCurrentMonth = true`(预估提示)| 同 | +| live 模式,真实今天 = 2026-05-15(月中) | `isCurrentMonth = false`(无预估) | 同 | +| sandbox 切换到 2026-03-01 | `isCurrentMonth = false`(因为真实今天 = 2026-05-04 第 4 日)❌ | `isCurrentMonth = true`(基于 sandbox_date day=1 ≤ 5)✅ | +| sandbox 切换到 2026-03-15 | `isCurrentMonth = false`(同上 4 日)❌ | `isCurrentMonth = false`(基于 sandbox_date day=15 > 5)✅ | +| 业务时钟拉取失败 | 用 `new Date()`(降级一致)| 用 `new Date()`(降级一致)| + +## 五、验证 + +### 工程层 + +- ✅ tsc `board-finance.ts` 零编译错误(其他遗留 component / app 类型错误与本次改动无关) +- ✅ getBusinessClock 已存在 `runtime-clock.ts`,接口稳定 +- ✅ 降级路径(getBusinessClock 失败)保留 fallback `new Date()`,不会破坏 live 行为 + +### 成果层(留 W1-T8) + +按 P20 SPEC §14.3.6 走查清单,W1-T8 时: +- 切 sandbox → 2026-03-01 → 进 board-finance → 经营一览 标题应显示"(预估)" +- 切 sandbox → 2026-03-15 → 进 board-finance → 经营一览 标题不应显示"(预估)" +- 切回 live → 行为应恢复 + +## 六、commit 建议消息 + +``` +feat(miniprogram): board-finance isCurrentMonthFilter 接入业务时钟 (W1-T1 / P0-3) + +Wave 1 Day 2 看板沙箱接入。 + +调研修正: +- xcx_board.py + board_service.py 服务层已接入 runtime_context (L264/L712) +- board-customer.ts / board-coach.ts 0 处使用 new Date(),无需改 +- 唯一遗漏:board-finance.ts `isCurrentMonthFilter` 用 new Date().getDate() + 沙箱回放历史日期会判错"本月前 5 天预估"提示 + +修复: +- import getBusinessClock from utils/runtime-clock +- isCurrentMonthFilter 接收可选 businessDate 参数;空值降级 new Date() +- data 加 businessDate: '' +- onShow 拉业务时钟 + 修正 isCurrentMonth +- onTimeChange 用 this.data.businessDate + +参考: docs/audit/changes/2026-05-04__wave1_t1_board_sandbox_clock.md +``` + +## 七、未覆盖 / 后续 + +- W1-T8 §14.3.6/7/8 走查时实地验证沙箱模式下三大看板行为 +- 多角色身份走查(W1-T8 触发,主线提醒 Neo 切身份) +- 单测覆盖留 Wave 2(`tests/board-finance/test_isCurrentMonthFilter.ts`)