# 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 前端团队*