From 6ec4ff9666e4302a6aa8dd15fa67787445467faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AD=A3?= Date: Thu, 8 Jan 2026 14:34:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=95=B4=E8=BF=81=E7=A7=BB=E8=B0=83?= =?UTF-8?q?=E6=95=B4=20vue=20=E5=B9=B6=E4=B8=94=E8=9E=8D=E5=90=88=E5=8E=9F?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursorrules | 305 ++++ .eslintrc.cjs | 5 + ...FINYX-AI】数据资产产品规划-20260105-2.xlsx | Bin 23069 -> 0 bytes docs/前端开发规范.md | 528 +++++++ package-lock.json | 144 ++ package.json | 21 +- src/App.tsx | 25 - src/App.vue | 28 +- src/components/ConfirmDialog.tsx | 62 - src/components/ConfirmDialog.vue | 2 +- src/components/ProgressBar.tsx | 20 - src/components/SidebarItem.tsx | 21 - src/components/SidebarItem.vue | 10 +- src/components/TableCheckItem.tsx | 18 - src/components/Toast.tsx | 116 -- src/components/app-link/index.vue | 42 + src/components/svg-icon/index.vue | 36 + src/contexts/ToastContext.tsx | 30 - src/finyx_designV2.tsx | 1403 ----------------- src/index.css | 68 + src/layouts/AppLayout.vue | 68 + src/layouts/MainLayout.tsx | 57 - src/layouts/MainLayout.vue | 2 +- src/layouts/Sidebar.tsx | 60 - src/layouts/Sidebar.vue | 24 +- src/layouts/components/AppHeader.vue | 61 + src/layouts/components/AppMain.vue | 22 + src/layouts/components/Sidebar.vue | 116 ++ src/layouts/components/UserAvatar.vue | 15 + src/layouts/components/sidebar-item/index.vue | 96 ++ src/main.ts | 6 + src/main.tsx | 10 - src/pages/DashboardView.tsx | 103 -- src/pages/EngagementView.tsx | 200 --- src/pages/EngagementView.vue | 56 +- src/pages/ProjectListView.tsx | 182 --- src/pages/ProjectListView.vue | 25 +- src/pages/engagement/ContextStep.tsx | 142 -- src/pages/engagement/ContextStep.vue | 2 +- src/pages/engagement/DeliveryStep.tsx | 407 ----- src/pages/engagement/InventoryStep.tsx | 536 ------- src/pages/engagement/SetupStep.tsx | 213 --- src/pages/engagement/ValueStep.tsx | 126 -- src/pages/engagement/ValueStep.vue | 1 - src/router/index.ts | 67 + src/router/routes.ts | 317 ++++ src/utils/createPlaceholderView.ts | 15 + src/views/chat/index.vue | 9 + src/views/error/404.vue | 23 + src/views/knowledge/dataset/importDoc.vue | 1 + src/views/knowledge/dataset/index.vue | 1 + .../knowledge/dataset/traceabilityDetails.vue | 1 + src/views/knowledge/dataset/view.vue | 1 + src/views/knowledge/dataset/viewParagraph.vue | 1 + src/views/knowledge/document/create.vue | 1 + src/views/knowledge/document/index.vue | 1 + src/views/knowledge/document/list.vue | 1 + src/views/knowledge/document/reviewDoc.vue | 1 + src/views/knowledge/document/uploadDoc.vue | 1 + src/views/knowledge/tag/create.vue | 1 + src/views/knowledge/tag/index.vue | 1 + src/views/knowledge/tag/viewTag.vue | 1 + src/views/login/index.vue | 9 + src/views/smart/answer/index.vue | 1 + src/views/smart/answer/setting.vue | 9 + src/views/smart/document/create.vue | 1 + src/views/smart/document/list.vue | 1 + src/views/smart/review/audit/index.vue | 12 + src/views/smart/review/extraction/index.vue | 12 + src/views/smart/review/list.vue | 12 + src/views/smart/review/plan.vue | 1 + src/views/smart/review/rule/index.vue | 12 + src/views/smart/writing/index.vue | 1 + src/views/smart/writing/list.vue | 1 + tailwind.config.js | 26 +- 75 files changed, 2134 insertions(+), 3822 deletions(-) create mode 100644 .cursorrules delete mode 100644 docs/【FINYX-AI】数据资产产品规划-20260105-2.xlsx create mode 100644 docs/前端开发规范.md delete mode 100644 src/App.tsx delete mode 100644 src/components/ConfirmDialog.tsx delete mode 100644 src/components/ProgressBar.tsx delete mode 100644 src/components/SidebarItem.tsx delete mode 100644 src/components/TableCheckItem.tsx delete mode 100644 src/components/Toast.tsx create mode 100644 src/components/app-link/index.vue create mode 100644 src/components/svg-icon/index.vue delete mode 100644 src/contexts/ToastContext.tsx delete mode 100644 src/finyx_designV2.tsx create mode 100644 src/layouts/AppLayout.vue delete mode 100644 src/layouts/MainLayout.tsx delete mode 100644 src/layouts/Sidebar.tsx create mode 100644 src/layouts/components/AppHeader.vue create mode 100644 src/layouts/components/AppMain.vue create mode 100644 src/layouts/components/Sidebar.vue create mode 100644 src/layouts/components/UserAvatar.vue create mode 100644 src/layouts/components/sidebar-item/index.vue delete mode 100644 src/main.tsx delete mode 100644 src/pages/DashboardView.tsx delete mode 100644 src/pages/EngagementView.tsx delete mode 100644 src/pages/ProjectListView.tsx delete mode 100644 src/pages/engagement/ContextStep.tsx delete mode 100644 src/pages/engagement/DeliveryStep.tsx delete mode 100644 src/pages/engagement/InventoryStep.tsx delete mode 100644 src/pages/engagement/SetupStep.tsx delete mode 100644 src/pages/engagement/ValueStep.tsx create mode 100644 src/router/index.ts create mode 100644 src/router/routes.ts create mode 100644 src/utils/createPlaceholderView.ts create mode 100644 src/views/chat/index.vue create mode 100644 src/views/error/404.vue create mode 100644 src/views/knowledge/dataset/importDoc.vue create mode 100644 src/views/knowledge/dataset/index.vue create mode 100644 src/views/knowledge/dataset/traceabilityDetails.vue create mode 100644 src/views/knowledge/dataset/view.vue create mode 100644 src/views/knowledge/dataset/viewParagraph.vue create mode 100644 src/views/knowledge/document/create.vue create mode 100644 src/views/knowledge/document/index.vue create mode 100644 src/views/knowledge/document/list.vue create mode 100644 src/views/knowledge/document/reviewDoc.vue create mode 100644 src/views/knowledge/document/uploadDoc.vue create mode 100644 src/views/knowledge/tag/create.vue create mode 100644 src/views/knowledge/tag/index.vue create mode 100644 src/views/knowledge/tag/viewTag.vue create mode 100644 src/views/login/index.vue create mode 100644 src/views/smart/answer/index.vue create mode 100644 src/views/smart/answer/setting.vue create mode 100644 src/views/smart/document/create.vue create mode 100644 src/views/smart/document/list.vue create mode 100644 src/views/smart/review/audit/index.vue create mode 100644 src/views/smart/review/extraction/index.vue create mode 100644 src/views/smart/review/list.vue create mode 100644 src/views/smart/review/plan.vue create mode 100644 src/views/smart/review/rule/index.vue create mode 100644 src/views/smart/writing/index.vue create mode 100644 src/views/smart/writing/list.vue diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..e740367 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,305 @@ +# Finyx AI 前端开发 Cursor Rules + +## 项目概述 +这是一个基于 Vue 3 + TypeScript + Tailwind CSS 的企业级数据资产管理平台前端项目。项目需要与旧系统(Vue 3 + Element Plus + SCSS)融合,保持设计风格一致性。 + +## 技术栈约束 + +### 必须使用 +- Vue 3.4.0+ (Composition API) +- TypeScript 5.2.2+ +- Tailwind CSS 3.3.6+ +- Pinia 2.1.0+ (状态管理) +- Vue Router 4.2.0+ (路由) +- Lucide Vue Next 0.344.0+ (图标) + +### 禁止使用 +- ❌ React 及其相关库 +- ❌ Element Plus (新功能中) +- ❌ SCSS/SASS (统一使用 Tailwind CSS) +- ❌ 内联样式 (除非动态样式) +- ❌ `any` 类型 (除非特殊情况) +- ❌ `@ts-ignore` (除非有充分理由) + +## 色彩系统规范 + +### 必须使用 Tailwind 配置的颜色类 +所有颜色必须使用 `tailwind.config.js` 中定义的颜色变量: + +```typescript +// ✅ 正确 +
按钮
+
内容
+
边框
+ +// ❌ 错误 - 禁止硬编码颜色 +
按钮
+
按钮
+``` + +### 颜色变量映射 +- `app-primary`: #3067EF (主色) +- `app-bg`: #F4F8FF (背景色) +- `app-text`: #1f2329 (主文本) +- `app-text-secondary`: #646a73 (次文本) +- `app-text-disable`: #bbbfc4 (禁用文本) +- `app-border`: #dee0e3 (边框) +- `app-white`: #ffffff (白色) +- `app-sidebar-bg`: #ffffff (侧边栏背景) +- `app-hover`: rgba(48, 103, 239, 0.06) (悬停背景) +- `app-active`: #ECF2FF (激活背景) + +## 组件开发规范 + +### Vue 组件结构 +```vue + + + + + +``` + +### TypeScript 类型规范 +- 所有 Props 必须定义 interface +- 所有响应式数据必须指定类型 +- 禁止使用 `any`,使用 `unknown` 或具体类型 +- 使用 `type` 定义联合类型,`interface` 定义对象类型 + +```typescript +// ✅ 正确 +interface Project { + id: string + name: string + progress: number +} + +type Step = 'setup' | 'inventory' | 'context' | 'value' | 'delivery' + +const count = ref(0) +const projects = ref([]) + +// ❌ 错误 +const count = ref(0) // 缺少类型 +const data: any = {} // 使用 any +``` + +## 样式规范 + +### Tailwind CSS 使用 +- 优先使用 Tailwind 工具类 +- 使用配置的颜色变量(app-*) +- 响应式设计使用 Tailwind 断点(sm, md, lg, xl) +- 避免自定义 CSS,除非绝对必要 + +```vue + +``` + +### 字体和间距 +- 基础字号: 14px (`text-base`) +- 基础间距单位: 8px +- 页面内边距: 24px (`p-6`) +- 卡片内边距: 16px (`p-4`) 或 24px (`p-6`) + +## 文件命名规范 + +### 组件文件 +- Vue 组件: PascalCase,如 `SidebarItem.vue` +- TypeScript 文件: camelCase,如 `mockData.ts` +- 工具函数: camelCase,如 `formatDate.ts` + +### 组件命名 +- 组件名必须与文件名保持一致 +- 使用 PascalCase + +## 代码质量要求 + +### 必须遵循 +1. 所有组件必须使用 TypeScript +2. 所有 Props 必须定义类型 +3. 所有事件必须使用 `defineEmits` 定义 +4. 使用 Composition API,禁止 Options API +5. 使用 `ref` 和 `computed` 管理状态 +6. 异步操作必须使用 try-catch +7. 错误信息使用 Toast 显示 + +### 性能优化 +- 使用 `computed` 而非 `watch`(如可能) +- 使用 `v-show` 而非 `v-if`(频繁切换) +- 大列表考虑虚拟滚动 +- 图片使用懒加载 + +### 可访问性 +- 使用语义化 HTML 标签 +- 为交互元素添加 `aria-label` +- 确保键盘导航可用 +- 确保颜色对比度符合标准 + +## 与旧系统融合注意事项 + +### 色彩一致性 +- 必须使用旧系统的色彩变量 +- 禁止使用新的颜色值(除非经过批准) + +### 组件风格 +- 侧边栏: 白色背景 (`bg-app-sidebar-bg`) +- 卡片: 白色背景 (`bg-app-white`),浅色边框 (`border-app-border`) +- 按钮: 主色背景 (`bg-app-primary`) +- 激活状态: 浅蓝色背景 (`bg-app-active`) + +### 技术栈兼容 +- 新功能使用 Vue 3 + TypeScript + Tailwind CSS +- 旧功能保持 Vue 3 + Element Plus + SCSS +- 新功能作为独立模块,通过路由集成 + +## 代码生成规则 + +### 生成新组件时 +1. 使用 Vue 3 Composition API +2. 完整的 TypeScript 类型定义 +3. 使用 Tailwind CSS 样式 +4. 使用配置的颜色变量 +5. 添加必要的注释 +6. 遵循项目目录结构 + +### 修改现有代码时 +1. 保持现有代码风格 +2. 不破坏现有功能 +3. 更新相关类型定义 +4. 确保样式一致性 + +## 常见错误避免 + +### ❌ 禁止 +```typescript +// 禁止使用 any +const data: any = {} + +// 禁止硬编码颜色 +
按钮
+ +// 禁止使用内联样式 +
内容
+ +// 禁止使用 React +import React from 'react' + +// 禁止使用 Element Plus (新功能) +import { ElButton } from 'element-plus' +``` + +### ✅ 正确 +```typescript +// 使用具体类型 +const data: Project = {} + +// 使用 Tailwind 配置的颜色 +
按钮
+ +// 使用 Tailwind 类 +
内容
+ +// 使用 Vue +import { ref } from 'vue' + +// 使用 Tailwind 构建组件 + +``` + +## 代码审查重点 + +在生成或修改代码时,确保: +1. ✅ 使用正确的颜色变量(app-*) +2. ✅ TypeScript 类型完整 +3. ✅ 使用 Tailwind CSS 而非内联样式 +4. ✅ 遵循 Vue 3 Composition API +5. ✅ 组件结构清晰 +6. ✅ 无 ESLint 错误 +7. ✅ 响应式设计正确 +8. ✅ 与旧系统风格一致 + +## 项目结构参考 + +``` +src/ +├── components/ # 通用可复用组件 +├── layouts/ # 布局组件 +├── pages/ # 页面组件 +├── stores/ # Pinia 状态管理 +├── types/ # TypeScript 类型定义 +├── utils/ # 工具函数 +└── api/ # API 接口定义 +``` + +## 重要提醒 + +1. **色彩系统**: 始终使用 `tailwind.config.js` 中定义的颜色变量 +2. **技术栈**: 新功能必须使用 Vue 3 + TypeScript + Tailwind CSS +3. **类型安全**: 所有代码必须使用 TypeScript,避免 `any` +4. **样式一致性**: 与旧系统保持设计风格一致 +5. **代码质量**: 遵循最佳实践,确保可维护性 diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 2617cbb..94902df 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -13,6 +13,11 @@ module.exports = { parserOptions: { ecmaVersion: 2021, }, + ignorePatterns: [ + '旧代码/**', + 'dist/**', + 'node_modules/**', + ], rules: { 'vue/multi-word-component-names': 'off', }, diff --git a/docs/【FINYX-AI】数据资产产品规划-20260105-2.xlsx b/docs/【FINYX-AI】数据资产产品规划-20260105-2.xlsx deleted file mode 100644 index 3ff48732dad259ce1f830a66177bdf3024eff994..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23069 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 diff --git a/docs/前端开发规范.md b/docs/前端开发规范.md new file mode 100644 index 0000000..eab0eae --- /dev/null +++ b/docs/前端开发规范.md @@ -0,0 +1,528 @@ +# Finyx AI 前端开发规范与约束 + +## 📋 文档说明 + +本文档定义了 Finyx AI 前端项目的开发规范、约束和最佳实践,确保代码质量、一致性和可维护性。所有开发人员必须遵循本规范。 + +**最后更新**: 2025-01-XX +**适用版本**: v1.0.0+ + +--- + +## 🎨 设计系统规范 + +### 1. 色彩系统 + +#### 1.1 主色调 +- **主色 (Primary)**: `#3067EF` - 用于主要操作按钮、链接、激活状态 +- **背景色 (Background)**: `#F4F8FF` - 页面主背景色 +- **白色 (White)**: `#ffffff` - 卡片、侧边栏背景 + +#### 1.2 文本颜色 +- **主文本**: `#1f2329` - 主要文本内容 +- **次文本**: `#646a73` - 次要文本、说明文字 +- **禁用文本**: `#bbbfc4` - 禁用状态的文本 + +#### 1.3 边框颜色 +- **默认边框**: `#dee0e3` - 卡片、输入框边框 +- **激活边框**: `#3067EF` - 激活状态的边框 + +#### 1.4 状态颜色 +- **悬停背景**: `rgba(48, 103, 239, 0.06)` - 鼠标悬停时的背景色 +- **激活背景**: `#ECF2FF` - 激活状态的背景色 + +#### 1.5 使用规范 +```typescript +// ✅ 正确:使用 Tailwind 配置的颜色类 +
按钮
+
内容
+ +// ❌ 错误:直接使用颜色值 +
按钮
+
按钮
+``` + +### 2. 字体系统 + +#### 2.1 字体族 +- **主字体**: `PingFang SC`, `AlibabaPuHuiTi`, `sans-serif` +- **等宽字体**: 用于代码显示 + +#### 2.2 字体大小 +- **基础字号**: `14px` (Tailwind: `text-base`) +- **标题层级**: + - H1: `24px` (`text-2xl`) + - H2: `20px` (`text-xl`) + - H3: `18px` (`text-lg`) + - H4: `16px` (`text-base`) + +#### 2.3 字重 +- **常规**: `400` (`font-normal`) +- **中等**: `500` (`font-medium`) +- **加粗**: `600` (`font-bold`) + +### 3. 间距系统 + +#### 3.1 基础间距单位 +- **基础单位**: `8px` (对应 Tailwind 的 `1` 单位) +- **常用间距**: + - `4px` (`p-1`, `m-1`) + - `8px` (`p-2`, `m-2`) + - `12px` (`p-3`, `m-3`) + - `16px` (`p-4`, `m-4`) + - `24px` (`p-6`, `m-6`) + +#### 3.2 页面内边距 +- **页面容器**: `24px` (`p-6`) +- **卡片内边距**: `16px` (`p-4`) 或 `24px` (`p-6`) + +### 4. 阴影系统 + +#### 4.1 标准阴影 +- **卡片阴影**: `0px 2px 4px 0px rgba(31, 35, 41, 0.12)` (`shadow-app`) +- **悬停阴影**: 可适当增强 + +--- + +## 🏗️ 技术栈约束 + +### 1. 核心框架 +- **前端框架**: Vue 3.4.0+ (Composition API) +- **开发语言**: TypeScript 5.2.2+ +- **构建工具**: Vite 5.0.8+ +- **样式方案**: Tailwind CSS 3.3.6+ + +### 2. 状态管理 +- **状态管理库**: Pinia 2.1.0+ +- **路由管理**: Vue Router 4.2.0+ + +### 3. UI 组件库 +- **图标库**: Lucide Vue Next 0.344.0+ +- **图表库**: Recharts 2.10.3+ (如需要) + +### 4. 禁止使用的技术 +- ❌ **React**: 本项目已迁移至 Vue,禁止使用 React +- ❌ **Element Plus**: 旧系统使用,新功能应使用 Tailwind CSS 构建 +- ❌ **SCSS/SASS**: 统一使用 Tailwind CSS,禁止使用 SCSS +- ❌ **内联样式**: 除非动态样式,否则使用 Tailwind 类 + +--- + +## 📁 项目结构规范 + +### 1. 目录结构 +``` +src/ +├── components/ # 通用可复用组件 +│ ├── common/ # 基础通用组件 +│ └── business/ # 业务相关组件 +├── layouts/ # 布局组件 +├── pages/ # 页面组件 +│ └── engagement/ # 业务模块子页面 +├── stores/ # Pinia 状态管理 +├── types/ # TypeScript 类型定义 +├── utils/ # 工具函数 +├── api/ # API 接口定义 +├── assets/ # 静态资源 +└── styles/ # 全局样式(如需要) +``` + +### 2. 文件命名规范 + +#### 2.1 组件文件 +- **Vue 组件**: 使用 PascalCase,如 `SidebarItem.vue` +- **TypeScript 文件**: 使用 camelCase,如 `mockData.ts` +- **工具函数**: 使用 camelCase,如 `formatDate.ts` + +#### 2.2 组件命名 +- **单文件组件**: 使用 PascalCase +- **组件名**: 与文件名保持一致 + +```vue + + + + + +``` + +### 3. 导入顺序规范 +```typescript +// 1. Vue 核心 +import { ref, computed, onMounted } from 'vue' + +// 2. 第三方库 +import { useRouter } from 'vue-router' +import { useToast } from '@/components/Toast' + +// 3. 项目内部组件 +import { SidebarItem } from '@/components/SidebarItem' + +// 4. 类型定义 +import type { Project, Step } from '@/types' + +// 5. 工具函数 +import { formatDate } from '@/utils/date' + +// 6. 样式(如需要) +import './styles.css' +``` + +--- + +## 💻 代码规范 + +### 1. TypeScript 规范 + +#### 1.1 类型定义 +```typescript +// ✅ 正确:使用 interface 定义对象类型 +interface Project { + id: string + name: string + progress: number +} + +// ✅ 正确:使用 type 定义联合类型 +type Step = 'setup' | 'inventory' | 'context' | 'value' | 'delivery' + +// ❌ 错误:使用 any +function processData(data: any) { } +``` + +#### 1.2 Props 定义 +```vue + +``` + +#### 1.3 响应式数据 +```vue + +``` + +### 2. Vue 组件规范 + +#### 2.1 Composition API +```vue + +``` + +#### 2.2 组件结构 +```vue + + + + + +``` + +#### 2.3 事件处理 +```vue + + + +``` + +### 3. 样式规范 + +#### 3.1 Tailwind CSS 使用 +```vue + +``` + +#### 3.2 响应式设计 +```vue + +``` + +#### 3.3 自定义样式 +```vue + +``` + +### 4. 组件设计规范 + +#### 4.1 组件职责 +- **单一职责**: 每个组件只负责一个功能 +- **可复用性**: 通用组件应设计为可复用 +- **可组合性**: 复杂组件应由简单组件组合而成 + +#### 4.2 Props 设计 +```vue + +``` + +#### 4.3 事件定义 +```vue + +``` + +--- + +## 🎯 与旧系统融合规范 + +### 1. 色彩一致性 +- **必须使用**: 旧系统的色彩变量(已在 `tailwind.config.js` 中定义) +- **禁止**: 使用新的颜色值,除非经过设计团队批准 + +### 2. 组件风格 +- **侧边栏**: 白色背景,浅色激活状态 +- **卡片**: 白色背景,浅色边框,标准阴影 +- **按钮**: 主色背景,圆角设计 + +### 3. 技术栈兼容 +- **新功能**: 使用 Vue 3 + TypeScript + Tailwind CSS +- **旧功能**: 保持 Vue 3 + Element Plus + SCSS +- **融合策略**: 新功能作为独立模块,通过路由集成 + +### 4. 样式隔离 +- **新组件**: 使用 Tailwind CSS,避免全局样式污染 +- **旧组件**: 保持现有样式,不强制迁移 +- **冲突处理**: 使用 CSS Modules 或作用域样式 + +--- + +## 🚫 禁止事项 + +### 1. 代码层面 +- ❌ 禁止使用 `any` 类型(除非特殊情况) +- ❌ 禁止使用 `@ts-ignore`(除非有充分理由) +- ❌ 禁止使用内联样式(除非动态样式) +- ❌ 禁止直接修改 DOM(使用 Vue 响应式系统) + +### 2. 样式层面 +- ❌ 禁止使用 SCSS/SASS(统一使用 Tailwind) +- ❌ 禁止使用硬编码颜色值(使用 Tailwind 配置的颜色) +- ❌ 禁止使用 `!important`(除非绝对必要) + +### 3. 依赖层面 +- ❌ 禁止引入 React 相关依赖 +- ❌ 禁止引入 Element Plus(新功能) +- ❌ 禁止引入未经过审查的第三方库 + +--- + +## ✅ 最佳实践 + +### 1. 性能优化 +- 使用 `computed` 而非 `watch`(如可能) +- 使用 `v-show` 而非 `v-if`(频繁切换) +- 大列表使用虚拟滚动 +- 图片使用懒加载 + +### 2. 可访问性 +- 使用语义化 HTML 标签 +- 为交互元素添加 `aria-label` +- 确保键盘导航可用 +- 确保颜色对比度符合 WCAG 标准 + +### 3. 错误处理 +- 使用 try-catch 处理异步操作 +- 使用 Toast 显示错误信息 +- 提供友好的错误提示 + +### 4. 代码注释 +```typescript +// ✅ 正确:清晰的注释 +/** + * 计算项目进度百分比 + * @param completed - 已完成步骤数 + * @param total - 总步骤数 + * @returns 进度百分比 (0-100) + */ +const calculateProgress = (completed: number, total: number): number => { + return Math.round((completed / total) * 100) +} +``` + +--- + +## 📝 提交规范 + +### 1. Commit 消息格式 +``` +(): + + + +