diff --git a/CHANGELOG.md b/CHANGELOG.md index ee626efa..3b59026f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # 印度占星 Skill 更新日志 +## 2026-09-30 — 报告导出面板重新设计(待验收) + +- 点「导出」后先选语言(中文 / English,下面一句建议问 AI 时选 English),再选「整篇报告」或「自选章节」。选整篇直接点底部按钮就导出;选自选才展开清单,按正文 / 分盘 / 核对表分组,每组一个勾选框可整组选,分盘和核对表默认收起。 +- 文件大小只在底部按钮上显示一次,例如「导出整篇 · 约 195 KB」。去掉了「常用 / 全部分盘 / 仅核对表」、全选按钮和每块的字数。 +- 导出面板里选语言不会改变你正在读的页面语言。 +- Skill 版本不 bump。不改数据库结构。 + ## 2026-09-30 — 手机上报告页更紧凑:一个加载画面、操作栏一行、导出框看得到分块;切换中英停在同一章(待验收) - 打开报告时只出现一个加载画面(带顶栏、居中),不再先闪一个贴在顶部的转圈(BUG-1106)。 diff --git a/docs/tasks/PROGRESS-surface-feedback-20260929.md b/docs/tasks/PROGRESS-surface-feedback-20260929.md index 87a8fab0..5b8fea50 100644 --- a/docs/tasks/PROGRESS-surface-feedback-20260929.md +++ b/docs/tasks/PROGRESS-surface-feedback-20260929.md @@ -53,3 +53,10 @@ 本机浏览器验证方式:生产构建 + `next start`,Playwright 驱动本机 Chromium(390×844、触摸、深色),`/api/**` 由脚本拦截返回虚构报告(引擎真实 golden 中英对),不连任何真实账号。 既有断言改动(本轮):`report-export-drawer.test.tsx` 三处——窄屏 70 → 85(d)vh、桌面不继承窄屏上限的断言值 70 → 85、关闭按钮按读屏名查找;统计行格式断言改为新格式。原值 / 新值 / 原因写在测试里。 + +## 第三轮:导出面板重设计(产品 2026-09-30「分块导出太丑,需要重新设计」,选定「两步:整篇 / 自选」) + +- `report-export-drawer.tsx` 重写:语言分段(面板内独立,不切整页)→ 整篇 / 自选单选卡 → 自选时分组清单(三态组复选框、分盘与核对表默认收起)→ 按钮上显示大小。`report-actions.tsx` 只在面板打开时为中英两版各建一次导出块;阅读页把两版都传进来。 +- CSS:删除套餐、网格卡片、统计行、提示条等旧类;桌面面板宽 720 → 520px。 +- 本机 390×844 与 1280×860 截图检查(整篇、自选、部分勾选的 − 状态)。 +- 既有断言改动:`report-export-drawer.test.tsx` 7 条测试名全部保留、主体按新设计重写(每条开头写原值 / 新值 / 原因);`report-english-edition.test.tsx`「the export drawer suggests English…」、`surface-feedback-20260929.test.tsx` 手机面板断言、`report-buttons-scope-20260928.test.ts` 选中态不得用 action-soft 的约束迁到新选中态,均写明三栏。 diff --git a/docs/testing/surface-feedback-20260929.md b/docs/testing/surface-feedback-20260929.md index 0cf89330..eef9fb99 100644 --- a/docs/testing/surface-feedback-20260929.md +++ b/docs/testing/surface-feedback-20260929.md @@ -24,4 +24,6 @@ | 13 | 手机点开一份已完成的报告 | 只出现一个加载画面(顶栏在、转圈居中),不先闪一个贴顶部的转圈 | | | 14 | 报告打开后看顶部 | 一行:左边一个返回箭头,右边「中文 / English」和「导出」并排 | | | 15 | 滚到「强度、关系与 Ashtakavarga 技术页」,点 English | 页面停在同一章,标题变成「Strength, Relationships and Ashtakavarga」,下面的表格、说明都是英文。**如果仍是中文,请录屏发我,并告诉我 iOS 版本** | | -| 16 | 点「导出」 | 顶部只有标题、×、一行「已选 … 块 · 约 … KB(全篇约 … KB)」、一行可左右滑的按钮,下面能看到多个分块 | | +| 16 | 点「导出」 | 面板从下往上弹出:标题「导出报告」和 ×;「中文 / English」;「整篇报告」(已选中)和「自选章节」两张卡;底部按钮「导出整篇 · 约 … KB」 | | +| 17 | 点「自选章节」 | 下面出现清单:「正文 7/7」展开、「分盘」「核对表」收起;取消一章后「正文」前的框变成 −,按钮变成「导出 6 项 · 约 … KB」 | | +| 18 | 在面板里点 English,再点导出 | 下载的文件名以 `-EN.md` 结尾,内容是英文;页面本身仍是原来的语言 | | diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 5b9d50e1..41f58057 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -581,8 +581,8 @@ one skeleton, none of them carrying the sidebar. ### Personal report reader - **Buttons on the report surfaces:** the BUG-1078 report-only override (2026-09-28) was replaced the same day by the site-wide solid-button pair (BUG-1079, see Button); report buttons look as before. Outline buttons there (the header 「生成报告」 while submitting) now fade like outline buttons elsewhere instead of the report-only grey. The selected export preset is `--color-canvas-muted` + `--color-ink` + a `--color-ink-secondary` edge, not the `--color-action-soft` fill (a red-brown block in dark mode). -- **分块导出对话框 (2026-09-24):** native modal ``, bottom-aligned below 860px and centered at ≥860px, app colours only. Desktop is viewport-centered (`inset: 0; margin: auto`) regardless of sidebar state, width `min(720px, 92vw)`, max-height 80vh, with full `--radius-lg` corners. Its flex panel is capped at `calc(80vh - 2px)` to reserve the dialog border; desktop rules follow the narrow-screen dvh upgrade so 70dvh cannot override them. Below 860px the content-column offset, bottom alignment and at most 85vh/85dvh (70 until 2026-09-30, BUG-1107) remain unchanged. Header and single export footer stay visible; the block list scrolls, two columns at the existing 860px breakpoint. Escape, backdrop or 关闭 dismisses; focus returns without scrolling. All controls have 44px targets. No spinner, clipboard or extra fetch. -- **Export selection:** default 常用 selects every body block from the single outline's lossless source slices (lead = 摘要); 全部分盘 and 仅核对表 replace the selection. Manual toggles clear preset highlighting. 全选 / 取消全选 works across all groups; zero selected disables export. Counts are actual Unicode code points in serialized block text, including SVG markup, not approximate prose counts. Beside them the header shows the file size for the whole report and for the selection — the UTF-8 byte length of exactly what would download (`TextEncoder` over `joinExportBlocks`), as 「约 186 KB」 / 「约 1.4 MB」, never a per-character estimate. +- **导出报告 sheet (redesigned 2026-09-30, product: 「分块导出太丑」):** native modal ``, bottom sheet below 860px (up to 85dvh) and a centred `min(520px, 92vw)` panel at ≥860px, app colours only. Top to bottom: title 「导出报告」 with a × (读屏名「关闭导出」); when the report has an English edition, a two-way segment 中文 / English (starts on the edition being read, does not switch the page) with one caption 「拿去问 ChatGPT、Claude 等 AI,建议选 English,术语与原典一致。」, otherwise one caption saying why there is no English; two radio cards 「整篇报告」 (subline counts 章正文 / 张分盘 / 张核对表) and 「自选章节」; only in 自选, a plain checklist grouped 正文 / 分盘 / 核对表, each group one tri-state checkbox (✓ / − / empty) plus a chevron, 正文 open and the other groups folded. Rows are borderless 44px lines, no per-block character counts. The file size appears once, on the footer button: 「导出整篇 · 约 195 KB」 / 「导出 6 项 · 约 80 KB」 / disabled 「先勾选要导出的部分」. Selected states use `--color-canvas-muted` + `--color-ink`, never `--color-action-soft` (BUG-1078). Escape, backdrop or × dismisses; focus returns without scrolling. No spinner, clipboard or extra fetch. +- **Export selection:** 整篇 = every block of the chosen edition; 自选 starts with all 正文 chapters ticked. Switching the sheet's language resets the checklist to that edition's chapters (block ids differ per edition). Presets 常用 / 全部分盘 / 仅核对表 and 全选 were retired with the redesign. - **Export content:** full body alone remains byte-identical to the retired ordinary Markdown export. Selected chart/table blocks append new content; selecting all groups is not byte-identical to body-only. D13 requires safe SVG in each chart, reusing the projected engine SVG or the existing hook-free Vedic drawing; offline fallback uses readable monochrome glyphs/legend and presentation attributes. No chart JSON fences or internal audit fields. Fact-table labels/precision/subtables match the reader. Full body uses the historical filename; excerpt adds `-节选`. The existing downloader appends `.md`. - **Structure:** inside the app shell, like every other secondary page — `/reports/[reportId]` was the last standalone full-screen route, and opening a report used to drop the reader out of the app entirely. 「个人报告」 sits in the 46px header; below it the reader owns its own scroll boundary, carrying the sticky screen chrome (back and the labelled 「导出」 button) and then the longform Markdown body beside a persistent TOC rail. Every phase — loading, generating, timed-out, and each error — renders inside the same shell, so the way back is always the rail. The five-chapter writer document is storage-only and is not rendered. Reports without Markdown show “旧版本报告,请重新生成”. diff --git a/frontend/docs/VOICE.md b/frontend/docs/VOICE.md index 58fb153f..a5962605 100644 --- a/frontend/docs/VOICE.md +++ b/frontend/docs/VOICE.md @@ -143,13 +143,10 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、 ## 报告分块导出与删除 - 列表已完成行只留「查看报告」。阅读页操作条只留「返回报告中心」、「中文 / English」切换(只在有英文版的报告上出现)与带字的「导出」按钮(读屏名「导出报告」,2026-09-29 起不再是只有图标的按钮)。不再显示「下载原始附录」或「打印」入口文案;浏览器 Ctrl+P 排版仍保留。 -- 导出对话框标题仍为「分块导出」。说明:「勾选要带走的章节,导出成一个 .md 文件。全选就是整篇。」这里全选包含完整正文及另选的盘 / 表;与旧整篇逐字节相同的套餐是「常用」(全部正文)。 -- 套餐:「常用 / 全部分盘 / 仅核对表」;动作:「全选 / 取消全选」「导出选中 (N)」。成功才提示「已导出 N 块」,下载失败或微信限制不提示成功。本轮 D5 保持既有微信系统浏览器提示原文,不借删除打印入口顺带改它。 -- 统计行(2026-09-30 起,BUG-1107):「已选 n/N 块 · 约 X KB(全篇约 Y KB)」一行说完;大小是实际下载内容的字节数(`TextEncoder`),写「约 186 KB」「约 1.4 MB」,不按字数估。每个分块仍在卡片上显示自己的字数(`Array.from(text).length`,含安全 SVG / Markdown 标记),不是估计文章字数;列表页仍不杜撰章数或盘数。 +- 导出面板(2026-09-30 重设计):标题「导出报告」;语言「中文 / English」下一句「拿去问 ChatGPT、Claude 等 AI,建议选 English,术语与原典一致。」;范围「整篇报告」(副行如「7 章正文 + 22 张分盘 + 8 张核对表」)/「自选章节」(副行「只带走需要的部分」);分组名「正文 / 分盘 / 核对表」带「n/N」;按钮「导出整篇 · 约 X KB」「导出 n 项 · 约 X KB」,一项没选时「先勾选要导出的部分」。成功提示「已导出」。没有英文版时只写一句:「这份报告生成于英文版上线前,重新生成即可获得中英两版。」或「这份报告的英文版没有生成成功,只能导出中文版。」 - 删除确认:「确定删除这份报告?」;按钮:「删除 / 取消」;请求中:「删除中…」;失败:「报告没能删除,请稍后重试。」失败保留原行。 - 日期中文全写;深度「简版 / 标准深度 / 深度 / 研究级」;主题复用现有中文映射,未知 id 原样回退,不造含义。 -- 英文版提示(2026-09-29):当前看中文、且这份报告有英文版时,导出框顶部写「要拿去问 ChatGPT、Claude 等 AI?建议切到 English 再导出,术语与原典一致,AI 读得更准。」按钮「切到 English」。当前是英文时说明句末加「导出的是英文版。」。英文版上线前的报告写「这份报告生成于英文版上线前,重新生成即可获得中英两版。」;英文版没生成成功的写「这份报告的英文版没有生成成功,只能导出中文版。」 - 英文版正文的口径:用 BPHS / PL9 通行的英文术语(Exalted、Own Sign、Mooltrikona、Sub lord……),不音译中文、不中英混写;限制说明用固定英文四句(见 `report-public-projection.ts` 的 `ENGLISH_LIMITATION_COPY`)。应用外壳(侧栏、标题、按钮、到底部)始终是中文。 ## 报告里的限制说明 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 6359310d..e784c902 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -3994,57 +3994,50 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class @media (min-width: 860px) { .report-export-drawer { inset: 0; - width: min(720px, 92vw); + width: min(520px, 92vw); max-height: 80vh; margin: auto; border-radius: var(--radius-lg); } .report-export-panel { max-height: calc(80vh - 2px); } } -.report-export-header, .report-export-footer { padding: var(--space-4); flex-shrink: 0; } -.report-export-heading { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); } -.report-export-heading h2 { margin: 0; font-size: var(--type-title-lg); } -.report-export-header p { margin: var(--space-2) 0; font-size: var(--type-body-sm); color: var(--color-ink-secondary); } -.report-export-count { font-variant-numeric: tabular-nums; } -/* 「问 AI 建议导出英文版」: one quiet band inside the header, with the switch beside it. */ -.report-export-ai-hint { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin: var(--space-2) 0; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas-muted); } -.report-export-ai-hint p { margin: 0 !important; color: var(--color-ink) !important; } -.report-export-ai-hint > button { flex-shrink: 0; } -.report-export-edition-note { color: var(--color-ink-tertiary) !important; font-size: var(--type-caption) !important; } -@media (max-width: 767px) { .report-export-ai-hint { flex-direction: column; align-items: stretch; } } -.report-export-presets { display: flex; flex-wrap: wrap; gap: var(--space-2); } -/* Selected preset: neutral raised fill + full ink + a visible edge. The old - --color-action-soft fill reads as a dark red-brown block in the dark theme - (#3a2620) — BUG-1078. */ -.report-export-presets [aria-pressed="true"] { background: var(--color-canvas-muted); color: var(--color-ink); border-color: var(--color-ink-secondary); } -.report-export-list { overflow-y: auto; overscroll-behavior: contain; min-height: 0; padding: 0 var(--space-4) var(--space-4); } -.report-export-list h3 { font-size: var(--type-title-sm); margin: var(--space-3) 0 var(--space-2); } -.report-export-grid { display: grid; gap: var(--space-2); } -.report-export-block { display: flex; align-items: center; text-align: left; gap: var(--space-3); min-height: 44px; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: transparent; color: var(--color-ink); font-size: var(--type-body-sm); overflow-wrap: anywhere; } -.report-export-block[aria-checked="true"] { background: var(--color-selected); border-color: var(--color-action); } -.report-export-block small { display: block; color: var(--color-ink-tertiary); font-size: var(--type-caption); } -.report-export-check { flex-shrink: 0; width: 18px; height: 18px; border: 1px solid var(--color-border-strong); border-radius: 4px; text-align: center; line-height: 16px; } -.report-export-block:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; } +/* 导出报告 sheet (redesigned 2026-09-30): title row; the edition as a two-way + segment with one caption; two radio cards, 整篇报告 / 自选章节; the checklist + only in 自选; the size on the button. No bordered block cards, no presets. */ +.report-export-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: var(--space-3) var(--space-3) var(--space-1) var(--space-5); flex-shrink: 0; } +.report-export-header h2 { margin: 0; font-size: var(--type-title-md); font-weight: 600; } +.report-export-body { display: grid; gap: var(--space-4); min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: var(--space-2) var(--space-5) var(--space-4); } +.report-export-field { display: grid; gap: var(--space-2); } +.report-export-segment { display: grid; grid-template-columns: 1fr 1fr; padding: 3px; border-radius: var(--radius-md); background: var(--color-canvas-muted); } +.report-export-segment button { border: 0; border-radius: calc(var(--radius-md) - 2px); background: transparent; color: var(--color-ink-secondary); font: inherit; font-size: var(--type-body-sm); cursor: pointer; } +.report-export-segment button[aria-checked="true"] { background: var(--color-canvas); color: var(--color-ink); font-weight: 600; box-shadow: 0 1px 2px color-mix(in srgb, var(--color-ink) 12%, transparent); } +.report-export-caption { margin: 0; color: var(--color-ink-tertiary); font-size: var(--type-caption); line-height: 1.5; } +.report-export-modes { display: grid; gap: var(--space-2); } +.report-export-mode { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: transparent; color: var(--color-ink); font: inherit; text-align: left; cursor: pointer; } +.report-export-mode[aria-checked="true"] { border-color: var(--color-ink-secondary); background: var(--color-canvas-muted); } +.report-export-mode strong { display: block; font-size: var(--type-body-md); font-weight: 600; } +.report-export-mode small { display: block; margin-top: 2px; color: var(--color-ink-tertiary); font-size: var(--type-caption); } +.report-export-radio { flex-shrink: 0; width: 18px; height: 18px; border: 1.5px solid var(--color-border-strong); border-radius: 50%; } +.report-export-mode[aria-checked="true"] .report-export-radio { border: 5px solid var(--color-ink); } +.report-export-list { display: grid; gap: var(--space-1); } +.report-export-group + .report-export-group { border-top: 1px solid var(--color-border); padding-top: var(--space-1); } +.report-export-group-head { display: flex; align-items: center; } +.report-export-row { display: flex; flex: 1; align-items: center; gap: var(--space-3); width: 100%; padding: 0 var(--space-1); border: 0; background: transparent; color: var(--color-ink); font: inherit; font-size: var(--type-body-sm); text-align: left; cursor: pointer; overflow-wrap: anywhere; } +.report-export-row:not(.is-group) { padding-left: calc(var(--space-1) + 18px + var(--space-3)); } +.report-export-row:not(.is-group) .report-export-box { margin-left: calc(-18px - var(--space-3)); } +.report-export-row.is-group { font-weight: 600; } +.report-export-row small { margin-left: var(--space-2); color: var(--color-ink-tertiary); font-size: var(--type-caption); font-weight: 400; font-variant-numeric: tabular-nums; } +.report-export-box { flex-shrink: 0; display: grid; place-items: center; width: 18px; height: 18px; border: 1.5px solid var(--color-border-strong); border-radius: 4px; color: var(--color-canvas); } +.report-export-box svg { width: 13px; height: 13px; stroke-width: 3; } +.report-export-row:is([aria-checked="true"], [aria-checked="mixed"]) .report-export-box { border-color: var(--color-ink); background: var(--color-ink); } +.report-export-expand { flex-shrink: 0; border: 0; background: transparent; color: var(--color-ink-secondary); cursor: pointer; } +.report-export-expand svg { width: 18px; height: 18px; transition: transform 140ms ease-out; } +.report-export-expand[aria-expanded="true"] svg { transform: rotate(90deg); } +.report-export-drawer button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; } .report-export-drawer button { min-height: 44px; min-width: 44px; } -.report-export-footer { border-top: 1px solid var(--color-border); padding-bottom: max(var(--space-4), env(safe-area-inset-bottom)); } +.report-export-footer { flex-shrink: 0; padding: var(--space-3) var(--space-5); border-top: 1px solid var(--color-border); padding-bottom: max(var(--space-4), env(safe-area-inset-bottom)); } .report-export-footer > button { width: 100%; } .report-export-footer p { margin: 0 0 var(--space-2); color: var(--color-danger); font-size: var(--type-body-sm); } -@media (min-width: 860px) { .report-export-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } -/* Phones (2026-09-30): the header had grown to half the sheet — title, a two-line - description, a two-line count, wrapped presets — and the block list was - pushed out of sight. The description goes (the title and presets say it), - the count is one short line, presets scroll sideways in one row, and the - sheet may use more of the screen. */ -@media (max-width: 767px) { - #report-export-description { display: none; } - .report-export-header { padding: var(--space-3) var(--space-4) var(--space-2); } - .report-export-header p { margin: var(--space-1) 0; } - .report-export-count { font-size: var(--type-caption); } - .report-export-presets { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; margin-inline: calc(-1 * var(--space-4)); padding-inline: var(--space-4); } - .report-export-presets [data-slot="button"] { flex: 0 0 auto; height: 40px; padding: 0 var(--space-3); } - .report-export-ai-hint { padding: var(--space-2) var(--space-3); gap: var(--space-2); } - .report-export-ai-hint p { font-size: var(--type-caption); } -} @media print { .report-export-drawer { display: none !important; } } /* ============================================================ @@ -5208,7 +5201,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class @media (min-width: 860px) { .chart-page-type-sheet { inset: 0; - width: min(720px, 92vw); + width: min(520px, 92vw); max-height: 80vh; margin: auto; border-radius: var(--radius-lg); diff --git a/frontend/src/components/personal-report/personal-report-page.tsx b/frontend/src/components/personal-report/personal-report-page.tsx index 283d6e81..b7ae6ca9 100644 --- a/frontend/src/components/personal-report/personal-report-page.tsx +++ b/frontend/src/components/personal-report/personal-report-page.tsx @@ -554,9 +554,10 @@ export function PersonalReportPage({ reportId, initialLanguage = "zh" }: { repor | null; + /** Edition currently shown; the switch highlights it and the export sheet starts on it. */ language?: ReportLanguage; englishEdition?: EnglishEditionStatus; onLanguageChange?: (language: ReportLanguage) => void; @@ -31,15 +33,17 @@ export function ReportActions({ charts, factTables, reportDate, + english = null, language = "zh", englishEdition = "legacy", onLanguageChange, }: ReportActionsProps) { const [exportOpen, setExportOpen] = useState(false); - const blocks = useMemo( - () => buildReportExportBlocks({ markdown, charts, factTables, language }), - [markdown, charts, factTables, language], - ); + // Built only while the sheet is open; each edition labels its own blocks. + const editions = useMemo(() => (exportOpen ? { + zh: buildReportExportBlocks({ markdown, charts, factTables, language: "zh" }), + ...(english ? { en: buildReportExportBlocks({ markdown: english.markdown, charts, factTables: english.factTables, language: "en" }) } : {}), + } : null), [exportOpen, markdown, charts, factTables, english]); const canSwitch = englishEdition === "ready" && onLanguageChange !== undefined; return ( @@ -75,14 +79,12 @@ export function ReportActions({ >