16 KiB
16 KiB
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() 中
export default {
data() {
return {
// 基础类型
count: 0,
message: 'hello',
isVisible: true,
// 对象和数组
formData: {
name: '',
email: '',
age: 0
},
chartOptions: {
title: { text: '图表标题' },
series: []
}
}
}
}
3.2.3 组件导入规范
// 使用动态导入和 handlerImport
const component = () => handlerImport(import('@/views/路径/组件.vue'))
3.3 样式编写规范
3.3.1 样式位置
所有样式必须写在组件的 <style lang="scss" scoped> 标签中
<!-- ✅ 正确 -->
<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
// 正确:使用 px 单位
.container {
width: 300px;
height: 200px;
margin: 16px;
}
3.3.3 Style 标签显式声明
<!-- ✅ 正确:显式声明 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 错误示例 ❌
// 错误:父路由直接使用页面组件
{
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 正确示例 ✅
// 正确:使用 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 关键原则
- 父子路由分离:父路由负责布局,子路由负责内容
- 布局组件选择:使用
Empty组件或包含<router-view>的布局组件 - 重定向配置:设置合适的默认子路由重定向
- 元信息配置:正确区分
isMenu和isSubMenu属性
5.4 常用布局组件
Empty:空布局组件,仅包含<router-view>,适用于需要嵌套子路由的场景WebLayout:适用于本项目的默认布局
5.5 路由组织规范
// 所有页面路由应在父路由的 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 字段配置
必填校验
// 使用 required: true (chenxf,2025年12月23日)
{
prop: 'fieldName',
required: true,
// ❌ 错误:不需要在 rules 中定义必填校验
// rules: [{ required: true, message: '请输入', trigger: 'blur' }]
}
占位符
// 在 formProps.placeholders 中定义 (chenxf,2025年12月23日)
fields: [
{
formProps: {
placeholders: '请输入内容' // 仅在需要自定义时填写,一般由EasyForm组件自动生成
}
}
]
// ❌ 错误:不在 field 中定义 placeholder
默认值
// ❌ 错误:非业务要求场景下,fields 中不应定义 defaultValue (chenxf,2025年12月23日)
// ✅ 正确:直接对 formData 赋值
this.formData.fieldName = '初始值'
6.2.3 码值配置
本地码值表
// 使用 options 字段
{
prop: 'status',
label: '状态',
type: 'select',
options: [
{ label: '启用', value: '1' },
{ label: '禁用', value: '0' }
]
}
全局码值表
// 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 组件属性定义
// element-plus 组件属性应在 formProps 中定义 (chenxf,2025年12月23日)
formConfig: {
formProps: {
// ✅ 正确:在这里定义
size: 'large',
labelWidth: '120px'
}
}
// ❌ 错误:不在 fieldProps 中定义 element-plus 属性
6.2.5 字段单位
// 使用 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日)
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日)
// ✅ 正确:使用 options 字段定义码值表
{
prop: 'status',
label: '状态',
options: [
{ label: '启用', value: '1' },
{ label: '禁用', value: '0' }
]
}
// ❌ 错误:不要使用 formatter 进行码值转换 (chenxf,2025年12月23日)
6.4 通用开发规范
6.4.1 异步操作处理
// 按钮 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日)
// ✅ 正确示例
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 前端团队