# 07 — 组件设计与交互反馈 > 参考:vue-vben-admin(Api 类命令式组件)、soybean-admin(hook 优先 + 组件薄壳)、vue-pure-admin(页面内聚五件套 + ReDialog)、ant-design-pro(trigger 弹窗 + ProTable 声明式) --- ## 一、组件分层与词汇表 ``` ┌─ 全局组件 src/components/ ──────────────────────┐ │ PageContainer 页面容器(页头/面包屑/slot) │ │ ListPage 分页列表(fetcher+筛选slot+表格) │ │ 权限组件 / 图标选择 / 主题切换 / 动态菜单 │ ├─ 域组件 views//components/ ─────────────┤ │ XxxFormDialog 表单弹窗 │ │ XxxDetail 详情抽屉 │ │ 单元格渲染器(状态 tag、操作按钮组) │ ├─ 页面骨架 views//.vue ────────────┤ │ 只做编排:组合上面两层,≤400 行 │ └─────────────────────────────────────────────────┘ ``` **页面骨架的行数上限是硬约束**(400 行)。pure 的同构落地是「页面内聚五件套」:`index.vue`(纯模板,只解构 hook)+ `form/index.vue`(弹窗表单)+ 左树/右表子组件 + `utils/{hook.tsx, rule.ts, types.ts}`(逻辑/校验/类型全在本域)——`index.vue` 零逻辑。 两种封装哲学(按团队选择,**不混用**): | 哲学 | 代表 | 做法 | |------|------|------| | schema 驱动重封装 | vben | FormApi/ModalApi 类 + schema 数据生成表单/弹层 | | hook 优先薄壳 | soybean | 逻辑进 UI 无关 hook(useTable/useFormRules),组件只是组件库薄壳 | --- ## 二、弹层设计:trigger 模式与命令式 Api ### 2.1 trigger 模式(adp,声明式首选) ```tsx // UpdateForm.tsx —— 调用方零状态 编辑} // 传入触发器 onFinished={async.value => { actionRef.current?.reload() }} /> // 内部 cloneElement(trigger, { onClick: onOpen }) 注入打开事件 // 表单本体 StepsForm + render 进 Modal(destroyOnHidden) ``` Vue 对应写法:dialog 组件接 `trigger` slot,或保持「页面持 visible + 子组件 emit close」的朴素方案(够用)。**关键是弹窗组件自包含表单逻辑**,页面只传初始数据和回调。 ### 2.2 命令式 Api 类(vben,复杂弹层) ```ts // useVbenModal 返回 [Modal, api];ModalApi 持有独立响应式 store const [Modal, modalApi] = useVbenModal({ connectedComponent: MyModal }) modalApi.open({ payload: { userId: 1 } }) // 命令式打开并传参 modalApi.onConfirm(async () => { ... }) // 生命周期钩子 ``` 适用:全局单例弹层(登录过期框、错误确认框——gva 的 ErrorPreview 是此模式的事件总线版)。普通 CRUD 弹窗**不必上这套**,props/emit 足矣。 Vue 侧最易落地的命令式弹窗是 pure 的 **ReDialog**:模块级 `dialogStore` + `addDialog({ title, contentRenderer, beforeSure: done => {...} })`,`done()` 关闭——无 provide/inject、无工厂,App.vue 挂载一次全局可用。表单弹窗若要从 props/emit 升级命令式,ReDialog 是阻力最小路线。 --- ## 三、表单范式:手写、配置驱动与 schema 驱动 | 范式 | 适用 | 代表 | 成本 | |------|------|------|------| | 手写 el-form + rules | 表单结构常变、交互复杂(联动/自定义校验) | 常见 Vue 工程 | 低门槛高纪律 | | hook 优先(不封表单组件) | 逻辑可测、UI 薄壳 | soybean `useFormRules`(`satisfies` 约束规则表) | 中 | | 表单 SFC + 弹窗 contentRenderer | 弹窗内容即组件 | pure `form/index.vue` | 低 | | 配置驱动 columns | 「一列三态」:同一份列配置生成表格列+搜索项+表单项 | adp ProTable(`valueType/valueEnum/hideInForm/formItemRender`) | 学习成本中,CRUD 提速极高 | | schema 驱动 FormApi | 表单完全由 schema 数据生成,动态增删字段 | vben useVbenForm(zod 校验、响应式 schema) | 高,适合平台型产品 | | RHF + zod | React 端 | react-hook-form + zod(schema 即校验源) | 中 | **表单通用规则(必须)**: 1. 提交按钮 loading 态(防双击重复提交) 2. 校验消息中文、具体(「请输入用户名」而非「username 不能为空」) 3. 编辑态回填后 reset 时机明确(打开时回填,关闭时重置) 4. 危险输入(密码)用 `show-password`;密钥类只写不读(placeholder 提示「留空则不修改」) --- ## 四、表格范式:列表组件是资产 ### 4.1 标准列表组件协议(强制走它) ``` 内部:「取消 + 版本号」双重防护(见 03 章)+ 分页三件套 ``` pure 的三层表格范式同构,可作演进对照:`@pureadmin/table`(全局表格组件,列用 tsx cellRenderer)+ `RePureTableBar`(刷新/列设置拖拽排序/密度/全屏工具条,作用域插槽透传 columns)+ 业务 `useUser()` hook(form/dataList/pagination/CRUD 全收编)——`index.vue` 只剩模板解构,**页面零逻辑**。 ### 4.2 表格规范 ``` 列设计 操作列 fixed right;状态列 tag 化;时间列统一格式化函数 宽表 超 6 列启用横向滚动而非挤压;关键列 min-width 而非 width 空数据 empty 占位 + 「新增第一条」引导(不是裸的「暂无数据」) 加载 表格 v-loading(不用全局遮罩) 批量 多选 + 批量操作按钮常驻 disabled(选中方可点,视觉自说明) 行操作 ≤3 个平铺;>3 个收进更多下拉 数字列 右对齐 + tabular-nums 密度 行高全站统一一档(default 48px 或 compact 40px,不混用) 图标 行内图标按钮 16px,与文字间距 6px,风格全站统一(见 06 章 4.4) ``` ### 4.3 详情复用列配置(adp 思路) ProTable 的 columns 同时喂给 `ProDescriptions`(详情抽屉)——**列定义是数据**,表格、搜索、详情三处消费同一份。Vue 端可低成本借鉴:列数组中带 `formatter/render` 的描述导出给详情组件。 --- ## 五、交互反馈分级(全站强制) ### 5.1 反馈决策表 | 场景 | 反馈 | 理由 | |------|------|------| | 操作成功(删除/保存) | ❌ 不弹框——界面已更新 | 降噪 | | 操作失败 | ✅ 错误提示(原因必带) | 用户要知情 | | 危险操作(删除/批量/不可逆) | ✅ 确认框(对象名进文案) | 防误操作 | | 恢复默认/静默保存 | ❌ 不弹框 | 降噪 | | 按钮已禁用 | ❌ 不弹框,视觉已说明 | 降噪 | | 长任务(>1s) | ✅ 按钮 loading;>3s 进度条 | 可感知 | | 登录/MFA 失败 | ✅ 表单内联错误(不 toast) | 上下文相关 | | 401 过期 | ✅ 单例弹窗/单次跳转 | 防并发轰炸 | ```js // ❌ 冗余 —— 成功也弹 await deleteMenu(id); ElMessage.success('删除成功') // ✅ 专业 —— 静默,界面即反馈 await deleteMenu(id); loadMenus() ``` ### 5.2 三态完整性检查清单 每个数据展示组件必须显式处理三态,缺一即 bug: ``` 加载态 skeleton(首屏)/ loading(刷新) 空态 empty + 引导动作 错误态 错误占位 + 重试按钮(不是 console.error 完事) ``` --- ## 六、Composables / Hooks 规范 ### 6.1 命名与分层 ``` composables/hooks 依赖业务上下文(store/请求层):usePagedQuery、useTable utils 纯函数无依赖:confirm、格式化函数 ``` 规则:**composable 内禁止直接操作 DOM**(返回响应式状态);一个 composable 只做一件事;返回值用对象而非数组(追加字段不破坏调用方)。 soybean 分层补充:框架无关的 hook(useTable 泛型四参)与 UI 适配 hook(useNaiveTable)分层——换 UI 库只动适配层。 ### 6.2 props 与事件命名规范 - props 用名词(`fetcher`、`initialData`、`permissions`),事件用动词(`@close`、`@submit`、`@success`) - 双向绑定统一 `v-model:xxx` 约定(如 `v-model:visible`),**禁止**拆成 `visible` + `visibleChange` 两个接口 - 布尔 props 必须显式声明默认值;透传型组件(ListPage)用 `attrs` 透传 UI 库 props,并在组件头注释列出可透传项 - **避免过度封装**:props + slots 能解决的扩展,不引入 render 函数 / schema;封装层数超过两层时先考虑是否该拆组件 --- ## 七、巨型单文件拆分实战 ### 7.1 典型场景:定时任务管理页(约 1400 行单文件) ``` views/cron/ ├── Cron.vue # ~120 行:页面容器 + 子组件编排 ├── components/ │ ├── TaskTable.vue # 任务列表(列定义 + 批量选择) │ ├── TaskFormDialog.vue # 新建/编辑任务(cron 表达式编辑器独立) │ ├── CronExprInput.vue # cron 表达式输入 + 下次执行时间预览 │ ├── ExecutionHistory.vue# 执行记录(独立 tab/抽屉) │ └── ExecutionLogDrawer.vue # 日志查看(自动刷新控制) └── composables/ └── useCronTasks.js # CRUD 编排(复用列表 hook) ``` ### 7.2 典型场景:监控大屏页(约 900 行单文件) ``` views/monitor/ ├── Monitor.vue # ~100 行:单机/集群切换 + 布局 ├── components/ │ ├── MetricCard.vue # 指标卡(CPU/内存/磁盘/网络 通用) │ ├── TrendChart.vue # 趋势图(echarts 按需引入收口在此) │ └── HostSelector.vue # 主机选择 └── composables/ └── useMetricsStream.js # WebSocket 连接/重连退避/卸载断开 ``` 拆分判据:**一个文件出现 ≥3 个「功能岛」**(各自的数据/状态/UI 块)即拆;拆出的单元可在测试里独立渲染。组织法参照 pure 页面内聚模式(index 纯模板 + form/ 子组件 + utils/hook 收编逻辑)。 --- ## 八、最佳实践清单 ✅ **要做** 1. 新列表页一律「页面容器 + 列表组件」起步(模板见 05 章) 2. 表单弹窗独立组件,页面只传数据与回调 3. 每个数据组件三态齐全(加载/空/错误) 4. composable 返回对象、单职责、无直接 DOM 操作 5. 组件 props 加类型与默认值;emit 声明 events ❌ **不要** 1. 不要在页面里写第二个 ``(复用列配置或拆子组件) 2. 不要跨层 emit 链超过两级(改用 provide/inject 或提升状态) 3. 不要在组件内散落成功类 toast(见反馈决策表) 4. 不要用 index 作 v-for key(列表刷新错位之源) 5. 不要复制粘贴超过两次的 UI 块不抽组件(三次法则) --- ## 九、AI 检查清单(对比目标工程逐项核对) **P0** 1. 超过 400 行的视图文件全部列出,按第七节方案给出拆分目录树 2. 成功类 toast 违反反馈决策表的调用点列出(应删) 3. v-for 是否有用 index 作 key 的列表(存在动态增删即不合格) **P1** 4. 列表页是否统一走列表组件协议?手写分页/裸请求的页面列出迁移清单 5. 表单弹窗自查三件套:提交 loading、校验消息中文、关闭重置 6. 数据组件三态是否齐全(抽检空态与错误态渲染) 7. 权限控制是否逐按钮挂载(对照权限码清单) **P2** 8. 是否值得引入列配置「一列三态」复用(adp 思路) 9. 弹窗是否需要升级命令式(全局单例场景用 ReDialog/ModalApi) --- > 下一篇:[08-工程化与质量保障](08-工程化与质量保障.md) —— 让规范自动执行。