Files
Genarrative/.hermes/plans/2026-05-08_120646-profile-feedback-entry.md

585 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 我的页签反馈入口与反馈页 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<void>;
};
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
<div className="platform-page-stage platform-remap-surface min-h-0 min-w-0 overflow-y-auto px-4 py-4 sm:px-6 lg:px-8">
<div className="mx-auto flex w-full max-w-2xl flex-col gap-4">
<header className="platform-surface platform-surface--soft rounded-[1.6rem] px-4 py-4">
<button type="button" onClick={onBack} ...>
<ArrowLeft ... />
</button>
<h1></h1>
</header>
...
</div>
</div>
```
注意:不要写大段“功能说明类文案”;字段 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
<ProfileShortcutButton
label="反馈"
subLabel="问题与建议"
icon={MessageCircle}
onClick={onOpenFeedback}
/>
```
如果参考图中入口位置不同,按参考图调整;但仍必须进入独立路由。
**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' && (
<motion.div
key="platform-profile-feedback"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<PlatformFeedbackView
onBack={() => {
setPlatformTab('profile');
setSelectionStage('platform');
}}
/>
</motion.div>
)}
```
**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(<PlatformFeedbackView onBack={vi.fn()} />);
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 <fixed-files>
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 约束落地。