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

16 KiB
Raw Blame History

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 调试信息规范

  • 禁止: 除捕获异常外,不要在控制台中打印任何调试信息 chenxf2025年12月23日
  • 允许: 异常捕获时的 console.error

4. 设计规范

4.1 屏幕适配

  • 仅支持桌面端:系统专为桌面环境设计,不兼容小屏幕设备(如手机、平板)
  • 最小屏幕宽度:设计最小支持宽度为 1200px推荐使用 1920px 或更高分辨率

4.2 UI 组件使用

  • 主要框架: Element Plus
  • 组件尺寸: 优先使用 mediumlarge
  • 布局组件: 使用 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 关键原则

  1. 父子路由分离:父路由负责布局,子路由负责内容
  2. 布局组件选择:使用 Empty 组件或包含 <router-view> 的布局组件
  3. 重定向配置:设置合适的默认子路由重定向
  4. 元信息配置:正确区分 isMenuisSubMenu 属性

5.4 常用布局组件

  • Empty:空布局组件,仅包含 <router-view>,适用于需要嵌套子路由的场景
  • WebLayout:适用于本项目的默认布局

5.5 路由组织规范

// 所有页面路由应在父路由的 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 字段配置

必填校验

// 使用 required: true chenxf2025年12月23日
{
  prop: 'fieldName',
  required: true,
  // ❌ 错误:不需要在 rules 中定义必填校验
  // rules: [{ required: true, message: '请输入', trigger: 'blur' }]
}

占位符

// 在 formProps.placeholders 中定义 chenxf2025年12月23日
fields: [
  {
    formProps: {
      placeholders: '请输入内容' // 仅在需要自定义时填写一般由EasyForm组件自动生成
    }
  }
]
// ❌ 错误:不在 field 中定义 placeholder

默认值

// ❌ 错误非业务要求场景下fields 中不应定义 defaultValue chenxf2025年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 中声明码表 chenxf2025年12月23日
// ❌ 错误:不要使用 formatter 进行码值转换 chenxf2025年12月23日

6.2.4 组件属性定义

// element-plus 组件属性应在 formProps 中定义 chenxf2025年12月23日
formConfig: {
  formProps: {
    // ✅ 正确:在这里定义
    size: 'large',
    labelWidth: '120px'
  }
}

// ❌ 错误:不在 fieldProps 中定义 element-plus 属性

6.2.5 字段单位

// 使用 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日

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日

// ✅ 正确:使用 options 字段定义码值表
{
  prop: 'status',
  label: '状态',
  options: [
    { label: '启用', value: '1' },
    { label: '禁用', value: '0' }
  ]
}

// ❌ 错误:不要使用 formatter 进行码值转换 chenxf2025年12月23日

6.4 通用开发规范

6.4.1 异步操作处理

// 按钮 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日

// ✅ 正确示例
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 前端团队