人生轨迹功能模块补充
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
# 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<LifeEvent, 'id' | 'aiFeedback'>) => Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### 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<void>;
|
||||
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<void>;
|
||||
onSwitchToScript: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 用户菜单组件 (UserMenu.jsx)
|
||||
|
||||
```jsx
|
||||
/**
|
||||
* 用户菜单弹窗组件
|
||||
* 查看和编辑用户资料
|
||||
*/
|
||||
interface UserMenuProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
interface UserMenuState {
|
||||
isEditing: boolean;
|
||||
editData: Partial<UserProfile>;
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
Reference in New Issue
Block a user