From 499a6ebd7b9f30be6ef98e2a046d5da62dbb4805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Sat, 8 Aug 2026 17:00:49 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E5=AF=BC=E5=87=BA=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E8=81=8A=E5=A4=A9HTML(=E2=91=A2,=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=8E=A5=E7=BA=BF;=E5=90=8E=E7=AB=AF=20export.py=20=E5=B7=B2?= =?UTF-8?q?=E5=B0=B1=E7=BB=AA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- ...026-08-08-yunke-export-chat-html-design.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-08-yunke-export-chat-html-design.md diff --git a/docs/superpowers/specs/2026-08-08-yunke-export-chat-html-design.md b/docs/superpowers/specs/2026-08-08-yunke-export-chat-html-design.md new file mode 100644 index 0000000..30e05ab --- /dev/null +++ b/docs/superpowers/specs/2026-08-08-yunke-export-chat-html-design.md @@ -0,0 +1,100 @@ +# 导出会话聊天为 HTML(③,前端接线)设计 + +> 日期:2026-08-08|分支:`feat/yunke-data-mirror`|路线图 ③ +> 前置(已就绪):后端 `GET /api/export/chat-html`(`app/api/routers/export.py`)已实现、已注册、有测试——按 (channel, owner_account_id, talker_id|room_id, start?, end?) 拉消息 → 渲染**自包含 HTML 气泡页**(is_send 左右、带时间/文本)→ `text/html` attachment 下载。 +> 数据/交互:全本地(运行时不碰云客)。云客「导出聊天记录」**2026-08-08 真机核实**:异步任务 → 下载中心(`POST /wechatweb/msg/exportChatData2Html`,参数 startdate/enddate/wechatId/friendId)。 +> 用户决策(2026-08-08):**按我们的内部逻辑做**——不复刻云客的异步下载中心;HTML 渲染沿用已有 export.py(即"我们的逻辑")。 + +## 1. 目标与范围 + +**目标**:在 3 处加「导出」入口,触发已有的 `/api/export/chat-html`,让用户把一段会话(员工×客户/群,可带日期窗)下载成 HTML 文件。 + +**在本 plan(纯前端,后端不动)**: +1. **ChatTimeline 头部「导出」按钮**:用当前会话(channel/owner/talkerId 或 roomId)+ 当前 dateRange(start/end)→ 下载该窗内聊天 HTML。仅在 `hasActive` 时可用。 +2. **CustomerList 操作列「导出HTML」**(在「查看聊天记录」旁):该 owner×friend_id,**全量消息**(列表无日期选择器)。 +3. **GroupList 操作列「导出HTML」**:该 owner×room_id,全量消息。 + +**机制(我们的内部逻辑)**:直接浏览器下载——构造 `/api/export/chat-html?...` URL,`window.open(url, '_blank')` 触发后端返回的 attachment 下载。**同步、无下载中心**(本地小工具;后端 attachment `Content-Disposition` 强制下载)。 + +**不在本 plan**: +- 云客的异步任务 / 下载中心(不需要)。 +- 导出多个会话 / 批量导出(云客也是单个会话导出)。 +- HTML 排版改版(沿用 export.py 现有渲染)。 +- 媒体字节内嵌(HTML 里媒体保持链接,不内嵌;export.py 现状)。 + +## 2. 后端契约(已就绪,引用) + +`GET /api/export/chat-html`: +- query:`channel`(wechat/whatsapp)、`owner_account_id`(必填)、`talker_id?`、`room_id?`(二选一)、`start?`、`end?`(ms-epoch,可省=全量)。 +- 返回:`text/html; charset=utf-8`,`Content-Disposition: attachment; filename="chat-...html"`。 +- 逻辑:DM 限定 `room_id` 空;按 timestamp asc;渲染气泡(is_send 右、否则左)。 +- 本 plan **不改后端**。 + +## 3. 前端 + +### 3.1 共用 URL 构造(`yunkes/src/api/index.ts`) +```typescript +/** 构造 /api/export/chat-html 下载 URL(浏览器直接下载,不走 getJson)。 */ +export function exportChatUrl(params: { + channel: Channel + owner_account_id: string + talker_id?: string + room_id?: string + start?: number + end?: number +}): string { + const q = clean({ + channel: params.channel, + owner_account_id: params.owner_account_id, + talker_id: params.talker_id, + room_id: params.room_id, + start: params.start, + end: params.end, + }) + const usp = new URLSearchParams(q as Record) + return `/api/export/chat-html?${usp.toString()}` +} +``` +> 触发下载:`window.open(exportChatUrl({...}), '_blank')`(attachment 强制下载;本地 viewer 无 api_token,window.open 可达)。 + +### 3.2 ChatTimeline.vue +- 头部 `.yk-chat__filters` 区(step 5 已建,含日期选择器/搜索)加「导出」`el-button`:`v-if="hasActive"`,`@click` → `window.open(exportChatUrl({channel: channelTyped, owner_account_id: props.ownerId, talker_id: props.talkerId, room_id: props.roomId, start: winStart, end: winEnd}))`。 +- 即「导出当前会话在当前日期窗内的聊天」。 + +### 3.3 CustomerList.vue(操作列) +- 在「查看聊天记录」按钮旁加 `导出HTML`(wechat + WA 两 tab 的操作列)→ `window.open(exportChatUrl({channel: row.channel, owner_account_id: row.owner_account_id, talker_id: row.friend_id}))`(全量,无 start/end)。 +- plan 核实两 tab 操作列结构(Plan 2 已建「查看聊天记录」→ chat)。 + +### 3.4 GroupList.vue(操作列) +- 在「查看聊天记录」旁加「导出HTML」→ `exportChatUrl({channel: row.channel, owner_account_id: row.owner_account_id, room_id: row.room_id})`(全量)。 + +## 4. 关键决策(已与用户确认 2026-08-08) + +1. **后端已就绪**(export.py),③ = 纯前端 3 处按钮接线。 +2. **同步直接下载**(`window.open` + attachment),不做云客异步下载中心。 +3. **HTML 渲染沿用 export.py**(我们的内部逻辑,不改)。 +4. **ChatTimeline 导出带 dateRange**;CustomerList/GroupList 导出**全量**(列表无日期选择器)。 +5. 媒体保持链接(export.py 现状)。 + +## 5. 文件清单 + +- 改 `yunkes/src/api/index.ts`(`exportChatUrl` helper)。 +- 改 `yunkes/src/views/ChatTimeline.vue`(头部「导出」按钮)。 +- 改 `yunkes/src/views/CustomerList.vue`(操作列「导出HTML」)。 +- 改 `yunkes/src/views/GroupList.vue`(操作列「导出HTML」)。 +- **无后端改动**;export.py 不动。 + +## 6. 验收 + +- `vue-tsc -b` exit 0。 +- **浏览器实测**(本地 smoke.db,无云客): + - ChatTimeline:进一个会话 → 「导出」→ 下载 `chat-wechat-{owner}-{talker}.html`,打开含该窗内消息气泡(员工右/客户左、时间、文本);改 dateRange 再导出 → 内容随窗变。 + - CustomerList/GroupList:操作列「导出HTML」→ 下载该 owner×friend/room 的全量聊天 HTML。 + - network:仅打 `/api/export/chat-html`(无云客)。 + +## 7. 风险 + +- **window.open 被拦截**:用户手势触发(button click)一般不拦;若拦可降级为隐藏 `` + `click()`。 +- **api_token 鉴权**:本地 viewer 默认无 token(require_auth no-op)→ window.open 可达;若启用 token 需改 fetch+blob(本 plan 不涉及,记录)。 +- **大对话导出耗时**:同步生成;超大对话可能慢,可接受(本地工具);后续必要时再异步。 +- **CustomerList/GroupList 操作列结构**:plan 核实两 tab(wechat/WA)操作列「查看聊天记录」位置,照旁加按钮。