feat(business-crud-page): 统一独立表单页顶部页面标题样式及展示

- 独立表单页 shipping-template、transport-plan、waybill-manage 加入统一的 archive-page-form__title 页面标题支持
- 全局样式抽离 archive-page-form__title 至 element-ui.scss,实现统一样式和竖条修饰
- loading-manage/form 页面新增页标题展示,保持风格一致
- master-order 编辑模式下新增页面标题支持,动态显示编辑或新增总单
- business-crud-page 组件新增 formPageTitle 属性,支持父级传递页面标题数据
- 优化多个业务页面表格和弹窗样式,调整列宽及边框圆角阴影,提高视觉一致性
- 删除 customer-archive.vue 中重复本地标题样式定义,使用全局统一样式
- 全站弹窗分组组件 section-card 相关设计规范文档更新,强化统一卡片风格
- 移除 cargo-type.vue 添加 "新建" 按钮逻辑,调整按钮显示权限
- 统一所有新增弹窗的底部操作区浮动效果及内容区最大高度限制,提升交互体验
This commit is contained in:
2026-08-21 11:07:32 +08:00
parent 10878d4aee
commit 21b7877ff1
19 changed files with 485 additions and 189 deletions
+23
View File
@@ -35,3 +35,26 @@
- 合同表单无独立页面标题(page-header 区域在 breadcrumb 处,不在 main 标题区)
- 行为验证码:登录页拼图/旋转求解基础设施已搭好——`/tmp/captcha_solve.py`CORS 修复后支持 puzzle+rotate 双模式)、`/tmp/ab-install/node_modules/.bin/agent-browser --browser-channel chrome`。登录流程:访问 → 切"账号密码登录" → 点登录 → 检测 `.puzzle-stage`/`.rotate-image` → POST 至 http://127.0.0.1:8765 → 取 xDisp/angle → JS PointerEvent 模拟拖动 .track-handle 即可。
- 提交:63256a9 feat(business-crud-page): 合同管理表单页改为多分组卡片风格(1 file, +86/-2)。未提交改动(process-config.js、element-ui.scss、master-order-editor.vue、loading-manage.vue、project-apply.vue、formal-settlement.vue、customer-archive.vue)与本任务无关,未纳入。
## shipping-template 独立表单页加 archive-page-form__title(参考 customer-archive
- 需求:独立表单页 `/business/shipping-template/form?mode=add&name=新运费发货模板` 顶部缺页面标题,参考 customer-archive 的 `.archive-page-form__title` 加标题。
- 改动(单文件 `src/views/business/components/business-crud-page.vue`):
1. `PageAvueForm`5578 行)声明 `props.formPageTitle`render 时若非空则先 push `<div class="archive-page-form__title">{{ title }}</div>` 再渲染 avue-formtitle 在表单容器 `shipping-template-dialog` 内、avue-form 之前)。
2. 模板 `<component :is="crudContainer">` 新增 `:form-page-title="isStandaloneFormPage ? formPageTitle : undefined"`
3. 新增 computed `formPageTitle()``isStandaloneFormPage && isShippingTemplatePage` 时返回 `this.$route.query.name || '新增'/'编辑' + config.title`,否则 ''。
4. 样式(scoped 末尾,用 `:global` 因为 title div 由 PageAvueForm 函数式组件渲染、无 scoped 属性):`:global(.business-crud-page--form-page .archive-page-form__title)` 复制 customer-archive 的 18px/600/#303133 + margin-bottom:20px::before 为空,与参考一致,仅纯文字标题)。
- 仅作用于 shipping-template 独立表单页;其他独立表单页(waybill/transport-plan/contract`formPageTitle` 返回 '',不受影响。
## transport-plan 独立表单页也加 archive-page-form__title
- 需求:`/business/transport-plan/form?mode=add&name=新运费运输计划` 也要同样的页面标题。
- 改动:`formPageTitle()` 条件由 `isStandaloneFormPage && isShippingTemplatePage` 改为 `isStandaloneFormPage && (isShippingTemplatePage || isTransportPlanPage)`(复用已有 `isTransportPlanPage` computed`config.permission==='transport_plan'`)。标题文案取 `this.$route.query.name`(「新运费运输计划」)或回退「新增/编辑运输计划」。
- 验证:Vite 2889 编译通过(HTTP 200,无错误)。
- 验证:Vite 2889 编译通过(HTTP 200,无 transform/syntax 错误),HMR 生效。如需扩展到其他独立表单页,去掉 `isShippingTemplatePage` 限定即可。
## 将 .archive-page-form__title 全局化,并再加 waybill / loading-manage / master-order 三页
- 需求:把页面标题扩展到 `/business/waybill-manage/form``/business/loading-manage/form``/business/master-order?mode=editor` 三页,并与 customer-archive / shipping-template / transport-plan 视觉统一。
- **全局化样式**`.archive-page-form__title` 原本分散在 `customer-archive.vue`scoped)与 `business-crud-page.vue``:global`,::before 为空纯文字)。现统一提取到 `src/styles/element-ui.scss`5b 段):18px/600/#303133 + margin-bottom:20px + `::before` 4px 主色竖条 + 8px 间距(补全竖条,与 section-card 卡头风格一致)。并删除上述两处本地重复定义。
- **waybill-manage/form**:在 `business-crud-page.vue``formPageTitle()` 条件再并入 `isWaybillDetailLayout``config.permission==='waybill_manage'`),复用既有 computed。标题取 `query.name || '新增/编辑运单管理'`
- **loading-manage/form**:该路由直接映射到 `loading-manage.vue`(非 business-crud-page)。新增 computed `formPageTitle()``query.name || '新增/编辑配载管理'`),并在独立表单页的 `<component>` 内、body 之前加 `<div v-if="isStandaloneFormPage" class="archive-page-form__title">{{ formPageTitle }}</div>`
- **master-order?mode=editor**`master-order.vue` 渲染 `master-order-editor.vue`。将 `<master-order-editor>` 包进 `<template v-else-if="mode==='editor'">`,前置 `<div class="archive-page-form__title">{{ masterEditorTitle }}</div>`;新增 computed `masterEditorTitle``routeId ? '编辑总单' : '新增总单'`)。
- 验证:4 个 SFC + element-ui.scss 经 Vite 2889 编译均 HTTP 200、无 transform/syntax 错误。
+35 -82
View File
@@ -2,95 +2,48 @@
## 设计约定(经用户确认的决策)
### 弹窗内多字段表单分组:使用全局 `<section-card>` 组件(项目统一标准)
### 弹窗分组:统一用全局 `<section-card>` 组件
- 整弹窗 body 由 `src/styles/element-ui.scss` 全局 `.el-dialog__body { background:#f5f6fa; padding:16px }` 统一灰底;每组 = 一张白底卡片(4px 主色竖条 + 8px 间距卡头 + 白底 body,圆角 6px + 极淡阴影)。
- 组件已全局注册 `<section-card title="...">`,支持 `#title` / `#extra` slot。严禁再手写 `.xxx-form__block` + `.dialog-section-title` 拼凑卡片。
- 仅 3 项的小分组用 `el-col :span="8"` 均分一行。
**整体结构**
- 整个弹窗 body 由 `src/styles/element-ui.scss` 全局 `.el-dialog__body { background: #f5f6fa; padding: 16px }` 统一灰底(无需页面再覆盖)
- 每个业务分组 = 一张白底卡片。用全局组件 `<section-card title="分组名">...</section-card>`,自动渲染 4px 主色竖条 + 8px 间距的卡头与白底 body(圆角 6px + 极淡阴影),与 `loading-manage.vue` / `customer-archive.vue` 等已落地页面一致。
- 严禁再手写 `.xxx-form__block` + `.dialog-section-title` override 拼凑卡片(项目已有 `<section-card>` 标准组件)。
### Avue 内置弹窗内用 section-card:需 dialogCustomClass 透明化 avue-form
- 全站 `.el-dialog .avue-form { background:#fff; padding:8px 16px 4px }` 会把表单整体包白卡,内置弹窗放 section-card 会白卡叠白卡
- 解法:option 顶层加 `dialogCustomClass:'xxx-dialog'`,再在 element-ui.scss 写三条:
`.xxx-dialog .avue-form { background:transparent; box-shadow:none; padding:0 }`
`.xxx-dialog .avue-form__group > .el-col > .el-form-item { margin-bottom:0 }`(防 form-item margin 与卡距叠加)
`.xxx-dialog .avue-dialog__footer, .avue-crud__dialog .xxx-dialog .avue-dialog__footer { margin-top:0 !important }`(抵消全局 947 行 `-30px !important` 上移)。
**HTML 结构**(每组三件套):
```vue
<section-card title="分组标题">
<el-row :gutter="18">
<el-col :span="8">...</el-col>
</el-row>
<!-- 同一组的所有 row 都放同一张白卡内 -->
</section-card>
```
### 弹窗底部操作栏浮动(全站统一,全局 CSS 实现)
- `.el-dialog__body { max-height: calc(100vh - 200px); overflow-y:auto; overflow-x:hidden }``.el-dialog__footer { margin:0; padding:12px 20px; background:#fff; border-top:1px solid #ebeef5; box-shadow:0 -2px 8px rgba(0,0,0,.04) }`
- **易踩坑**Avue 内置弹窗 footer 真实 class 是 `.avue-dialog__footer`(非 `.el-dialog__footer`),且渲染在 `.el-dialog__body` 内、随 body 滚动滚出视口。要让它贴底须 `position:sticky; bottom:0; flex:none; background:#fff`(配合 body flex 列布局 + overflow-y:auto)。手写 el-dialog 的 `.el-dialog__footer` 在 body 外天然可见。
- **弹窗高度链**:① `.avue-dialog`(库内置)`max-height:calc(100% - 200px)` 控制整体上限;② `.avue-crud__dialog .el-dialog__body { max-height:calc(100vh - 200px); overflow-y:auto }`(451-457 行);③ 专项弹窗(shipping-template-dialog / transport-plan-dialog`.el-dialog` 自身 `margin-top:4vh; height/max-height:calc(100vh-4vh-24px); display:flex; flex-direction:column` + header flex:none / body flex:1 1 auto; min-height:0; overflow-y:auto; max-height:none!important / footer sticky。**改这类弹窗尺寸只改 section-card 周边规则,不要动 body 内 max-height。**
**适用**:新增/编辑弹窗中字段超过 ~10 个、需按业务语义分组时。
### 上传证件区域尺寸规范(transportCapacity 强制)
- 二代身份证 85.6×53.98mm → 宽:高 ≈ **1.586:1**;取 ~75% → 固定 **240×151px**el-col 内 `margin:0` 左对齐。
-`<image-upload-field large>` 适用(身份证/行驶证/道路运输证/登记本/船舶各证)。每页 scoped 覆盖 `.xxx-uploader--large``width:240px`)与 `--large .el-upload``width:100%; height:151px`);注意 `.xxx-uploader` 须写在 `.xxx-uploader--large` 之前(同特定性,源码顺序决胜)。
### 简单分组(仅 3 项
- 联系信息等仅 3 项的分组,可用 `el-col :span="8"` 让三项均分一行,避免 4 列网格留白
### 上传图片点击放大预览(全局组件 `image-upload-field`
- `src/components/image-upload-field/main.vue`:裸 `<img>` `<el-image :preview-src-list="[value]" fit="contain" preview-teleported :z-index="3000" @click.stop>`。图片 `@click.stop` 阻止冒泡到 el-upload(否则重传);"更换"经隐藏 `input.el-upload__input``.click()`el-upload 无 openFileDialog)。readonly 仅预览。driver/vehicle/ship 三页自动获得。z-index 必须 3000 + teleported 否则被 dialog 遮罩盖住
### Avue CRUD 内置弹窗内使用 section-card:需 dialogCustomClass 透明化 avue-form(重要
- 全站规则 `.el-dialog .avue-form { background:#fff; padding:8px 16px 4px }` 会把 Avue 弹窗表单整体包成一张白卡,内置弹窗里放 `<section-card>` 会白卡叠白卡、看不出分组间隔
- 解法(process-config 已落地):option 顶层加 `dialogCustomClass: 'xxx-dialog'`,再在 element-ui.scss 写三条规则:① `.xxx-dialog .avue-form { background:transparent; box-shadow:none; padding:0 }`;② `.xxx-dialog .avue-form__group > .el-col > .el-form-item { margin-bottom:0 }`(防 formslot 外层 form-item margin 与卡片 margin 叠加拉大卡距);③ `.xxx-dialog .avue-dialog__footer, .avue-crud__dialog .xxx-dialog .avue-dialog__footer { margin-top:0 !important }` 抵消全局 947 行 `-30px !important` 上移(否则 footer 压卡),双选择器兼顾 dialog 是否 teleport
### 弹窗底部操作栏浮动效果(全站统一规范)
- **需求来源**:用户要求参考 `business/project-apply`(手写 el-dialog + `#footer` slotbody `max-height:76vh;overflow-y:auto`footer 固定底部)实现"取消/保存按钮浮动在底部"。因绝大多数页面是 Avue CRUD 内置弹窗(无法用 `#footer` slot),统一用**全局 CSS** 实现,全站自动生效
- **实现位置**`src/styles/element-ui.scss`(在 `.el-dialog .el-form-item` 规则之后新增)。
- **规则**
- `.el-dialog__body { max-height: calc(100vh - 200px); overflow-y: auto; overflow-x: hidden; }` —— 内容超高时独立滚动,footer(位于 body 之外)始终可见。
- `.el-dialog__footer { margin:0; padding:12px 20px; background:#fff; border-top:1px solid var(--el-border-color-lighter,#ebeef5); box-shadow:0 -2px 8px rgba(0,0,0,.04); border-radius:0 0 var(--el-dialog-border-radius,4px); .el-button+.el-button{margin-left:8px} }` —— 视觉底栏(白底+上边框+轻阴影),与主内容区分。
- **注意**`.el-dialog__body``max-height` 会作用于所有弹窗(含纯展示/表格弹窗),无 footer 的弹窗不受影响;project-apply 自身 `.project-apply-dialog .el-dialog__body{76vh}` 特异性更高仍优先。如需让某弹窗豁免,加更具体 class 覆盖即可。
- **Avue 内置 CRUD 弹窗的 footer 真实 class(重要,易踩坑)**:Avue 内置弹窗的"提交/取消"按钮 class 是 `.avue-dialog__footer`**不是** `.el-dialog__footer`),且**渲染在 `.el-dialog__body` 内部**、随 body 滚动会滚出视口。要让它像手写 `#footer` 那样始终贴底,必须针对 `.avue-dialog__footer``position: sticky; bottom: 0; flex: none; background: #fff`(配合 body `flex` 列布局 + `overflow-y:auto`)。手写 `el-dialog` 的 footer 才是 `.el-dialog__footer` 且在 body 外、天然可见。典型落地见 `shipping-template-dialog` / `transport-plan-dialog` 两条全局规则。
- **弹窗高度链分层(重要,易混淆)**:
- `.avue-dialog { max-height: calc(100% - 200px); min-height: 176px; display: flex; flex-direction: column; ... }` —— 这是 **Avue 库自身内置**src/ 与 node_modules 源码都搜不到出处),作用在弹窗容器外层,控制整体高度上限(视口减去 200px 安全区);项目改不动根,只能用 `!important`/更高特异性覆盖。
- `.avue-crud__dialog .el-dialog__body { max-height: calc(100vh - 200px); overflow-y: auto; }` —— 见 `src/styles/element-ui.scss` 451-457 行,作用于 avue 内置 CRUD 弹窗 body,让内容超出时 body 内滚动(注意是 **100vh**,且仅作用于 `.avue-crud__dialog` 限定范围内)。
- 特定页面专项弹窗(如 `shipping-template-dialog` / `transport-plan-dialog`)的 `.el-dialog` 自身用 `margin-top:4vh !important; height/max-height: calc(100vh - 4vh - 24px) 或 72vh; display: flex; flex-direction: column` 重新分配高度,并配合 `.el-dialog__header { flex:none }` / `.el-dialog__body { flex:1 1 auto; min-height:0; overflow-y:auto; max-height:none !important }` / `.avue-dialog__footer { position:sticky; bottom:0; flex:none; background:#fff }` 三段 flex 分摊。**改 shipping-template 这类弹窗尺寸,只改这些 section-card 周边规则即可,不要动 body 内的 `max-height`**。
### 上传证件区域尺寸规范(transportCapacity 模块强制)
- **比例**:中国二代身份证标准尺寸 85.6 × 53.98 mm → 宽:高 = **1.586 : 1**(横放)
- **尺寸**:取真实身份证的 ~75% → 固定 **240 × 151 px**1.586:1 精确:151 × 1.586 ≈ 240
- **位置**:在 el-col 列内 `margin: 0` 左对齐显示(不再 `width: 100%` 占满整栏,也不再居中)
- **适用范围**:所有 `<image-upload-field large>` 上传区,包括身份证正反面、大头照、机动车行驶证主页正/反面/副页正/反面、道路运输证、机动车登记本、船舶所有权/安全环保/国籍/最低安全配员/光船租赁/营业运输 等所有 cert 图片
- **实现方式**:在每个页面 scoped 样式里覆盖 class-prefix 对应的 `--large` 选择器(`.driver-uploader--large` / `.vehicle-uploader--large` / `.ship-uploader--large`):
```scss
:deep(.xxx-uploader) { display: block; } // 不再 width:100%
:deep(.xxx-uploader--large) {
display: block;
width: 240px;
margin: 0;
}
:deep(.xxx-uploader--large .el-upload),
:deep(.xxx-uploader--large.el-upload) {
width: 100%;
height: 151px;
}
```
- **CSS 顺序要点**`.xxx-uploader`(特定性 0,0,1,0)必须写在 `.xxx-uploader--large`(同为 0,0,1,0)之前,源码位置决定覆盖生效。
### 上传图片点击放大预览(全局组件 `image-upload-field` 已实现)
- **组件**`src/components/image-upload-field/main.vue`。原裸 `<img>` 改为 `<el-image :preview-src-list="[value]" fit="contain" preview-teleported :z-index="3000" @click.stop>`。点击图片即全屏预览(滚轮缩放 / 旋转 / 下载),零额外依赖。
- **预览与上传分离**:图片 `@click.stop` 阻止冒泡到 `el-upload`(否则会触发重传);"更换"入口(form 模式图片下方 `el-link`、card 模式 head 的 `actionText`)通过 `el-upload` 内部隐藏 `input.el-upload__input` 的 `.click()` 触发文件框。
- **为什么用 DOM click**Element Plus `el-upload` 仅 expose `abort/submit/clearFiles/handleStart/handleRemove`**没有** `openFileDialog`,故用 `this.$refs.uploadRef.$el.querySelector('.el-upload__input').click()`。
- **readonly 模式**:仅渲染 `el-image` 预览,不显示上传入口(查看档案时也能放大看证件)。
- **z-index 注意**:务必 `preview-teleported`(预览层挂 body+ `:z-index="3000"`,否则会被 `el-dialog` 遮罩(2000 区间)盖住。
- **适用范围**:全仓通用组件,driver / vehicle / ship 三个页面自动获得该能力(改动一次组件即可)。
### 独立表单页标题 `archive-page-form__title`(全局统一,参考 customer-archive
- 样式已**全局化**到 `src/styles/element-ui.scss`5b 段):18px/600/#303133 + margin-bottom:20px + `::before` 4px 主色竖条 + 8px 间距。原 `customer-archive.vue`scoped)与 `business-crud-page.vue``:global`)两处本地重复定义已删除,改由全局规则统一驱动(全站任意页面用此 class 即得统一标题)
- **business-crud-page 系列**shipping-template / transport-plan / waybill-manage 独立表单页):`PageAvueForm`render `h('div',{class:dialogCustomClass},[titleDiv, avue-form])`)声明 `props.formPageTitle`,模板 `:form-page-title="isStandaloneFormPage ? formPageTitle : undefined"`computed `formPageTitle()``isStandaloneFormPage && (isShippingTemplatePage || isTransportPlanPage || isWaybillDetailLayout)` 时返回 `this.$route.query.name || '新增'/'编辑'+config.title``isWaybillDetailLayout` = `config.permission==='waybill_manage'`
- **loading-manage/form**:路由直接映射 `loading-manage.vue`(非 business-crud-page)。新增 computed `formPageTitle()``query.name || '新增/编辑配载管理'`),并在 `<component :is="isStandaloneFormPage?'div':'el-dialog'">` 内、body 前加 `<div v-if="isStandaloneFormPage" class="archive-page-form__title">`
- **master-order?mode=editor**`master-order.vue``<master-order-editor>` 包进 `<template v-else-if="mode==='editor'">`,前置 `<div class="archive-page-form__title">{{ masterEditorTitle }}</div>`computed `masterEditorTitle` = `routeId ? '编辑总单' : '新增总单'`)。
- 扩展原则:新增独立表单页要带此标题,要么并入 business-crud-page 的 `formPageTitle` 条件(business-crud-page 系列),要么在对应页面模板手写 `<div class="archive-page-form__title">`loading-manage / master-order 模式)
## 关键文件
- 通用分组白卡组件:`src/components/section-card/main.vue`(已全局注册为 `<section-card>`,支持 `title` / `#title` / `#extra` slot
- 弹窗灰底 body + section-card 全局样式:`src/styles/element-ui.scss``.el-dialog__body`、`.section-card { &__header / &__bar / &__title / &__extra / &__body }`
- 司机档案弹窗:`src/views/transportCapacity/driver.vue`(手写 `el-form` + `<section-card>` 4 大分组:基础身份信息 / 驾驶证信息 / 从业资格证信息 / 司机联系信息)。
- 车辆档案弹窗:`src/views/transportCapacity/vehicle.vue`(手写 `el-form` + `<section-card>` 2 大分组:基础车辆信息 / 车辆资质图片;6 张大图按身份证比例 240×151 左对齐)。
- 船舶档案弹窗:`src/views/transportCapacity/ship.vue`(手写 `el-form` + `<section-card>` 2 大分组:基础船舶信息 / 船舶证书信息;内部 6 张 cert 图片按身份证比例 240×151 左对齐;子证书分段用 `.cert-block` + border-top)。
- 客商档案弹窗:`src/views/vehicle/customer-archive.vue`(工商信息 / 联系信息 / 财务/信用信息 / 其他信息 等分组也用 `<section-card>`)。
- 项目补录弹窗:`src/views/business/project-apply.vue`(手写 `__grid` / `__table` / `__textarea-list` 同款白卡结构,先于 `<section-card>` 组件化之前落地,可视为视觉参照)。
- 分组白卡:`src/components/section-card/main.vue`
- 全局弹窗/section-card 样式:`src/styles/element-ui.scss`
- 客商档案弹窗:`src/views/vehicle/customer-archive.vue`(用 `archive-page-form__title` 作页面标题,`:deep(.archive-dialog .el-dialog__body){max-height:72vh}`)。
- 司机/车辆/船舶档案:`src/views/transportCapacity/{driver,vehicle,ship}.vue`(手写 el-form + section-card + 240×151 证件图)。
- 项目补录弹窗:`src/views/business/project-apply.vue`(手写 el-dialog + #footer 浮动,视觉参照)。
## 环境注意
- Vite 代理跨域:Saber3 axios 开 `withCredentials=true`,直连绝对地址后端须 CORS `Allow-Origin` 为具体域名(不能用 `*`),否则浏览器拒绝。改 `.env`/`vite.config.mjs` 必须重启 dev。
- Vite 代理跨域:axios `withCredentials=true`,直连绝对地址后端须 CORS `Allow-Origin` 为具体域名(不能用 `*`。改 `.env`/`vite.config.mjs` 必须重启 dev。
### business-crud-page 封装页面的弹窗高度改法(重要,复用模式)
- `src/views/business/components/business-crud-page.vue` 被 waybill-manage / transport-plan 等多个业务页共用,内部 `<avue-crud :option="option">`option 由传入 prop `crudOption` 经 `cloneOption`(组件内 6261 行,浅展开+重映射 column,**保留所有顶层属性**)克隆而来
- 因此给各业务页 `option` 顶层加 `dialogCustomClass: 'xxx-dialog'`,再在 `element-ui.scss` 写 `.xxx-dialog.el-dialog { height/max-height: calc(100vh - 40px) !important; ... }` 等规则,即可**只影响该页**、不波及其他共用页面(已验证 waybill-manage / transport-plan 走同一机制生效)
- 注意 avue-crud 弹窗带 `avue-crud__dialog` class,会被全局 `.avue-crud__dialog .avue-dialog__footer { margin-top: -30px !important }`element-ui.scss 863 行)影响,自定义 footer 规则需加 `margin-top: 0 !important` 抵消;纯 dialog-form 弹窗(如 shipping-template)无此 class 不受影响
### 合同管理独立表单页:多分组卡片(PageAvueForm 路径)
- **入口**`/business/contract-manage/form?mode=add|edit`contract-manage.vue 传 `contract-form-page` prop
- **渲染组件**`PageAvueForm`business-crud-page.vue 5577 行)—— `h('div', {class: option.dialogCustomClass || 'business-crud-form-page-dialog'}, [h('avue-form', ...)])`**不包 el-dialog**。底部按钮(form_menu)天然渲染在 form 内部、随内容滚动,**不会浮动贴底**(与 project-apply 的 el-dialog #footer 浮动有差异)。
- **7 个分区**option.column 用 order 分块):基本信息(200) / 合同文件(30) / 计费信息(10) / 结算单规则(-10) / 付款比例设置(-30) / 其它附件(-50) / 变更记录(-70)
- **多分组生成**`buildContractFormGroupOption(option)`6861 行)按 order 降序遍历,遇到 `*Title` 列就切片;第 1 组做 `column`,后 6 组做 `group: [{label:'', arrow:false, column: g}, ...]`。`groups.length<=1` 回退原 option(不影响其他业务弹窗)。`pageFormOption`6205 行)`isContractFormPage` 时调用。
- **样式**business-crud-page.vue scoped 末尾):`.contract-manage-form-dialog` 灰底 `#f5f6fa` + `padding:20px 24px 96px``.avue-form { background:transparent; padding:0 }``.avue-form__group` 和 `.avue-group` 统一白底 + 6px 圆角 + 轻阴影 + `margin-bottom:12px` + `padding:14px 16px 4px`。
- **不适用本模式**:其他业务弹窗(waybill/transport-plan/shipping-template)走 Avue 内置 CRUD 弹窗,弹窗内用 `<section-card>` 是另一套规则(见"Avue CRUD 内置弹窗内使用 section-card"条目)。
## business-crud-page 复用模式(被 waybill/transport-plan/shipping-template/contract 共用
- option 由 prop `crudOption``cloneOption` 克隆(保留所有顶层属性)。给各页 option 顶层加 `dialogCustomClass:'xxx-dialog'` + element-ui.scss `.xxx-dialog.el-dialog{height/max-height:calc(100vh - 40px)!important}` 可只影响该页
- avue-crud 弹窗带 `avue-crud__dialog` class,受全局 `.avue-crud__dialog .avue-dialog__footer{margin-top:-30px!important}` 影响,自定义 footer 须加 `margin-top:0!important` 抵消
- 独立表单页(contract-manage / shipping-template / waybill / transport-plan)走 `PageAvueForm`,不包 el-dialog,底部按钮随内容滚动不浮动。`pageFormOption`6205 行)负责装配。contract 用 `buildContractFormGroupOption`6861 行)按 order 分 7 组白卡