Files
tms-erp-web/.workbuddy/memory/MEMORY.md
T
gxwebsoft 10878d4aee feat(business-crud-page): 优化合同管理表单页为多分组卡片风格
- 为合同管理独立表单页设置 dialogCustomClass 以实现表单背景透明
- 按业务分区拆分表单列,分组渲染多张白底卡片,卡间距定为 12px
- 调整卡片边距由 16px 至 12px,优化视觉层次与空间感
- 统一多分组表单样式,标题区块采用主色蓝条突出显示
- 透明化弹窗内 Avue 表单背景,解决白卡叠白卡问题
- 兼顾表单底部按钮滚动展示,不同于弹窗底部浮动布局
2026-08-21 08:55:56 +08:00

12 KiB
Raw Blame History

项目长期记忆(tms-erp-web-ws / Saber3 客商模块)

设计约定(经用户确认的决策)

弹窗内多字段表单分组:使用全局 <section-card> 组件(项目统一标准)

整体结构

  • 整个弹窗 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> 标准组件)。

HTML 结构(每组三件套):

<section-card title="分组标题">
  <el-row :gutter="18">
    <el-col :span="8">...</el-col>
  </el-row>
  <!-- 同一组的所有 row 都放同一张白卡内 -->
</section-card>

适用:新增/编辑弹窗中字段超过 ~10 个、需按业务语义分组时。

简单分组(仅 3 项)

  • 联系信息等仅 3 项的分组,可用 el-col :span="8" 让三项均分一行,避免 4 列网格留白。

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:autofooter 固定底部)实现"取消/保存按钮浮动在底部"。因绝大多数页面是 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__bodymax-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__footerposition: 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 px1.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):
    :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 clickElement 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 三个页面自动获得该能力(改动一次组件即可)。

关键文件

  • 通用分组白卡组件: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> 组件化之前落地,可视为视觉参照)。

环境注意

  • Vite 代理跨域:Saber3 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 crudOptioncloneOption(组件内 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|editcontract-manage.vue 传 contract-form-page prop
  • 渲染组件PageAvueFormbusiness-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(不影响其他业务弹窗)。pageFormOption6205 行)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"条目)。