本文是 frontend/web 新增或修改前端业务功能时的权威规范。当前 Web 项目基于 Vue 3、Vite、Element Plus、Pure Admin、Pinia、Vue Router 和项目自有 @/utils/http 封装。开发时必须优先复用现有目录结构、组件和工具链。
本文只覆盖 frontend/web,不覆盖 frontend/app。
前端业务模块按“页面结构 + API 类型封装 + 业务 hook + 表单/弹窗 + 页面私有组件”组织。
优先参考这些现有模块:
frontend/web/src/views/system/postfrontend/web/src/views/system/userfrontend/web/src/views/system/rolefrontend/web/src/views/system/noticefrontend/web/src/views/loginfrontend/web/src/api/system/post.ts不要绕过 @/utils/http 直接使用 Axios。不要在 index.vue 中堆积复杂列表逻辑。不要硬编码字典状态文本、值或标签样式。
开发 frontend/web 业务功能前,应先确认页面所属业务域、后端接口、菜单路由、权限标识、字典依赖和相似页面。本文是 Web 前端业务功能开发的统一规范。
开始编码前必须先做一次相似模块调研,确认目标功能最接近 system/post、system/user、system/role、system/notice、login 还是其他模块,再按相似模块的页面结构、API 类型、hook、表单、字典、表格、分页、排序、删除、导出、权限和组件命名风格实现。
业务页面放在 frontend/web/src/views/<area>/<module>/:
frontend/web/src/views/<area>/<module>/
├── index.vue
├── form.vue 或 xxx-form-modal.vue
├── components/
│ ├── private-panel.vue
│ └── private-fragment.vue
└── utils/
├── hook.tsx
├── rule.ts
└── types.ts
API 放在 frontend/web/src/api/<area>/<module>.ts:
frontend/web/src/api/<area>/<module>.ts
components/、rule.ts、types.ts 按需创建,不要求每个模块都存在。
只被某个页面或模块使用的组件,放在该模块目录下的 components/。现有示例是 frontend/web/src/views/login/components。
页面私有组件适合承载:
页面私有组件不应承载整个业务模块的主流程。主列表、主表单、主弹窗仍按现有模块风格放在 index.vue、form.vue 或 xxx-form-modal.vue。
如果组件开始被第二个业务模块复用,应提升到 frontend/web/src/components,不要继续放在某个页面私有目录中。只有在明确属于某个业务域且多个同域模块复用时,才考虑创建 views/<area>/components;当前项目还没有这个模式,新增前应谨慎。
页面私有组件的判断标准是“是否只服务当前页面或当前模块”。如果组件包含通用交互、通用展示、通用上传、通用选择器等能力,应优先设计为公共组件,而不是藏在某个业务页面目录下。
index.vue 负责页面结构:
defineOptions({ name }),组件 name 应与菜单表中的 router_name 保持一致。utils/hook.tsx 负责列表业务状态和行为:
el-tag。form.vue 或 xxx-form-modal.vue 负责新增/编辑:
v-model 控制显示。type、row 等必要 props。success 事件。utils/rule.ts 负责复杂或可复用的表单校验规则。简单模块也可以把规则放在表单组件内,保持与现有代码一致。
utils/types.ts 只放页面私有类型。跨 API 复用的请求/响应类型优先放在对应 src/api/<area>/<module>.ts。
每个业务模块在 src/api/<area>/<module>.ts 中封装后端接口和 TypeScript 类型。
接口调用使用项目 HTTP 封装:
http.request<ResponseData<T>>("get", "/path", { params });
http.request<ResponseData<void>>("post", "/path", { data });
文件下载使用:
http.download("/path/excel", fileName, { params });
类型命名优先遵循现有风格:
XxxListCommand:列表查询参数。XxxPageResponse:列表响应行。AddXxxCommand:新增请求。UpdateXxxCommand:修改请求。删除接口如果后端接收 ids 查询参数,按现有写法把数组转成字符串,避免 Axios 序列化为 ids[0]、ids[1]。
列表页优先使用 PureTableBar + pure-table。
分页使用 PaginationProps,并通过 CommonUtils.fillPaginationParams() 填充查询参数。
排序使用 Element Plus Sort,并通过 CommonUtils.fillSortParams() 传给后端。
时间范围查询使用 beginTime、endTime,优先复用现有 computed 写法处理 el-date-picker 的双向绑定。
批量操作必须维护 multipleSelection,并在空选择时给出提示。
字典来自登录配置并缓存在用户 store 中。
下拉选项使用:
useUserStoreHook().dictionaryList["common.status"]
表格状态渲染使用:
useUserStoreHook().dictionaryMap["common.status"]
不要在页面中硬编码状态 label、value、el-tag 类型。字典不存在或可能为空时,新增代码应避免直接访问导致运行时报错。
按钮图标优先使用 useRenderIcon() 和现有 iconify 图标。
新增/编辑弹窗优先复用 VDialog 或相似模块的弹窗写法。
单条删除使用 el-popconfirm 或模块现有风格。批量删除使用 ElMessageBox.confirm 时,必须处理取消分支并清理选择状态。
操作成功后刷新列表。弹窗通过 v-model 和 success 事件与父组件通信。
跨业务通用能力优先放在 src/components 或 src/utils;页面私有能力保持在页面目录内。
后台业务菜单主要依赖后端菜单和动态路由。新页面的组件 name 必须与菜单表中的 router_name 保持一致。
不要随意新增静态路由。只有登录页、重定向、个人中心等固定页面才参考 src/router/modules 中的静态路由模式。
如需要按钮权限,优先复用项目已有的 ReAuth 或 auth directive 模式,不要自行实现一套权限判断。
前端改动后优先运行:
pnpm --dir frontend/web typecheck
pnpm --dir frontend/web lint
如果依赖未安装或环境不允许运行,说明原因。
如果只修改文档,不需要运行前端 typecheck 或 lint;如果修改了 TypeScript、Vue、样式或路由/API 文件,应优先运行上述检查。
业务页面至少检查: