diff --git a/docs/features/FEAT-MO-001-market-overview/development-summary.md b/docs/features/FEAT-MO-001-market-overview/development-summary.md index d25ac03..734e6e6 100644 --- a/docs/features/FEAT-MO-001-market-overview/development-summary.md +++ b/docs/features/FEAT-MO-001-market-overview/development-summary.md @@ -31,6 +31,7 @@ - 根据二次 Review 继续修复涨跌家数接受非整数、成交额受市场定义顺序影响、指数/板块跨交易日混排和后端从不生成 `delayed` 的边界;宽度增加核心指数日期参考,不新增基础设施或第三方依赖。 - 根据第三轮独立 Review 修复 8 位畸形日期被误认为有效公历日期、成交额接受倒序日期对与日期/金额数组错位,以及服务测试依赖运行当天的问题。 - 根据第四轮独立 Review 修复指数/板块未校验前一根 K 线日期的问题;非法、重复或倒序的前一日期不再生成涨跌信号。 +- 修复首屏更新时间直接展示 UTC ISO 字符串的问题:保留 API 的标准 UTC `asOf` 契约,Web 统一按 `Asia/Shanghai` 展示完整日期时间和时分秒。 ## 未完成与延期项 @@ -40,7 +41,7 @@ ## 关键提交 -- `9a965fa`、`d283ee3`、`6eb34b5`、`c2fa929`、`15a2500`、`27bc3c1`、`8261948`、`ff8ada8`、`49f6be9`、`7e4dcd9`、`ae2a7e0`、`c2fdfa6` +- `9a965fa`、`d283ee3`、`6eb34b5`、`c2fa929`、`15a2500`、`27bc3c1`、`8261948`、`ff8ada8`、`49f6be9`、`7e4dcd9`、`ae2a7e0`、`c2fdfa6`、本轮待提交的上海时区显示修复 ## 验证证据 @@ -53,3 +54,4 @@ - 浏览器桌面与 390px 移动视口验证无横向溢出,视角与引擎切换可用。 - 五轮独立 Review 均已归档:前四轮发现的数据完整性、状态、日期真实性/顺序、测试时钟和前一根日期校验问题均已关闭;第五轮最终结论为 `Ready to merge: Yes`,无 Critical、Important 或 Minor 问题。 - 开发 Schema 按用户确认跳过,沿用 `v0.0.10 captured_empty` 追溯边界,不读取生产库。 +- 本轮前端日期工具测试覆盖 UTC 到上海时区转换、无效值占位和两个概览组件接线;未修改后端、OpenAPI、数据库或消息契约。 diff --git a/docs/features/FEAT-MO-001-market-overview/gitnexus/detected-changes.md b/docs/features/FEAT-MO-001-market-overview/gitnexus/detected-changes.md index f74a3c3..e3265e8 100644 --- a/docs/features/FEAT-MO-001-market-overview/gitnexus/detected-changes.md +++ b/docs/features/FEAT-MO-001-market-overview/gitnexus/detected-changes.md @@ -17,3 +17,8 @@ npx gitnexus detect-changes --repo "$PWD" --scope staged ## 处置 本轮 medium 来自指数/板块相邻日期比较的局部映射变更,不是未知跨业务扩散。已用 TDX 映射/服务测试、类型检查和 `git diff --check` 验证;前一提交已覆盖 HTTP/OpenAPI、前端状态、构建、浏览器视觉和 4/4 E2E,未发现用户、消息、数据库、Redis、RocketMQ、Scheduler、Worker、认证或部署调用方。当前环境已使用 GitNexus MCP 与同版本 CLI 交叉确认,没有将工具限制误判为低风险。 + +## 第六轮显示修复 + +- GitNexus 对 `useMarketOverview` upstream impact 为 LOW,直接消费者为 `MarketOverviewPage.vue` 和状态测试;Vue 单文件组件文件级符号由查询结果确认,未发现后端或跨模块调用方。 +- 本轮仅变更 Web 日期格式化工具及两个概览组件显示,不修改 API、OpenAPI、TDX、数据库、Redis、RocketMQ、Scheduler 或 Worker。 diff --git a/docs/features/FEAT-MO-001-market-overview/qa/test-results.md b/docs/features/FEAT-MO-001-market-overview/qa/test-results.md index 510dea0..5895945 100644 --- a/docs/features/FEAT-MO-001-market-overview/qa/test-results.md +++ b/docs/features/FEAT-MO-001-market-overview/qa/test-results.md @@ -2,7 +2,7 @@ ## 自动化结果 -- 前端 Vitest:8 个文件、20 个测试通过。 +- 前端 Vitest:9 个文件、28 个测试通过,新增 UTC 到上海时区格式化和概览组件显示回归。 - 后端 Vitest:11 个文件、67 个测试通过;API 启动器 Node 测试 2/2 通过,Schema、治理、边界和部署门禁通过。 - `pnpm verify:e2e`:桌面/移动端页面和健康契约共 4 个测试通过。 - `pnpm typecheck`、`pnpm build`、`pnpm format:check`:通过。 @@ -17,3 +17,4 @@ - 第三轮 Review 修复后,非法月份/日期、倒序日期对和日期/金额数组错位均被映射层排除;服务测试统一冻结时钟,不依赖执行当天。 - 第四轮 Review 修复后,指数前一根非法、重复或倒序日期不再生成涨跌幅,板块对应记录不进入强弱排序。 - 桌面默认视口与 390px 移动视口无横向溢出;视角和引擎切换可用。 +- 浏览器实测确认结论区显示 `2026-08-24 22:59:55`、指数卡片显示 `22:59:27`,不再显示 UTC 的 `14:59` 时间;桌面和移动视口均无横向溢出。 diff --git a/docs/features/FEAT-MO-001-market-overview/review/code-review.md b/docs/features/FEAT-MO-001-market-overview/review/code-review.md index c7b0100..306f40d 100644 --- a/docs/features/FEAT-MO-001-market-overview/review/code-review.md +++ b/docs/features/FEAT-MO-001-market-overview/review/code-review.md @@ -84,3 +84,12 @@ - 验证:指数非法、重复、倒序前一日期参数化用例和板块过滤用例齐全;后端 Vitest `67/67`、启动器 `2/2`、服务端类型检查与 `git diff --check` 均通过。安全、性能及 PRD/设计/测试/QA 口径一致。 最终结论:passed + +## 第六轮显示修复 Review + +- 审查范围:市场概览前端日期显示工具、结论组件、指数组件及回归测试。 +- 根因:API `asOf` 是带 `Z` 的标准 UTC 时间,前端原样展示并直接截取 UTC 时分秒。 +- 处置:新增 `Asia/Shanghai` 格式化工具;不修改后端时间契约,非法值统一显示占位符。 +- 安全与性能:仅使用原生 `Intl.DateTimeFormat`,无新依赖、无网络请求、无敏感数据处理;格式化开销只发生在两个展示节点。 +- 验证:前端 Vitest 9 个文件/28 个测试、typecheck、全量 `pnpm verify` 和桌面/移动浏览器实测通过。 +- 结论:passed diff --git a/docs/features/FEAT-MO-001-market-overview/review/standards-review.md b/docs/features/FEAT-MO-001-market-overview/review/standards-review.md index de8dc84..51421d9 100644 --- a/docs/features/FEAT-MO-001-market-overview/review/standards-review.md +++ b/docs/features/FEAT-MO-001-market-overview/review/standards-review.md @@ -25,5 +25,6 @@ - 第三轮 Review 的日期真实性、先后顺序、数组对齐和测试时钟问题已按 TDD 修复;需求、设计、测试计划、测试结果、发布说明和开发总结已同步。 - 第四轮 Review 的前一根日期校验问题已按 TDD 修复;治理结论继续保持 `pending`,等待本次代码修复再次独立确认。 - 第五轮最终独立 Review 无 Critical、Important 或 Minor 问题并给出 `Ready to merge: Yes`;中文提交、Gitea 推送、GitNexus、完整门禁和视觉证据均已复核。 +- 第六轮显示修复保持后端 UTC 契约、前端 Axios 边界和业务模块职责不变;新增工具有真实重复场景和独立测试依据,无新增依赖,安全、性能、类型、构建和 E2E 证据齐全。 最终结论:passed diff --git a/web/src/utils/datetime.test.ts b/web/src/utils/datetime.test.ts new file mode 100644 index 0000000..97a1a4c --- /dev/null +++ b/web/src/utils/datetime.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; + +import { formatShanghaiDateTime, formatShanghaiTime } from './datetime'; + +describe('A 股时间显示格式化', () => { + it('将 UTC ISO 时间转换为上海时区的完整日期时间', () => { + expect(formatShanghaiDateTime('2026-08-24T14:16:26.813Z')).toBe('2026-08-24 22:16:26'); + }); + + it('将 UTC ISO 时间转换为上海时区的时分秒', () => { + expect(formatShanghaiTime('2026-08-24T14:16:26.813Z')).toBe('22:16:26'); + }); + + it('收到空值或非法日期时返回占位符', () => { + expect(formatShanghaiDateTime(undefined)).toBe('—'); + expect(formatShanghaiTime('not-a-date')).toBe('—'); + }); +}); diff --git a/web/src/utils/datetime.ts b/web/src/utils/datetime.ts new file mode 100644 index 0000000..c7eb244 --- /dev/null +++ b/web/src/utils/datetime.ts @@ -0,0 +1,45 @@ +const SHANGHAI_TIME_ZONE = 'Asia/Shanghai'; + +type DatePart = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'; + +function getShanghaiDateParts(value: string | undefined): Record | null { + if (!value) return null; + + const date = new Date(value); + if (Number.isNaN(date.getTime())) return null; + + const parts = new Intl.DateTimeFormat('en-CA', { + timeZone: SHANGHAI_TIME_ZONE, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hourCycle: 'h23', + }).formatToParts(date); + + const result = Object.fromEntries( + parts + .filter((part): part is Intl.DateTimeFormatPart & { type: DatePart } => + ['year', 'month', 'day', 'hour', 'minute', 'second'].includes(part.type), + ) + .map((part) => [part.type, part.value]), + ) as Partial>; + + if (Object.values(result).some((part) => !part)) return null; + return result as Record; +} + +/** A 股页面统一使用上海时区,接口仍保留标准 UTC ISO 时间。 */ +export function formatShanghaiDateTime(value: string | undefined): string { + const parts = getShanghaiDateParts(value); + return parts + ? `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second}` + : '—'; +} + +export function formatShanghaiTime(value: string | undefined): string { + const parts = getShanghaiDateParts(value); + return parts ? `${parts.hour}:${parts.minute}:${parts.second}` : '—'; +} diff --git a/web/src/views/market-overview/components/IndexOverview.vue b/web/src/views/market-overview/components/IndexOverview.vue index c970db2..ff8c6fb 100644 --- a/web/src/views/market-overview/components/IndexOverview.vue +++ b/web/src/views/market-overview/components/IndexOverview.vue @@ -1,5 +1,6 @@