# Vue 3 + TypeScript 前端开发规范 ## 技术栈规范 - Vue 3.5.13 - TypeScript 5.8.3 - Element Plus UI - Art Design Pro UI 设计规范 ## 表格组件开发规范 ### 1. 表格行双击查看功能(强制性) #### 实现要求 - **✅ 必须实现**: 所有数据表格必须支持行双击查看详情功能 - **🎯 目标**: 提升用户体验,快速查看数据详情 #### 标准实现方式 ```vue ``` #### 标准事件处理 ```typescript /** 查看按钮操作 */ const handleView = async (row: any) => { // 1. 复制数据到查看表单 Object.assign(viewForm, row); // 2. 处理特殊字段(如图片列表等) if (row.images) { viewImageList.value = row.images.split(',').filter((img: string) => img.trim() !== ''); } // 3. 显示对话框 viewDialog.visible = true; viewDialog.title = '查看详情'; }; ``` #### 最佳实践建议 1. 查看对话框宽度建议 800px-1000px,根据内容自适应 2. 详情展示采用只读表单形式,提供良好的数据展示结构 3. 对于图片、文件等特殊字段,需要合适的预览组件 4. 长文本内容建议使用滚动容器限制高度 5. 必要时可以在详情页面添加相关操作按钮 ### 2. 表格长文本显示规范(强制性) #### 实现要求 - **✅ 必须实现**: 所有表格中的长文本字段必须限制显示行数 - **🎯 目标**: 保持表格整洁美观,同时提供完整内容的查看方式 #### 标准实现方式 ```vue {{ scope.row.description }} ``` #### 标准样式定义 ```css /* 长文本单元格样式 */ .description-cell { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; /* 显示3行 */ overflow: hidden; text-overflow: ellipsis; line-height: 1.5; max-height: 4.5em; /* 3行 * 1.5行高 */ text-align: left; padding: 0 10px; } ``` #### 最佳实践建议 1. 统一使用3行显示限制,超出显示省略号 2. 使用左对齐方式,提高长文本可读性 3. 设置合适的行高和内边距 4. 配合表格行双击查看功能,在详情弹窗中显示完整内容 5. 必要时可以添加鼠标悬停提示(el-tooltip)显示完整内容 ### 3. 组件开发标准和页面模板 ```vue ``` #### 列表页面标准模板 ```vue 搜索 重置 新增 修改 删除 导出 ``` #### 表单页面标准模板 ```vue 基本信息 提交 重置 ``` ### 3. API接口定义和状态管理 ```typescript // api/[module]/index.ts import request from '@/utils/request'; import type { AxiosPromise } from 'axios'; import type { DataVO, DataForm, DataQuery } from './types'; /** * 查询数据列表 * @param query 查询参数 */ export const listData = (query?: DataQuery): AxiosPromise => { return request({ url: '/api/data/list', method: 'get', params: query, }); }; /** * 查询数据详情 * @param id 数据ID */ export const getData = (id: string | number): AxiosPromise => { return request({ url: `/api/data/${id}`, method: 'get', }); }; /** * 新增数据 * @param data 数据信息 */ export const addData = (data: DataForm): AxiosPromise => { return request({ url: '/api/data', method: 'post', data: data, }); }; /** * 修改数据 * @param data 数据信息 */ export const updateData = (data: DataForm): AxiosPromise => { return request({ url: '/api/data', method: 'put', data: data, }); }; /** * 删除数据 * @param ids 数据ID数组 */ export const delData = (ids: Array): AxiosPromise => { return request({ url: `/api/data/${ids}`, method: 'delete', }); }; ``` ### TypeScript 类型定义 ```typescript // api/[module]/types.ts // 查询参数 export interface DataQuery extends PageQuery { name?: string; status?: string; createTime?: [DateModelType, DateModelType]; } // 数据表单 export interface DataForm { id?: string | number; name: string; status: string; remark?: string; } // 数据视图对象 export interface DataVO { id: string | number; name: string; status: string; createTime: string; updateTime: string; remark?: string; } ``` ## 5. 状态管理规范 ### Pinia Store 结构 ```typescript // stores/modules/[module].ts import { defineStore } from 'pinia'; import { ref, computed } from 'vue'; import type { DataVO, DataQuery } from '@/api/[module]/types'; import { listData, getData } from '@/api/[module]'; export const useDataStore = defineStore('[module]', () => { // 状态 const dataList = ref([]); const loading = ref(false); const total = ref(0); // 计算属性 const hasData = computed(() => dataList.value.length > 0); // 方法 const getList = async (query: DataQuery) => { loading.value = true; try { const { data } = await listData(query); dataList.value = data.rows; total.value = data.total; } finally { loading.value = false; } }; const getInfo = async (id: string | number) => { const { data } = await getData(id); return data; }; const reset = () => { dataList.value = []; total.value = 0; }; return { // 状态 dataList, loading, total, // 计算属性 hasData, // 方法 getList, getInfo, reset, }; }); ``` ## 6. 路由管理规范 ### 路由定义标准 ```typescript // router/modules/[module].ts import type { RouteRecordRaw } from 'vue-router'; import Layout from '@/layout/index.vue'; const moduleRouter: RouteRecordRaw = { path: '/[module]', component: Layout, redirect: '/[module]/list', name: 'Module', meta: { title: '模块管理', icon: 'example', alwaysShow: true, }, children: [ { path: 'list', component: () => import('@/views/[module]/index.vue'), name: 'ModuleList', meta: { title: '列表管理', activeMenu: '/[module]/list', }, }, { path: 'add', component: () => import('@/views/[module]/form.vue'), name: 'ModuleAdd', meta: { title: '新增数据', activeMenu: '/[module]/list', }, hidden: true, }, { path: 'edit/:id(\\d+)', component: () => import('@/views/[module]/form.vue'), name: 'ModuleEdit', meta: { title: '编辑数据', activeMenu: '/[module]/list', }, hidden: true, }, ], }; export default moduleRouter; ``` ## 7. 搜索表单布局规范(强制性) ### 7.1 设计目标 1. **一致性**:所有列表页面的检索栏保持统一外观和交互。 2. **响应式**:在 1920/1440/1280 等常用分辨率下均能保持整洁布局。 3. **易维护**:通过统一的栅格 + 样式类,后续页面可快速复用。 ### 7.2 栅格布局 1. 采用 Element Plus `el-row` / `el-col` 栅格系统。 2. **每行固定 4 个检索元素**,每项 `:span="6"`(24 ÷ 4)。 3. 若检索项不足 4 个,也应保持占位对齐,避免按钮漂移。 ```vue 搜索 重置 ``` ### 7.3 按钮固定右侧 1. 统一在搜索表单尾部添加按钮行。 2. 使用 `.search-btn-container` 类,将按钮通过 Flex **右对齐**。 ```scss /* 搜索按钮容器样式(全局复用,可写在组件 scoped 内部)*/ .search-btn-container { display: flex; justify-content: flex-end; margin-top: 10px; .el-form-item { margin-bottom: 0 !important; // 清除默认 margin } } ``` ### 7.4 应用示例 所有新页面或改造页面 **必须** 遵循以上规范。例如: * `hq/pm/repairOrder/index.vue` * `hq/pm/repairCategory/index.vue` * `hq/hallAudit/index.vue` * `hq/hallInfo/index.vue` * `hq/hallReservation/index.vue` ### 7.5 代码检查要点 1. 检查是否存在 `:inline="true"`,如有应移除。 2. 确认每行 4 列,使用 `span=6`。 3. 搜索/重置按钮是否放置在 `.search-btn-container` 内。 4. scoped 样式内是否包含上述 `.search-btn-container` 定义。 遵循此规范,可显著提升列表页检索区域的统一性与用户体验。 ## 8. 项目架构规范 ### 目录结构 ``` web/src/ ├── api/ # API接口定义 │ ├── [module]/ │ │ ├── index.ts # 接口方法 │ │ └── types.ts # 类型定义 ├── assets/ # 静态资源 │ ├── images/ # 图片资源 │ └── styles/ # 样式文件 │ ├── art-variables.scss # Art Design Pro 变量 │ ├── art-element-ui.scss # Element Plus 样式定制 │ ├── art-global.scss # 全局样式 │ ├── art-dark.scss # 暗色主题 │ └── index.scss # 样式入口 ├── components/ # 全局组件 ├── hooks/ # 组合式函数 ├── layout/ # 布局组件 ├── plugins/ # 插件配置 ├── router/ # 路由配置 ├── stores/ # 状态管理 ├── types/ # 全局类型定义 ├── utils/ # 工具函数 ├── views/ # 页面组件 └── main.ts # 应用入口 ``` ### 文件命名规范 - **组件文件**: PascalCase,如 `UserForm.vue` - **页面文件**: kebab-case,如 `user-list.vue` - **工具文件**: camelCase,如 `formatUtils.ts` - **类型文件**: PascalCase,如 `UserTypes.ts` ## 9. 样式开发规范 ### SCSS 变量使用 ```scss // 使用 Art Design Pro 变量系统 .custom-component { background-color: var(--art-main-bg-color); border: 1px solid var(--art-border-color); border-radius: var(--border-radius-lg); padding: var(--space-lg); .title { color: var(--art-text-gray-900); font-size: 16px; font-weight: 500; } .content { color: var(--art-text-gray-700); margin-top: var(--space-md); } } ``` ### UnoCSS 工具类 ```vue 标题 内容 ``` ## 10. 性能优化规范 ### 组件懒加载 ```typescript // 路由级别的懒加载 const UserList = () => import('@/views/user/index.vue'); // 组件级别的懒加载 const AsyncComponent = defineAsyncComponent(() => import('@/components/HeavyComponent.vue')); ``` ### 图片优化 ```vue ``` ## 11. 开发最佳实践 ### 代码规范 1. **组件命名**: 使用 PascalCase 2. **方法命名**: 使用 camelCase,语义化命名 3. **常量命名**: 使用 UPPER_SNAKE_CASE 4. **注释规范**: 使用 JSDoc 格式 ### 错误处理 ```typescript // API错误处理 const handleSubmit = async () => { try { loading.value = true; await submitData(formData); ElMessage.success('操作成功'); } catch (error) { console.error('提交失败:', error); ElMessage.error('操作失败'); } finally { loading.value = false; } }; ``` ### 类型安全 ```typescript // 严格的类型定义 interface UserForm { name: string; email: string; status: 0 | 1; } // 类型断言 const user = data as UserForm; ``` ## 12. 测试规范 ### 单元测试 ```typescript // 使用 Vitest 进行单元测试 import { describe, it, expect } from 'vitest'; import { mount } from '@vue/test-utils'; import UserForm from '@/components/UserForm.vue'; describe('UserForm', () => { it('should render correctly', () => { const wrapper = mount(UserForm); expect(wrapper.exists()).toBe(true); }); }); ``` ## 13. 构建与部署 ### 环境配置 ```typescript // .env.development VITE_APP_BASE_API = '/dev-api'; VITE_APP_PORT = 80; // .env.production VITE_APP_BASE_API = '/prod-api'; VITE_APP_PORT = 80; ``` ### 构建优化 ```typescript // vite.config.ts export default defineConfig({ build: { rollupOptions: { output: { chunkFileNames: 'js/[name]-[hash].js', entryFileNames: 'js/[name]-[hash].js', assetFileNames: '[ext]/[name]-[hash].[ext]', }, }, }, }); ``` --- ## 总结 本规范文档涵盖了杭州第一技师学院智慧校园管理系统前端开发的所有核心规范,包括最新的品牌色配置和统一的按钮使用规范。开发者应严格遵循这些规范,确保代码质量和用户体验的一致性。 **🔴 重要提醒**: 1. **按钮规范**: 工具栏第一个新增按钮必须使用 `type="primary" plain`,其他按钮使用默认样式 2. **颜色系统**: 使用最新的 Art Design Pro 颜色变量 3. **文档同步**: 代码变更后必须同步更新相关文档 ## 4. 附件管理统一规范(强制性) > 适用范围:所有使用 **Element Plus** + **Vue 3 + TS** 的前端页面(弹窗、新建、编辑、详情、预览等)涉及“附件/文件”功能的模块,必须遵循本规范,以保持 UI 及交互的一致性。 ### 4.1 组件结构 | 组件 | 说明 | |------|------| | `` | 核心上传组件,拖拽 + 点击上传,**单文件最大 10 MB**,**最多 15 个文件** | | 自定义 `#file` 模板 | 自绘「卡片样式」:缩略图 + 文件名 + 操作按钮组 | | `` | 卡片底部操作按钮:下载(success)、删除(danger) | | 自定义 CSS 类 | `.upload-file-card / thumb / name / actions` 等,详见样式规范 | ### 4.2 功能实现 1. **上传 Upload** - `auto-upload="true"` + 立即上传 - `accept="image/*,video/*,.pdf,.doc,.docx,.txt"` - 上传前 (`before-upload`) 校验大小 <10 MB - 上传成功: - 新建模式:将返回的 `id` 收集到 `tempAttachmentIds` - 编辑模式:文件已自动关联到工单 - 上传失败:Element Plus message 提示 2. **下载 Download (强制使用 fetch + blob)** - 统一函数 `handleDownloadAttachment(file)` / `downloadAttachment(attachment)` - 流程:`fetch(filePath) → blob → URL.createObjectURL → ` - 使用 `ElLoading.service()` 显示 *正在下载文件…* - 下载完成后 `revokeObjectURL` 释放内存 - 解决浏览器对图片/PDF 默认预览导致无法下载的问题 3. **删除 Delete(延迟删除机制)** - 点击删除 → `proxy.$modal.confirm()` 二次确认 - 已上传文件:只把 `attachment.id` 放入 `pendingDeleteAttachmentIds`,**不立即调用后端** - 表单保存时统一调用 `deleteAttachment` 批量删除 - 新增未上传文件:直接从 `tempFileList` / `attachmentList` 移除 4. **预览 Preview** - 判断 `fileType === 'image'` → 使用 `Element Plus ImageViewer` 预览 - 其他类型文件 `window.open(filePath,'_blank')` - 详情弹窗附件列表同样遵循该预览逻辑 ### 4.3 样式规范(SCSS) ```scss /* 让附件卡片一行展示 4 个 */ :deep(.el-upload-list--picture-card) { display: grid; grid-template-columns: repeat(4, 1fr); gap: 9px; margin-top: 12px; .el-upload-list__item { width: 166px !important; min-height: 180px !important; border-radius: 6px; overflow: hidden; } } /* 卡片整体布局 */ .upload-file-card { display: flex; flex-direction: column; min-height: 180px; } .upload-file-thumb { height: 120px; overflow: hidden; } .upload-file-name { font-size: 12px; padding: 8px 4px 4px; text-align: center; overflow: hidden; } .upload-file-card-actions { padding: 6px 8px; border-top: 1px solid #f0f0f0; background: #fafafa; display: flex; justify-content: center; } ``` ### 4.4 统一变量 & Hook ```ts const attachmentList = ref([]); // el-upload file-list const tempFileList = ref([]); // 新建时未上传的本地文件 const tempAttachmentIds = ref([]); // 新建时已上传待关联附件 id const pendingDeleteAttachmentIds = ref([]); // 编辑时标记待删除附件 id ``` ### 4.5 集成步骤(页面级) 1. 在 `
内容