# 项目长期记忆(tms-erp-web-ws / Saber3 客商模块) ## 设计约定(经用户确认的决策) ### 弹窗内多字段表单分组:使用全局 `` 组件(项目统一标准) **整体结构**: - 整个弹窗 body 由 `src/styles/element-ui.scss` 全局 `.el-dialog__body { background: #f5f6fa; padding: 16px }` 统一灰底(无需页面再覆盖)。 - 每个业务分组 = 一张白底卡片。用全局组件 `...`,自动渲染 4px 主色竖条 + 8px 间距的卡头与白底 body(圆角 6px + 极淡阴影),与 `loading-manage.vue` / `customer-archive.vue` 等已落地页面一致。 - 严禁再手写 `.xxx-form__block` + `.dialog-section-title` override 拼凑卡片(项目已有 `` 标准组件)。 **HTML 结构**(每组三件套): ```vue ... ``` **适用**:新增/编辑弹窗中字段超过 ~10 个、需按业务语义分组时。 ### 简单分组(仅 3 项) - 联系信息等仅 3 项的分组,可用 `el-col :span="8"` 让三项均分一行,避免 4 列网格留白。 ### 弹窗底部操作栏浮动效果(全站统一规范) - **需求来源**:用户要求参考 `business/project-apply`(手写 el-dialog + `#footer` slot,body `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%` 占满整栏,也不再居中) - **适用范围**:所有 `` 上传区,包括身份证正反面、大头照、机动车行驶证主页正/反面/副页正/反面、道路运输证、机动车登记本、船舶所有权/安全环保/国籍/最低安全配员/光船租赁/营业运输 等所有 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`。原裸 `` 改为 ``。点击图片即全屏预览(滚轮缩放 / 旋转 / 下载),零额外依赖。 - **预览与上传分离**:图片 `@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 三个页面自动获得该能力(改动一次组件即可)。 ## 关键文件 - 通用分组白卡组件:`src/components/section-card/main.vue`(已全局注册为 ``,支持 `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` + `` 4 大分组:基础身份信息 / 驾驶证信息 / 从业资格证信息 / 司机联系信息)。 - 车辆档案弹窗:`src/views/transportCapacity/vehicle.vue`(手写 `el-form` + `` 2 大分组:基础车辆信息 / 车辆资质图片;6 张大图按身份证比例 240×151 左对齐)。 - 船舶档案弹窗:`src/views/transportCapacity/ship.vue`(手写 `el-form` + `` 2 大分组:基础船舶信息 / 船舶证书信息;内部 6 张 cert 图片按身份证比例 240×151 左对齐;子证书分段用 `.cert-block` + border-top)。 - 客商档案弹窗:`src/views/vehicle/customer-archive.vue`(工商信息 / 联系信息 / 财务/信用信息 / 其他信息 等分组也用 ``)。 - 项目补录弹窗:`src/views/business/project-apply.vue`(手写 `__grid` / `__table` / `__textarea-list` 同款白卡结构,先于 `` 组件化之前落地,可视为视觉参照)。 ## 环境注意 - 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 等多个业务页共用,内部 ``,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 不受影响。