110 lines
8.4 KiB
Markdown
110 lines
8.4 KiB
Markdown
# VoiceHub Agent 行为规范
|
||
|
||
## 1. 项目概览
|
||
|
||
VoiceHub — Nuxt 4 校园广播站点歌管理系统。
|
||
|
||
- **前端**: Nuxt 4 (Vue 3), Tailwind CSS
|
||
- **图标**: 自定义 `Icon.vue` 组件(内联 SVG),辅以 `@lucide/vue`
|
||
- **后端**: Nuxt Server API (Nitro)
|
||
- **数据库**: PostgreSQL, Drizzle ORM
|
||
- **语言**: 后端 TypeScript, Vue SFC 用纯 JavaScript(无 `lang="ts"`)
|
||
|
||
## 2. 规范
|
||
|
||
### 2.1. 语言
|
||
注释、文档、Git 信息均用简体中文。注释中禁止写思考过程等无用内容,只需关键部分。
|
||
- CSS 注释只保留关键信息(变量用途、值来源、防误删警告),禁止写设计意图、过程或对比说明(如“取值与现状一致”“视觉零变化”“浅色模式下需清晰可见”“对齐某分支观感”等)
|
||
|
||
### 2.2. Vue 组件
|
||
- 统一 `<script setup>`(纯 JS,不加 `lang="ts"`,禁止类型注解)
|
||
- API 调用用 `useFetch` 或 `$fetch`,需错误处理
|
||
- 模态框用 `<Teleport to="body">`
|
||
- 图标用 `<Icon name="..." />`,name 需在 `Icon.vue` 中有定义;图标语义必须符合实际功能(如音源控制用 `ListMusic`),禁止使用语义不符的图标
|
||
- 项目未开启组件自动导入,模板中使用的自定义组件(含 `Icon`)必须在 `<script setup>` 中显式导入;漏导入不会报错,会被渲染成无内容的原生未知标签(如 `<icon>`),表现为占位但不显示
|
||
- 下拉选择统一复用 `~/components/UI/Common/CustomSelect.vue`;多选使用其 `multiple` 模式,禁止为普通业务配置新增原生 `<select>`
|
||
- 状态管理用 Composables,不用 Pinia
|
||
|
||
### 2.3. 后端
|
||
- 导入: 项目根目录(如 `server/`)用 `~~/`,app 目录用 `~/`(`~/` 即 `app/`);drizzle 位于 `app/drizzle/`,服务端导入必须写 `~/drizzle/db`、`~/drizzle/schema`,禁止 `~~/drizzle/...`(静态检查不报错,部署构建解析失败)
|
||
- 错误: 用户可见的业务错误统一用 `createApiError(statusCode, code, message, data?)`(`~~/server/utils/apiError.ts`),code 取自 `SERVER_ERROR_CODES`;认证错误 401
|
||
- 时间戳: 服务端取当前时间统一用 `getServerTimestamp()` / `getServerDate()`(`~~/server/utils/serverTime.ts`),禁止直接写 `Date.now()` / `new Date()`
|
||
|
||
### 2.4. 第三方库
|
||
- otplib: `import otplib from 'otplib'` 然后 `const { authenticator } = otplib`
|
||
|
||
### 2.5. 国际化 (i18n)
|
||
- 支持 `zh-CN`(基底,静态内置)与 `en-US`(动态按需加载);词典 `app/utils/locale/{zh-CN,en-US}.ts` 结构必须完全一致,新增文案键须两文件同步添加
|
||
- 组件取文案用 `useLocale()` 分区 + `useLocaleText`/`useSafeLocale`,禁止自行实现 `callLocale`/`getNestedMessage` 等取值函数
|
||
- 服务端错误码本地化:服务端 `createApiError` 抛码 → 客户端 `useServerErrors().localize(err)` 展示;动态值用第四参 `{ params: [...] }`,词典值用 `{0}`/`{1}` 占位符
|
||
- 新增错误码须三处同步:`SERVER_ERROR_CODES` + zh/en 的 `serverErrors`(键完全对齐)
|
||
- 英文长文本须考虑页面布局排布:按钮/Tab/徽章/菜单/开关标签等空间受限位置优先使用缩写(如 Previous → Prev),避免溢出或换行错位;描述性文本(Desc/Placeholder/Hint/Message 等)可自然换行不受限;缩写仅调整 en-US 词典值,键名与词典结构保持不变
|
||
|
||
## 3. 项目关键模式
|
||
|
||
### 3.1. 音频播放器
|
||
- `useAudioPlayer.ts` — 全局状态
|
||
- `useAudioPlayerControl.ts` — `<audio>` 元素控制、进度拖拽
|
||
- `useAudioPlayerSync.ts` — 状态同步
|
||
- 连续失败保护: `consecutiveSkipCount`,上限 3 次
|
||
|
||
### 3.2. 音源
|
||
- 多音源搜索(netease、tencent、bilibili)
|
||
- QQ 搜索失败自动降级到网易云,选项卡同步切换
|
||
- 搜索结果含 `actualMusicPlatform` 字段
|
||
|
||
### 3.3. 字符串匹配
|
||
- 归一化唯一权威实现: `app/utils/song-name-normalize.ts` 的 `normalizeForMatch`(`server/utils` 下同名文件仅转发导出;前端组件禁止内联重复实现)
|
||
- 口径: 先繁→简(`toSimplifiedChinese`,映射表 `app/utils/data/cjkT2sMap.ts` 由 OpenCC 数据自动生成、禁止手工编辑),再按单词边界移除 `feat.`/`ft.`,然后移除标点和空格,最后 `&`/`&` → `and`
|
||
|
||
### 3.4. 专辑详情
|
||
- `AlbumDetailsModal.vue`:仅网易云支持,使用 `AbortController` 防止竞态
|
||
- QQ 音乐专辑链接不可点击
|
||
|
||
### 3.5. 加载动画
|
||
- 加载转圈统一用 `~/components/UI/Common/AppSpinner.vue`(scoped CSS 实现,不依赖 UnoCSS utility),支持 `size`(直径 px,默认 32)、`borderWidth`、`label` 属性;复杂加载状态(标题/进度/步骤)用 `LoadingState.vue`(其 circle 类型内部复用 AppSpinner)
|
||
- 禁止手写 `border-*-20/30 border-t-primary rounded-full animate-spin` 或自建 `.loading-spinner` 圆环类;此写法依赖 UnoCSS 生成的 border 工具类,preflight 未设置 `border-style: solid` 时整个圆环不可见
|
||
- 按钮内加载态可用 Lucide 图标(`Loader2`/`RefreshCw` 等)+ `animate-spin`;错误语义色(如网易云红色转圈)保留专用类
|
||
- 全局 border 重置:`main.css` 的 `*` 规则含 `border-style: solid; border-width: 0`,勿删除
|
||
|
||
## 4. 配置与扩展功能开发规范
|
||
|
||
### 4.1. 常量与白名单收敛
|
||
- 业务白名单/枚举只允许一个权威定义:服务端放 `server/config/constants.ts`(如 `MUSIC_SOURCE_PLATFORMS`),前端放共享模块(如 `app/utils/platforms.ts`);其他文件一律 import 引用,禁止重新定义同名数组或映射
|
||
- 新增业务实体(平台等)需同步:constants + 前端共享模块 + `app/drizzle/schema.ts` 默认值 + 迁移文件,缺一不可
|
||
|
||
### 4.2. 数据库迁移
|
||
- 迁移文件(SQL + snapshot + journal 条目)必须且只能通过 `pnpm db:generate` 自动生成,禁止手工编写 SQL、手工创建 snapshot、手工编辑 `_journal.json`;手工迁移会导致 snapshot 格式与 drizzle-kit 版本不兼容,后续 `db:generate`/`db:check`/`db:migrate` 均会失败
|
||
- 迁移时间戳使用真实生成时刻,禁止随意编造
|
||
- 迁移文件命名保持 drizzle-kit 自动生成的时间戳格式,禁止手动改名(改名会导致数据库中已执行的迁移记录无法匹配,产生重复迁移)
|
||
|
||
### 4.3. 新增 SystemSettings 字段同步清单
|
||
新增字段必须全部同步,遗漏会导致备份能进不能出、初始化缺字段:
|
||
1. `app/drizzle/schema.ts`(含默认值)
|
||
2. `server/utils/system-settings-defaults.ts` 的 `SYSTEM_SETTINGS_DEFAULTS`
|
||
3. `server/api/admin/system-settings/index.ts` 建行分支
|
||
4. `server/api/admin/backup/restore.post.ts` 与 `restore-chunk.post.ts` 的字段白名单(最易遗漏)
|
||
5. 需公开时加入 `PUBLIC_SETTINGS_FIELDS`
|
||
6. 迁移文件
|
||
|
||
### 4.4. 组合字段交叉校验
|
||
- 多个配置字段存在相互约束(如启用列表与排序列表交集非空)时,保存接口必须做交叉一致性校验:提交字段与当前持久化值合并后校验,禁止只独立校验单字段;失败用 `createApiError` + `COMMON_INVALID_PARAMS`
|
||
|
||
### 4.5. 开关/禁用类功能
|
||
- 实现平台/功能开关时,必须梳理该功能的全部调用路径(含既有降级、兜底、跨平台升级路径),在每条路径的源头按配置过滤,禁止只拦截主入口
|
||
- 后台任务(如歌词跨平台升级)遇到被禁用的目标应显式跳过并记录日志,不能静默失败
|
||
|
||
### 4.6. 配置读取容错
|
||
- 读取 DB/外部配置(JSON 数组等)必须容错:非法 JSON、空数组、未知值回退默认值(参考 `usePlatformConfig` 的 `parsePlatformArray`),禁止直接采用解析结果
|
||
|
||
### 4.7. 模块级缓存与异步配置 UI
|
||
- 模块级共享缓存加载必须防并发(in-flight promise 去重);`refresh` 类方法加 `import.meta.server` 守卫,防止 SSR 污染共享状态
|
||
- 依赖异步配置的 UI 在配置加载完成前不渲染(如 `v-if="loaded"`),避免 SSR 首屏闪烁或禁用项短暂可见
|
||
|
||
### 4.8. 抛错与错误展示
|
||
- 抛错必须用 `Error` 实例(可附加 `data`),禁止 `throw { data: ... }` 字面量
|
||
- 客户端用户可见错误统一 `useServerErrors().localize(err)` 展示,禁止直接取 `error.data.message`/`error.message`(中文消息在英文界面会泄漏)
|
||
|
||
## 5. 文件变更提醒
|
||
|
||
**每次完成任务后,如果新增或删除了文件/目录,必须同步更新 `README.md` 的"项目结构"部分,保持与实际文件系统一致。(注意:数据库迁移文件除外)** |