# 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
```
