# 前端开发规范 ## 目录结构 ``` frontend/src/ ├── app/ # Next.js App Router 页面 │ ├── page.tsx # 首页(视频生成) │ ├── publish/ # 发布页面 │ ├── admin/ # 管理员页面 │ ├── login/ # 登录页面 │ └── register/ # 注册页面 ├── lib/ # 公共工具函数 │ ├── axios.ts # Axios 实例(含 401/403 拦截器) │ └── auth.ts # 认证相关函数 └── proxy.ts # 路由代理(原 middleware) ``` --- ## iOS Safari 安全区域兼容 ### 问题 iPhone Safari 浏览器顶部(刘海/灵动岛)和底部(Home 指示条)有安全区域,默认情况下页面背景不会延伸到这些区域,导致白边。 ### 解决方案(三层配合) #### 1. Viewport 配置 (`layout.tsx`) ```typescript import type { Viewport } from "next"; export const viewport: Viewport = { width: 'device-width', initialScale: 1, viewportFit: 'cover', // 允许内容延伸到安全区域 themeColor: '#0f172a', // 顶部状态栏颜色(与背景一致) }; ``` #### 2. 全局背景统一到 body (`layout.tsx`) ```tsx {children} ``` #### 3. CSS 安全区域支持 (`globals.css`) ```css html { background-color: #0f172a !important; min-height: 100%; } body { margin: 0 !important; min-height: 100dvh; padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); } ``` ### 关键要点 - **渐变背景放 body,不放页面 div** - 安全区域在 div 之外 - **使用 `100dvh` 而非 `100vh`** - dvh 是动态视口高度,适配移动端 - **themeColor 与背景边缘色一致** - 避免状态栏色差 - **页面 div 移除独立背景** - 使用透明,继承 body 渐变 --- ## 移动端响应式规范 ### Header 按钮布局 ```tsx // 移动端紧凑,桌面端宽松
``` ### 常用响应式断点 | 断点 | 宽度 | 用途 | |------|------|------| | 默认 | < 640px | 移动端 | | `sm:` | ≥ 640px | 平板/桌面 | | `lg:` | ≥ 1024px | 大屏桌面 | --- ## API 请求规范 ### 必须使用 `api` (axios 实例) 所有需要认证的 API 请求**必须**使用 `@/lib/axios` 导出的 axios 实例。该实例已配置: - 自动携带 `credentials: include` - 遇到 401/403 时自动清除 cookie 并跳转登录页 **使用方式:** ```typescript import api from '@/lib/axios'; // GET 请求 const { data } = await api.get('/api/materials'); // POST 请求 const { data } = await api.post('/api/videos/generate', { text: '...', voice: '...', }); // DELETE 请求 await api.delete(`/api/materials/${id}`); // 带上传进度的文件上传 await api.post('/api/materials', formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (e) => { if (e.total) { const progress = Math.round((e.loaded / e.total) * 100); setProgress(progress); } }, }); ``` ### SWR 配合使用 ```typescript import api from '@/lib/axios'; // SWR fetcher 使用 axios const fetcher = (url: string) => api.get(url).then(res => res.data); const { data } = useSWR('/api/xxx', fetcher, { refreshInterval: 2000 }); ``` --- ## 日期格式化规范 ### 禁止使用 `toLocaleString()` `toLocaleString()` 在服务端和客户端可能返回不同格式,导致 Hydration 错误。 **错误示例:** ```typescript // ❌ 会导致 Hydration 错误 new Date(timestamp * 1000).toLocaleString('zh-CN') ``` **正确做法:** ```typescript // ✅ 使用固定格式 const formatDate = (timestamp: number) => { const d = new Date(timestamp * 1000); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const hour = String(d.getHours()).padStart(2, '0'); const minute = String(d.getMinutes()).padStart(2, '0'); return `${year}/${month}/${day} ${hour}:${minute}`; }; ``` --- ## 新增页面 Checklist 1. [ ] 导入 `import api from '@/lib/axios'` 2. [ ] 所有 API 请求使用 `api.get/post/delete()` 而非原生 `fetch` 3. [ ] 日期格式化使用固定格式函数,不用 `toLocaleString()` 4. [ ] 添加 `'use client'` 指令(如需客户端交互) --- ## 声音克隆 (Voice Clone) 功能 ### API 端点 | 接口 | 方法 | 功能 | |------|------|------| | `/api/ref-audios` | POST | 上传参考音频 (multipart/form-data: file + ref_text) | | `/api/ref-audios` | GET | 列出用户的参考音频 | | `/api/ref-audios/{id}` | DELETE | 删除参考音频 (id 需 encodeURIComponent) | ### 视频生成 API 扩展 ```typescript // EdgeTTS 模式 (默认) await api.post('/api/videos/generate', { material_path: '...', text: '口播文案', tts_mode: 'edgetts', voice: 'zh-CN-YunxiNeural', }); // 声音克隆模式 await api.post('/api/videos/generate', { material_path: '...', text: '口播文案', tts_mode: 'voiceclone', ref_audio_id: 'user_id/timestamp_name.wav', ref_text: '参考音频对应文字', }); ``` ### 在线录音 使用 `MediaRecorder` API 录制音频,格式为 `audio/webm`,上传后后端自动转换为 WAV (16kHz mono)。 ```typescript // 录音需要用户授权麦克风 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' }); ``` ### UI 结构 配音方式使用 Tab 切换: - **EdgeTTS 音色** - 预设音色 2x3 网格 - **声音克隆** - 参考音频列表 + 在线录音 + 参考文字输入