diff --git a/.workbuddy/memory/2026-08-06.md b/.workbuddy/memory/2026-08-06.md index 25737b8..8212325 100644 --- a/.workbuddy/memory/2026-08-06.md +++ b/.workbuddy/memory/2026-08-06.md @@ -23,3 +23,85 @@ - 客商材料、变更记录两个非表单分区也改用 ``,其中客商材料的操作按钮通过 `#extra` 插槽放在卡头右侧。 - 清理了失效的 `.archive-form__group`、`.customer-material-section`、`.change-record-section`、`.section-title` 等 scoped 样式。 - 验证:`@vue/compiler-sfc` 编译 `customer-archive.vue` OK;sass 编译 `element-ui.scss` OK;section-card 标签开合平衡。 + +## 继续推广:transportCapacity/driver.vue 弹窗 +- 用户反馈司机管理页面(`/transportCapacity/driver`)新增/编辑弹窗的 UI 与「重大项目补录」等不一致,仍是红色竖条 + 字段直接落在灰底上的扁平样式,要求统一为「灰底弹窗 + 白底分组卡片」。 +- 改造 `src/views/transportCapacity/driver.vue`: + - 4 个分组(基础身份信息 / 驾驶证信息 / 从业资格证信息 / 司机联系信息)由原 `
` + 扁平 `` 改为标准 `` 包裹。 + - 删除手写的 `.section-title`(红色竖条)、`.driver-form__block`(白底分组卡)、自定义 `.driver-dialog { :deep(.el-dialog__body) { background: #f0f2f5; ... } }` ——以上视觉均由 `src/styles/element-ui.scss` 的全局 `.el-dialog__body { background: #f5f6fa; padding: 16px; }` + `.section-card { ... }` 自动覆盖。 + - 弹窗内 4 个分组的二级 row 网格不变(3列/4列 12栏布局),由每张白卡的 body 承载。 +- 教训:第一次改动按 `project-apply.vue` 的手写方式做了一版(手写 `dialog-section-title` override + `__block`),与已落的「全项目通用标准」重复。**今后新页面/新弹窗直接用 ``,不要再手写卡片样式**。 + +## 调整 `/base/fee-item` 弹窗左右表单项间距 +- 用户反馈费用项弹窗中「费用类型」与「费用项代码」左右列太挤。 +- 修改 `src/views/base/fee-item.vue` 的 scoped 样式:将弹窗内 `.avue-form__row` / `.el-row` 的负边距与 `.el-col` 的水平 padding 从 `36px` 加大到 `48px`,等效 gutter 由 72px 增至 96px。 +- 该样式限定在 `.avue-crud__dialog` 内,仅影响新增/编辑弹窗,不影响列表搜索栏。 + +## 调整 `/vehicle/violation-record` 弹窗两栏横向间距 +- 用户反馈违章记录新增弹窗左右两列表单项间距太小,列与列之间距离过近。 +- 修改 `src/views/vehicle/violation-record.vue` 的 scoped 样式:限定在 `.el-dialog__body .avue-form` 内,将 `.el-row` 负边距与 `.el-col` 水平 padding 均设为 `24px`,等效 gutter 由默认 `20px` 增至 `48px`。 +- 仅作用于弹窗内的 Avue 表单,不影响列表搜索栏;保持表单项纵向间距不变。 + +## 将 `/business/process-config` 弹窗改为 section-card 卡片式布局 +- 用户反馈过程配置新增弹窗需将「基本信息」「现行过程节点」「节点设置」等分组改为白卡(参考重大项目补录)。 +- 改造 `src/views/business/process-config.vue` + `src/option/business/process-config.js`: + - 取消本页 `.avue-crud__dialog .avue-form` 的全局自动白卡(避免白底套白底),改为灰底上直接叠 ``。 + - `basicInfo-form` 用 `` 包裹;将原独立 Avue 字段 `includedNodes` 改为 formslot 并移入该卡片内部,用 `el-select` multiple 自定义渲染,保留 `searchLabel` 使搜索栏不受影响。 + - `nodeConfigJson-form` 拆分为 `` 和 `` 两张白卡。 + - 新增 `includedNodeValues` 计算属性,实现下拉框与 `nodeRows.enabled` / `form.includedNodes` 的双向同步。 + - 添加 `.process-config-page__field-label--required` 样式给必填项标红 `*`。 + - 通过 scoped 覆盖消除 formslot 外层 `.el-form-item` 的额外 `margin-bottom`,由 section-card 自身控制卡片间距。 +- 验证:`@vue/compiler-sfc` 编译 `process-config.vue` template/script OK;sass 编译其 scoped style OK;section-card 标签开合平衡。 + +## 修正 process-config 弹窗「卡片看不见」的真正根因(重要坑) +- 现象:用 `` 包了基本信息/现行过程节点/节点设置三张白卡,但弹窗里看不出卡片,用户反馈"还是不对"。 +- 排查发现:之前方向的"灰底缺失"是错的——全局 `.el-dialog__body { background:#f5f6fa }` 与 `.el-dialog .avue-form { 白卡 }` 早就定义在 `src/styles/element-ui.scss`(216/222 行),灰底一直有。 +- **真正根因**:Avue CRUD 弹窗默认 `append-to-body:true`,DOM 挂在 `` 下、在组件模板之外。组件 scoped 样式里的 `:deep(.avue-crud__dialog .avue-form)` 编译为 `[data-v-x] .avue-form`,要求祖先带 scoped 属性,而 append-to-body 弹窗没有该祖先 → 规则不命中 → 全局 `.el-dialog .avue-form` 白卡未被取消 → 白卡套白卡,section-card 被白色表单卡盖住看不见。 +- 修正:作用于 append-to-body 弹窗的样式必须用 `:global(...)`(完全全局、不依赖 scoped 祖先)。给 option 加 `dialogCustomClass: 'process-config-dialog'`(`dialogCustomClass` 由 Avue 直接挂在 `.el-dialog` 根元素上),把所有弹窗内覆盖规则改为 `:global(.process-config-dialog .avue-form)` + `!important`(取消全局 `.el-dialog .avue-form` 白卡,露出灰底),并清零 formslot 的 `.el-form-item`/`.el-col`/`.el-form-item__content`/`.avue-form__row` 占位与内边距让卡片满宽贴边。列表内的 `:deep(.el-table)` 仍有效(列表在组件 DOM 内),弹窗内表格需用 `:global(.process-config-dialog .el-table)`。 +- **二级坑(本次才真正修对)**:DOM 路径里**不要写 `.avue-crud__dialog`**。该 class 只存在于 avue 默认 `index.css`,而 Avue 组件 JS(dialog-form3.mjs)实际只把 `dialogCustomClass` 加到 `.el-dialog` 根、内部是 `.el-dialog__body > .avue-form`;`.avue-crud__dialog` 在运行时根本不渲染。之前写 `.process-config-dialog .avue-crud__dialog .avue-form` 整体不命中,白卡一直没被取消 → 卡片照样看不见。正确路径:`dialogCustomClass`(=`.el-dialog`) 直接作祖先,下接 `.avue-form`/`.el-form-item`/`.el-col` 等。 +- **三级坑**:`includedNodes` 列虽已改 formslot 且在 basicInfo 卡片内手动渲染,但它没有关掉表单显示,Avue 仍会为其渲染一个**空的 `.el-form-item`**(无对应 `#includedNodes-form` 槽),在「基本信息」与「节点设置」卡片间留下空隙。修正:给 `includedNodes` 加 `addDisplay:false, editDisplay:false`(保留 `search:true`+`searchLabel` 不影响搜索栏)。 +- 教训(可复用):**凡要给 Avue/Element 的 append-to-body 弹窗(el-dialog 默认 append-to-body)写 scoped 覆盖样式,一律用 `:global()`,`:deep()` 在弹窗 DOM 上不生效;且选择器祖先只用 `dialogCustomClass`(挂在 `.el-dialog`),不要误加 `.avue-crud__dialog`(运行时不存在)。** 之前 vehicle/customer-archive、loading-manage 等自定义弹窗若也用 append-to-body,弹窗内 `:deep` 覆盖同样可能失效(它们当时多靠全局 `.el-dialog .avue-form`/`.section-card` 兜底,未暴露)。 + +## 继续推广:transportCapacity 三页面弹窗 + 上传区整改 +- 用户确认 `/transportCapacity/driver` 改造效果正确,并要求: + - `/transportCapacity/vehicle`、`/transportCapacity/ship` 两个页面的弹窗也按同样 `` 标准整改。 + - 上传证件区域整改为:① 宽高比 = 中国二代身份证 1.586:1(85.6 × 53.98 mm);② 区域大小取真实身份证的 50%,固定 165 × 104 px;③ 适用范围 = **全部**上传区(含机动车行驶证主页正/反/副页正/反、道路运输证、机动车登记本、船舶所有权/安全环保/国籍/最低安全配员/光船租赁/营业运输 共 6 张 + 行驶证 4 张),居中显示不再 100% 占栏。 + +- 已落地:`src/views/transportCapacity/driver.vue` + `vehicle.vue` + `ship.vue` 三页面: + - vehicle.vue: 2 个分组 `
` 替换为 ``;删除 `.section-title`、`.section-title:first-child` scoped 规则。 + - ship.vue: 2 个分组替换为 ``;6 个子证书仍保留 `.cert-block` + `.cert-block__title` 做内部子分段(外层白卡 + 内层 1px border-top 分隔线,视觉层次合理);删除 `.section-title`、`.section-title--wide`、`.section-title:first-child` scoped 规则。 + - driver.vue: 之前已用 `` 完成,本次仅收尾上传区尺寸。 + - 三页面的 `uploader` 样式统一改造:`.xxx-uploader` 取消强制 width:100%;新增 `.xxx-uploader--large { display:block; width:165px; margin:0 auto }` 与 `.xxx-uploader--large .el-upload { width:100%; height:104px }`(`--large` 选择器特定性 0,0,1,0 写在最后覆盖默认规则,源码位置在 `.xxx-uploader` 之后,确保 CSS 顺序覆盖生效)。 + - 保留每个 class-prefix 的非 large 默认高度(170px)、placeholder 边框/背景/居中等样式。 + +- 验证:`@vue/compiler-sfc` 编译三个 .vue 均 OK;`` 标签数量 driver 4/4、vehicle 2/2、ship 2/2 平衡;vite dev 服务对 `/transportCapacity/{driver,vehicle,ship}` 均返回 200;prettier 通过。 + +- 沉淀到 MEMORY.md 的设计约定:「上传证件区域 = `aspect-ratio: 1.586` + 固定 165 × 104 px + 居中」为全 transportCapacity 模块统一的硬规范。后续若其他模块也使用 `` 应套用同款规范。 + +## 上调 transportCapacity 三页面上传证件区域尺寸(165×104 → 240×151,并左对齐) +- 用户反馈上一轮的 165×104(真实身份证 50%)偏小、看不清楚。 +- 三页面(`driver/vehicle/ship.vue`)的 `.xxx-uploader--large` 尺寸统一上调到 **240 × 151 px**(约真实身份证 75%,保持 1.586:1),对齐方式由 `margin: 0 auto` 居中改为 `margin: 0` 左对齐。 +- 仅改三页面 scoped 样式里的 `width / height / margin` 三处数值,未动模板与组件逻辑;非 large 默认高度 170px 等样式保留。 +- 同步更新 MEMORY.md「上传证件区域尺寸规范」为 240×151 + 左对齐。 + +## 规划:将 process-config.vue 弹窗布局对标 project-apply.vue(尚未落地) +- 用户要求参考 `/business/project-apply.vue` 重构 `process-config.vue`,**主要是 UI 布局**。 +- 现状回顾:process-config 弹窗卡片**已用 `` 包裹**(basicInfo / 现行过程节点 / 节点设置),但弹窗机制仍是 **Avue `avue-crud` 表单槽(`#basicInfo-form`/`#nodeConfigJson-form`)+ `before-open`/`row-save`/`row-update`**;字段用 `el-row/el-col :span=12` + 手写 `.process-config-page__field`(flex 左标签右控件)。 +- 目标对标 project-apply:① 列表只做表格/搜索,新增/编辑/查看改为**自定义 `el-dialog` + 手写 `el-form`**(去掉 Avue 表单槽与 before-open/row-save/row-update);② 字段布局从 `el-row/el-col + 手写 label` 改为 **CSS 两列网格 `.__grid`(column-gap 72px)+ 原生 `el-form-item` label(right/auto)**;③ 卡片继续用 ``(项目统一标准,不回头手写白卡);④ 底部「取消/提交」(查看态隐藏提交)。 +- 逻辑(NODE_TEMPLATES、parseNodeConfig、validateRow、loadProjectOptions、includedNodeValues 计算属性)全部保留,仅把 Avue 钩子改为自定义 `openConfigDialog/submitConfig`;`api.submit/copy/enable/disable/remove/getDetail` 不变。 +- 待确认决策:卡片实现用 ``(推荐,符合项目标准)还是 1:1 还原 project-apply 的手写白卡(与项目标准不一致)。用户仅要求先规划,未授权落地。 + +## 已落地:process-config.vue 弹窗重构为自定义 el-dialog(对标 project-apply,卡片用 section-card) +- 已按方案执行:`src/views/business/process-config.vue` 改为「列表 avue-crud(仅表格/搜索)+ 自定义 el-dialog 新增/编辑/查看」结构。 +- 关键改造:① 删除 `@before-open/@row-save/@row-update` 与 `#basicInfo-form`/`#nodeConfigJson-form` 表单槽;② 新增 `el-dialog.process-config-dialog`(1440px / top4vh / append-to-body / destroy-on-close)+ 手写 `el-form.ref=configForm`(label right/auto)+ 两列 CSS 网格 `.process-config-form__grid`(column-gap 72px);③ 三张白卡仍用 ``(基本信息 / 现行过程节点 / 节点设置);④ 底部「取消 / 提交」(查看态隐藏提交)。 +- 逻辑保留:NODE_TEMPLATES、parseNodeConfig、validateRow、loadProjectOptions、includedNodeValues 计算属性、节点表格/时间轴渲染均不变;仅把 Avue 钩子换成 `openConfigDialog/resetConfigDialog/submitConfig`,新增 `formRules` + `configDialogTitle` + `handleValidateFail`。 +- 样式:删除原 `:global(.process-config-dialog .avue-form …)` 及其 `.el-form-item/.el-col/.avue-form__row` 覆盖(不再用 Avue 表单);新增 `:global(.process-config-dialog .el-dialog__body){ max-height:76vh; overflow-y:auto }`(灰底 16px 已由全局规则提供);`.process-config-form` 作用域样式(grid/label min-width160/节点表头 #f5f7fa/偶数行 #fafafa/timeline/require-*)。 +- 注意:自定义 el-dialog 是 `append-to-body`,作用于弹窗的覆盖样式仍须 `:global()`(`.el-dialog__body`);`.process-config-form` 及其 `:deep()` 因内容由本组件渲染、带 scoped 属性可命中。 +- 验证:`@vue/compiler-sfc` 编译 template+script OK、scoped style 括号配平 OK;无 beforeOpen/rowSave/forms lot 残留引用。 + +## 全局组件 image-upload-field 增加点击放大预览 +- 用户要求证件图片可点击放大查看。确认方案:用 Element Plus 原生 el-image(preview-src-list)+ 改全局组件一次、driver/vehicle/ship 三页自动生效。 +- 改造 `src/components/image-upload-field/main.vue`:① 两模式(form / card)的 `` 都换成 `el-image`(preview-src-list=[value], fit=contain, preview-teleported, z-index=3000, @click.stop);② form 模式图片下方加 `el-link`「更换」、card 模式 head 的 actionText 改 `el-link` 可点;③ 新增 `triggerUpload()` 用 `el-upload` 隐藏 `input.el-upload__input` 的 `.click()` 触发文件框(el-upload 仅 expose abort/submit/clearFiles/handleStart/handleRemove,无 openFileDialog,须 DOM 方式);④ `@click.stop` 阻止图片点击冒泡到 el-upload 误触发重传;⑤ readonly 时仅预览、无上传入口。 +- 兼容性:三页 scoped 样式同时写了 `.xxx-uploader .el-upload`(后代)与 `.xxx-uploader.el-upload`(同元素)两种选择器,把 `.xxx-uploader` 从 el-upload 移到外层 div 后高度样式仍生效;`.xxx-uploader--large` 同理。 +- 验证:`@vue/compiler-sfc` 解析 main.vue + 三页均 OK;vite dev 对三页均返回 200;确认 el-upload 内部隐藏 input 类名为 `.el-upload__input`(源码 `ns.e('input')`)。 + diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 188f057..1f718fc 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -1,16 +1,66 @@ # 项目长期记忆(tms-erp-web-ws / Saber3 客商模块) ## 设计约定(经用户确认的决策) -- 弹窗内多字段表单分组:**「灰色分组块 + 白色内层卡片」双色方案**(用户最终确认)。 - - 外层 `.archive-form__group`:灰底 `#f0f2f5` + 8px 圆角 + `16px 20px` 内边距,分组标题(`.dialog-section-title`,自带 4px 主色竖条)浮于灰底上。 - - 内层字段区:直接把分组内唯一的 `> .el-row` 设为白底(`#fff` + 6px 圆角 + 极淡阴影 `0 1px 2px rgba(0,0,0,.04)` + `18px 16px 4px` 内边距),形成「白卡片浮于灰底」的层次,而非逐个输入框强制白底。 - - 理由:契合 AGENTS.md「去卡片化」哲学(不嵌套 `el-card`),同时用户明确要灰底+白内容对比,全局 `.dialog-section-title` 直接复用即可。 - - 适用:新增/编辑弹窗中字段超过 ~10 个、需按业务语义分组时。 + +### 弹窗内多字段表单分组:使用全局 `` 组件(项目统一标准) + +**整体结构**: +- 整个弹窗 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 列网格留白。 +### 上传证件区域尺寸规范(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/views/vehicle/customer-archive.vue`(手写 `el-form`,非 Avue option 驱动;分组结构见 `.archive-form__group`)。 -- 全局弹窗分区标题规范:`src/styles/element-ui.scss` 的 `.dialog-section-title`。 +- 通用分组白卡组件:`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。 diff --git a/src/components/image-upload-field/main.vue b/src/components/image-upload-field/main.vue index 802c128..cb01185 100644 --- a/src/components/image-upload-field/main.vue +++ b/src/components/image-upload-field/main.vue @@ -1,28 +1,57 @@ - - - - @@ -392,6 +420,7 @@ import * as api from '@/api/business/process-config'; import { getList as getProjectList } from '@/api/business/project-apply'; import { exportBlob } from '@/api/common'; import { config, option } from '@/option/business/process-config'; +import { nodeOptions } from '@/option/business/common'; import { getToken } from '@/utils/auth'; import { downloadXls } from '@/utils/util'; import { mapGetters } from 'vuex'; @@ -488,6 +517,41 @@ export default { total: 0, }, selectionList: [], + configBox: false, + dialogType: 'add', + submitLoading: false, + formRules: { + configName: [ + { required: true, message: '请输入配置名称', trigger: 'blur' }, + { max: 100, message: '配置名称不能超过100个字符', trigger: 'blur' }, + ], + projectNames: [{ required: true, message: '请选择项目', trigger: 'change' }], + defaultFinishDays: [ + { + validator: (rule, value, callback) => { + if (value && !/^[1-9]\d{0,2}$/.test(String(value))) { + callback(new Error('请输入1-999的正整数')); + } else { + callback(); + } + }, + trigger: 'blur', + }, + ], + remark: [{ max: 500, message: '备注不能超过500个字符', trigger: 'blur' }], + includedNodes: [ + { + validator: (rule, value, callback) => { + if (!this.includedNodeValues || !this.includedNodeValues.length) { + callback(new Error('请至少选择一个过程节点')); + } else { + callback(); + } + }, + trigger: 'change', + }, + ], + }, }; }, computed: { @@ -497,6 +561,23 @@ export default { addBtn: this.hasPermission('process_config_add'), }; }, + includedNodeValues: { + get() { + return String(this.form.includedNodes || '') + .split(',') + .map(item => item.trim()) + .filter(Boolean); + }, + set(value) { + const selected = value || []; + this.form.includedNodes = selected.join(','); + const selectedNames = new Set(selected); + this.nodeRows.forEach(node => { + node.enabled = selectedNames.has(node.name); + }); + this.syncNodeFields(); + }, + }, isAdmin() { const authority = this.userInfo && this.userInfo.authority; return Array.isArray(authority) @@ -509,6 +590,10 @@ export default { enabledNodes() { return this.nodeRows.filter(item => item.enabled); }, + configDialogTitle() { + const text = { add: '新增', edit: '编辑', view: '查看' }[this.dialogType] || '新增'; + return `${text}过程配置`; + }, }, created() { this.resetNodeRows(); @@ -769,48 +854,75 @@ export default { } return true; }, - stopSubmitLoading(loading) { - if (typeof loading === 'function') { - loading(); - } - }, - rowSave(row, done, loading) { - this.syncNodeFields(); - const submitRow = this.normalizeRow(row); - if (!this.validateRow(submitRow)) { - this.stopSubmitLoading(loading); + openConfigDialog(type, row = {}) { + this.dialogType = type; + this.dialogReadonly = type === 'view'; + this.configBox = true; + if (type === 'add') { + this.form = { + configName: '', + projectIds: '', + projectNames: '', + defaultFinishDays: '', + remark: '', + status: 0, + }; + this.selectedProjectId = ''; + this.resetNodeRows(); return; } - api.submit(submitRow).then( - () => { - this.onLoad(this.page); - this.$message({ type: 'success', message: '操作成功!' }); - done(); - }, - error => { - window.console.log(error); - this.stopSubmitLoading(loading); - } - ); - }, - rowUpdate(row, index, done, loading) { - this.syncNodeFields(); - const submitRow = this.normalizeRow(row); - if (!this.validateRow(submitRow)) { - this.stopSubmitLoading(loading); - return; + if (['edit', 'view'].includes(type)) { + api.getDetail(row.id).then(res => { + this.form = res.data.data || {}; + this.selectedProjectId = this.form.projectIds ? String(this.form.projectIds) : ''; + this.ensureSelectedProjectOption(); + this.parseNodeConfig(this.form); + }); } - api.submit(submitRow).then( - () => { - this.onLoad(this.page); - this.$message({ type: 'success', message: '操作成功!' }); - done(); - }, - error => { - window.console.log(error); - this.stopSubmitLoading(loading); + }, + resetConfigDialog() { + this.$refs.configForm?.clearValidate(); + this.form = {}; + this.selectedProjectId = ''; + this.resetNodeRows(); + }, + handleValidateFail() { + this.$message.warning('请完善表单必填项后再提交'); + this.$nextTick(() => { + const field = this.$el.querySelector('.is-error'); + if (field && typeof field.scrollIntoView === 'function') { + field.scrollIntoView({ behavior: 'smooth', block: 'center' }); } - ); + }); + }, + submitConfig() { + this.$refs.configForm.validate(valid => { + if (!valid) { + this.handleValidateFail(); + return; + } + this.syncNodeFields(); + const submitRow = this.normalizeRow(this.form); + if (!this.validateRow(submitRow)) { + return; + } + this.submitLoading = true; + api + .submit(submitRow) + .then( + () => { + this.$message({ type: 'success', message: '操作成功!' }); + this.configBox = false; + this.onLoad(this.page); + }, + error => { + window.console.log(error); + } + ) + .finally(() => { + this.submitLoading = false; + }); + }); }, rowDel(row) { this.$confirm('确定将选择数据删除?', { @@ -854,34 +966,6 @@ export default { } this.selectionClear(); }, - beforeOpen(done, type) { - this.dialogReadonly = type === 'view'; - if (type === 'add') { - this.form = { - configName: '', - projectIds: '', - projectNames: '', - defaultFinishDays: '', - remark: '', - status: 0, - }; - this.selectedProjectId = ''; - this.resetNodeRows(); - done(); - return; - } - if (['edit', 'view'].includes(type)) { - api.getDetail(this.form.id).then(res => { - this.form = res.data.data || {}; - this.selectedProjectId = this.form.projectIds ? String(this.form.projectIds) : ''; - this.ensureSelectedProjectOption(); - this.parseNodeConfig(this.form); - done(); - }); - return; - } - done(); - }, searchReset() { this.query = {}; this.page.currentPage = 1; @@ -986,24 +1070,44 @@ export default { diff --git a/src/views/transportCapacity/driver.vue b/src/views/transportCapacity/driver.vue index 656de4a..6a3f633 100644 --- a/src/views/transportCapacity/driver.vue +++ b/src/views/transportCapacity/driver.vue @@ -92,21 +92,13 @@ @@ -129,349 +121,361 @@ :disabled="readonly" class="driver-form" > -
基础身份信息
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
驾驶证信息
- - - - - - - - - - - - - - - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + + + 是 + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + -
-
-
- - - - 是 - - - -
- - - - - - - - - -
从业资格证信息
- - - - - - - - - - - + + + + + 是 + + + + + + + - - - - - + + - - - - - - 是 - - - - - - - - - - - - + + +
-
司机联系信息
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +