diff --git a/.hermes/plans/2026-05-08_120646-profile-feedback-entry.md b/.hermes/plans/2026-05-08_120646-profile-feedback-entry.md new file mode 100644 index 00000000..c7cfeece --- /dev/null +++ b/.hermes/plans/2026-05-08_120646-profile-feedback-entry.md @@ -0,0 +1,584 @@ +# 我的页签反馈入口与反馈页 Implementation Plan + +> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task. + +**Goal:** 在平台“我的”页签中新增“反馈”入口,点击后进入独立反馈路由,并按用户提供的参考图落地反馈页面 UI。 + +**Architecture:** 复用现有前端单页路由体系:`SelectionStage` 负责页面阶段,`appPageRoutes.ts` 负责 URL 映射,`PlatformEntryFlowShellImpl` 负责按阶段渲染视图。“我的”页签只增加一个入口回调,不在当前面板下方展开内容;反馈页作为独立页面组件挂到新阶段。首版先做前端静态表单与本地提交成功态,不新增后端表结构或 SpacetimeDB 写入,除非产品补充明确要求持久化反馈。 + +**Tech Stack:** React 19、TypeScript、Tailwind utility class、lucide-react、现有 Genarrative 平台入口组件体系。 + +--- + +## Current context / assumptions + +## Reference image + +![帮助与反馈参考图](assets/profile-feedback-reference-2026-05-08.png) + +参考图是一张移动端“帮助与反馈”页面,视觉和信息结构如下: + +- 页面整体:浅灰背景,白色圆角卡片,黑/深灰标题文字,浅灰 placeholder,蓝色主按钮与蓝色文本链接。 +- 顶部栏:白色导航/header,左侧为小 home 图标,中间标题为“帮助与反馈”,右侧为胶囊形更多/控制区。项目实现时可按现有平台导航规范简化为返回按钮 + 居中标题;若需要完全贴近图片,可使用 home 图标作为返回到“我的”页签的按钮。 +- 内容区 section label:左上灰色文字“反馈问题”。 +- 第一张表单卡:标题“问题描述”,大文本输入区域,placeholder 为“请填写10个字以上的问题描述以便我们提供更好的帮助,温馨提醒您请勿填写身份证号等个人隐私信息。”,右下角字数统计“0/200”。 +- 第二张表单卡:标题“上传凭证(提供问题截图)”,左侧虚线边框上传方块,内含图片/上传 + 加号图标,文字“上传凭证”“(最多四张)”。 +- 第三张表单卡:标题“联系电话”,placeholder 为“选填,如您填写则将会同步开发者与您联系”。 +- 底部操作:大号蓝色圆角按钮“提交”,下方居中蓝色链接“查看反馈与投诉记录”。 + +实现约束: + +- 反馈页面应命名为“帮助与反馈”,但“我的”页签入口可显示为“反馈”或“帮助与反馈”,优先以清爽短入口为准。 +- 问题描述最少 10 个字、最多 200 个字,并实时显示 `当前字数/200`。 +- 上传凭证首版如不接后端,可先支持前端选择/预览最多 4 张图片,提交时仅进入成功态;如无法快速安全实现预览,可先保留上传占位并在文档中标注待接入。 +- 联系电话为选填。 +- “查看反馈与投诉记录”首版无后端记录时可以先禁用、隐藏,或点击后给出轻量提示;若保留可见,应在计划/PRD 标明记录页不在首版范围。 + +1. 当前工作区是 `/home/dsk/workspace/Genarrative/.worktrees/hermes-19e77eb0`,不要额外拼接 `Genarrative/`。 +2. 平台首页复用 `src/components/rpg-entry/RpgEntryHomeView.tsx`;`src/components/platform-entry/PlatformEntryHomeView.tsx` 只是 re-export。 +3. “我的”页签的常用功能区域位于 `src/components/rpg-entry/RpgEntryHomeView.tsx:3958-4000`,现有入口包括“每日任务 / 邀请好友 / 填邀请码 / 玩家社区”。 +4. 当前页面阶段类型位于 `src/components/platform-entry/platformEntryTypes.ts:16-38`;路由映射位于 `src/routing/appPageRoutes.ts:7-27`。 +5. `src/App.tsx:60-63` 调用 `pushAppHistoryPath(resolvePathForSelectionStage(stage))`,所以新增阶段必须同步 `APP_STAGE_ROUTES`。 +6. `PlatformEntryFlowShellImpl` 在 `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx:5081+` 根据 `selectionStage` 渲染不同页面,平台首页在 `selectionStage === 'platform'` 分支。 +7. 参考图片已保存到 `.hermes/plans/assets/profile-feedback-reference-2026-05-08.png`,计划与实现均以该图片内容为主要 UI 依据。 +8. 按项目约束,工程修改需同步文档;若没有更具体 PRD,需要先补一份简洁落地文档到 `docs/`。 + +## Proposed approach + +新增一个轻量前端反馈页面阶段: + +- 路由:`/profile/feedback` +- 阶段:`profile-feedback` +- 组件:`src/components/platform-entry/PlatformFeedbackView.tsx` +- “我的”页签入口:在常用功能区增加“反馈”按钮,点击调用新 prop `onOpenFeedback`。 +- 页面行为: + - 顶部返回按钮返回 `platform` 阶段,并切回 `profile` 页签。 + - 未登录用户点击入口时,优先弹登录;如果产品允许匿名反馈,可改为允许进入。 + - 表单字段首版只在前端维护:问题描述、上传凭证图片、联系电话。 + - 提交后显示成功态,不做 API 请求;后续如要持久化,再补 `shared-contracts + api-server + SpacetimeDB` 方案。 + +## Step-by-step plan + +### Task 1: 补充反馈页落地文档 + +**Objective:** 先把反馈入口和页面边界写清楚,避免编码时需求漂移。 + +**Files:** +- Create: `docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md` + +**Step 1: 新建 PRD 文档** + +写入内容建议包含: + +```markdown +# 我的页签反馈入口 PRD + +## 目标 +- 在“我的”页签提供反馈入口。 +- 点击入口进入独立反馈路由 `/profile/feedback`。 +- 反馈页移动端优先,桌面端居中卡片展示。 + +## 首版范围 +- 前端表单:问题描述、上传凭证占位/前端图片预览、联系电话。 +- 问题描述 10-200 字,显示实时字数统计。 +- 提交后显示成功态。 +- 不新增后端存储,不修改 SpacetimeDB 表结构。 + +## 交互 +- 已登录用户:点击“反馈”进入反馈页。 +- 未登录用户:点击入口触发登录弹窗。 +- 返回:回到平台首页并定位“我的”页签。 + +## UI +- 以 `.hermes/plans/assets/profile-feedback-reference-2026-05-08.png` 为准,落地“帮助与反馈”移动端表单。 +- 不在 UI 中堆叠说明性长文案。 +- 入口是独立页面导航,不在“我的”面板下方展开。 + +## 验收 +- `/profile/feedback` 可被浏览器前进/后退访问。 +- “我的”页签反馈入口可进入该路由。 +- 移动端和桌面端均不溢出。 +- `npm run check:encoding`、`npm run typecheck` 通过。 +``` + +**Step 2: 验证文档编码** + +Run: `npm run check:encoding` + +Expected: PASS,无中文编码错误。 + +**Step 3: Commit** + +```bash +git add docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md +git commit -m "docs: add profile feedback entry prd" +``` + +### Task 2: 扩展页面阶段与路由映射 + +**Objective:** 让 `/profile/feedback` 成为主应用可识别的独立路由。 + +**Files:** +- Modify: `src/components/platform-entry/platformEntryTypes.ts` +- Modify: `src/routing/appPageRoutes.ts` + +**Step 1: 修改 SelectionStage 类型** + +在 `SelectionStage` union 中追加: + +```ts + | 'profile-feedback' +``` + +推荐放在 `'platform'` 附近或末尾,保持字面量清晰。 + +**Step 2: 修改 STAGE_ROUTE_ENTRIES** + +在 `src/routing/appPageRoutes.ts` 的 `STAGE_ROUTE_ENTRIES` 中追加: + +```ts + ['profile-feedback', '/profile/feedback'], +``` + +建议放在 `['platform', '/']` 后面,表示平台个人页子路由。 + +**Step 3: 验证类型推导** + +Run: `npm run typecheck` + +Expected: 若还未创建渲染组件,可能只通过路由类型;若出现 exhaustive 相关错误,留到后续任务处理。 + +**Step 4: Commit** + +```bash +git add src/components/platform-entry/platformEntryTypes.ts src/routing/appPageRoutes.ts +git commit -m "feat: add profile feedback route stage" +``` + +### Task 3: 新建反馈页面组件 + +**Objective:** 创建移动端优先的独立反馈页面。 + +**Files:** +- Create: `src/components/platform-entry/PlatformFeedbackView.tsx` + +**Step 1: 创建组件 props** + +组件接口建议: + +```ts +export type PlatformFeedbackViewProps = { + onBack: () => void; + onSubmit?: (payload: PlatformFeedbackPayload) => void | Promise; +}; + +export type PlatformFeedbackPayload = { + description: string; + contactPhone: string; + evidenceFiles: File[]; +}; +``` + +**Step 2: 实现 UI 状态** + +使用 `useState` 管理: + +- `description` +- `contactPhone` +- `evidenceFiles` +- `evidencePreviewUrls` +- `error` +- `isSubmitting` +- `submitted` + +**Step 3: 实现页面结构** + +建议结构: + +```tsx +import { ArrowLeft, CheckCircle2, Home, ImagePlus, Send } from 'lucide-react'; +import { useEffect, useState } from 'react'; + +const MAX_FEEDBACK_DESCRIPTION_LENGTH = 200; +const MIN_FEEDBACK_DESCRIPTION_LENGTH = 10; +const MAX_FEEDBACK_EVIDENCE_COUNT = 4; +``` + +页面外壳建议复用现有视觉变量: + +```tsx +
+
+
+ +

反馈

+
+ ... +
+
+``` + +注意:不要写大段“功能说明类文案”;字段 label 简短即可。 + +**Step 4: 表单校验** + +提交时: + +- `description.trim().length < 10`:提示“请填写10个字以上的问题描述” +- `description.trim().length > 200`:提示“问题描述不能超过 200 字” +- `contactPhone.trim().length > 40`:提示“联系电话不能超过 40 字” +- 上传凭证最多 4 张;超出时提示“最多上传四张凭证” + +**Step 5: 提交行为** + +首版无后端时: + +```ts +await onSubmit?.({ + description: description.trim(), + contactPhone: contactPhone.trim(), + evidenceFiles, +}); +setSubmitted(true); +``` + +如果没有传 `onSubmit`,也显示成功态。代码注释说明: + +```ts +// 中文注释:首版反馈页只完成前端收集与成功态;接入后端时在 onSubmit 中替换为 API 调用。 +``` + +**Step 6: Commit** + +```bash +git add src/components/platform-entry/PlatformFeedbackView.tsx +git commit -m "feat: add platform feedback view" +``` + +### Task 4: 在“我的”页签增加反馈入口 prop + +**Objective:** 让 Profile 页面能触发反馈路由,同时保持组件职责清晰。 + +**Files:** +- Modify: `src/components/rpg-entry/RpgEntryHomeView.tsx` +- Modify: `src/components/platform-entry/PlatformEntryHomeView.tsx`(通常无需改,re-export 类型会自动带出) + +**Step 1: 扩展 Props** + +在 `RpgEntryHomeViewProps` 中新增: + +```ts + onOpenFeedback?: () => void; +``` + +**Step 2: 从 props 解构** + +在 `RpgEntryHomeView` 函数参数解构区新增: + +```ts + onOpenFeedback, +``` + +**Step 3: 增加入口按钮** + +在 `profileContent` 的常用功能 grid 中,建议在“玩家社区”后追加: + +```tsx + +``` + +如果参考图中入口位置不同,按参考图调整;但仍必须进入独立路由。 + +**Step 4: 未提供回调时行为** + +`ProfileShortcutButton` 已允许 `onClick` 为空;此处传 `onOpenFeedback` 即可。若希望按钮始终可点,应在父组件必传。 + +**Step 5: 验证类型** + +Run: `npm run typecheck` + +Expected: PASS 或只剩父组件未传 prop 的问题。 + +**Step 6: Commit** + +```bash +git add src/components/rpg-entry/RpgEntryHomeView.tsx src/components/platform-entry/PlatformEntryHomeView.tsx +git commit -m "feat: add feedback shortcut to profile tab" +``` + +### Task 5: 接入 PlatformEntryFlowShellImpl 渲染与导航 + +**Objective:** 点击“反馈”进入 `/profile/feedback`,返回后回到“我的”页签。 + +**Files:** +- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx` + +**Step 1: 导入组件** + +在 imports 中新增: + +```ts +import { PlatformFeedbackView } from './PlatformFeedbackView'; +``` + +**Step 2: 创建打开反馈页函数** + +在 `const { setPlatformTab } = platformBootstrap;` 附近新增: + +```ts +const openProfileFeedback = useCallback(() => { + if (!authUi?.user) { + authUi?.openLoginModal(); + return; + } + + setPlatformTab('profile'); + setSelectionStage('profile-feedback'); +}, [authUi, setPlatformTab, setSelectionStage]); +``` + +如产品允许匿名反馈,则移除登录判断。 + +**Step 3: 给首页传入入口回调** + +在 `PlatformEntryHomeView` props 中加入: + +```tsx +onOpenFeedback={openProfileFeedback} +``` + +**Step 4: 增加渲染分支** + +在 `selectionStage === 'platform'` 分支后、详情页分支前新增: + +```tsx +{selectionStage === 'profile-feedback' && ( + + { + setPlatformTab('profile'); + setSelectionStage('platform'); + }} + /> + +)} +``` + +**Step 5: 直接访问路由的 tab 同步** + +为处理用户直接访问 `/profile/feedback` 后返回,返回逻辑已 `setPlatformTab('profile')`。如需要进入反馈页时也设置 tab,可加 effect: + +```ts +useEffect(() => { + if (selectionStage === 'profile-feedback') { + setPlatformTab('profile'); + } +}, [selectionStage, setPlatformTab]); +``` + +**Step 6: Commit** + +```bash +git add src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +git commit -m "feat: wire profile feedback navigation" +``` + +### Task 6: 增加路由与反馈页基础测试 + +**Objective:** 用自动化测试覆盖新路由映射和反馈页核心交互。 + +**Files:** +- Create or Modify: `src/routing/appPageRoutes.test.ts` +- Create: `src/components/platform-entry/PlatformFeedbackView.test.tsx` + +**Step 1: 路由测试** + +如果已有 `appPageRoutes.test.ts`,追加;否则创建: + +```ts +import { describe, expect, it } from 'vitest'; +import { + resolvePathForSelectionStage, + resolveSelectionStageFromPath, +} from './appPageRoutes'; + +describe('appPageRoutes', () => { + it('resolves profile feedback route', () => { + expect(resolveSelectionStageFromPath('/profile/feedback')).toBe('profile-feedback'); + expect(resolvePathForSelectionStage('profile-feedback')).toBe('/profile/feedback'); + }); +}); +``` + +**Step 2: 反馈页测试** + +测试重点: + +- 渲染“帮助与反馈”标题。 +- 问题描述过短时提交显示错误。 +- 输入有效问题描述后提交显示成功态。 +- 字数统计随输入更新。 +- 上传凭证入口最多接受 4 张图片。 +- 点击返回调用 `onBack`。 + +示例: + +```tsx +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { PlatformFeedbackView } from './PlatformFeedbackView'; + +describe('PlatformFeedbackView', () => { + it('validates content before submit', () => { + render(); + fireEvent.click(screen.getByRole('button', { name: '提交' })); + expect(screen.getByText('请填写10个字以上的问题描述')).toBeInTheDocument(); + }); +}); +``` + +注意检查项目当前 test setup 是否已引入 jest-dom matcher;若没有,使用 truthy DOM 节点断言: + +```ts +expect(screen.getByText('请补充反馈内容')).toBeTruthy(); +``` + +**Step 3: 运行定向测试** + +Run: + +```bash +npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx +``` + +Expected: PASS。 + +**Step 4: Commit** + +```bash +git add src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx +git commit -m "test: cover profile feedback route and form" +``` + +### Task 7: 全量前端验证与移动端 smoke + +**Objective:** 确认新增页面不破坏编码、类型和基础交互。 + +**Files:** +- No code changes unless validation finds issues. + +**Step 1: 编码检查** + +Run: `npm run check:encoding` + +Expected: PASS。 + +**Step 2: ESLint** + +Run: `npm run lint:eslint` + +Expected: PASS。 + +**Step 3: TypeScript** + +Run: `npm run typecheck` + +Expected: PASS。 + +**Step 4: 测试** + +Run: `npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx` + +Expected: PASS。 + +**Step 5: 本地页面 smoke** + +Run: `npm run dev:web` + +手动验证: + +1. 打开 `http://127.0.0.1:3000/`。 +2. 登录后进入“我的”页签。 +3. 点击“反馈”。 +4. 地址变为 `/profile/feedback`。 +5. 页面显示反馈表单。 +6. 提交空内容出现错误。 +7. 输入有效内容后显示成功态。 +8. 点击返回后回到首页“我的”页签。 +9. 直接打开 `http://127.0.0.1:3000/profile/feedback` 能显示反馈页。 +10. 使用移动端视口(如 390×844)确认按钮和表单不溢出。 + +**Step 6: Commit validation fixes if any** + +```bash +git add +git commit -m "fix: polish profile feedback validation" +``` + +## Files likely to change + +- `docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`:新增反馈入口落地文档。 +- `src/components/platform-entry/platformEntryTypes.ts`:新增 `profile-feedback` 阶段。 +- `src/routing/appPageRoutes.ts`:新增 `/profile/feedback` 路由映射。 +- `.hermes/plans/assets/profile-feedback-reference-2026-05-08.png`:反馈页参考图。 +- `src/components/platform-entry/PlatformFeedbackView.tsx`:新增反馈页面。 +- `src/components/rpg-entry/RpgEntryHomeView.tsx`:新增“我的”页签反馈入口和 `onOpenFeedback` prop。 +- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`:接入反馈页打开与返回导航。 +- `src/routing/appPageRoutes.test.ts`:新增路由映射测试。 +- `src/components/platform-entry/PlatformFeedbackView.test.tsx`:新增反馈页交互测试。 + +## Tests / validation + +Minimum required: + +```bash +npm run check:encoding +npm run typecheck +npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx +``` + +Recommended before merge: + +```bash +npm run lint:eslint +npm run test +npm run build +``` + +Manual smoke: + +- 登录后“我的”页签显示“反馈”入口。 +- 点击入口进入 `/profile/feedback`。 +- 浏览器后退和页面返回按钮行为符合预期。 +- 移动端视口无横向溢出。 +- 页面没有把反馈表单展开在“我的”页签下方。 + +## Risks, tradeoffs, and open questions + +1. **参考图落地风险:** 参考图是浅色移动端表单,而项目现有平台 UI 可能偏游戏化/深色变量;实现时需要优先复刻信息结构与交互,不要为了完全一致而破坏现有主题适配。 +2. **反馈是否需要后端存储:** 本计划首版不新增后端,只做前端收集和成功态。若产品要求真实提交,需要新增后端方案:`shared-contracts` DTO、`api-server` 路由、SpacetimeDB 表/迁移、后台查看入口,并按 SpacetimeDB skills 执行。 +3. **登录要求:** 计划默认未登录用户点击入口弹登录。若希望匿名反馈,应取消该限制,并在 payload 中允许无用户身份。 +4. **入口位置:** 当前建议放在“我的”页签常用功能 grid 中。若参考图明确是列表项或设置区入口,应按图调整,但仍进入独立路由。 +5. **图标复用:** 可先用 `MessageCircle` 或 `MessageSquareText`,避免引入新依赖。 +6. **现有大文件风险:** `RpgEntryHomeView.tsx` 很大,实施时必须局部补丁,避免整文件重写导致中文编码或格式大范围变化。 + +## Implementation notes + +- 所有中文注释和文案保持 UTF-8。 +- 不要新增 `.env.local` 到 `.gitignore`。 +- 不要把反馈页做成“我的”页签内部展开面板。 +- 不要新增后端或数据库,除非用户确认反馈必须持久化。 +- 若后续接入后端,必须先补技术文档,再按 DDD 与 SpacetimeDB 约束落地。 diff --git a/.hermes/plans/assets/profile-feedback-reference-2026-05-08.png b/.hermes/plans/assets/profile-feedback-reference-2026-05-08.png new file mode 100644 index 00000000..6ce2e9f3 Binary files /dev/null and b/.hermes/plans/assets/profile-feedback-reference-2026-05-08.png differ