主题
前端工程规范
面向对象:前端开发者、代码评审者、AI Agent。
本文维护 digital-construction-ui(Vue 3 + TypeScript + Element Plus)的代码组织与实现规范。页面视觉、布局与交互状态见界面与交互规范,跨端契约见公共开发约定。
决策优先级
- 当前用户指令、适用业务基线、已核实的接口与权限契约,以及有依据的合理假设;
- 项目本地 UI 规范;
- 同类型成熟页面和公共组件;
- Vue 3、Element Plus、项目 hooks/API/types 的既有约定;
- 通用前端实践。
成熟页面是本地证据,不是绝对真理。若它与前两项冲突,说明差异并按目标修正。
当前用户指令、适用业务基线和原型表达的业务要求决定业务内容与动作含义;信息分组、布局、控件形态和样式由 UI 规范与成熟组件决定,原型不作为 UI 还原稿(见原型与证据)。不能套成熟布局就自动增加无来源的 Tab、统计卡或业务动作;标准 B 端页面优先复用成熟组件,不为装饰性细节另造组件。冲突按根 AGENTS.md 的授权、暂停和范围规则处理。
开始方式
- 核对当前任务的业务流程、接口契约和权限要求,沿用已有基线,不另设确认点。
- 核对根因和必须保持的正确行为,从页面、API/types、共享组件与 Hook 的直接依赖开始,按任务分级依据证据扩展影响边界并收敛调查,不按改动行数预判风险。新增模式、上下文不足或涉及共享机制时,核验最接近的成熟页面。原型驱动任务记录图/页/区域定位,仅记录歧义或有意改动的短对照,不逐字段抄录;成熟页面只是证据,不自动带入原型未显示且无需求来源的功能。
- 按实际可见影响读取界面与交互规范相关章节:内容、按钮数量、文字长度、显隐、交互或布局变化都可触发,不限于名称含“样式”的任务。工具栏、操作列等标准区域优先复用经核验符合当前规范的共享结构与样例;整页调整按实际覆盖范围扩大阅读。
- 判断是标准 CRUD、树表、强业务页、工作流页,还是仅 API/types 修改。
优先参考的代码来源
- 标准单表:
src/views/system/client/index.vue、src/api/system/client/index.ts、src/api/system/client/types.ts。 - 树表:
src/views/workflow/category/index.vue。 - 系统复杂页:
src/views/system/user/index.vue、system/role、system/post、system/config。 - 强业务页:
- 工程结构树与单位工程:
src/views/business/engineeringConfig/index.vue(含nodeForm.vue、moveDialog.vue、sitePicker.vue) - 项目范围与授权(单页多 Tab 工作台):
src/views/business/projectScope/index.vue(含components/GrantPanel.vue、components/UserScopePanel.vue、components/NodeMembersPanel.vue、components/TemplatePanel.vue) - 工点库:
src/views/business/siteLibrary/index.vue - 工程手续办理(台账 + 流程表单):
src/views/business/engineeringProcedures/**(annualPlan/index.vue、flowForm/index.vue、stage/ProcedureLedgerPage.vue、summary/index.vue)
- 工程结构树与单位工程:
- 公共 hooks:
src/hooks/async/useLoading.ts、src/hooks/dialog/*、src/hooks/form/*、src/hooks/table/*、src/hooks/tree/*。 - 项目内 gen 模板:
gen/api.ts.ftl、gen/types.ts.ftl、gen/index.vue.ftl、gen/index-tree.vue.ftl。
基础栈与格式
- Vue 3 + TypeScript + Element Plus + Vite + Pinia + UnoCSS。
- pnpm;
pnpm run fmt(oxfmt)、pnpm lint(oxlint)。 .editorconfig:UTF-8、LF、2 空格缩进。- 不写本机绝对路径到交付内容;跨环境引用用 Gitee URL + 分支 + 路径。
两套页面风格
标准 CRUD / 系统页
<script setup name="Xxx" lang="ts">。- 根节点
class="p-2 app-container <module>-<business>-page"。 - 结构:搜索卡片
search-panel→ 表格卡片table-panel→ 工具栏toolbar-shell→data-table→right-toolbar→pagination→el-dialog。 - hooks:
useSearchToggle、useLoading(true)+withLoading、useTableSelection<XxxVO>(item => item.id)、useFormDialog({ form, formRef, initialFormData })、useDateRangeQuery、useSearchReset。 - 参考
src/views/system/client/index.vue。
既有强业务页
src/views/business/**使用自定义布局:el-card分栏、el-tree、el-dialog、proxy?.$modal、v-hasPermi、scoped SCSS、自定义 class(如project-config-page、structure-card、tree-scroll)。- 修改时只增量改;新增子组件/弹窗参考同目录现有子组件。
- 新强业务页优先模仿这些页面,不套
gen。
页面实现契约
- 标准 B 端页面复用成熟页面的布局、筛选区、表格、弹窗、权限控制和交互节奏,只重新设计真实业务差异。
- 数据源、枚举含义、查询范围、保存粒度和流程推进等会改变行为的内容先查资料、调用链和同级先例,能确定时沿用并记录;仍缺关键事实时按根
AGENTS.md只澄清具体缺项,暂停依赖该决定的实现,其余已授权工作继续。实施统一遵循根规则的“开始实施”开关。本次适用的鉴权、校验、错误处理和失败恢复落实在责任边界并复用统一机制,不逐层重复实现,也不因此扩建产品能力。 - 新强业务页面或权限、只读、状态、提交行为变化时,明确受影响的最小状态矩阵:业务状态 × 用户角色 × 可见/只读/可操作 × 动作结果。矩阵覆盖实际不同的权限或行为分支,不枚举不存在的组合;相同行为可选代表性用例,但不能合并权限边界不同的分支。局部改动沿用已有矩阵,只核对受影响项;它是实现检查表,不要求额外创建正式文档。
- 类型、枚举和状态含义来自真实接口契约,优先复用项目 API/types 和字典;不得用
any、重复常量或页面内临时映射掩盖契约不一致。 - 防御、校验、组件与 Hook 抽取及异常处理统一按实现简洁性执行。
API 文件规则
- 标准 API 放
src/api/<module>/<business>/index.ts,同目录维护types.ts。 - 业务 API 示例:
src/api/business/projectScope/index.ts、src/api/business/engineeringConfig/projectNode.ts、src/api/business/engineeringProcedure/index.ts。 AxiosPromise从@/utils/api-types引入,PageResult从@/api/types引入;不要从axios引AxiosPromise。- 列表分页通常返回
AxiosPromise<PageResult<XxxVO>>;树表通常返回AxiosPromise<XxxVO[]>。 - 标准函数命名:
listXxx、getXxx、addXxx、updateXxx、delXxx、changeXxxStatus。 - query string 用
params,请求体用data。 - 加密、防重复提交等 headers 直接写在请求配置里。
- 相邻文件已有
export const或export function时跟随;新增标准 CRUD 优先跟随gen/api.ts.ftl和相邻模块。
类型文件规则
- 标准类型定义
VO、Form、Query,必要时补InfoVO、TreeVO、ResetPwdForm等扩展类型。 Form通常继承BaseEntity;非树表Query继承PageQuery;树表Query通常不继承PageQuery。- ID 通常
string | number,批量删除参数string | number | Array<string | number>。 - 日期范围查询保留项目所需的
params结构;新增类型优先使用Record<string, unknown>,修改旧代码时避免无关的大范围类型重写。 - 列表、表单、查询对象职责分开;不得用
any掩盖接口字段、枚举或状态不一致。框架边界确实无法收窄时才局部使用,并说明原因。
大整数 ID 约定
ID 的接收、传递、列表与详情合并及接口排查统一遵循公共开发约定 · 前后端大整数 ID 约定。
组织单位选择器 OrgUnitSelect 内部树节点与选中键统一字符串匹配,兼容 CSV 字符串 ID 与接口安全数字 ID;对外已选项保持调用方原值、新选项透传组织接口原值,不将任何 ID 数字化。
权限规则
- 权限复用项目路由、菜单和按钮机制,并区分不可见、只读、不可操作;前端隐藏按钮不能代替鉴权,也不能绕过只读和数据范围限制。
- 权限码格式、前后端与菜单的一致性要求及回归检查见公共开发约定 · 三段式权限码。
- 默认
v-hasPermi;el-dropdown-item延迟加载不可靠时用v-if="checkPermi([...])"。
Vue 页面行为规则
标准页
getList负责设置 loading、调用列表接口、回填列表和total。handleQuery先把queryParams.value.pageNum = 1,再getList();树表无分页时只getList()。resetQuery使用useSearchReset,分页页传pageNumKey: 'pageNum'。handleAdd使用openDialog('添加xxx')。handleUpdate先reset(),查详情,Object.assign(form.value, res.data),showDialog('修改xxx')。submitForm校验通过后设buttonLoading,按主键新增/修改,成功后modal.msgSuccess('操作成功')、关弹窗、刷新。handleDelete用modal.confirm(...)二次确认,删除后提示并刷新。handleExport用requestDownload('<module>/<business>/export', { ...queryParams.value }, '<business>_<timestamp>.xlsx')。- 状态切换失败时回滚 switch 值。
强业务页
- 保持现有方法命名和交互(如
onNodeClick、handleAddChild、filterNode)。 - 弹窗用
el-dialog或ElMessageBox,按原页面写法。 - 修改树结构后按原逻辑刷新左树、中列、详情。
- 新增子页面/组件先看同目录现有子组件再动手。
- 状态矩阵按“页面实现契约”的适用范围核对,不因修改强业务页就重新整理全页状态和角色。
异步与失败状态
按本次受影响的异步交互处理适用状态:读取数据时处理加载、空数据和请求失败;写入动作处理防重复触发、成功后的刷新/关闭/跳转及失败后的状态恢复;存在无权限、只读或服务端数据变化语义时处理对应反馈。无需为不适用的状态增加页面功能。
- 提交、删除、状态切换等操作防重复触发;失败后恢复按钮、表单或开关状态。
- 成功后明确刷新、关闭或跳转行为,避免页面仍显示旧数据。
- 服务端数据已变化或当前状态不再允许操作时,展示可理解的反馈,并按需要重新获取最新数据。
可访问性底线
表单标签、图标按钮、键盘焦点和状态提示满足基础可访问性;状态和危险程度不能只靠颜色表达,禁用状态应能解释原因。操作不能只依赖颜色表达。
组件、样式与字典
- 复用
right-toolbar、pagination、DictTag/dict-tag、ImageUpload、ImagePreview、FileUpload、Editor、TreePanel。 - 标准页 scoped SCSS:
@use '@/assets/styles/components/page-shell' as pageShell;。 - 强业务页按现有 scoped 样式继续,不因新增功能重排全局样式。
- 类名保持模块语义:
project-config-page、site-library-page、engineering-procedure-page。 - 颜色只用语义令牌,不在页面里写死浅色字面值;令牌表、状态色用法和
<style scoped>里暗色规则的写法见界面与交互规范第 2.1 节。深色模式状态统一走@/hooks/useThemeMode,页面不得自建第二份主题状态。 - 字典:
const { sys_normal_disable } = toRefs(useDict('sys_normal_disable'));,不要用any掩盖字典类型。 - 常用:
modalfrom@/plugins/modal、download as requestDownloadfrom@/utils/request、useDictfrom@/utils/dict、handleTree/parseStrEmptyfrom@/utils/ruoyi。
树表规则
- 列表返回数组,
handleTree<T>(res.data, 'id', 'parentId')组树。 - 使用
row-key、:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"。 - 展开/折叠使用
useTreeTableExpand。 - 表单上级节点使用
el-tree-select;新增子节点从当前行回填parentId。 - 工程结构树注意节点类型(ROOT/LINE/PACKAGE/BID/SITE)与状态标签逻辑,不要把历史
PROJECT值当作可创建节点。
与 gen 模板的关系
- 新增标准页面读
gen/index.vue.ftl、gen/api.ts.ftl、gen/types.ts.ftl;树表读gen/index-tree.vue.ftl。 gen是骨架不是答案;落地时对照目标模块真实页面。- 修改强业务页不要把
gen简化逻辑覆盖现有业务。
视觉设计边界
标准 CRUD、树表、系统管理和普通强业务后台不做大范围视觉重设计。官网、登录页、门户、大屏等表现型页面沿用本地 UI 规范、既有组件和本规范的接口、权限、异步状态与页面验证要求;决策驾驶舱类大屏参考 src/views/cockpit/** 的现有实现:独立全屏路由(登录必需、菜单隐藏)、平台令牌取色、echarts 随浅深主题重建、演示数据集中于 src/views/cockpit/mock.ts 并在页面标注模拟数据,不套用 B 端台账的令牌与台账验收条目;确需视觉调整时按实际覆盖范围读取界面与交互规范。方案自查不是用户批准点,审批边界遵循根 AGENTS.md;本地 UI 规范与项目既有组件始终优先于外部设计参考。
验证
- 任务分级、真实页面证据及完成边界遵循根
AGENTS.md;测试复用、覆盖缺口和共享机制范围遵循公共开发约定 · 验证范围。通过验证入口执行所选检查,不因缺少浏览器自动增加与风险无关的类型、lint 或接口检查。 - 可见界面按本次需求来源检查漏做、改义和多做;有原型时核对原型来源和对应区域,没有原型不增加材料门槛。源码、类型检查、构建和示例预览不构成真实页面证据,常规任务不强制先做预览。
- 验证方式遵循根
AGENTS.md:Agent 根据实际影响和风险选择静态、定向测试、接口或浏览器等足够的证据,用户显式要求浏览器时执行;不因功能交互、改动数量或页面类型机械启动浏览器。局部样式、文案默认核对差异与规范,公共样式即使只改一处也先核实影响;不自动把未选浏览器的任务转交用户待验收。 - 选用浏览器后,从实际页面入口点击、输入、选择和提交,不以 API 请求或内部状态注入冒充用户操作;文件上传的工具限制按根规则例外处理,随后继续所需页面核验。网络、日志和数据库各证明其覆盖的结果。按实际影响覆盖相关角色、状态、动作结果及适用失败场景,不因页面属于强业务页扩大为全角色回归。使用主要角色的真实账号或等价角色切换能力,核对可见、只读/禁用、可操作及结果;工具栏和操作列变化还需核对对齐、顺序、完整显示、间距、固定列及显隐变化后的布局。关键页面或明确要求的视觉问题保留截图,普通任务不强制逐页留图。
- 小改动验证直接影响范围,普通页面按需执行相关 spec、类型检查或 lint。公共 hooks 和构建配置按验证范围的共享机制原则核对实际影响,能可靠收敛时执行相关检查;影响范围无法可靠收敛、实际改变构建流程或产物组织及打包行为、大范围页面变化及正式模块交付时执行完整构建。不因文件属于公共 hooks 或构建配置而将无行为变化的改动升级为完整构建。
- lint、格式检查优先传入受影响文件或目录;类型检查保留项目级依赖分析,不机械裁成单文件检查。自动化针对实际行为和稳定契约,普通文案、样式或实现形式不新增低价值测试。
- 登录、角色选择和验证受阻排障见环境配置与运行 · 验证范围。证据只记录角色,不泄露凭据;单一管理员账号检查或类型、lint、build 结果不能表述为多角色业务验收通过。
避免事项
- 不要从
axios引入AxiosPromise。 - 不要绕开
request/requestDownload自造请求/下载封装。 - 不要删除日期范围
params、权限指令、导出、导入、树筛选、列显隐。 - 不要把强业务页(
business/**)重写成 hooks 标准页,除非明确要求。 - 不要为了“更整洁”大改用户已写好的复杂页面。
- 不要为可能的未来复用提前抽取大量组件、Hook 或配置层。
- 不要在同一文件混入两套风格。
- 不要自造偏离 UI 规范的面板/滚动结构(自造页头卡、筛选卡、自定义
calc(100vh - Npx)滚动容器等),不要引入规范色板之外的色系。