aiceps-mobile/.github/copilot-instructions.md

600 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 调试信息规范
- **禁止**: 除捕获异常外,不要在控制台中打印任何调试信息 chenxf2025年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 中 chenxf2025年12月23日
// 路径需要继承父路由,并写全路径
{
path: '/contract/detail/functionList',
children: [
{
path: '/contract/detail/functionList/detail', // ✅ 正确:继承父路径并写全路径
},
{
path: '/detail' // ❌ 错误:不应使用不是父路由作为前缀的路径
}
]
}
```
**规范来源**:合同功能清单路由组织实践 chenxf2025年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>` 开发 chenxf2025年12月23日
- **字段复用**: 尽可能复用列表页面组件中定义的 fields chenxf2025年12月23日
- **职责分离**:
- 侧边查询条件组件只负责生成查询条件数据 chenxf2025年12月23日
- 通过事件通知父组件执行搜索查询 chenxf2025年12月23日
#### 6.2.2 字段配置
**必填校验**
```javascript
// 使用 required: true chenxf2025年12月23日
{
prop: 'fieldName',
required: true,
// ❌ 错误:不需要在 rules 中定义必填校验
// rules: [{ required: true, message: '请输入', trigger: 'blur' }]
}
```
**占位符**
```javascript
// 在 formProps.placeholders 中定义 chenxf2025年12月23日
fields: [
{
formProps: {
placeholders: '请输入内容' // 仅在需要自定义时填写一般由EasyForm组件自动生成
}
}
]
// ❌ 错误:不在 field 中定义 placeholder
```
**默认值**
```javascript
// ❌ 错误非业务要求场景下fields 中不应定义 defaultValue chenxf2025年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 中声明码表 chenxf2025年12月23日
// ❌ 错误:不要使用 formatter 进行码值转换 chenxf2025年12月23日
```
#### 6.2.4 组件属性定义
```javascript
// element-plus 组件属性应在 formProps 中定义 chenxf2025年12月23日
formConfig: {
formProps: {
// ✅ 正确:在这里定义
size: 'large',
labelWidth: '120px'
}
}
// ❌ 错误:不在 fieldProps 中定义 element-plus 属性
```
#### 6.2.5 字段单位
```javascript
// 使用 append 字段定义单位 chenxf2025年12月23日
{
prop: 'price',
label: '价格',
append: '元'
}
```
#### 6.2.6 码表值类型
- **优先使用**: string 类型 chenxf2025年12月23日
- **例外**: 当接口明确定义为 number 类型时
### 6.3 EasyTable 开发规范
#### 6.3.1 格式化字段使用规范
**✅ 应该使用 formatter 的场景**
- 日期格式化(如时间戳转为 YYYY-MM-DD
- 数字格式化(如金额添加千分位)
- 文本处理(如截断、拼接等)
**编写位置**tableProps.formatter chenxf2025年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 字段定义对应的码值表 chenxf2025年12月23日
```javascript
// ✅ 正确:使用 options 字段定义码值表
{
prop: 'status',
label: '状态',
options: [
{ label: '启用', value: '1' },
{ label: '禁用', value: '0' }
]
}
// ❌ 错误:不要使用 formatter 进行码值转换 chenxf2025年12月23日
```
### 6.4 通用开发规范
#### 6.4.1 异步操作处理
```javascript
// 按钮 handler 中的异步操作应 return Promise chenxf2025年12月23日
handlers: {
async handleSubmit() {
// ✅ 正确return Promise
return await this.saveData()
}
}
```
#### 6.4.2 编辑查看页面复用
- **原则**: 如查看页面不存在和编辑页面复杂的结构差异 chenxf2025年12月23日
- **实现**:
- 编辑和查看页面应使用同一个页面 chenxf2025年12月23日
- 使用 `<easy-form>` 开发 chenxf2025年12月23日
- 通过 `formConfig.isView` 字段控制是否处于编辑或查看状态 chenxf2025年12月23日
#### 6.4.3 字段复用
- **原则**: 同一个业务文件下easy-component 的各个组件的 fields 应尽可能复用 chenxf2025年12月23日
- **实践**: 提取公共 fields 配置,多处引用
#### 6.4.4 错误处理规范
**API 调用必须使用 try-catch 包裹** zhenghl2025年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 前端团队*