# Design Document: PncyssD 页面重构 ## Overview 本设计文档描述了将 PncyssD 原型页面重构为 course-web 项目规范的 React 组件的技术方案。重构将保持原有的视觉设计风格(深色主题、玻璃拟态、橙色强调色)和后端接口调用逻辑,同时采用 React + Vite + Tailwind CSS 的现代前端架构。 ## Architecture ### 整体架构 ``` PncyssD/ → course-web/src/ ├── index.html ├── main.jsx (入口) ├── index.js (App 入口) ├── App.jsx (路由控制) ├── state.js (状态管理) ├── utils/store.js (已存在) ├── login.js (登录页) ├── pages/LoginPage.jsx (重构) ├── onboarding.js (引导页) ├── pages/OnboardingPage.jsx (重构) ├── dashboard.js (仪表盘) ├── pages/DashboardPage.jsx (重构) ├── components.js (UI组件) ├── components/ui/*.jsx (复用) ├── api.js (AI服务) ├── utils/aiLogic.js (重构) └── style.css (样式) └── index.css (合并样式) ``` ### 页面流程 ```mermaid stateDiagram-v2 [*] --> LandingPage: 首次访问 LandingPage --> LoginPage: 点击登录 LoginPage --> OnboardingPage: 登录成功(新用户) LoginPage --> DashboardPage: 登录成功(老用户) OnboardingPage --> DashboardPage: 完成引导 DashboardPage --> TimelineView: 默认视图 DashboardPage --> ScriptView: 点击爽文剧本 DashboardPage --> PathView: 点击实现路径 ``` ## Components and Interfaces ### 1. 登录页面组件 (LoginPage.jsx) ```jsx /** * 登录页面组件 * 提供手机号验证码登录功能 */ interface LoginPageProps { onLoginSuccess: () => void; // 登录成功回调 onBack: () => void; // 返回首页回调 } // 内部状态 interface LoginState { phone: string; // 手机号 code: string; // 验证码 countdown: number; // 倒计时秒数 loading: boolean; // 登录中状态 error: string; // 错误信息 } ``` ### 2. 引导页面组件 (OnboardingPage.jsx) ```jsx /** * 引导流程页面组件 * 5步骤用户信息采集 */ interface OnboardingPageProps { onFinish: () => void; // 完成引导回调 } // 步骤数据结构 interface OnboardingFormData { // Step 1: 基本信息 nickname: string; gender: 'male' | 'female' | 'secret'; zodiac: string; mbti: string; hobbies: string[]; // Step 2-4: 人生记忆 history: { childhood: { date: string; content: string }; peak: { date: string; content: string }; valley: { date: string; content: string }; }; // Step 5: 未来愿景 futureVision: string; } // 灵感标签配置 const INSPIRATION_TAGS = { childhood: ['秋千', '晚霞', '糖果', '奔跑', '蝉鸣', '雨后泥土', '旧书包', '风筝'], peak: ['海浪', '拥抱', '掌声', '晨曦', '破土而出', '默契', '星空', '释放'], valley: ['落叶', '雨伞', '长廊', '深呼吸', '自愈', '沉潜', '坚韧', '等待', '破茧'] }; ``` ### 3. 仪表盘页面组件 (DashboardPage.jsx) ```jsx /** * 仪表盘页面组件 * 包含导航栏和内容区域 */ interface DashboardState { activeTab: 'timeline' | 'script' | 'path'; isMobileMenuOpen: boolean; isUserMenuOpen: boolean; isMusicPlaying: boolean; } // 导航项配置 const NAV_ITEMS = [ { id: 'timeline', icon: BookOpen, label: '生命长河' }, { id: 'script', icon: Film, label: '爽文剧本' }, { id: 'path', icon: Map, label: '实现路径' } ]; ``` ### 4. 生命长河视图组件 (TimelineView.jsx) ```jsx /** * 生命长河视图组件 * 时间线形式展示人生事件 */ interface LifeEvent { id: number; title: string; time: string; content: string; aiFeedback: string; } interface TimelineViewProps { events: LifeEvent[]; onAddEvent: (event: Omit) => Promise; } ``` ### 5. 爽文剧本视图组件 (ScriptView.jsx) ```jsx /** * 爽文剧本视图组件 * AI生成个性化剧本 */ interface Script { id: number; theme: string; style: string; length: string; content: string; date: string; } interface ScriptParams { theme: string; style: '都市' | '古风' | '爱情' | '科幻' | '喜剧' | '悬疑' | '恐怖'; length: '短' | '中' | '长'; } interface ScriptViewProps { scripts: Script[]; selectedScriptId: number | null; userProfile: UserProfile; onGenerateScript: (params: ScriptParams) => Promise; onSelectScript: (id: number) => void; onSwitchToPath: () => void; } ``` ### 6. 实现路径视图组件 (PathView.jsx) ```jsx /** * 实现路径视图组件 * 将剧本转化为行动计划 */ interface PathStep { title: string; content: string; } interface PathViewProps { selectedScript: Script | null; path: PathStep[] | null; onGeneratePath: () => Promise; onSwitchToScript: () => void; } ``` ### 7. 用户菜单组件 (UserMenu.jsx) ```jsx /** * 用户菜单弹窗组件 * 查看和编辑用户资料 */ interface UserMenuProps { isOpen: boolean; onClose: () => void; onLogout: () => void; } interface UserMenuState { isEditing: boolean; editData: Partial; } ``` ## Data Models ### Store 数据结构 ```javascript const STORE_SCHEMA = { onboardingComplete: false, // 是否完成引导 audioMuted: true, // 音乐是否静音 userProfile: { nickname: "", gender: "secret", zodiac: "", mbti: "", hobbies: [], history: { childhood: { date: "", content: "" }, peak: { date: "", content: "" }, valley: { date: "", content: "" } }, futureVision: "" }, lifeTimeline: [], // 生命事件列表 generatedScripts: [], // 生成的剧本列表 paths: [], // 实现路径列表 selectedScriptId: null, // 当前选中的剧本ID selectedPath: null // 当前选中的路径 }; ``` ### API 接口 ```javascript // 认证接口 (保持不变) POST /auth/sms-code?phone={phone} // 发送验证码 POST /auth/login // 登录 Body: { phone, smsCode } Response: { accessToken } // 用户资料接口 (保持不变) GET /user/profile // 获取用户资料 POST /user/profile // 创建用户资料 PUT /user/profile // 更新用户资料 // AI 服务接口 (OpenRouter) POST https://openrouter.ai/api/v1/chat/completions Headers: { Authorization: Bearer {API_KEY} } Body: { model, messages } ``` ## Correctness Properties *A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* ### Property 1: 手机号格式验证 *For any* 输入字符串,手机号验证函数应当仅对11位数字且以1开头的字符串返回 true,对其他所有输入返回 false。 **Validates: Requirements 1.2** ### Property 2: 步骤导航数据完整性 *For any* 引导流程中的步骤切换操作(前进或后退),用户在各步骤填写的数据应当被完整保留,不会因为步骤切换而丢失。 **Validates: Requirements 2.7, 2.8** ### Property 3: 导航视图切换一致性 *For any* 仪表盘导航项点击操作,当前激活的视图应当与点击的导航项对应,且导航项的高亮状态应当正确反映当前视图。 **Validates: Requirements 3.3** ### Property 4: 事件列表渲染完整性 *For any* 生命事件数组,时间线视图应当渲染所有事件,且每个事件卡片应当包含标题、时间、内容和 AI 洞察四个字段。 **Validates: Requirements 4.4** ### Property 5: 事件时间排序正确性 *For any* 包含多个事件的生命事件数组,时间线视图应当按时间倒序排列事件,即最新的事件显示在最前面。 **Validates: Requirements 4.6** ### Property 6: 数据持久化往返一致性 *For any* 有效的用户数据对象,保存到 localStorage 后再读取,应当得到与原始数据等价的对象。 **Validates: Requirements 10.1, 10.2, 10.3** ## Error Handling ### 网络错误处理 ```javascript // API 调用统一错误处理 try { const response = await request.post('/auth/login', data); // 处理成功响应 } catch (error) { if (error.response) { // 服务器返回错误 showToast(error.response.data.message || '请求失败'); } else if (error.request) { // 网络错误 showToast('网络连接异常,请检查网络设置'); } else { // 其他错误 showToast('发生未知错误'); } } ``` ### 表单验证错误 ```javascript // 表单验证规则 const VALIDATION_RULES = { phone: { required: true, pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }, code: { required: true, length: 6, message: '请输入6位验证码' }, nickname: { required: true, maxLength: 20, message: '昵称不能为空且不超过20字' } }; ``` ### 存储错误处理 ```javascript // localStorage 存储错误处理 try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch (error) { if (error.name === 'QuotaExceededError') { showToast('存储空间不足,部分数据可能无法保存'); } } ``` ## Testing Strategy ### 单元测试 使用 Vitest 进行单元测试,覆盖以下场景: 1. **工具函数测试** - 手机号格式验证函数 - 数据序列化/反序列化函数 - 日期格式化函数 2. **组件渲染测试** - 各页面组件的基本渲染 - 条件渲染逻辑(空状态、加载状态) - 用户交互响应 3. **状态管理测试** - Store 的 CRUD 操作 - 数据持久化逻辑 ### 属性测试 使用 fast-check 进行属性测试,验证以下属性: 1. **Property 1**: 手机号验证 - 生成各种字符串测试验证函数 2. **Property 2**: 步骤导航 - 生成随机步骤切换序列测试数据保留 3. **Property 5**: 事件排序 - 生成随机事件数组测试排序正确性 4. **Property 6**: 数据持久化 - 生成随机数据对象测试往返一致性 ### 测试配置 ```javascript // vitest.config.js export default { test: { environment: 'jsdom', globals: true, setupFiles: ['./src/test/setup.js'] } }; // 属性测试最小迭代次数: 100 ```