From 09f1af9a0a1dffa5ed643e715b7d406bcd1aa872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 9 Sep 2026 10:31:22 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix(styles):=20=E8=B0=83=E6=95=B4=E5=BF=85?= =?UTF-8?q?=E5=A1=AB=E6=98=9F=E5=8F=B7=E4=B8=8E=E6=A0=87=E7=AD=BE=E9=A6=96?= =?UTF-8?q?=E8=A1=8C=E5=AF=B9=E9=BD=90=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 .el-form-item__label 的对齐方式使必填星号与首行文字齐平 - 设置多标签布局下星号对齐,兼容单行和多行标签显示 - 调整 ::before 伪元素行高和外边距,确保星号基线一致 fix(project-apply): 更新表单字段“资金需求”为“履约保证金” - 修改项目申请页面中“资金需求”标签为“履约保证金” - 更新对应验证规则标签名称,保持校验一致性 chore(config): 更新开发服务器端口及代理设置 - 注释旧服务器配置,启用新端口 2889 - 更新 /api 代理目标地址为 172.16.203.228:8000 - 保持代理跨域设置不变 --- .workbuddy/memory/2026-09-09.md | 5 +++++ src/styles/element-ui.scss | 22 ++++++++++++++-------- src/views/business/project-apply.vue | 4 ++-- vite.config.mjs | 26 +++++++++++++------------- 4 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 .workbuddy/memory/2026-09-09.md diff --git a/.workbuddy/memory/2026-09-09.md b/.workbuddy/memory/2026-09-09.md new file mode 100644 index 0000000..7a11bee --- /dev/null +++ b/.workbuddy/memory/2026-09-09.md @@ -0,0 +1,5 @@ + +## 必填星号与 label 首行齐平(全局) +- 根因:`element-ui.scss` 中 `.el-form-item--label-right .el-form-item__label { align-items:center }` 使 inline-flex label 的星号 `::before`(独立 flex 子项)在多行折行时被垂直居中。 +- 修复:label 改 `align-self:center`(整体仍随行垂直居中)+ `align-items:flex-start`(星号/文字首行齐平),覆盖 label-right/left/top 三种位置;旧 inline-block 注释已修正。 +- 影响页面:project-apply 独立表单页等所有多行 label 场景(弹窗内 `white-space:nowrap` 不受影响)。 diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss index e5439c6..e826a61 100644 --- a/src/styles/element-ui.scss +++ b/src/styles/element-ui.scss @@ -1047,23 +1047,29 @@ .el-form-item--default .el-form-item__label { line-height: 14px; } -.el-form-item--label-right .el-form-item__label { - align-items: center; +// ============ 必填星号 * 与 label 首行齐平(全局) ============ +// label 为 inline-flex,星号 ::before 与文字是两个独立 flex 子项; +// 多行折行 label 若 align-items: center,星号会被垂直居中到第二行位置。 +// align-items: flex-start 让星号与文字首行齐平; +// align-self: center 让 label 整体不随表单行(32px)拉伸,单行 label 仍与输入框垂直居中,视觉不变。 +.el-form-item--label-right .el-form-item__label, +.el-form-item--label-left .el-form-item__label, +.el-form-item--label-top .el-form-item__label { + align-self: center; + align-items: flex-start; } .el-form-item--default { margin-bottom: 14px; } -// ============ 必填星号 * 与首行文字垂直居中 ============ -// Element Plus 默认 .el-form-item__label 为 inline-flex + align-items: flex-start, -// 星号 :before 高度(1em) 与文字行盒(line-height) 不一致时,多行/折行 label 会偏上。 -// 让 :before 跟随 label 行高、以 inline-block 参与行内对齐即可修复, -// 兼容单行(默认 32px)与多行(auto + 18px)两种 label 高度场景。 -// 注:客商档案(customer-archive)的 label 为 auto 高度 + padding 下沉方案,scoped 特异性更高会覆盖本规则。 +// 辅助:星号跟随 label 行高,保证与首行文字基线一致(单行/多行通用)。 +// 注:真正的首行对齐由上方 .el-form-item__label 的 align-items: flex-start 完成; +// 客商档案(customer-archive)的 label 为 auto 高度 + padding 下沉方案,scoped 特异性更高会覆盖本规则。 .el-form-item.is-required .el-form-item__label::before { display: inline-block; line-height: inherit; margin-right: 4px; + margin-top: 2px !important; } //.el-form-item { // margin-bottom: 15px !important; diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 42ced0b..4ab860c 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -376,7 +376,7 @@ 万元 - + handleNumberInput('fundDemand', value)" @@ -1025,7 +1025,7 @@ export default { ], projectScale: [{ label: '项目规模', validator: validateAmount, trigger: 'blur' }], estimatedProfit: [{ label: '预估利润', validator: validateAmount, trigger: 'blur' }], - fundDemand: [{ label: '资金需求', validator: validateAmount, trigger: 'blur' }], + fundDemand: [{ label: '履约保证金', validator: validateAmount, trigger: 'blur' }], handlerUserId: [ { required: true, diff --git a/vite.config.mjs b/vite.config.mjs index cc153b2..8f859d0 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -47,26 +47,26 @@ export default ({ mode, command }) => { __VUE_I18N_LEGACY_API__: true, __INTLIFY_PROD_DEVTOOLS__: false, }, - server: { - port: 2888, - proxy: { - '/api': { - target: 'http://localhost', - //target: 'https://saber3.bladex.cn/api', - changeOrigin: true, - rewrite: path => path.replace(/^\/api/, ''), - }, - }, - }, // server: { - // port: 2889, + // port: 2888, // proxy: { // '/api': { - // target: 'http://172.16.203.228:8000', + // target: 'http://localhost', + // //target: 'https://saber3.bladex.cn/api', // changeOrigin: true, + // rewrite: path => path.replace(/^\/api/, ''), // }, // }, // }, + server: { + port: 2889, + proxy: { + '/api': { + target: 'http://172.16.203.228:8000', + changeOrigin: true, + }, + }, + }, resolve: { alias: { '~': resolve(__dirname, './'), From 06f6950434bb8963c775405e8f77ada3ebc08f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 9 Sep 2026 10:39:30 +0800 Subject: [PATCH 2/7] =?UTF-8?q?refactor(contract-manage):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=BB=93=E7=AE=97=E8=B4=A7=E5=B8=81=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 结算货币选项中仅显示字典值,去除字典键拼接 - 注释掉样式中 el-form-item 标签的行高设置,避免样式冲突 --- src/styles/element-ui.scss | 2 +- src/views/business/contract-manage.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss index e826a61..f9ca9fb 100644 --- a/src/styles/element-ui.scss +++ b/src/styles/element-ui.scss @@ -1045,7 +1045,7 @@ } .el-form-item--default .el-form-item__label { - line-height: 14px; + //line-height: 14px; } // ============ 必填星号 * 与 label 首行齐平(全局) ============ // label 为 inline-flex,星号 ::before 与文字是两个独立 flex 子项; diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue index 7ef0aef..26052f0 100644 --- a/src/views/business/contract-manage.vue +++ b/src/views/business/contract-manage.vue @@ -1790,7 +1790,7 @@ export default { return Array.isArray(data) ? data : data.records || []; }; this.settlementCurrencyOptions = records(currencyRes).map(item => ({ - label: `${item.dictKey} - ${item.dictValue}`, + label: item.dictValue, value: item.dictKey, })); this.settlementModeDictOptions = records(methodRes).map(item => ({ From be2888d4289206a1c7649fbdbe423818b79503f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 9 Sep 2026 11:11:39 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix(styles):=20=E4=BF=AE=E5=A4=8D=E5=BF=85?= =?UTF-8?q?=E5=A1=AB=E6=98=9F=E5=8F=B7=E9=A6=96=E8=A1=8C=E5=AF=B9=E9=BD=90?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整 `.is-required .el-form-item__label` 采用 `align-items: normal` 实现星号首行对齐 - 覆盖 Element Plus 固定高度 `height: 32px` 为 `height: auto` 以支持内容自适应高度 - 保持 `align-self: center` 使单行 label 与输入框垂直居中,折行时星号贴首行 - 修改星号 `::before` 的高度为 `auto` 并继承行高,保证首行基线一致 - 修复多行 label 下星号因 flex 居中导致位置偏移的问题 - 兼容 label 多种位置(右、左、顶)及多行折行场景,影响 project-apply 等表单页 --- .workbuddy/memory/2026-09-09.md | 9 ++++++++ src/styles/element-ui.scss | 37 ++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/.workbuddy/memory/2026-09-09.md b/.workbuddy/memory/2026-09-09.md index 7a11bee..248b9fe 100644 --- a/.workbuddy/memory/2026-09-09.md +++ b/.workbuddy/memory/2026-09-09.md @@ -3,3 +3,12 @@ - 根因:`element-ui.scss` 中 `.el-form-item--label-right .el-form-item__label { align-items:center }` 使 inline-flex label 的星号 `::before`(独立 flex 子项)在多行折行时被垂直居中。 - 修复:label 改 `align-self:center`(整体仍随行垂直居中)+ `align-items:flex-start`(星号/文字首行齐平),覆盖 label-right/left/top 三种位置;旧 inline-block 注释已修正。 - 影响页面:project-apply 独立表单页等所有多行 label 场景(弹窗内 `white-space:nowrap` 不受影响)。 + +## 必填星号首行对齐:改用 align-items: normal(stretch 方案) +- 最终写法(`src/styles/element-ui.scss`):`.is-required .el-form-item__label { align-self:center; align-items:normal; }` + `::before { display:inline-block; height:auto; line-height:inherit; margin-right:4px }`。 +- 原理:stretch 让星号盒子在「换行时」被拉伸到 label 全高、内容从顶部排布 → 贴首行;单行时盒子=一行高,视觉不变(比 flex-start 更贴合「只换行才生效」)。 +- 前提:::before 高度必须 auto 且继承行高;label 不能是 固定 height + 等高 line-height,否则退化成居中,需 JS 判行数加 class。 + +## 星号方案补丁(关键) +- EP 的 `.el-form-item__label` 写死 `height:32px; line-height:32px`(项目只覆盖了 line-height:14px),导致 stretch 方案文字顶到 32px 盒子顶部、单行不居中。 +- 修复:`.is-required .el-form-item__label { height:auto; align-self:center; align-items:normal }`——height:auto 收缩到内容高 + align-self:center 行内居中 + normal(stretch) 仅换行时星号贴首行。 diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss index f9ca9fb..7eb6e4a 100644 --- a/src/styles/element-ui.scss +++ b/src/styles/element-ui.scss @@ -708,9 +708,9 @@ .avue-crud .el-table__body-wrapper tr.el-table__row:nth-child(even) td.el-table__cell, .avue-crud .el-table__body-wrapper tr.el-table__row:nth-child(even) td.el-table-fixed-column--left, .avue-crud - .el-table__body-wrapper - tr.el-table__row:nth-child(even) - td.el-table-fixed-column--right { +.el-table__body-wrapper +tr.el-table__row:nth-child(even) +td.el-table-fixed-column--right { background-color: #fafafa; } @@ -1045,31 +1045,34 @@ } .el-form-item--default .el-form-item__label { - //line-height: 14px; + line-height: 14px; } -// ============ 必填星号 * 与 label 首行齐平(全局) ============ -// label 为 inline-flex,星号 ::before 与文字是两个独立 flex 子项; -// 多行折行 label 若 align-items: center,星号会被垂直居中到第二行位置。 -// align-items: flex-start 让星号与文字首行齐平; -// align-self: center 让 label 整体不随表单行(32px)拉伸,单行 label 仍与输入框垂直居中,视觉不变。 -.el-form-item--label-right .el-form-item__label, -.el-form-item--label-left .el-form-item__label, -.el-form-item--label-top .el-form-item__label { +.el-form-item--label-right .el-form-item__label { + align-items: center; +} +// ============ 必填星号 * 与 label 首行齐平(仅换行时生效) ============ +// label 为 inline-flex,星号 ::before 与文字是两个独立 flex 子项。 +// align-items: normal(= stretch):单行时星号盒子 = 一行高,位置不变; +// 折行后星号盒子被拉伸到 label 全高,但其内部按 line-height 从顶部排布 → 星号停在第一行。 +// height: auto 覆盖 Element Plus 写死的 32px,让 label 收缩到内容高; +// align-self: center 再让收缩后的 label 在表单行内垂直居中(单行与输入框对齐)。 +.is-required .el-form-item__label { + height: auto; align-self: center; - align-items: flex-start; + align-items: normal; } .el-form-item--default { margin-bottom: 14px; } -// 辅助:星号跟随 label 行高,保证与首行文字基线一致(单行/多行通用)。 -// 注:真正的首行对齐由上方 .el-form-item__label 的 align-items: flex-start 完成; -// 客商档案(customer-archive)的 label 为 auto 高度 + padding 下沉方案,scoped 特异性更高会覆盖本规则。 +// 配套:星号必须保持「高度 auto + 继承行高」,stretch 才会按首行排布; +// 写死 height 或 line-height 与 height 相等时效果会退化成居中。 +// 注:客商档案(customer-archive)的 label 为 auto 高度 + padding 下沉方案,scoped 特异性更高会覆盖本规则。 .el-form-item.is-required .el-form-item__label::before { display: inline-block; + height: auto; line-height: inherit; margin-right: 4px; - margin-top: 2px !important; } //.el-form-item { // margin-bottom: 15px !important; From 7eb6cce2dbf877b9883cb45acf2e3c66a99eb088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 9 Sep 2026 11:14:44 +0800 Subject: [PATCH 4/7] =?UTF-8?q?fix(project-apply):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=AE=80=E7=A7=B0=E8=BE=93=E5=85=A5=E6=A1=86?= =?UTF-8?q?=E7=9A=84=E5=BF=85=E5=A1=AB=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 取消了项目简称表单项的 required 属性 - 在项目简称的验证规则中新增必填校验 - 使得项目简称必填校验逻辑与表单项显示一致 - 保持校验触发事件为失焦(blur)事件 --- src/views/business/project-apply.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 4ab860c..ff02ac0 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -157,7 +157,7 @@ :disabled="isBasicInfoReadonly" /> - + Date: Wed, 9 Sep 2026 11:20:52 +0800 Subject: [PATCH 5/7] =?UTF-8?q?style(ship):=20=E8=B0=83=E6=95=B4=E8=88=AA?= =?UTF-8?q?=E5=8C=BA=E5=AD=97=E6=AE=B5=E5=B8=83=E5=B1=80=E5=8F=8A=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E5=99=A8=E5=AE=BD=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将航区选择控件从宽度12调整为宽度6的列中展示 - 保留航区选择及提示信息功能不变,位置更紧凑 - 调整上传组件宽度从240px增加到248px以适配新布局 --- src/views/transportCapacity/ship.vue | 70 ++++++++++++++-------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/views/transportCapacity/ship.vue b/src/views/transportCapacity/ship.vue index b6f24f7..04799b9 100644 --- a/src/views/transportCapacity/ship.vue +++ b/src/views/transportCapacity/ship.vue @@ -179,6 +179,40 @@ + + + + + + + + + + + + + 参考载货量 + 航区 + A级 + B级 + FA + FB + + + 载货量(t) + 864.00 + 864.00 + 965.00 + 1015.00 + + + + + + + + + @@ -231,40 +265,6 @@ /> - - - - - - - - - - - - - 参考载货量 - 航区 - A级 - B级 - FA - FB - - - 载货量(t) - 864.00 - 864.00 - 965.00 - 1015.00 - - - - - - - - - @@ -1351,7 +1351,7 @@ export default { :deep(.ship-uploader--large) { display: block; - width: 240px; + width: 248px; margin: 0; } From 4d5b99da823f87c8627e16248e4d774e99bb2095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 9 Sep 2026 11:57:52 +0800 Subject: [PATCH 6/7] =?UTF-8?q?feat(contract):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=90=88=E5=90=8C=E5=8F=98=E6=9B=B4=E5=8F=8A=E8=BD=A6=E8=BE=86?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=AE=A1=E6=A0=B8=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 合同变更页「其它附件」上传按钮样式调整,与变更材料区按钮统一 - 新增分区标题必填星号样式,改用 ::after 避免冲突 - 变更页 load() 强制清空表单关键字段,避免带入旧数据 - 实现 mergeChangeAttachments() 合并上次变更附件,去重并格式化大小 - 合同变更记录表格新增「变更内容」列,支持单行摘要显示与悬浮全量提示 - 操作列由单「流程」改为「详情 + 流程」按钮,详情文字统一 - 变更记录值格式化增强,支持方案名、支付比例、结算规则等摘要展示 - buildDetailChangeRecordRows 过滤条件更新,前后均为空时跳过展示 - 车辆认证审核弹窗样式及结构重构,使用 section-card 白卡分组,内容补全证件图片区 - 证件图片实现只读大图展示,复用全局样式,无需新增规则 - label 单位统一为中文全角括号,提升界面一致性 - 新增长期有效显示逻辑,避免日期未填「-」与长期有效混淆 - 删除无用计算属性 auditDetailItems,改用简洁 auditValue 方法返回展示值 - 全站弹窗标题统一增加 4px 主色竖条,符合设计规范,且对无标题弹窗进行排除处理 - 客户端临时额度申请期限字段拆分为两个独立字段,清晰明了 - 统一合同变更页变更原因必填标识及上传按钮样式 - 修正 EP 表单项标签高度和对齐,完善星号方案兼容性设计 --- .workbuddy/memory/2026-09-09.md | 25 ++ src/styles/element-ui.scss | 11 + src/views/business/contract-manage-change.vue | 16 +- src/views/business/contract-manage.vue | 134 +++++++- src/views/transportCapacity/vehicle.vue | 293 ++++++++++++++++-- src/views/vehicle/customer-archive.vue | 48 ++- 6 files changed, 468 insertions(+), 59 deletions(-) diff --git a/.workbuddy/memory/2026-09-09.md b/.workbuddy/memory/2026-09-09.md index 248b9fe..419ea76 100644 --- a/.workbuddy/memory/2026-09-09.md +++ b/.workbuddy/memory/2026-09-09.md @@ -9,6 +9,31 @@ - 原理:stretch 让星号盒子在「换行时」被拉伸到 label 全高、内容从顶部排布 → 贴首行;单行时盒子=一行高,视觉不变(比 flex-start 更贴合「只换行才生效」)。 - 前提:::before 高度必须 auto 且继承行高;label 不能是 固定 height + 等高 line-height,否则退化成居中,需 JS 判行数加 class。 +## 合同变更页(contract-manage-change.vue) +- 「其它附件」上传按钮改为 `type="primary" plain icon="el-icon-upload"`,与「变更材料」区 vehicle-attachment-upload 内置按钮一致。 +- 分区标题必填星号:`.dialog-section-title.is-required::after { content:'*'; color:#f56c6c }`(`::before` 已被蓝色竖条占用,星号走 `::after`)。 +- 再次变更逻辑:`load()` 里 form 强制重置 `changeContent/changeReason/changeAttachmentsJson = ''`、`changeMaterials = []`(不带上一次内容);新增 `mergeChangeAttachments()` 把上一次的 `changeAttachmentsJson` 归集进 `attachments`(其它附件),按 url/名去重,数字 size 转格式化文本,提交时随 attachmentsJson 持久化。 + +## 合同变更记录对齐客商变更记录(contract-manage.vue) +- 编辑页与详情弹窗的两处「变更记录」表格新增「变更内容」列:`formatContractChangeContent(row)` 复用 `buildDetailChangeRecordRows` 生成「字段:变更前:x → 变更后:y」摘要,单行省略 + `el-tooltip` 全量悬浮(样式 `.contract-change-record-content-cell/-tooltip` 照搬客商 `change-record-content-*`)。 +- 编辑页操作列由单个「流程」改为「详情 + 流程」(`openDetailChangeRecord` + `openFlow`,宽度 120→150);详情弹窗表格「查看详情」改「详情」与客商一致。 +- 值格式化增强:`billingPlanJson`→方案名(默认)列表、`paymentRatioJson`→「第N笔 xx%」、`settlementRuleJson/preSettlementConfigJson/formalSettlementConfigJson`→可读摘要(自动生成开关/结算类型/周期类型/截单日/周期天数,含预结算+正式结算双段)。 +- `buildDetailChangeRecordRows` 过滤条件补「前后都为空则跳过」(原来只比 JSON 字符串,'' 与 null 会漏)。 + ## 星号方案补丁(关键) - EP 的 `.el-form-item__label` 写死 `height:32px; line-height:32px`(项目只覆盖了 line-height:14px),导致 stretch 方案文字顶到 32px 盒子顶部、单行不居中。 - 修复:`.is-required .el-form-item__label { height:auto; align-self:center; align-items:normal }`——height:auto 收缩到内容高 + align-self:center 行内居中 + normal(stretch) 仅换行时星号贴首行。 + +## 车辆认证审核弹窗重构(transportCapacity/vehicle.vue) +- 背景:用户反馈「样式不统一、内容不全」。根因是审核弹窗用 ``(大白表 + 居中 label),与同页编辑弹窗的 `` 白卡分组风格不一致。 +- 改造:弹窗主体改为 4 个 `section-card` 分组 —— 基础信息 / 尺寸重量 / 证件信息 / 证件图片;内部 `el-form label-position="right" label-width="auto"` + `el-row/el-col`(3 列 `:span="8"`,所属组织与备注占 `:span="24"`)。 +- 内容补全:新增「证件图片」分组,6 张证件照用 `` 只读展示(可点击放大);证件图片尺寸 240×151 复用已有全局 `:deep(.vehicle-uploader--large)` 规则,无需新写。 +- label 单位统一为中文全角括号:`外廓长度(毫米)` / `核定载质量(KG)`(原为半角 `外廓长度(mm)`)。 +- 方法重构:`formatAuditValue(item)` → `auditValue(prop)`,签名更简洁;新增 longTermMap,对 drivingLicenseEndDate / roadTransportCertEndDate / annualReviewEndDate / compulsoryScrapDate 在勾选长期有效时返回「长期有效」,避免与未填写的「-」混淆(与列表页 formatEndDate 行为一致)。 +- 删除已无引用的 `auditDetailItems` computed。 + +## 全站弹窗标题补 4px 主色竖条(全局 element-ui.scss) +- 发现:`.el-dialog__title` 全局只配了 `inline-flex + gap:8px`,**没有竖条**(竖条此前只在 `.dialog-section-title::before`),导致全站 el-dialog 标题都不带竖条,不符合 AGENTS.md 4.8。 +- 修复:新增 `.el-dialog__title:not(:empty)::before { width:4px; height:16px; border-radius:2px; background:var(--el-color-primary) }`。 +- 注意:必须加 `:not(:empty)`,否则无标题弹窗会出现孤立蓝条。 +- customer-archive.vue 评分详情弹窗:将「临时额度申请使用期限」区间行(span=12 + 至 连接)拆分为两个 span=6 独立字段「临时申请额度有效期起 / 止」,字段名 tempCreditStartDate/End 不变,删除废弃样式 .score-detail-form__range。 diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss index 7eb6e4a..b40123f 100644 --- a/src/styles/element-ui.scss +++ b/src/styles/element-ui.scss @@ -213,6 +213,17 @@ gap: 8px; } +// 全站弹窗标题统一带 4px 主色竖条(与 .dialog-section-title 保持一致) +// :not(:empty) 避免无标题弹窗出现孤立竖条 +.el-dialog__title:not(:empty)::before { + content: ''; + flex: none; + width: 4px; + height: 16px; + border-radius: 2px; + background-color: var(--el-color-primary); +} + .dialog-section-title::before { content: ''; flex: none; diff --git a/src/views/business/contract-manage-change.vue b/src/views/business/contract-manage-change.vue index 787be4e..ca7315b 100644 --- a/src/views/business/contract-manage-change.vue +++ b/src/views/business/contract-manage-change.vue @@ -79,13 +79,13 @@ 其它附件批量下载 {{ row.originalName || row.name }}删除 - 上传附件 + 上传附件 变更内容 - 变更原因 + 变更原因 变更材料 @@ -138,8 +138,17 @@ export default { mounted() { this.load(); }, watch: { settlementConfigTab(tab, oldTab) { if (tab === oldTab) return; if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; this.settlementRule = { ...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig) }; } }, methods: { - async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, changeContent: '', changeReason: '', changeAttachmentsJson: '', copyCount: normalizeOptionalPositiveInteger(data.copyCount), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), changeType: '合同信息变更' }; this.changeMaterials = []; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.parse(data.attachmentsJson); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; this.preSettlementConfig = rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy); this.formalSettlementConfig = rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); }, + async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, changeContent: '', changeReason: '', changeAttachmentsJson: '', copyCount: normalizeOptionalPositiveInteger(data.copyCount), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), changeType: '合同信息变更' }; this.changeMaterials = []; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.mergeChangeAttachments(this.parse(data.attachmentsJson), this.parse(data.changeAttachmentsJson)); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; this.preSettlementConfig = rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy); this.formalSettlementConfig = rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); }, parse(value) { try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : []; } catch { return []; } }, + // 审核通过后上一次的变更材料归集到「其它附件」,按文件地址/文件名去重,避免重复展示 + mergeChangeAttachments(attachments = [], changeAttachments = []) { + const key = item => this.attachmentUrl(item) || this.attachmentName(item); + const exists = new Set(attachments.map(key)); + const merged = changeAttachments + .filter(item => !exists.has(key(item))) + .map(item => ({ ...item, size: /^\d+$/.test(String(item.size ?? '')) ? this.formatFileSize(item.size) : item.size })); + return [...attachments, ...merged]; + }, parseObject(value) { try { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15, ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15 }; } }, positiveIntegerInput(prop, value) { this.form[prop] = String(value ?? '').replace(/\D/g, '').replace(/^0+/, ''); }, addPlan() { this.planEditorIndex = -1; this.planEditor = { planName: `计费方案${this.plans.length + 1}`, defaultPlan: !this.plans.length, remark: '', rules: [{}] }; this.planDialogVisible = true; }, @@ -187,6 +196,7 @@ export default { .contract-basic-section :deep(.el-date-editor) { width: 360px; max-width: 100%; } .dialog-section-title { margin-bottom: 18px; font-size: 16px; font-weight: 600; } .dialog-section-title::before { display: inline-block; width: 4px; height: 16px; margin-right: 8px; vertical-align: -2px; background: #409eff; content: ''; } +.dialog-section-title.is-required::after { margin-left: 4px; color: #f56c6c; content: '*'; } .section-head { display: flex; align-items: center; justify-content: space-between; } .change-table { margin: 16px 0; } .ratio-tip { margin-bottom: 8px; color: #f56c6c; } diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue index 26052f0..c422580 100644 --- a/src/views/business/contract-manage.vue +++ b/src/views/business/contract-manage.vue @@ -480,6 +480,20 @@ /> + + + + + + {{ formatContractChangeContent(row) }} + + + {{ + formatContractChangeContent(row) + }} + + + - + + 详情 流程 @@ -759,6 +774,20 @@ /> + + + + + + {{ formatContractChangeContent(row) }} + + + {{ + formatContractChangeContent(row) + }} + + + 查看详情详情 @@ -2250,12 +2279,73 @@ export default { .filter(Boolean); return names.length ? names.join('、') : '空'; }, + formatChangeRecordBillingPlans(value) { + const list = Array.isArray(value) ? value : []; + if (!list.length) return '空'; + return list + .map(item => { + if (!item || typeof item !== 'object') return String(item ?? ''); + const name = item.planName || item.name || '未命名方案'; + return item.defaultPlan ? `${name}(默认)` : name; + }) + .filter(Boolean) + .join('、'); + }, + formatChangeRecordPaymentRatios(value) { + const list = Array.isArray(value) ? value : []; + if (!list.length) return '空'; + return list + .map(item => { + if (!item || typeof item !== 'object') return String(item ?? ''); + const term = item.paymentTerm || ''; + const ratio = item.ratioLimit === undefined || item.ratioLimit === null || item.ratioLimit === '' ? '' : `${item.ratioLimit}%`; + return [term, ratio].filter(Boolean).join(' '); + }) + .filter(Boolean) + .join(';'); + }, + formatChangeRecordSettlementRule(value) { + const normalized = this.normalizeChangeRecordValue(value); + if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) { + return this.isEmptyChangeRecordValue(normalized) ? '空' : String(normalized); + } + const summary = config => { + if (!config || typeof config !== 'object') return ''; + const parts = []; + const autoGenerate = config.autoGenerate; + if (autoGenerate !== undefined && autoGenerate !== null && autoGenerate !== '') { + parts.push(`自动生成结算单:${Number(autoGenerate) === 1 ? '开启' : '关闭'}`); + } + if (config.settlementType) parts.push(`结算类型:${config.settlementType}`); + if (config.billCycleType) parts.push(`账单周期类型:${config.billCycleType}`); + if (config.billCutoffDay) parts.push(`账单截单日:${config.billCutoffDay}日`); + if (config.cycleDays) parts.push(`周期天数:${config.cycleDays}天`); + if (config.billStartDate) parts.push(`账单起始日期:${config.billStartDate}`); + return parts.join(','); + }; + if (normalized.preSettlementConfig || normalized.formalSettlementConfig) { + return [ + `预结算:${summary(normalized.preSettlementConfig) || '空'}`, + `正式结算:${summary(normalized.formalSettlementConfig) || '空'}`, + ].join(';'); + } + return summary(normalized) || '空'; + }, formatContractChangeValue(field, value) { const normalized = this.normalizeChangeRecordValue(value); if (this.isEmptyChangeRecordValue(normalized)) return '空'; if (['contractFileJson', 'attachmentsJson', 'changeAttachmentsJson'].includes(field)) { return this.formatChangeRecordAttachments(normalized); } + if (field === 'billingPlanJson') return this.formatChangeRecordBillingPlans(normalized); + if (field === 'paymentRatioJson') return this.formatChangeRecordPaymentRatios(normalized); + if ( + ['settlementRuleJson', 'preSettlementConfigJson', 'formalSettlementConfigJson'].includes( + field + ) + ) { + return this.formatChangeRecordSettlementRule(normalized); + } if (field === 'legalSealFlag') return Number(normalized) === 1 ? '是' : '否'; if (field === 'feeGenerationMode') return normalized === 'manual' ? '手动生成' : '系统生成'; if (field === 'invoiceCycle' || field === 'paymentDays') return `${normalized}天`; @@ -2264,16 +2354,33 @@ export default { } return String(normalized); }, + // 列表「变更内容」列:把变更前后内容拼成一段摘要(参照客商变更记录) + formatContractChangeContent(row = {}) { + const rows = this.buildDetailChangeRecordRows(row).filter( + item => item.field !== '变更原因' + ); + return rows + .map(item => `${item.field}:变更前:${item.before} → 变更后:${item.after}`) + .join(';'); + }, buildDetailChangeRecordRows(row = {}) { const beforeData = this.parseChangeRecordData(row.beforeData); const afterData = this.parseChangeRecordData(row.afterData); const fields = [...new Set([...Object.keys(beforeData), ...Object.keys(afterData)])]; const rows = fields - .filter( - field => + .filter(field => { + // 前后都为空的字段不展示('' 与 null 序列化不同,单靠 JSON 比对会漏掉) + if ( + this.isEmptyChangeRecordValue(beforeData[field]) && + this.isEmptyChangeRecordValue(afterData[field]) + ) { + return false; + } + return ( JSON.stringify(this.normalizeChangeRecordValue(beforeData[field])) !== JSON.stringify(this.normalizeChangeRecordValue(afterData[field])) - ) + ); + }) .map(field => ({ field: this.getContractChangeFieldLabel(field), before: this.formatContractChangeValue(field, beforeData[field]), @@ -2496,6 +2603,21 @@ export default { } } +.contract-change-record-content-cell { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.contract-change-record-content-tooltip { + max-width: 900px; + max-height: 320px; + overflow: auto; + white-space: pre-wrap; + word-break: break-word; +} + .contract-change-record-detail-meta { display: flex; flex-wrap: wrap; @@ -2505,6 +2627,8 @@ export default { } :deep(.contract-change-record-detail-dialog .el-dialog__body) { + max-height: 65vh; + overflow: auto; padding-top: 12px; } diff --git a/src/views/transportCapacity/vehicle.vue b/src/views/transportCapacity/vehicle.vue index b324d3a..e7afbbd 100644 --- a/src/views/transportCapacity/vehicle.vue +++ b/src/views/transportCapacity/vehicle.vue @@ -120,15 +120,252 @@ - - - - {{ formatAuditValue(item) }} - - - + + + + + + + {{ auditValue('organizationName') }} + + + + + + + {{ auditValue('plateNo') }} + + + + + {{ auditValue('plateColor') }} + + + + + {{ auditValue('vehicleType') }} + + + + + + + {{ auditValue('businessRelation') }} + + + + + {{ auditValue('energyType') }} + + + + + + {{ auditValue('compulsoryScrapDate') }} + + + + + + + + + + + {{ auditValue('outerLength') }} + + + + + {{ auditValue('outerWidth') }} + + + + + {{ auditValue('outerHeight') }} + + + + + + + {{ auditValue('approvedLoadKg') }} + + + + + {{ auditValue('tractionMassKg') }} + + + + + + + + + + {{ auditValue('customsRecordNo') }} + + + + + {{ auditValue('drivingLicenseNo') }} + + + + + + {{ auditValue('drivingLicenseStartDate') }} + + + + + + + + + {{ auditValue('drivingLicenseEndDate') }} + + + + + + + {{ auditValue('roadTransportCertNo') }} + + + + + + + {{ auditValue('roadTransportCertStartDate') }} + + + + + + + + + {{ auditValue('roadTransportCertEndDate') }} + + + + + + + {{ auditValue('annualReviewEndDate') }} + + + + + + {{ auditValue('registrationNo') }} + + + + + + + {{ auditValue('registrationDate') }} + + + + + + + {{ auditValue('remark') }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -731,18 +968,6 @@ export default { } return this.vehicleForm.id ? '修改车辆' : '新增车辆'; }, - auditDetailItems() { - return [ - ['所属组织', 'organizationName'], ['车牌号', 'plateNo'], ['车牌颜色', 'plateColor'], - ['车辆类型', 'vehicleType'], ['外廓长度(mm)', 'outerLength'], ['外廓宽度(mm)', 'outerWidth'], - ['外廓高度(mm)', 'outerHeight'], ['核定载质量(KG)', 'approvedLoadKg'], ['准牵引总质量(KG)', 'tractionMassKg'], - ['业务关系', 'businessRelation'], ['能源类型', 'energyType'], ['强制报废日期', 'compulsoryScrapDate'], - ['海关备案号', 'customsRecordNo'], ['行驶证档案编号', 'drivingLicenseNo'], ['行驶证有效期起', 'drivingLicenseStartDate'], - ['行驶证有效期止', 'drivingLicenseEndDate'], ['道路运输证号', 'roadTransportCertNo'], ['道路运输证有效期起', 'roadTransportCertStartDate'], - ['道路运输证有效期止', 'roadTransportCertEndDate'], ['道路运输年审有效期', 'annualReviewEndDate'], - ['机动车登记编号', 'registrationNo'], ['机动车登记日期', 'registrationDate'], ['备注', 'remark'], - ].map(([label, prop]) => ({ label, prop })); - }, expiryTagOptions() { return [ { label: '全部', value: '', statKey: 'total' }, @@ -849,8 +1074,19 @@ export default { this.certificationAuditBox = true; }); }, - formatAuditValue(item) { - const value = this.certificationAuditForm[item.prop]; + auditValue(prop) { + // 长期有效字段:勾选后显示「长期有效」,避免与未填写的「-」混淆 + const longTermMap = { + drivingLicenseEndDate: 'drivingLicenseLongTerm', + roadTransportCertEndDate: 'roadTransportCertLongTerm', + annualReviewEndDate: 'annualReviewLongTerm', + compulsoryScrapDate: 'compulsoryScrapLongTerm', + }; + const longTermProp = longTermMap[prop]; + if (longTermProp && this.certificationAuditForm[longTermProp] === 1) { + return '长期有效'; + } + const value = this.certificationAuditForm[prop]; return value === undefined || value === null || value === '' ? '-' : value; }, handleCertificationApprove() { @@ -1422,6 +1658,19 @@ export default { } } +// 车辆认证审核弹窗:与编辑弹窗一致,灰底 + section-card 白卡分组 +.certification-audit { + &__value { + display: block; + color: #303133; + word-break: break-all; + } + + &__reason { + margin-top: 12px; + } +} + .vehicle-form { :deep(.el-date-editor.el-input), :deep(.el-date-editor.el-input__wrapper), diff --git a/src/views/vehicle/customer-archive.vue b/src/views/vehicle/customer-archive.vue index 48aa6a8..12061bf 100644 --- a/src/views/vehicle/customer-archive.vue +++ b/src/views/vehicle/customer-archive.vue @@ -1181,24 +1181,25 @@ - - - - - 至 - - + + + - + + + + + + .el-input), @@ -4911,17 +4912,6 @@ export default { } } -.score-detail-form__range { - display: flex; - align-items: center; - gap: 14px; - - :deep(.el-date-editor) { - flex: 1; - min-width: 0; - } -} - .score-category-table { width: calc(100% - 72px); margin: 8px 36px 16px; From e73e1da87745358dc1996ed50e930a2d6d7f5c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 9 Sep 2026 12:45:46 +0800 Subject: [PATCH 7/7] =?UTF-8?q?feat(contract-manage):=20=E5=B0=86=E4=BB=98?= =?UTF-8?q?=E6=AC=BE=E6=AF=94=E4=BE=8B=E8=AE=BE=E7=BD=AE=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E6=94=B9=E4=B8=BAtooltip=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 付款比例设置区块标题后添加问号图标悬浮提示,替换原红色文字说明 - 编辑弹窗、详情弹窗、合同变更页统一使用el-tooltip + el-icon-question-filled组件 - 移除contract-manage.vue中原有.red tip提示及相应样式 feat(temporary-credit-limit): 优化临时额度申请界面及校验规则 - 增加新增/编辑/查看临时额度申请标题,覆盖默认菜单注入标题 - 所有表单字段统一设置placeholder为“请输入/请选择”无字段名 - 搜索栏占位符统一为“请输入/请选择”,与其他模块一致 - 临时额度申请有效期至字段增加“必须晚于当前日期”日期校验规则 - 增添futureDateRule通用校验规则,支持多种日期格式即时触发验证 feat(project-apply): 新增资金使用风险快速筛选及风险提示 - 项目申请列表增加资金使用风险列,显示高风险/中风险标签 - 新增资金使用风险快速筛选区,可点击标签筛选项目风险等级 - 提供资金使用风险阈值配置,支持中风险≥80%、高风险≥90% - 调整页面样式及交互,优化风险筛选体验 - 新增后端接口getFundRiskStats,用于获取风险统计数据并实时刷新 --- .workbuddy/memory/2026-09-09.md | 18 ++++ src/api/business/project-apply.js | 8 ++ src/option/business/common.js | 26 ++++++ src/option/business/project-apply.js | 21 +++++ src/option/business/temporary-credit-limit.js | 25 ++++- src/views/business/contract-manage-change.vue | 7 +- src/views/business/contract-manage.vue | 19 ++-- src/views/business/project-apply.vue | 91 ++++++++++++++++++- src/views/business/temporary-credit-limit.vue | 12 ++- 9 files changed, 209 insertions(+), 18 deletions(-) diff --git a/.workbuddy/memory/2026-09-09.md b/.workbuddy/memory/2026-09-09.md index 419ea76..5ab529a 100644 --- a/.workbuddy/memory/2026-09-09.md +++ b/.workbuddy/memory/2026-09-09.md @@ -1,4 +1,8 @@ +## 付款比例设置提示改为 tooltip(contract-manage / contract-manage-change) +- 需求:将「付款比例设置」区块原有的红色文字提示改为标题右侧的 `?` 图标悬停提示。 +- 改动:编辑弹窗、详情弹窗、合同变更页三处标题后添加 `el-tooltip` + `el-icon-question-filled`,content 为「非必填;配置付款比例时,合计必须等于100%。」;同时移除 contract-manage.vue 中原 `.contract-manage-form__tip` 红色提示文字及对应样式。 + ## 必填星号与 label 首行齐平(全局) - 根因:`element-ui.scss` 中 `.el-form-item--label-right .el-form-item__label { align-items:center }` 使 inline-flex label 的星号 `::before`(独立 flex 子项)在多行折行时被垂直居中。 - 修复:label 改 `align-self:center`(整体仍随行垂直居中)+ `align-items:flex-start`(星号/文字首行齐平),覆盖 label-right/left/top 三种位置;旧 inline-block 注释已修正。 @@ -37,3 +41,17 @@ - 修复:新增 `.el-dialog__title:not(:empty)::before { width:4px; height:16px; border-radius:2px; background:var(--el-color-primary) }`。 - 注意:必须加 `:not(:empty)`,否则无标题弹窗会出现孤立蓝条。 - customer-archive.vue 评分详情弹窗:将「临时额度申请使用期限」区间行(span=12 + 至 连接)拆分为两个 span=6 独立字段「临时申请额度有效期起 / 止」,字段名 tempCreditStartDate/End 不变,删除废弃样式 .score-detail-form__range。 +- temporary-credit-limit 文案调整:option 增加 addTitle/editTitle/viewTitle=「新增/编辑/查看临时额度申请」(覆盖 crud-dialog-title mixin 按菜单名注入的「临时额度管理」);remark label 改「临时额度信息」(hide:true 无表格副作用,rules 同步);projectName 表单 label 用 `#projectName-label` 插槽改「项目额度信息」,列 label 保持「项目名称」不动表格列头/搜索。Avue 支持-{prop}-label / -header / -search 插槽分离表单/表头/搜索文案。 +- temporary-credit-limit「申请有效期至」增加「必须晚于当前日期」校验:common.js 新增通用 `futureDateRule(label)`(trigger: ['change','blur'],空值/非法值放行,按 0 点比较,当天也算过期),temporary-credit-limit.js 的 validUntil rules 追加该规则。已验证 Element Plus 通用 picker 在选中日期时调用 formItem.validate('change'),故选择即刻提示;form.validate() 不带 trigger 时会跑全部规则,提交仍拦截。 +- temporary-credit-limit 搜索占位文案统一:createCrudOption(withSearchPlaceholders([...])),搜索栏 placeholder 变为「请输入/请选择」不带字段名,与 waybill-manage / transport-plan / shipping-template 同一模式。 +- temporary-credit-limit 弹窗表单 placeholder 统一为「请输入/请选择」不带字段名:option 各列加 placeholder(projectCode/undertakeDeptName/projectFundLimit/usedFundLimit/remainingFundLimit/remark=请输入,validUntil=请选择),视图 formslot 自定义控件(projectName select、applyLimit input)placeholder 同步改。 + +## 【项目管理】列表增加「项目资金使用预警」(project-apply) +- 需求:列表新增「资金使用风险」列,高风险红色、中风险黄色;列表头部增加「资金使用风险」快速筛选标签(高风险(数)/中风险(数)),点击筛选。规则 tooltip:中风险≥80%、高风险≥90%,使用率=已使用资金/项目资金使用额度×100%。 +- 前端(已落地,src/views/business/project-apply.vue + src/option/business/project-apply.js + src/api/business/project-apply.js): + - option/project-apply.js 新增列 `fundUseRisk`(slot:true, hide 列表显示) + 导出 `fundUseRiskOptions`(high/medium/none) 与 `fundUseRiskThreshold`{high:90, medium:80}。 + - view 新增 `#header` 插槽:`el-check-tag` 快速筛选(复用 contract-manage 到期标签交互),带数量(fundRiskStats[statKey])与 `?` tooltip 规则;新增 `#fundUseRisk` 插槽渲染彩色 `el-tag`(high→type=danger 红,medium→type=warning 黄,无→"-")。 + - data 加 fundRiskScope/fundRiskStats{high,medium,none}/fundRiskTagOptions;onLoad 追加 `fundUseRisk` 筛选参数 + 成功后 refreshFundRiskStats;新增 changeFundRiskScope(再次点击取消)、refreshFundRiskStats;searchReset 清空风险筛选。 + - api 新增 `getFundRiskStats` → GET /blade-transport/project-apply/fund-risk-stats。 +- 后端契约(未改,待确认数据来源):ProjectApplyVO 补齐 `fundUseRisk`(high/medium/none)、`fundUseRiskName`、`fundUseRate`、`usedFundLimit`/`remainingFundLimit`;列表 selectProjectApplyPage 填充每行风险 + buildQuery 支持 `fundUseRisk` 过滤;新增 `/fund-risk-stats` 返回 {high,medium,none}。 +- ⚠️ 关键阻塞:系统里项目与任何费用表(运输计划/运单/账单/客商)均无关联字段,`usedFundLimit`(已使用资金) 无真实取数来源;临时额度模块的 project-apply/list 返回里 usedFundLimit 实际为空。需业务方确认「已使用资金」口径后才能算风险等级,否则列表风险列全空、统计为 0。 diff --git a/src/api/business/project-apply.js b/src/api/business/project-apply.js index a08c3ea..cf95c97 100644 --- a/src/api/business/project-apply.js +++ b/src/api/business/project-apply.js @@ -9,6 +9,14 @@ export const getDetail = api.getDetail; export const submit = api.submit; export const remove = api.remove; +// 项目资金使用风险统计(高风险 / 中风险数量),供列表头部快速筛选标签使用 +export const getFundRiskStats = params => + request({ + url: `${baseUrl}/fund-risk-stats`, + method: 'get', + params, + }); + export const saveDraft = row => request({ url: `${baseUrl}/save-draft`, diff --git a/src/option/business/common.js b/src/option/business/common.js index 9b15eac..b418ada 100644 --- a/src/option/business/common.js +++ b/src/option/business/common.js @@ -169,6 +169,32 @@ export const nonNegativeRule = label => ({ trigger: 'blur', }); +// 日期必须晚于当前日期(当天不可选):选择后立即校验,trigger 固定为 change +// 取值支持 YYYY-MM-DD / YYYY-MM-DD HH:mm:ss / 时间戳 +export const futureDateRule = label => ({ + validator: (rule, value, callback) => { + if (value === undefined || value === null || value === '') { + callback(); + return; + } + const raw = String(value).trim().replace(/-/g, '/'); + const selected = new Date(raw); + if (Number.isNaN(selected.getTime())) { + callback(); + return; + } + const today = new Date(); + today.setHours(0, 0, 0, 0); + selected.setHours(0, 0, 0, 0); + if (selected.getTime() <= today.getTime()) { + callback(new Error(`${label}必须晚于当前日期`)); + return; + } + callback(); + }, + trigger: ['change', 'blur'], +}); + export const phoneRule = label => ({ pattern: /^1[3-9]\d{9}$/, message: `${label}格式不正确`, diff --git a/src/option/business/project-apply.js b/src/option/business/project-apply.js index c0aacec..f4cfa61 100644 --- a/src/option/business/project-apply.js +++ b/src/option/business/project-apply.js @@ -16,6 +16,18 @@ const projectEffectiveTypeOptions = [ { label: '正式', value: 'formal' }, ]; +export const fundUseRiskOptions = [ + { label: '高风险', value: 'high' }, + { label: '中风险', value: 'medium' }, + { label: '正常', value: 'none' }, +]; + +// 项目资金额度使用率阈值:中风险 ≥ 80%,高风险 ≥ 90% +export const fundUseRiskThreshold = { + high: 90, + medium: 80, +}; + const amountRules = label => [ { required: true, message: `请输入${label}`, trigger: 'blur' }, nonNegativeRule(label), @@ -402,5 +414,14 @@ export const option = createCrudOption([ span: 24, hide: true, }, + { + label: '资金使用风险', + prop: 'fundUseRisk', + slot: true, + minWidth: 130, + addDisplay: false, + editDisplay: false, + hide: false, + }, ...auditColumns.map(column => ({ ...column, hide: true })), ]); diff --git a/src/option/business/temporary-credit-limit.js b/src/option/business/temporary-credit-limit.js index 4e8befd..ac9692b 100644 --- a/src/option/business/temporary-credit-limit.js +++ b/src/option/business/temporary-credit-limit.js @@ -2,8 +2,10 @@ import { approvalStatusOptions, auditColumns, createCrudOption, + futureDateRule, nonNegativeRule, textRule, + withSearchPlaceholders, } from './common'; const listDicFormatter = res => { @@ -118,7 +120,10 @@ export const config = { export const option = { dialogCustomClass: 'temporary-credit-limit-dialog', - ...createCrudOption([ + addTitle: '新增临时额度申请', + editTitle: '编辑临时额度申请', + viewTitle: '查看临时额度申请', + ...createCrudOption(withSearchPlaceholders([ { label: '申请单号', prop: 'applicationNo', @@ -158,6 +163,7 @@ export const option = { prop: 'projectCode', hide: true, disabled: true, + placeholder: '请输入', span: 6, minWidth: 150, rules: textRule('项目编号', 50), @@ -167,6 +173,7 @@ export const option = { prop: 'undertakeDeptName', hide: true, disabled: true, + placeholder: '请输入', span: 6, minWidth: 150, rules: textRule('承办部门', 50, true), @@ -176,6 +183,7 @@ export const option = { prop: 'projectFundLimit', hide: true, disabled: true, + placeholder: '请输入', span: 6, minWidth: 200, rules: [nonNegativeRule('项目资金使用额度')], @@ -185,6 +193,7 @@ export const option = { prop: 'usedFundLimit', hide: true, disabled: true, + placeholder: '请输入', span: 6, minWidth: 220, value: 0, @@ -195,6 +204,7 @@ export const option = { prop: 'remainingFundLimit', hide: true, disabled: true, + placeholder: '请输入', span: 6, minWidth: 230, value: 0, @@ -217,9 +227,13 @@ export const option = { type: 'date', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD', + placeholder: '请选择', span: 6, minWidth: 140, - rules: [{ required: true, message: '请选择申请有效期至', trigger: 'change' }], + rules: [ + { required: true, message: '请选择申请有效期至', trigger: 'change' }, + futureDateRule('申请有效期至'), + ], }, { label: '申请部门', @@ -285,15 +299,16 @@ export const option = { editDisplay: false, }, { - label: '备注', + label: '临时额度信息', prop: 'remark', type: 'textarea', span: 24, minRows: 2, hide: true, + placeholder: '请输入', maxlength: 200, showWordLimit: true, - rules: textRule('备注', 200), + rules: textRule('临时额度信息', 200), }, { label: '附件', @@ -303,7 +318,7 @@ export const option = { hide: true, }, ...auditColumns.map(column => ({ ...column, hide: true })), - ]), + ])), dialogWidth: '96%', menuFixed: 'right', menuWidth: 320, diff --git a/src/views/business/contract-manage-change.vue b/src/views/business/contract-manage-change.vue index ca7315b..612a872 100644 --- a/src/views/business/contract-manage-change.vue +++ b/src/views/business/contract-manage-change.vue @@ -63,7 +63,12 @@ - 付款比例设置 + + 付款比例设置 + + + + diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue index c422580..514e742 100644 --- a/src/views/business/contract-manage.vue +++ b/src/views/business/contract-manage.vue @@ -425,10 +425,14 @@ - 付款比例设置 + + 付款比例设置 + + + + 添加 - 非必填;配置付款比例时,合计必须等于100%。 @@ -740,7 +744,12 @@ - 付款比例设置 + + 付款比例设置 + + + + @@ -2892,8 +2901,4 @@ export default { white-space: normal; line-height: 1.2; } -.contract-manage-form__tip { - color: #ff0000 !important; - font-size: 12px; -} diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index ff02ac0..a176e10 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -58,6 +58,40 @@ {{ displayStatus(row, 'approvalStatus') }} + + + + 资金使用风险 + + + 中风险:项目资金额度使用率 ≥ 80% + 高风险:项目资金额度使用率 ≥ 90% + 使用率 = 已使用资金 / 项目资金使用额度 × 100% + + + + + + {{ item.label }}({{ fundRiskStats[item.statKey] || 0 }}) + + + + + + + {{ row.fundUseRiskName || '高风险' }} + + + {{ row.fundUseRiskName || '中风险' }} + + - + + { const result = res.data.data || {}; this.page.total = result.total || 0; this.data = result.records || []; this.selectionClear(); + this.refreshFundRiskStats({ ...params, ...this.query, allDept: this.allDept }); }) .finally(() => { this.loading = false; @@ -1355,6 +1397,7 @@ export default { }, searchReset() { this.query = {}; + this.fundRiskScope = ''; this.page.currentPage = 1; this.onLoad(this.page); }, @@ -1364,6 +1407,18 @@ export default { this.onLoad(this.page, params); done(); }, + // 切换「资金使用风险」快速筛选:再次点击已选中项则取消筛选 + changeFundRiskScope(value) { + this.fundRiskScope = this.fundRiskScope === value ? '' : value; + this.page.currentPage = 1; + this.onLoad(this.page, this.query); + }, + refreshFundRiskStats(params) { + this.api.getFundRiskStats(params).then(res => { + const stats = res.data?.data || {}; + this.fundRiskStats = { ...this.fundRiskStats, ...stats }; + }); + }, selectionChange(list) { this.selectionList = list; }, @@ -2474,8 +2529,40 @@ export default { .project-apply-page-form { min-height: 100%; margin-bottom: 60px; - //padding: 20px 24px 92px; - //background: #f5f6fa; +} + +.project-apply-page { + &__risk-search { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-top: 8px; + margin-bottom: 16px; + + :deep(.el-check-tag) { + font-weight: 500; + } + } + + &__risk-label { + display: inline-flex; + align-items: center; + color: #606266; + font-weight: 600; + margin-right: 4px; + } + + &__risk-help { + margin-left: 4px; + color: #a8abb2; + cursor: help; + font-size: 14px; + } + + &__risk-none { + color: #c0c4cc; + } } .project-apply-dialog__footer { diff --git a/src/views/business/temporary-credit-limit.vue b/src/views/business/temporary-credit-limit.vue index 44747e8..a4e923f 100644 --- a/src/views/business/temporary-credit-limit.vue +++ b/src/views/business/temporary-credit-limit.vue @@ -50,11 +50,13 @@ + 项目额度信息 + @@ -822,7 +824,7 @@ export default { .el-form-item__label { height: auto !important; line-height: 18px; - padding-top: 7px; // 视觉补偿:让单行 label 仍接近 input 中线 + //padding-top: 7px; // 视觉补偿:让单行 label 仍接近 input 中线 } // 强制必填星号垂直居中于行盒,与第一行汉字字符中心同基线(关键) @@ -841,4 +843,8 @@ export default { .el-form-item--default .el-form-item__label { height: auto !important; } +.el-dialog .avue-form{ + padding: 16px 16px 4px !important; +} +
非必填;配置付款比例时,合计必须等于100%。