# Youfool-LLM 前端开发最佳实践 本文档为 Youfool PMS Web 项目的前端开发最佳实践,专门用于指导 LLM 进行规范化的前端编码。 --- ## 1. 技术栈与约束 ### 1.1 核心框架 - **Vue.js**: 3.3.4 (选项式 API 优先) - **UI 框架**: Element Plus 2.7.4 (仅桌面端框架) - **状态管理**: Vuex 4.1.0 (模块化结构) - **路由**: Vue Router 4.2.5 (Hash 模式路由) - **构建工具**: Vite 4.4.5 (自定义插件) - **HTTP 客户端**: Axios 1.6.0 (综合请求封装) - **图表**: ECharts 5.6.0 (数据可视化) - **样式**: SCSS + PostCSS px-to-rem 转换 ### 1.2 开发约束 - **仅支持桌面端**:系统专为桌面环境设计,不兼容小屏幕设备(如手机、平板) - **最小分辨率**:设计最小支持宽度为 1200px,推荐使用 1920px 或更高分辨率 - **固定布局策略**:采用固定布局而非响应式布局,确保在桌面端的一致性体验 - **无需移动端适配**:开发时无需考虑移动端兼容性、触摸交互等移动特性 --- ## 2. 项目架构 ### 2.1 目录结构 ``` src/ ├── api/ # 按业务域组织的 API 服务模块 ├── components/ # 可复用 Vue 组件和 EasyComponent 系统 ├── extends/ # 可复用 Vue 指令 ├── icons/ # 整个项目所有的svg图标 ├── layout/ # 布局组件 (Web 等) ├── plugins/ # 自定义 Vue 插件和 EasyComponent 系统 ├── router/ # Vue Router 配置,支持基于角色的访问控制 ├── store/ # Vuex 模块 (app、user、permission、contract) ├── utils/ # 工具函数和请求封装 ├── views/ # 按业务域组织的页面组件 └── styles/ # 全局 SCSS 样式 ``` **注意**:模拟数据存放在 `src/mock/` 目录(产品设计阶段) ### 2.2 核心系统 #### 2.2.1 EasyComponent 插件系统 - **配置驱动组件**: EasyForm、EasyTable、EditTable、EasyDialog - **动态表单生成**: 基础选项加载与验证 - **业务扩展**: `src/components/EasyComponentExtend/` - **桌面端专用**: 专注于桌面端用户体验,不考虑移动端适配 #### 2.2.2 API 架构 - **模块化结构**: 中文文件名匹配业务域 - **集中请求封装**: `src/utils/request.js` 配置拦截器 - **标准响应格式**: `{code, data, msg}` - **自动 token 注入**: 401/403 自动登出 #### 2.2.3 路由架构 - **路由配置文件**: `src/router/routes.js` (所有路由配置的实际存放位置) - **路由结构**: - `constantRoutes`: 常量路由(公开访问,如登录、首页、404等) - `asyncRoutes`: 异步路由(需要权限控制,动态加载的业务路由) - **路由注册**: 使用 `handlerImport` 包装动态导入 - **⚠️ 重要**: 路由配置以实际代码文件 `src/router/routes.js` 为准,新增或修改路由必须在该文件中进行 --- ## 3. 编码规范 ### 3.1 代码风格 #### 3.1.1 ESLint 规范强制执行 **重要性**:⭐⭐⭐⭐⭐ - 项目配置了严格的 ESLint 规则,不符合规范的代码将被编译拦截 - 提交前必须执行 `npm run lint` 确保代码通过检查 - 所有文件必须以换行符结尾 (eol-last 规则) #### 3.1.2 命名约定 - **业务域文件**: 使用中文业务域 + 英文技术名 - **组件名**: PascalCase - **变量名**: camelCase - **常量名**: UPPER_SNAKE_CASE - **CSS 类名**: kebab-case #### 3.1.3 通用规范 - 遵循 Vue.js 官方风格指南 - 遵循 Element Plus 组件使用规范 ### 3.2 Vue 3 规范 #### 3.2.1 API 风格 - 严格遵守 Vue 3.3.4 语法规范 - 使用**选项式 API 优先** - 正确使用 JavaScript ES6+ 特性 #### 3.2.2 响应式数据定义 **所有响应式数据定义在 `data()` 中** ```javascript export default { data() { return { // 基础类型 count: 0, message: 'hello', isVisible: true, // 对象和数组 formData: { name: '', email: '', age: 0 }, chartOptions: { title: { text: '图表标题' }, series: [] } } } } ``` #### 3.2.3 组件导入规范 ```javascript // 使用动态导入和 handlerImport const component = () => handlerImport(import('@/views/路径/组件.vue')) ``` ### 3.3 样式编写规范 #### 3.3.1 样式位置 **所有样式必须写在组件的 ` ``` #### 3.3.2 单位使用 - **px 单位**:在样式代码中直接使用 px,构建工具会自动转换为 rem - **rem 单位**:由 PostCSS 自动转换 px → rem,手写不应使用 rem ```scss // 正确:使用 px 单位 .container { width: 300px; height: 200px; margin: 16px; } ``` #### 3.3.3 Style 标签显式声明 ```vue ``` ### 3.4 调试信息规范 - **禁止**: 除捕获异常外,不要在控制台中打印任何调试信息 (chenxf,2025年12月23日) - **允许**: 异常捕获时的 console.error --- ## 4. 设计规范 ### 4.1 屏幕适配 - **仅支持桌面端**:系统专为桌面环境设计,不兼容小屏幕设备(如手机、平板) - **最小屏幕宽度**:设计最小支持宽度为 1200px,推荐使用 1920px 或更高分辨率 ### 4.2 UI 组件使用 - **主要框架**: Element Plus - **组件尺寸**: 优先使用 `medium` 和 `large` - **布局组件**: 使用 Element Plus Layout ### 4.3 样式设计要求 - **固定单位**:使用 px 作为主要单位,配合 PostCSS px-to-rem 转换 - **最小交互区域**:按钮、链接等可交互元素最小尺寸为 32px × 32px - **字体大小**:正文字体不小于 14px,标题字体根据层级递增 - **色彩对比度**:确保文字与背景的对比度符合 WCAG 2.1 AA 标准 --- ## 5. 路由配置 ### 5.1 基础配置 #### 5.1.1 路由配置文件 - **实际存放位置**: `src/router/routes.js` - **⚠️ 重要**: 新增或修改路由必须在该文件中进行 #### 5.1.2 路由结构 - `constantRoutes`: 常量路由(公开访问,如登录、首页、404等) - `asyncRoutes`: 异步路由(需要权限控制,动态加载的业务路由) ### 5.2 父子路由配置 #### 5.2.1 配置原则 当配置带有子路由的菜单路由时,父路由组件必须包含 `` 来渲染子路由内容。 #### 5.2.2 错误示例 ❌ ```javascript // 错误:父路由直接使用页面组件 { path: '/gzt/datacenter', redirect: '/gzt/datacenter/overview', component: () => handlerImport(import('@/views/工作台/数据分析中心/index.vue')), meta: { title: '数据分析中心', icon: 'menu8-2', keepAlive: false, isMenu: true }, children: [ { path: '/gzt/datacenter/overview', component: () => handlerImport(import('@/views/工作台/数据分析中心/index.vue')), meta: { title: '总览分析', isSubMenu: true } } ] } ``` **问题**:父路由组件没有 ``,子路由无法正确渲染。 #### 5.2.3 正确示例 ✅ ```javascript // 正确:使用 Empty 布局组件作为父路由 { path: '/gzt/datacenter', redirect: '/gzt/datacenter/overview', component: Empty, // 使用带有 的布局组件 meta: { title: '数据分析中心', icon: 'menu8-2', keepAlive: false, isMenu: true }, children: [ { path: '/gzt/datacenter/overview', component: () => handlerImport(import('@/views/工作台/数据分析中心/index.vue')), meta: { title: '总览分析', isSubMenu: true } }, { path: '/gzt/datacenter/personal-performance', component: () => handlerImport(import('@/views/数据分析中心/个人绩效分析/index.vue')), meta: { title: '个人绩效分析', isSubMenu: true, keepAlive: true } } ] } ``` ### 5.3 关键原则 1. **父子路由分离**:父路由负责布局,子路由负责内容 2. **布局组件选择**:使用 `Empty` 组件或包含 `` 的布局组件 3. **重定向配置**:设置合适的默认子路由重定向 4. **元信息配置**:正确区分 `isMenu` 和 `isSubMenu` 属性 ### 5.4 常用布局组件 - `Empty`:空布局组件,仅包含 ``,适用于需要嵌套子路由的场景 - `WebLayout`:适用于本项目的默认布局 ### 5.5 路由组织规范 ```javascript // 所有页面路由应在父路由的 children 中 (chenxf,2025年12月23日) // 路径需要继承父路由,并写全路径 { path: '/contract/detail/functionList', children: [ { path: '/contract/detail/functionList/detail', // ✅ 正确:继承父路径并写全路径 }, { path: '/detail' // ❌ 错误:不应使用不是父路由作为前缀的路径 } ] } ``` **规范来源**:合同功能清单路由组织实践 (chenxf,2025年12月23日) ### 5.6 注意事项 - 父路由的 `component` 不能直接使用业务页面组件 - 子路由的 `path` 应使用父路由的路径作为前缀 - 确保布局组件已正确导入并注册 --- ## 6. 组件开发实践 ### 6.1 组件拆分原则 #### 6.1.1 业务模块组件拆分 - **文件夹组织**: 相关代码放在一个文件夹内 - **避免重复**: 禁止同名 .vue 文件和同名文件夹并存 - **共享组件**: 放在小范围公共文件中 #### 6.1.2 列表页面组件选型 **使用 `` 场景**: - 条件查询筛选功能 - 简单弹窗编辑/查看功能 - 行内编辑功能 **使用 `` 场景**: - 纯展示列表页面 #### 6.1.3 CSS 类名命名 - 参照其他相似页面保持一致 - 以方便维护公共 CSS 类为原则 - 遵循项目现有的命名约定 ### 6.2 EasyForm 开发规范 #### 6.2.1 侧边查询条件 - **使用组件**: 应使用 `` 开发 (chenxf,2025年12月23日) - **字段复用**: 尽可能复用列表页面组件中定义的 fields (chenxf,2025年12月23日) - **职责分离**: - 侧边查询条件组件只负责生成查询条件数据 (chenxf,2025年12月23日) - 通过事件通知父组件执行搜索查询 (chenxf,2025年12月23日) #### 6.2.2 字段配置 **必填校验** ```javascript // 使用 required: true (chenxf,2025年12月23日) { prop: 'fieldName', required: true, // ❌ 错误:不需要在 rules 中定义必填校验 // rules: [{ required: true, message: '请输入', trigger: 'blur' }] } ``` **占位符** ```javascript // 在 formProps.placeholders 中定义 (chenxf,2025年12月23日) fields: [ { formProps: { placeholders: '请输入内容' // 仅在需要自定义时填写,一般由EasyForm组件自动生成 } } ] // ❌ 错误:不在 field 中定义 placeholder ``` **默认值** ```javascript // ❌ 错误:非业务要求场景下,fields 中不应定义 defaultValue (chenxf,2025年12月23日) // ✅ 正确:直接对 formData 赋值 this.formData.fieldName = '初始值' ``` #### 6.2.3 码值配置 **本地码值表** ```javascript // 使用 options 字段 { prop: 'status', label: '状态', type: 'select', options: [ { label: '启用', value: '1' }, { label: '禁用', value: '0' } ] } ``` **全局码值表** ```javascript // main.js 中定义 BaseOptionsLoader: (baseCode)=>{ if(baseCode==='statusOptions'){ return [ { label: '启用', value: '1' }, { label: '禁用', value: '0' } ] } } // field 中引用 { prop: 'status', type: 'select', optionsBaseCode: 'statusOptions' } // ❌ 错误:不要在 data 中声明码表 (chenxf,2025年12月23日) // ❌ 错误:不要使用 formatter 进行码值转换 (chenxf,2025年12月23日) ``` #### 6.2.4 组件属性定义 ```javascript // element-plus 组件属性应在 formProps 中定义 (chenxf,2025年12月23日) formConfig: { formProps: { // ✅ 正确:在这里定义 size: 'large', labelWidth: '120px' } } // ❌ 错误:不在 fieldProps 中定义 element-plus 属性 ``` #### 6.2.5 字段单位 ```javascript // 使用 append 字段定义单位 (chenxf,2025年12月23日) { prop: 'price', label: '价格', append: '元' } ``` #### 6.2.6 码表值类型 - **优先使用**: string 类型 (chenxf,2025年12月23日) - **例外**: 当接口明确定义为 number 类型时 ### 6.3 EasyTable 开发规范 #### 6.3.1 格式化字段使用规范 **✅ 应该使用 formatter 的场景**: - 日期格式化(如时间戳转为 YYYY-MM-DD) - 数字格式化(如金额添加千分位) - 文本处理(如截断、拼接等) **编写位置**:tableProps.formatter (chenxf,2025年12月23日) ```javascript tableConfig: { tableProps: { // ✅ 正确:在这里定义 formatter: (row, column, cellValue) => { // 示例:日期格式化 return dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss') } } } ``` #### 6.3.2 码值转换规范 **❌ 不应该使用 formatter 的场景**: - 状态码转换(如 0/1 → 禁用/启用) - 类型码转换(如 type=1,2,3 → 业务类型文本) **正确做法**:使用 options 字段定义对应的码值表 (chenxf,2025年12月23日) ```javascript // ✅ 正确:使用 options 字段定义码值表 { prop: 'status', label: '状态', options: [ { label: '启用', value: '1' }, { label: '禁用', value: '0' } ] } // ❌ 错误:不要使用 formatter 进行码值转换 (chenxf,2025年12月23日) ``` ### 6.4 通用开发规范 #### 6.4.1 异步操作处理 ```javascript // 按钮 handler 中的异步操作应 return Promise (chenxf,2025年12月23日) handlers: { async handleSubmit() { // ✅ 正确:return Promise return await this.saveData() } } ``` #### 6.4.2 编辑查看页面复用 - **原则**: 如查看页面不存在和编辑页面复杂的结构差异 (chenxf,2025年12月23日) - **实现**: - 编辑和查看页面应使用同一个页面 (chenxf,2025年12月23日) - 使用 `` 开发 (chenxf,2025年12月23日) - 通过 `formConfig.isView` 字段控制是否处于编辑或查看状态 (chenxf,2025年12月23日) #### 6.4.3 字段复用 - **原则**: 同一个业务文件下,easy-component 的各个组件的 fields 应尽可能复用 (chenxf,2025年12月23日) - **实践**: 提取公共 fields 配置,多处引用 #### 6.4.4 错误处理规范 **API 调用必须使用 try-catch 包裹** (zhenghl,2025年12月24日) ```javascript // ✅ 正确示例 async getUnreadCount() { try { const response = await getUnreadMessageCount() this.unreadCount = response || 0 } catch (error) { console.error('获取未读消息数量失败:', error) } } // ❌ 错误示例:缺少错误处理 async getUnreadCount() { const response = await getUnreadMessageCount() this.unreadCount = response || 0 } ``` **规范要求**: - 所有 API 调用必须使用 try-catch 包裹 - 错误信息使用 console.error 记录 - 异常处理逻辑应包含用户友好的错误提示 --- ## 7. 附录 ### 7.1 规范来源说明 本规范文档的内容综合了项目开发实践和代码审查总结。其中 `docs/fix/` 目录记录了代码审查中发现的问题和规范要求,是当前最佳实践的重要来源。这些规范经过实践验证,是项目开发中必须遵守的重要规则。 --- *文档版本: v1.2.0* *最后更新时间: 2025-12-24* *维护者: Youfool 前端团队*