600 lines
16 KiB
Markdown
600 lines
16 KiB
Markdown
# 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 样式位置
|
||
|
||
**所有样式必须写在组件的 `<style lang="scss" scoped>` 标签中**
|
||
|
||
```vue
|
||
<!-- ✅ 正确 -->
|
||
<style lang="scss" scoped>
|
||
.chart-container {
|
||
width: 100%;
|
||
height: 400px;
|
||
}
|
||
</style>
|
||
|
||
<!-- ❌ 错误:外部样式文件无法被预处理器转换 -->
|
||
<script>
|
||
import './styles.css'
|
||
</script>
|
||
```
|
||
|
||
#### 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
|
||
<!-- ✅ 正确:显式声明 SCSS -->
|
||
<style lang="scss" scoped>
|
||
/* 组件样式 */
|
||
</style>
|
||
|
||
<!-- ❌ 错误:没有显式声明 SCSS -->
|
||
<style scoped>
|
||
/* 样式代码 */
|
||
</style>
|
||
```
|
||
|
||
### 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 配置原则
|
||
|
||
当配置带有子路由的菜单路由时,父路由组件必须包含 `<router-view>` 来渲染子路由内容。
|
||
|
||
#### 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
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**问题**:父路由组件没有 `<router-view>`,子路由无法正确渲染。
|
||
|
||
#### 5.2.3 正确示例 ✅
|
||
|
||
```javascript
|
||
// 正确:使用 Empty 布局组件作为父路由
|
||
{
|
||
path: '/gzt/datacenter',
|
||
redirect: '/gzt/datacenter/overview',
|
||
component: Empty, // 使用带有 <router-view> 的布局组件
|
||
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` 组件或包含 `<router-view>` 的布局组件
|
||
3. **重定向配置**:设置合适的默认子路由重定向
|
||
4. **元信息配置**:正确区分 `isMenu` 和 `isSubMenu` 属性
|
||
|
||
### 5.4 常用布局组件
|
||
|
||
- `Empty`:空布局组件,仅包含 `<router-view>`,适用于需要嵌套子路由的场景
|
||
- `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 列表页面组件选型
|
||
|
||
**使用 `<edit-table>` 场景**:
|
||
- 条件查询筛选功能
|
||
- 简单弹窗编辑/查看功能
|
||
- 行内编辑功能
|
||
|
||
**使用 `<easy-table>` 场景**:
|
||
- 纯展示列表页面
|
||
|
||
#### 6.1.3 CSS 类名命名
|
||
|
||
- 参照其他相似页面保持一致
|
||
- 以方便维护公共 CSS 类为原则
|
||
- 遵循项目现有的命名约定
|
||
|
||
### 6.2 EasyForm 开发规范
|
||
|
||
#### 6.2.1 侧边查询条件
|
||
|
||
- **使用组件**: 应使用 `<easy-form>` 开发 (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日)
|
||
- 使用 `<easy-form>` 开发 (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 前端团队*
|