Merge remote-tracking branch 'websoft/master'

# Conflicts:
#	src/views/business/components/business-crud-page.vue
#	src/views/business/contract-manage.vue
#	src/views/settlement/components/pre-settlement-editor.vue
#	src/views/settlement/formal-settlement.vue
This commit is contained in:
2026-08-22 21:12:26 +08:00
31 changed files with 1098 additions and 616 deletions
+132
View File
@@ -1,5 +1,21 @@
# 2026-08-21
## temporary-credit-limit 详情弹窗上下居中(align-center
- 需求:`/business/temporary-credit-limit` 详情弹窗(detailBox,走 business-crud-page 自定义详情)上下居中。
- 机制:Element Plus el-dialog 的 `align-center` prop —— use-dialog 源码确认:align-center 时 `.el-overlay-dialog``display:flex``.el-dialog.is-align-center { margin:auto }`,实现垂直+水平居中;此时 top prop 设置的 `--el-dialog-margin-top` 被更高特异性的 `margin:auto` 覆盖,不生效。
- 改动(3 处,开关式不影响其他页):
1. `src/views/business/components/business-crud-page.vue` 详情 el-dialog2504 行)加 `:align-center="detailAlignCenter"`
2. 同文件新增 computed `detailAlignCenter()``this.config.detailAlignCenter === true`(默认 falsecontract/waybill 等共用详情弹窗不受影响)。
3. `src/option/business/temporary-credit-limit.js` config 加 `detailAlignCenter: true`
- 安全性:全局 `.el-dialog__body { max-height: calc(100vh - 200px); overflow-y:auto }`element-ui.scss 182 行)保证内容超高时在 body 内滚动,dialog 总高 < 视口,flex 居中不会顶部裁切。
- 注意:element-plus 实际版本是 2.14.2package.json 声明 ^2.10.1,锁到 2.14.2)。el-dialog 的 class 经 $attrs 透传最终落在 `div.el-dialog` 根元素(ElDialogContent 未设 inheritAttrs:false);因此 scoped 里 `&__detail-dialog.is-transport-plan-detail { :deep(.el-dialog){...} }` 的 max-width/margin 规则实际选不到自身(仅 header/body 后代规则生效)——历史遗留,未处理。
- 验证:`node --check` 通过;Vite 2889 编译两个改动文件均 HTTP 200。
## temporary-credit-limit 弹窗宽度 1400px → 96%
- 需求:`/business/temporary-credit-limit` 新增/编辑弹窗加宽到 96%。
- 改动(单文件 `src/option/business/temporary-credit-limit.js`):`dialogWidth: 1400``dialogWidth: '96%'`
- 先例:waybill-manage / transport-plan 均为 `dialogWidth: '96%'`shipping-template 为 `'92%'`。business-crud-page 对 dialogWidth 无特殊处理(仅独立表单页强制 100%),百分比直接透传 avue-crudelement-ui.scss 的 `.temporary-credit-limit-dialog` 只覆 label 宽度、不覆盖 el-dialog 宽度,无冲突。
## process-config 弹窗卡片间隔(对齐 project-apply 风格)
- 需求:process-config 新增/编辑弹窗内三张 `<section-card>`(基本信息 / 现行过程节点 / 节点设置)要像 project-apply 表单页一样独立成卡、卡间露出灰底(12px)。
- 根因:全站规则 `.el-dialog .avue-form { background:#fff; padding:8px 16px 4px }`element-ui.scss ~246 行)把 Avue 弹窗表单整体包成一张白卡,section-card 白卡叠在其上看不出间隔。
@@ -58,3 +74,119 @@
- **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 错误。
## 综合台账弹窗 & 临时额度查看弹窗 布局调整
- vehicle-ledger / ship-ledger/transportCapacity 车辆·船舶综合台账弹窗):把 `<el-table>`+分页从 `el-tabs` 外移入每个 `<el-tab-pane>` 内(切换 tab 表格跟着切换);ship-ledger 同步把三个蓝标内容块(船舶基本信息/船舶证书信息/证书附件)由 `<section>`+`.dialog-section-title` 改为全局 `<section-card :title>`,卡片间距 12px。两文件均加 `:deep(.el-tabs__content){padding:0}` 去 border-card 默认内边距。
- 临时额度查看弹窗改两列(/business/temporary-credit-limit):
- 原"查看"调 Avue 内置 `rowView`(列数复用 column.span,呈 3 列)。改用 business-crud-page 内置自定义详情弹窗。
- `src/option/business/temporary-credit-limit.js` config 加 `detailButton:true``detailButtonIgnoreDraftStatus:true``detailSections:[申请信息(申请单号/备注 span:2 整行,其余两列)、额度信息]`
- `business-crud-page.vue``showDetailButton``if(this.config.detailButtonIgnoreDraftStatus) return true`(仅放开草稿态也走自定义详情,不动其他页面)。
## contract-manage/form 独立表单页也加 archive-page-form__title
- 需求:`/business/contract-manage/form?mode=add&name=新运费合同管理` 也要同样顶部页面标题。
- 改动:`business-crud-page.vue``formPageTitle()` 条件再并入 `isContractConfig``config.permission==='contract_manage'`contract-manage 已用 `contract-form-page` prop 走 `PageAvueForm`)。标题取 `query.name`(「新运费合同管理」)或回退「新增/编辑合同管理」。
- 验证:Vite 2889 编译通过(HTTP 200,无错误),HMR 生效。至此 business-crud-page 系列(shipping-template / transport-plan / waybill-manage / contract-manage)独立表单页均已带统一标题。
- 渲染走 `detailSections` 分支(`section-card` + `el-descriptions(:column=2)`),值经 `formatDetailValue` 转字典 label。
- 验证:`temporary-credit-limit.js``node --check` 语法通过;business-crud-page.vue 仅 template 表达式改动。待用户在 2889 刷新核对两列效果。
## 合同管理详情弹窗 label 对齐修复
- 需求:`/business/contract-manage` 详情弹窗里两列 `el-descriptions` 的 label(尤其右边列 label)对齐不一致,长 label(如"是否需要加盖法人章")换行后行高/垂直位置错乱。
- 根因:`business-crud-page.vue``&__detail-content .section-card` 中对 `.el-descriptions__label` 只写了 `width: 140px``text-align: right`,缺 `min/max-width``vertical-align``line-height``box-sizing`;对 `.el-descriptions__content` 也缺垂直对齐,导致跨列/跨行 label 视觉上不齐。另外 `contract-manage.vue` 底部的 `<style>`(未加 `scoped`)把 `.el-dialog .el-form-item__label``.el-col` 全局覆盖,会污染全站弹窗。
- 改动:
1. `src/views/business/components/business-crud-page.vue`15440 行附近):
- label 与 content 统一加 `box-sizing:border-box``vertical-align:middle``line-height:1.4`、一致 paddinglabel 加 `min-width:140px; max-width:140px` 防止被内容撑开或压缩,content 保持 `min-height:42px`
- **新增 `:deep(.el-descriptions__table) { width:100%; table-layout:fixed }`**:强制表格按固定列宽渲染,label 占 140px 后,左右两个 content 列均分剩余宽度,解决用户截图里"合同期限"卡片左右内容区宽度不一致的问题。
2. `src/views/business/contract-manage.vue`:把底部无 scoped 的全局样式改为 `<style scoped>`,并把选择器限定到 `.contract-manage-dialog`;删除污染性更强的 `.el-col { margin-bottom:0 !important }` 全局覆盖。
- 验证:Vite 2889 编译通过(HTTP 200),HMR 生效。待用户在 2889 刷新核对详情弹窗两列 label 是否左右/上下对齐、左右内容区宽度是否均等。
## 应付明细调整弹窗:控件撑满单元格 + 表头居中
- 需求:`/settlement/payable-detail`(即 `receivable-payable-detail.vue`)"调整费用"弹窗中,里程 / 杂项 / 装卸费等列的 `el-input-number` 默认宽度 180px,超出窄列后被截断(见截图);同时要求所有表头居中。
- 改动(单文件 `src/views/settlement/receivable-payable-detail.vue`):
1. 给调整弹窗 `<el-table>``class="settlement-detail-page__adjust-table"`
2. 页面级表格样式 `.settlement-detail-page__table, .settlement-detail-page :deep(.el-table)``th.el-table__cell` 增加 `text-align: center !important;`,统一所有表头居中(覆盖金额列 `align: right` 导致的表头右偏)。
3. 新增 `.settlement-detail-page__adjust-table` 样式块:`.el-input-number``.el-input``.el-select` 全部 `width: 100% !important;`,使控件撑满单元格不被截断;`el-input-number` 内部输入保持右对齐。
- 验证:Vite 2889 编译通过(HTTP 200),HMR 生效。待用户在 2889 刷新核对调整弹窗输入框是否完整显示、表头是否全部居中。
## formal-settlement:把表格放进 tabs
- 需求:`/settlement/formal-settlement` 原本 `el-tabs`(应付/应收) 是空的 type 切换器,表格+工具栏在 tabs 外面;要求把表格放进 tabs 内。
- 改动:
1. 新建 `src/views/settlement/components/formal-settlement-table-panel.vue`:把工具栏(新增/付款申请/同步金蝶/打印结算单/导出 + 刷新) + `el-table` + 分页整体抽成子组件;props 收 `rows/columns/loading/page/hasPermission`emits `create/payment/sync/export/refresh/page-change/update:selection/action`;行操作(查看/编辑/删除/提交/通过/驳回/作废)经 `rowActions(row)` 计算可见项后 emit `action {type,row}`;工具栏"打印"经 `emitSingle('print')` 取选中单行后 emit action(与父 `selectedOne` 行为一致)。
2. `src/views/settlement/formal-settlement.vue`:删除原 toolbar+table+pagination 块与对应 scoped 样式(`__toolbar/__toolbar-right/__links/__pagination`),在 `el-tabs` 两个 `el-tab-pane` 内各放一个 `<formal-settlement-table-panel>`DRY,共用同一组件);新增 `handlePageChange({current,size})``handleAction({type,row})` 分发行操作;`handlePrint(row)` 改为接收行参数(不再用 selectedOne);删除无用 `handleSizeChange`
- 验证:Vite build 通过(EXIT=0),无编译错误。待用户在 2889 刷新核对:应付/应收两个 tab 各自带完整表格与工具栏、切换 tab 触发 reload。
## 预结算单结算汇率输入框居左
- 需求:`/settlement/pre-settlement` 弹窗中「结算汇率」字段(el-input-number)默认居中,要求改为居左。
- 改动:
1. `src/option/settlement/preSettlementForm.js``exchangeRate` 字段新增 `class: 'exchange-rate-input'`
2. `src/views/settlement/components/pre-settlement-editor.vue`
- `el-input-number` 上绑定 `:class="field.class"`
- scoped style 的 `&__form` 内增加 `:deep(.exchange-rate-input .el-input__inner) { text-align: left; }`
- 影响:仅针对「结算汇率」字段,不改动其他数字输入框对齐方式。
## transport-reconciliation:同样把表格放进 tabs
- 需求:`/settlement/transport-reconciliation` 与 formal-settlement 同样问题——`el-tabs`(应付/应收) 是空 type 切换器、工具栏+表格在 tabs 外;要求把表格放进 tabs 内。
- 改动:
1. 新建 `src/views/settlement/components/transport-reconciliation-table-panel.vue`:工具栏(新增/导出+刷新)+`el-table`+分页抽成子组件;props 同 formal 面板,emits `create/export/refresh/page-change/update:selection/action`;列渲染差异:`.link` 列点击 emit view、`reconciliationStatusName``row.reconciliationStatus==='completed'?'success':'warning'` 的 el-tag;行操作 `rowActions(row)` 为 查看(常显)+编辑/删除/确认(均 `hasPermission && isEditable(row)`isEditable=`reconciliationStatus==='unfinished'`),确认非 danger。
2. `src/views/settlement/transport-reconciliation.vue`:删除原 toolbar+table+pagination 块与对应 scoped 样式(`__toolbar/__toolbar-right/__links/__pagination`),在两个 `el-tab-pane` 内各放一个面板;保留 `.reconciliation-page__settlement-tabs`(含 `:deep(.el-tabs__header){margin:0}`);新增 `handlePageChange``handleAction({type,row})`(view→openView/edit→openEdit/delete→handleDelete/complete→handleComplete);删除 `handleSizeChange`。导出按钮无权限守卫、仍 emit `export` 由父 `handleExport`(用 `this.settlementType`)处理。
- 验证:Vite build 通过(EXIT=0)。待用户在 2889 刷新核对应付/应收 tab 各自带完整表格与工具栏、切换触发 reload。
## process-config 弹窗重写为手写 el-dialog(对齐 credit-score-quantification 风格)
- 原因:用户参考 `/vehicle/credit-score-quantification` 的弹窗(手写 `el-dialog` + 多个 `<section-card>` 堆叠、卡间 12px、底部 #footer 按钮),要求 process-config 弹窗同样"白底卡片包住蓝标内容块、卡间距 12px、风格统一"。原实现用的是 Avue 内置 dialog + formslot,与参考页机制不一致(即使视觉接近)。
- 改动(3 文件):
1. `src/views/business/process-config.vue`:删除 Avue 内置 dialog 相关(`before-open``#basicInfo-form`/`#nodeConfigJson-form`/`#menu-form-before` 插槽、`rowSave/rowUpdate/submitRow/stopSubmitLoading/handleProcessConfigSave`、数据项 `submitStatus`);新增手写 `<el-dialog class="process-config-dialog" append-to-body width="96%" top="4vh" :close-on-click-modal="false">`,内部 `<div class="process-config-form">` 堆三张 `<section-card>`:基本信息 / 现行过程节点(timeline)/ 节点设置(node 表);`#footer` 放 取消 / 保存草稿(plain) / 保存并启用(primary)`submitting` 控制 loading。新增 `openConfig(row, readonly)``submitConfig(mode)``handleDialogClosed`,保留全部节点配置逻辑(NODE_TEMPLATES/cloneNode/parseNodeConfig/syncNodeFields/timelineDescriptions/validateRow/checkEnabledProjectBeforeSubmit/confirmDuplicateVoucher 等)。菜单 查看→`openConfig(row,true)`、编辑→`openConfig(row)`、新建→`openConfig()`
2. `src/option/business/process-config.js`option 删除仅作表单容器的 `basicInfo`/`nodeConfigJson` 两列与 `saveBtn/updateBtn/dialogTop/dialogWidth/dialogCustomClass` 等 dialog 配置。
3. `src/styles/element-ui.scss`:删除原先 `.process-config-dialog .avue-form / .avue-form__group / .avue-dialog__footer` 三处透明化/归零规则(已无用),改为 `.process-config-dialog .el-dialog__body { max-height: calc(100vh - 130px) }`
- 关键点:弹窗内容走 `append-to-body` 会被 teleport 出 `.process-config-page`,故所有弹窗内样式(timeline/node-table/require-*/finish-days)改用顶层 scoped 类 `.process-config-form__*`(不再嵌套在 `.process-config-page` 下),否则 scoped 选择器因缺祖先类而失效。12px 卡距由全局 `.section-card{margin-bottom:12px}` + 全局灰底 `.el-dialog__body{background:#f2f2f2;padding:12px}` 保证,与参考页一致。
- 验证:Vite build 通过(EXIT=0),无编译错误;2889 HMR 生效。待用户刷新核对弹窗卡片风格。
## 多页面「基本信息」类分区统一改为 4 列(span 8→6)
- 用户逐一要求把各结算/合同页的「基本信息」分区从 3 列改为 4 列。列数由 el-col `:span` 控制(24/8=3 → 24/6=4)。
- 手写 el-col 循环页面(直接改 span):
- `src/views/settlement/components/formal-settlement-editor.vue``:span="8"``:span="6"`(结算基本信息)。
- `src/views/settlement/components/transport-reconciliation-editor.vue``:span="8"``:span="6"`(导入外部账单核对)。
- `src/views/settlement/components/settlement-adjustment-editor.vue``:span="8"``:span="6"`(基本信息)。
- `src/views/settlement/components/pre-settlement-editor.vue`:动态 `:span="field.span || 8"``|| 6`(仅备注显式 span:24 仍整行)。
- `src/views/business/contract-manage-change.vue``基本信息``:span="8"``:span="6"`(变更类型/备注 `:span="24"` 保持整行)。
- Avue 分组表单页面(合同新增/编辑 form 页):非手写循环,`option.span=8` 控制默认列数。
- 改动 `src/views/business/components/business-crud-page.vue``buildFormGroupOption`:当 `this.isContractConfig` 时,对首组(基本信息,=groups[0] 顶层 column)每个非分区标题、非 `span===24` 的列设 `col.span = 6`;备注(span:24)与分区标题列保持原样。其余分组仍 3 列。
- 验证:2889 HMR 生效。注意合同 form 页走 Avue group,不能用简单 span 替换,须改 buildFormGroupOption。
## 续:更多页面「基本信息」类分区改 4 列(span 8→6)
- `src/option/business/temporary-credit-limit.js`Avue 配置页,扁平 column 列表,8 个表单字段 `span:8``span:6`(备注/附件 span:24 保持整行)。注意 createCrudOption 不设默认 span,可见字段都有显式 span。
- `src/views/transportCapacity/ship.vue`:「基础船舶信息」10 个 `:span="8"``:span="6"`;该页 12 空格缩进=基础船舶信息区、14 空格缩进=船舶证书信息区,**勿用全局 replace_all**12 空格串是 14 空格的子串会误伤证书区),必须按行号区间 sed(146,253)或带 label 上下文逐个替换。
- `src/views/business/project-apply.vue``.project-apply-form__grid` `repeat(3,...)``repeat(4,...)`label 规则(作用域 `__grid` 内)`width:100px !important; min-width:0 !important; white-space:normal`——6 汉字宽超出换行,`!important` 压过 label-width="auto" 的行内 width 与全局 min-width:160px。
## ship 页 label 规则:最多 6 汉字宽、超出换行
- `src/views/transportCapacity/ship.vue`el-form `label-width`(原 HEAD 为 auto,工作区历史为 150)→ `100`6 汉字≈84px+冒号≈98px100px 恰容 6 字);`.ship-form` 内新增 `:deep(.el-form-item__label) { white-space: normal; line-height: 20px }` 允许换行。适用整个 ship 表单(含证书区)。
- 注意:改该文件前先确认 git 暂存区状态,`git checkout -- <file>` 恢复的是 index 版本;本文件 index=HEAD=auto,工作区曾为 150(历史未提交改动),已被本次需求覆盖。
## master-order-dispatch 起-终路由条左侧节点被裁切修复
- 问题:`/business/master-order?mode=dispatch` 顶部 overview 的路由节点条(起/经/终),最左侧"起(梧州段)"被裁掉半字。根因:`.route-overview``justify-content: flex-end` 右对齐 + `overflow-x:auto`,节点总宽(4×130+3×56=688px)超出右侧栏可用宽度时,溢出从左侧跑出可视区,且节点 `flex:0 0 130px` 写死、长名字会撑爆。
- 改动(`src/views/business/components/master-order-dispatch.vue` scoped 样式,3 处):
1. `.route-overview``justify-content:flex-end``flex-start`padding 改为 `8px 24px 0`(左右留白,滚动时两端节点完整可见)。
2. `.route-overview__node``flex:0 0 130px``flex:0 0 auto; min-width:130px; max-width:200px; padding:0 4px`strong/small 加 `max-width:100%; overflow:hidden; text-overflow:ellipsis`(长名字省略号不撑爆)。
3. `.route-overview__line``flex:1 0 56px; min-width:56px``flex:0 1 56px; min-width:24px`(line 可收缩,滚动兜底更平滑)。
- 说明:1200px media query 里原有 `justify-content:flex-start` 现与默认一致,冗余但无害,保留未删。仅样式改动,无 JS 逻辑变化。
## master-order-dispatch 路由节点地址/联系人加 tooltip(hover 提示完整内容)
- 需求:起-终节点条地址过长截断("九头山货..."等)时,鼠标移上去能看到完整内容。
- 改动(`src/views/business/components/master-order-dispatch.vue` 模板,21-45 行):起/经、终节点各自的 strong(名称)+ 两个 small(地址、联系人)共 6 个文本元素分别用 `<el-tooltip placement="top" :show-after="120">` 包裹,`:content` 与原文本一致;保持原有 `text-overflow:ellipsis` 截断视觉不变。
- `:show-after="120"` 让鼠标停留 120ms 才弹,避免快速划过连续触发多个 tooltip;`placement="top"` 配合节点垂直居中位置弹出,体验稳定。
- 风险:仅模板增加 6 个 el-tooltip 实例,无 JS/style 改动;与之前段头 `el-tooltip`segment-checkbox-label)用法一致。
## shipping-template(发货模板)表单页:模板信息+基本信息 4 列 + label 统一宽度
- 需求:`/business/shipping-template/form` 的「模板信息」「基本信息」两组改 4 列;左侧 label 宽度一致右对齐;全站统一(6 汉字宽、超出换行)。
- 改动 3 处:
1. `business-crud-page.vue` `buildFormGroupOption` 新增 `isShippingTemplatePage` 分支:对 groups[0](模板信息)、groups[1](基本信息)的非标题、非 span24 列设 `span:6`(该页分组:templateInfoTitle/basicInfoTitle/shippingInfoTitle/goodsInfoTitle/attachmentTitle 均已在 sectionTitleProps,发货信息及之后组保持原样)。
2. `src/option/business/shipping-template.js`option 顶层加 `labelWidth: 100`(覆盖 createCrudOption 的 'auto',全表单 label 一致 100px;右对齐已由 labelPosition:'right' 保证)。
3. `src/styles/element-ui.scss` `.shipping-template-dialog .el-form-item__label { white-space: normal; line-height: 20px }`(长 label 换行,独立表单页容器也是 div.shipping-template-dialogclass 命中)。
- 模式沉淀:contractisContractConfig 首组)/ shipping-template(前两组)都走 buildFormGroupOption 统一处理;普通 el-col 循环页直接改 spanCSS grid 页改 repeat 数 + label 规则。4 列=span 66 汉字=100px。
## transport-plan / waybill-manage 表单页:整表单 4 列 + label 统一(全站统一)
- `business-crud-page.vue` `buildFormGroupOption` 重构:抽取 `setFourColumns(group)` helper(非标题、非 span24 列设 span:6);contract→首组、shipping-template→前两组、transport_plan/waybill_manage→**所有分组**统一 4 列(这两页很多字段无显式 span 会落到 Avue 默认 12=2 列,是之前不 4 列的原因)。
- `waybill-manage.js` option 加 `labelWidth:100`transport-plan.js 上轮已加);element-ui.scss 追加 `.waybill-manage-dialog .el-form-item__label` 换行规则(transport-plan-dialog 上轮已加)。
- 经验:Avue 表单「没 4 列」先查是否有字段漏设 span(默认 12);统一 4 列优先走 buildFormGroupOption 的页面级分支,避免逐个字段改。
## ⚠️ option.labelWidth 必须放在 createCrudOption 展开之后
- 第一次给 transport-plan.js、waybill-manage.js 加 `labelWidth: 100` 时写在 `...createCrudOption(...)` **之前**,被 createCrudOption 自带 `labelWidth: 'auto'` 覆盖,label 仍是 auto(按内容参差,看着像"150 不统一")。shipping-template 当时写在 createCrudOption 之后才生效。
- 教训:业务 option 文件顶层 `export const option = { labelWidth: 100, ...createCrudOption([...]) }` 写法**无效**,必须 `export const option = { ...createCrudOption([...]), labelWidth: 100, dialogWidth, ... }` 后写覆盖前写。
+2 -1
View File
@@ -28,7 +28,7 @@
### 独立表单页标题 `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'`)。
- **business-crud-page 系列**shipping-template / transport-plan / waybill-manage / contract-manage 独立表单页):`PageAvueForm`render `h('div',{class:dialogCustomClass},[titleDiv, avue-form])`)声明 `props.formPageTitle`,模板 `:form-page-title="isStandaloneFormPage ? formPageTitle : undefined"`computed `formPageTitle()``isStandaloneFormPage && (isShippingTemplatePage || isTransportPlanPage || isWaybillDetailLayout || isContractConfig)` 时返回 `this.$route.query.name || '新增'/'编辑'+config.title``isWaybillDetailLayout` = `config.permission==='waybill_manage'``isContractConfig` = `config.permission==='contract_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 模式)。
@@ -47,3 +47,4 @@
- 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 组白卡。
- **查看详情弹窗(detailBox)自定义机制**:列表"查看/详情"按钮默认调 Avue 内置 `rowView`(列数复用 column.span,常呈 3 列)。要改成自定义两列白卡详情,在页面 `config``detailButton:true` + `detailSections:[{title, fields:[[prop,label,span],...]}]`,组件渲染 `section-card` + `el-descriptions(:column=2)`field 默认占 1 列,span:2 整行)。数据经 `api.getDetail(row.id)` 拉取,值用 `formatDetailValue` 经 column.dicData 转字典显示 label。开关:`showDetailButton` 默认仅非草稿态显示"详情"(草稿态仍走 rowView 3 列);加 `config.detailButtonIgnoreDraftStatus:true` 可让所有状态(含草稿)都走自定义两列详情。statusProp='approvalStatus' 的页面草稿值须为 'draft'。
-19
View File
@@ -21,13 +21,6 @@ export const config = {
export const option = {
...createCrudOption([
{
label: '',
prop: 'basicInfo',
formslot: true,
span: 24,
hide: true,
},
{
label: '配置名称',
prop: 'configName',
@@ -167,22 +160,10 @@ export const option = {
dicData: [],
filterable: true,
},
{
label: '',
prop: 'nodeConfigJson',
formslot: true,
span: 24,
hide: true,
},
]),
saveBtn: false,
updateBtn: false,
addBtn: false,
viewBtn: false,
editBtn: false,
delBtn: false,
dialogTop: '4vh',
dialogWidth: '96%',
dialogCustomClass: 'process-config-dialog',
menuWidth: 220,
};
+2
View File
@@ -275,6 +275,8 @@ export const option = {
),
dialogWidth: '92%',
dialogTop: '4vh',
// label 宽度一致(容纳 6 汉字,超出换行由全局样式控制),右对齐由 labelPosition: 'right' 保证
labelWidth: 100,
// 自定义弹窗 class,用于让新增/编辑弹窗体占满视口高度、视觉居中(参照 vehicle/customer-archive
dialogCustomClass: 'shipping-template-dialog',
};
+39 -9
View File
@@ -45,6 +45,36 @@ export const config = {
editStatus: ['draft', 'withdrawn', 'rejected', '草稿', '已撤回'],
ignoreReadonlyEditStatus: ['draft', 'withdrawn', '草稿', '已撤回'],
ignoreReadonlyDeleteStatus: ['draft', '草稿'],
detailButton: true,
detailButtonIgnoreDraftStatus: true,
detailAlignCenter: true,
detailSections: [
{
title: '申请信息',
fields: [
['applicationNo', '申请单号', 2],
['projectName', '项目', 1],
['projectCode', '项目编号', 1],
['undertakeDeptName', '承办部门', 1],
['applyDeptName', '申请部门', 1],
['applicantName', '申请人', 1],
['approvalStatus', '审批状态', 1],
['currentNode', '当前节点', 1],
['currentProcessor', '当前处理人', 1],
['validUntil', '申请有效期至', 1],
['remark', '备注', 2],
],
},
{
title: '额度信息',
fields: [
['projectFundLimit', '项目资金使用额度(万元)', 1],
['usedFundLimit', '已使用项目资金使用额度(万元)', 1],
['remainingFundLimit', '剩余项目资金使用额度(万元)', 1],
['applyLimit', '申请临时额度(万元)', 1],
],
},
],
operations: [
{
action: 'submitApproval',
@@ -113,7 +143,7 @@ export const option = {
value: 'projectName',
},
filterable: true,
span: 8,
span: 6,
minWidth: 180,
rules: textRule('项目名称', 100, true),
},
@@ -128,7 +158,7 @@ export const option = {
prop: 'projectCode',
hide: true,
disabled: true,
span: 8,
span: 6,
minWidth: 150,
rules: textRule('项目编号', 50),
},
@@ -137,7 +167,7 @@ export const option = {
prop: 'undertakeDeptName',
hide: true,
disabled: true,
span: 8,
span: 6,
minWidth: 150,
rules: textRule('承办部门', 50, true),
},
@@ -146,7 +176,7 @@ export const option = {
prop: 'projectFundLimit',
hide: true,
disabled: true,
span: 8,
span: 6,
minWidth: 200,
rules: [nonNegativeRule('项目资金使用额度')],
},
@@ -155,7 +185,7 @@ export const option = {
prop: 'usedFundLimit',
hide: true,
disabled: true,
span: 8,
span: 6,
minWidth: 220,
value: 0,
rules: [nonNegativeRule('已使用项目资金额度')],
@@ -165,7 +195,7 @@ export const option = {
prop: 'remainingFundLimit',
hide: true,
disabled: true,
span: 8,
span: 6,
minWidth: 230,
value: 0,
rules: [nonNegativeRule('剩余项目资金使用额度')],
@@ -174,7 +204,7 @@ export const option = {
label: '申请临时额度(万元)',
prop: 'applyLimit',
formslot: true,
span: 8,
span: 6,
minWidth: 180,
rules: [
{ required: true, message: '请输入申请临时额度', trigger: 'blur' },
@@ -187,7 +217,7 @@ export const option = {
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
span: 8,
span: 6,
minWidth: 140,
rules: [{ required: true, message: '请选择申请有效期至', trigger: 'change' }],
},
@@ -274,7 +304,7 @@ export const option = {
},
...auditColumns.map(column => ({ ...column, hide: true })),
]),
dialogWidth: 1400,
dialogWidth: '96%',
menuFixed: 'right',
menuWidth: 320,
index: false,
+2
View File
@@ -605,6 +605,8 @@ export const option = {
},
])
),
// label 固定宽度(容纳 6 汉字),覆盖 createCrudOption 默认 labelWidth:'auto'
labelWidth: 100,
dialogWidth: '96%',
dialogTop: '4vh',
dialogCustomClass: 'transport-plan-dialog',
+2
View File
@@ -999,6 +999,8 @@ export const option = {
},
])
),
// label 固定宽度(容纳 6 汉字),覆盖 createCrudOption 默认 labelWidth:'auto'
labelWidth: 100,
dialogWidth: '96%',
dialogTop: '4vh',
menuWidth: 280,
+1 -1
View File
@@ -13,7 +13,7 @@ export const preSettlementFormFields = [
{ label: '结算类型', prop: 'settlementType', readonly: true },
{ label: '结算金额', prop: 'settlementAmount', readonly: true, money: true },
{ label: '汇率日期', prop: 'exchangeRateDate', type: 'date' },
{ label: '结算汇率', prop: 'exchangeRate', type: 'number' },
{ label: '结算汇率', prop: 'exchangeRate', type: 'number', class: 'exchange-rate-input' },
{ label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true },
{ label: '创建人', prop: 'createUserName', readonly: true },
{ label: '创建日期', prop: 'createTime', readonly: true },
@@ -42,11 +42,11 @@ export const tableColumns = [
{ label: '运输类型', prop: 'transportType', minWidth: 120 },
{ label: '货物名称', prop: 'cargoName', minWidth: 130 },
{ label: '货物类型', prop: 'cargoType', minWidth: 120 },
{ label: '运输总量', prop: 'transportQuantity', minWidth: 120, align: 'right' },
{ label: '里程(KM', prop: 'mileage', minWidth: 120, align: 'right' },
{ label: '运输总量', prop: 'transportQuantity', minWidth: 120 },
{ label: '里程(KM', prop: 'mileage', minWidth: 120 },
{ label: '批次号', prop: 'batchNo', minWidth: 120 },
{ label: '运输单价', prop: 'unitPriceText', minWidth: 120, align: 'right' },
{ label: '费用合计', prop: 'totalAmountText', minWidth: 130, align: 'right' },
{ label: '运输单价', prop: 'unitPriceText', minWidth: 120 },
{ label: '费用合计', prop: 'totalAmountText', minWidth: 130 },
{ label: '状态', prop: 'settlementStatusName', minWidth: 120 },
{ label: '创建人', prop: 'createUserName', minWidth: 120 },
{ label: '创建时间', prop: 'createTime', minWidth: 170 },
@@ -61,15 +61,16 @@ export const feeDetailBaseColumns = [
{ label: '运费计费类型', prop: 'billingType', minWidth: 130 },
{ label: '运输量', prop: 'transportQuantityText', minWidth: 120 },
{ label: '运费计算单位', prop: 'priceUnit', minWidth: 130 },
{ label: '运输单价', prop: 'unitPrice', minWidth: 120, align: 'right' },
{ label: '里程(KM', prop: 'mileage', minWidth: 120, align: 'right' },
{ label: '运输费', prop: 'freightAmount', minWidth: 120, align: 'right' },
{ label: '运输单价', prop: 'unitPrice', minWidth: 120 },
{ label: '里程(KM', prop: 'mileage', minWidth: 120 },
{ label: '运输费', prop: 'freightAmount', minWidth: 180 }
];
export const feeDetailTailColumns = [
{ label: '原总金额', prop: 'originalAmountText', minWidth: 120, align: 'right' },
{ label: '调整金额', prop: 'adjustAmountText', minWidth: 120, align: 'right' },
{ label: '调整后总金额', prop: 'afterAmountText', minWidth: 140, align: 'right' },
{ label: '原总金额', prop: 'originalAmountText', minWidth: 120 },
{ label: '调整金额', prop: 'adjustAmountText', minWidth: 120 },
{ label: '调整后总金额', prop: 'afterAmountText', minWidth: 140 },
{ label: '备注', prop: 'remark', minWidth: 160 },
{ label: '最后录入人', prop: 'updateUserName', minWidth: 120 },
{ label: '最后录入时间', prop: 'updateTime', minWidth: 170 },
@@ -116,10 +117,10 @@ export const generateWaybillColumns = [
{ label: '发货联系人', prop: 'departureContact', minWidth: 130 },
{ label: '到货地址', prop: 'arrivalAddress', minWidth: 220 },
{ label: '收货联系人', prop: 'arrivalContact', minWidth: 130 },
{ label: '单价', prop: 'unitPrice', minWidth: 110, align: 'right' },
{ label: '运费', prop: 'freight', minWidth: 110, align: 'right' },
{ label: '其他费用合计', prop: 'otherFeeTotal', minWidth: 140, align: 'right' },
{ label: '运费合计', prop: 'freightTotal', minWidth: 120, align: 'right' },
{ label: '单价', prop: 'unitPrice', minWidth: 110 },
{ label: '运费', prop: 'freight', minWidth: 110 },
{ label: '其他费用合计', prop: 'otherFeeTotal', minWidth: 140 },
{ label: '运费合计', prop: 'freightTotal', minWidth: 120 },
{ label: '客户合同', prop: 'contractName', minWidth: 160 },
{ label: '发货计划', prop: 'planName', minWidth: 140 },
{ label: '运输批次', prop: 'batchNo', minWidth: 140 },
@@ -1,6 +1,6 @@
export const settlementAdjustmentFormFields = [
{ label: '单据号', prop: 'adjustmentNo' },
{ label: '* 关联结算单号', prop: 'formalSettlementId' },
{ label: '关联结算单号', prop: 'formalSettlementId' },
{ label: '客户/客商名称', prop: 'customerName' },
{ label: '结算单类型', prop: 'settlementTypeName' },
{ label: '关联合同名称', prop: 'contractName' },
+27 -24
View File
@@ -37,7 +37,7 @@
}
.el-col {
margin-bottom: 8px;
margin-bottom: 0 !important;
}
.el-main {
@@ -510,6 +510,24 @@
background: #fff;
}
// 发货模板 label 固定宽度(option labelWidth:100 容纳 6 汉字),超出自动换行
.shipping-template-dialog .el-form-item__label {
white-space: normal;
line-height: 20px;
}
// 运输计划 label 固定宽度(option labelWidth:100 容纳 6 汉字),超出自动换行
.transport-plan-dialog .el-form-item__label {
white-space: normal;
line-height: 20px;
}
// 运单管理 label 固定宽度(option labelWidth:100 容纳 6 汉字),超出自动换行
.waybill-manage-dialog .el-form-item__label {
white-space: normal;
line-height: 20px;
}
// 运输计划新增/编辑弹窗:参照 vehicle/customer-archive,弹窗体接近全屏(上下各留 20px 边距);
// 同时覆盖 Avue 库内置 .avue-dialog 的 max-height: calc(100% - 200px) 上限,
// 改由 flex 控制,底部操作栏(avue 内置 footer,渲染在 body 内部)sticky 贴底始终可见
@@ -607,26 +625,11 @@
background: #fff;
}
// 过程配置新增/编辑弹窗:avue-form 整体白卡透明化,
// 让 <section-card> 分组白卡(基本信息 / 现行过程节点 / 节点设置)独立成卡、卡间露出灰底
// 与 business/project-apply 表单页的分组卡片风格统一
.process-config-dialog .avue-form {
background: transparent;
box-shadow: none;
padding: 0;
}
// formslot 外层 el-form-item 默认 margin-bottom 与 section-card 自身 margin 叠加会拉大卡距,
// 归零后卡间距统一为 section-card 自身的 12px(与 project-apply 一致)
.process-config-dialog .avue-form__group > .el-col > .el-form-item {
margin-bottom: 0;
}
// 抵消全局 .avue-crud__dialog .avue-dialog__footer 的 -30px 上移
// avue-form 已透明化,footer 若上移会压住最后一张分组卡片)
.process-config-dialog .avue-dialog__footer,
.avue-crud__dialog .process-config-dialog .avue-dialog__footer {
margin-top: 0 !important;
// 过程配置新增/编辑/查看弹窗(手写 el-dialog,参照 vehicle/credit-score-quantification):
// 灰底弹窗体(全站 .el-dialog__body 已统一)内堆叠 <section-card> 分组白卡
// 卡间 12px 间距由 section-card 自身 margin-bottom 保证,与项目其他弹窗风格统一
.process-config-dialog .el-dialog__body {
max-height: calc(100vh - 170px);
}
// 统一表格线条颜色
@@ -989,6 +992,6 @@
.el-form-item--default {
margin-bottom: 14px;
}
.el-form-item {
margin-bottom: 14px;
}
//.el-form-item {
// margin-bottom: 15px !important;
//}
+1 -1
View File
@@ -1012,7 +1012,7 @@ export default {
}
.el-input:first-child {
flex: 0 0 34%;
flex: 0 0 42.5%;
}
.el-button {
@@ -20,16 +20,28 @@
<template v-for="(route, index) in routes" :key="route.segmentNo">
<div class="route-overview__node">
<span :class="['route-badge', index === 0 ? 'start' : 'middle']">{{ index === 0 ? '起' : '经' }}</span>
<el-tooltip :content="route.departureName || '-'" placement="top" :show-after="120">
<strong>{{ route.departureName || '-' }}</strong>
</el-tooltip>
<el-tooltip :content="route.departureAddress || '-'" placement="top" :show-after="120">
<small>{{ route.departureAddress || '-' }}</small>
</el-tooltip>
<el-tooltip :content="contactText(route.departureContact, route.departurePhone)" placement="top" :show-after="120">
<small>{{ contactText(route.departureContact, route.departurePhone) }}</small>
</el-tooltip>
</div>
<div class="route-overview__line"></div>
<div v-if="index === routes.length - 1" class="route-overview__node">
<span class="route-badge end"></span>
<el-tooltip :content="route.arrivalName || '-'" placement="top" :show-after="120">
<strong>{{ route.arrivalName || '-' }}</strong>
</el-tooltip>
<el-tooltip :content="route.arrivalAddress || '-'" placement="top" :show-after="120">
<small>{{ route.arrivalAddress || '-' }}</small>
</el-tooltip>
<el-tooltip :content="contactText(route.arrivalContact, route.arrivalPhone)" placement="top" :show-after="120">
<small>{{ contactText(route.arrivalContact, route.arrivalPhone) }}</small>
</el-tooltip>
</div>
</template>
</div>
@@ -677,10 +689,10 @@ export default {
.overview-content { display: grid; grid-template-columns: minmax(400px, .9fr) minmax(620px, 1.6fr); gap: 28px; padding: 8px 24px 20px; }
.overview-meta { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px 24px; margin: 0; dt { margin-bottom: 6px; color: #909399; font-size: 13px; } dd { margin: 0; color: #409eff; font-size: 14px; word-break: break-all; } }
.overview-meta dd.overview-meta__plain-value { color: #303133; }
.route-overview { display: flex; align-items: flex-start; justify-content: flex-end; padding-top: 8px; overflow-x: auto; }
.route-overview__node { display: grid; flex: 0 0 130px; justify-items: center; gap: 6px; text-align: center; strong { font-size: 16px; white-space: nowrap; } small { color: #606266; white-space: nowrap; } }
.route-overview { display: flex; align-items: flex-start; justify-content: flex-start; padding: 8px 24px 0; overflow-x: auto; }
.route-overview__node { display: grid; flex: 0 0 auto; min-width: 130px; max-width: 200px; justify-items: center; gap: 6px; text-align: center; padding: 0 4px; strong { font-size: 16px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } small { color: #606266; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } }
.route-badge { display: inline-flex; width: 32px; height: 32px; align-items: center; justify-content: center; border-radius: 4px; background: #67c23a; color: #fff; font-weight: 600; &.start { background: #409eff; } &.end { background: #e6a23c; } }
.route-overview__line { flex: 1 0 56px; min-width: 56px; height: 32px; margin-top: 0; border-bottom: 6px solid #e4e7ed; }
.route-overview__line { flex: 0 1 56px; min-width: 24px; height: 32px; margin-top: 0; border-bottom: 6px solid #e4e7ed; }
.segment-header { min-height: 46px; padding: 0 20px; border-bottom: 1px solid #eff1f7; font-weight: 600; em { color: #409eff; font-style: normal; } }
.segment-content { padding: 16px 24px; }
.dispatch-form { :deep(.el-form-item) { margin-bottom: 16px; } :deep(.el-input), :deep(.el-select), :deep(.el-date-editor), :deep(.el-input-number) { width: 100%; } }
@@ -705,4 +717,7 @@ export default {
.table-actions { display: flex; flex-wrap: wrap; gap: 8px; }
@media screen and (max-width: 1200px) { .overview-content { grid-template-columns: 1fr; } .route-overview { justify-content: flex-start; } }
@media screen and (max-width: 992px) { .dispatch-bottom { left: 0; width: 100%; } }
.route-overview {
margin-top: -30px !important;
}
</style>
@@ -1752,7 +1752,7 @@ export default {
font-size: 16px;
}
.el-form-item {
margin-bottom: 16px;
margin-bottom: 14px;
}
}
.goods-heading {
+14 -14
View File
@@ -5,20 +5,20 @@
<div class="dialog-section-title">基本信息</div>
<el-row :gutter="28">
<el-col :span="24"><el-form-item label="变更类型"><el-radio-group v-model="form.changeType"><el-radio label="合同信息变更" /><el-radio label="终止合同" /></el-radio-group></el-form-item></el-col>
<el-col :span="8"><el-form-item label="合同编号"><el-input v-model="form.contractNo" disabled /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="合同名称" prop="contractName"><el-input v-model="form.contractName" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="合同类型"><el-select v-model="form.contractCategory" disabled><el-option label="客户合同" value="客户合同" /><el-option label="承运商合同" value="承运商合同" /></el-select></el-form-item></el-col>
<el-col :span="8"><el-form-item label="甲方"><el-input v-model="form.partyA" disabled /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="乙方"><el-input v-model="form.partyB" disabled /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="所属项目"><el-input v-model="form.projectName" disabled /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="合同期限"><el-date-picker v-model="period" type="daterange" value-format="YYYY-MM-DD" range-separator="至" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="所属组织"><el-input v-model="form.organizationName" disabled /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="签订日期"><el-date-picker v-model="form.signDate" type="date" value-format="YYYY-MM-DD" disabled /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="合同格式"><el-select v-model="form.contractFormat"><el-option label="电子合同" value="电子合同" /><el-option label="纸质合同" value="纸质合同" /></el-select></el-form-item></el-col>
<el-col :span="8"><el-form-item label="结算方式"><el-input v-model="form.settlementMode" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="是否需要加盖法人章"><el-select v-model="form.legalSealFlag"><el-option label="是" :value="1" /><el-option label="否" :value="0" /></el-select></el-form-item></el-col>
<el-col :span="8"><el-form-item label="一式"><div class="inline-field"><el-input-number v-model="form.copyCount" :min="1" controls-position="right" /><span></span></div></el-form-item></el-col>
<el-col :span="8"><el-form-item label="回款账期"><el-input-number v-model="form.paymentDays" :min="0" controls-position="right" /><span class="unit"></span></el-form-item></el-col>
<el-col :span="6"><el-form-item label="合同编号"><el-input v-model="form.contractNo" disabled /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="合同名称" prop="contractName"><el-input v-model="form.contractName" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="合同类型"><el-select v-model="form.contractCategory" disabled><el-option label="客户合同" value="客户合同" /><el-option label="承运商合同" value="承运商合同" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="甲方"><el-input v-model="form.partyA" disabled /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="乙方"><el-input v-model="form.partyB" disabled /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="所属项目"><el-input v-model="form.projectName" disabled /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="合同期限"><el-date-picker v-model="period" type="daterange" value-format="YYYY-MM-DD" range-separator="至" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="所属组织"><el-input v-model="form.organizationName" disabled /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="签订日期"><el-date-picker v-model="form.signDate" type="date" value-format="YYYY-MM-DD" disabled /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="合同格式"><el-select v-model="form.contractFormat"><el-option label="电子合同" value="电子合同" /><el-option label="纸质合同" value="纸质合同" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="结算方式"><el-input v-model="form.settlementMode" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="是否需要加盖法人章"><el-select v-model="form.legalSealFlag"><el-option label="是" :value="1" /><el-option label="否" :value="0" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="一式"><div class="inline-field"><el-input-number v-model="form.copyCount" :min="1" controls-position="right" /><span></span></div></el-form-item></el-col>
<el-col :span="6"><el-form-item label="回款账期"><el-input-number v-model="form.paymentDays" :min="0" controls-position="right" /><span class="unit"></span></el-form-item></el-col>
<el-col :span="24"><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" maxlength="2000" show-word-limit /></el-form-item></el-col>
</el-row>
</section>
+358 -373
View File
@@ -8,9 +8,6 @@
v-model="form"
ref="crud"
:permission="permissionList"
:before-open="beforeOpen"
@row-save="rowSave"
@row-update="rowUpdate"
@row-del="rowDel"
@search-change="searchChange"
@search-reset="searchReset"
@@ -25,7 +22,7 @@
type="primary"
icon="el-icon-plus"
v-if="hasPermission('process_config_add')"
@click="$refs.crud.rowAdd()"
@click="openConfig()"
>新建过程配置
</el-button>
<el-button
@@ -46,27 +43,6 @@
</el-button>
</template>
<template #menu-form-before="{ disabled }">
<template v-if="!dialogReadonly">
<el-button
plain
:loading="disabled"
:disabled="disabled"
@click="handleProcessConfigSave('draft')"
>
保存草稿
</el-button>
<el-button
type="primary"
:loading="disabled"
:disabled="disabled"
@click="handleProcessConfigSave('enabled')"
>
保存并启用
</el-button>
</template>
</template>
<template #status="{ row }">
<el-tag :type="statusTagType(row.status)">
{{ row.statusName || displayStatus(row.status) }}
@@ -79,300 +55,18 @@
</span>
</template>
<template #basicInfo-form>
<section-card title="基本信息">
<el-form
:model="form"
label-position="right"
label-width="calc(6em + 24px)"
>
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="配置名称" required>
<el-input
v-model="form.configName"
:disabled="dialogReadonly"
maxlength="100"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="项目" required>
<el-select
v-model="selectedProjectId"
clearable
filterable
remote
:remote-method="loadProjectOptions"
:loading="projectLoading"
:disabled="dialogReadonly"
placeholder="请选择"
style="width: 100%"
@change="handleProjectChange"
>
<el-option
v-for="item in projectOptions"
:key="item.id"
:label="formatProjectLabel(item)"
:value="String(item.id)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="运输完成时限">
<div class="process-config-page__finish-days">
<span>默认</span>
<el-input
v-model="form.defaultFinishDays"
:disabled="dialogReadonly"
maxlength="3"
placeholder="请输入"
@input="handleFinishDaysInput"
/>
<span>天后完成运输</span>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input
v-model="form.remark"
type="textarea"
:rows="4"
:disabled="dialogReadonly"
maxlength="500"
show-word-limit
placeholder="请输入"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</section-card>
</template>
<template #nodeConfigJson-form>
<div class="process-config-page__custom-form">
<section-card title="现行过程节点">
<div class="process-config-page__timeline" v-if="enabledNodes.length">
<div class="process-config-page__timeline-line"></div>
<div
v-for="node in enabledNodes"
:key="node.key"
class="process-config-page__timeline-item"
>
<span class="process-config-page__timeline-dot"></span>
<div class="process-config-page__timeline-name">{{ node.name }}</div>
<div class="process-config-page__timeline-desc">
<div v-for="text in timelineDescriptions(node)" :key="text">{{ text }}</div>
</div>
</div>
</div>
<el-empty v-else description="请在节点设置中勾选过程节点" :image-size="72" />
</section-card>
<section-card title="节点设置">
<el-table
class="process-config-page__node-table"
border
:data="nodeRows"
:span-method="nodeSpanMethod"
>
<el-table-column label="节点设置" width="136">
<template #default="{ row, $index }">
<div class="process-config-page__node-cell">
<el-checkbox
v-model="row.enabled"
:disabled="dialogReadonly"
@change="syncNodeFields"
/>
<span class="process-config-page__node-name">{{ row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="操作要求" min-width="660">
<template #default="{ row }">
<div class="process-config-page__requirements">
<template v-if="row.key === 'accept'">
<div class="process-config-page__require-line">
<span class="process-config-page__require-label">是否确认</span>
<el-radio-group
v-model="row.confirmMode"
:disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields"
>
<el-radio label="yes"></el-radio>
<el-radio label="no_confirm_accept">无需确认接单</el-radio>
</el-radio-group>
</div>
<div class="process-config-page__require-line">
<span class="process-config-page__require-label">确认接单人</span>
<el-checkbox
v-model="row.confirmDriver"
:disabled="dialogReadonly || !row.enabled || row.confirmMode !== 'yes'"
@change="syncNodeFields"
>司机</el-checkbox
>
</div>
</template>
<template v-else-if="row.key === 'return'">
<div class="process-config-page__require-line">
<span class="process-config-page__require-label">是否确认</span>
<el-radio-group
v-model="row.confirmMode"
:disabled="dialogReadonly || !row.enabled"
@change="handleReturnConfirmChange(row)"
>
<el-radio label="yes"></el-radio>
<el-radio label="no_confirm_complete">无需确认完成</el-radio>
</el-radio-group>
</div>
<div class="process-config-page__require-line">
<span class="process-config-page__require-label">确认完成</span>
<el-checkbox
v-model="row.confirmInternal"
:disabled="dialogReadonly || !row.enabled || row.confirmMode !== 'yes'"
@change="syncNodeFields"
>内部人员</el-checkbox
>
</div>
</template>
<template v-else>
<div class="process-config-page__require-grid">
<div class="process-config-page__require-line">
<span class="process-config-page__require-label">是否打卡</span>
<el-radio-group
v-model="row.punch"
:disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields"
>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</div>
<div class="process-config-page__require-line" v-if="row.supportCargo">
<span class="process-config-page__require-label">上传货量</span>
<el-radio-group
v-model="row.uploadCargo"
:disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields"
>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</div>
<div
class="process-config-page__require-line process-config-page__require-line--wide"
v-if="row.key === 'transit'"
>
<span class="process-config-page__require-label">打卡频次</span>
<el-input
v-model="row.frequencyDays"
:disabled="dialogReadonly || !row.enabled || !row.punch"
maxlength="3"
@input="value => handleFrequencyInput(row, value)"
/>
<span>天打卡1次</span>
</div>
<div class="process-config-page__require-line">
<span class="process-config-page__require-label">打卡定位</span>
<el-radio-group
v-model="row.location"
:disabled="dialogReadonly || !row.enabled || !row.punch"
@change="syncNodeFields"
>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</div>
<div class="process-config-page__require-line" v-if="row.supportCargo">
<span class="process-config-page__require-label">上传货量类型</span>
<el-checkbox-group
v-model="row.cargoTypes"
:disabled="dialogReadonly || !row.enabled || !row.uploadCargo"
@change="syncNodeFields"
>
<el-checkbox label="重量">重量</el-checkbox>
<el-checkbox label="体积">体积</el-checkbox>
<el-checkbox label="数量">数量</el-checkbox>
</el-checkbox-group>
</div>
<div
class="process-config-page__require-line process-config-page__require-line--wide"
v-if="row.key === 'transit'"
>
<span class="process-config-page__require-label">打卡时段</span>
<el-time-picker
v-model="row.timeStart"
:disabled="dialogReadonly || !row.enabled || !row.punch"
format="HH:mm"
value-format="HH:mm"
placeholder="开始"
/>
<span>-</span>
<el-time-picker
v-model="row.timeEnd"
:disabled="dialogReadonly || !row.enabled || !row.punch"
format="HH:mm"
value-format="HH:mm"
placeholder="结束"
/>
</div>
</div>
</template>
</div>
</template>
</el-table-column>
<el-table-column label="附件要求" min-width="320">
<template #default="{ row }">
<div class="process-config-page__attachments" v-if="row.supportVoucher">
<div class="process-config-page__require-line">
<span class="process-config-page__require-label">上传凭证</span>
<el-radio-group
v-model="row.uploadVoucher"
:disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields"
>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</div>
<div class="process-config-page__require-line">
<span class="process-config-page__require-label">凭证类型</span>
<el-checkbox-group
v-model="row.voucherTypes"
:disabled="dialogReadonly || !row.enabled || !row.uploadVoucher"
@change="syncNodeFields"
>
<el-checkbox v-for="item in row.voucherOptions" :key="item" :label="item">
{{ item }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</template>
</el-table-column>
</el-table>
</section-card>
</div>
</template>
<template #menu="{ row, index }">
<el-link
type="primary"
v-if="hasPermission('process_config_view')"
@click="$refs.crud.rowView(row, index)"
@click="openConfig(row, true)"
>
查看
</el-link>
<el-link
type="primary"
v-if="canEdit(row)"
@click="$refs.crud.rowEdit(row, index)"
@click="openConfig(row)"
>
编辑
</el-link>
@@ -407,6 +101,311 @@
</template>
</avue-crud>
<el-dialog
:title="dialogReadonly ? '查看过程配置' : form.id ? '编辑过程配置' : '新建过程配置'"
append-to-body
v-model="configBox"
width="96%"
top="4vh"
class="process-config-dialog"
:close-on-click-modal="false"
@closed="handleDialogClosed"
>
<div class="process-config-form">
<section-card title="基本信息">
<el-form
:model="form"
label-position="right"
label-width="calc(6em + 24px)"
:disabled="dialogReadonly"
>
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="配置名称" required>
<el-input
v-model="form.configName"
maxlength="100"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="项目" required>
<el-select
v-model="selectedProjectId"
clearable
filterable
remote
:remote-method="loadProjectOptions"
:loading="projectLoading"
placeholder="请选择"
style="width: 100%"
@change="handleProjectChange"
>
<el-option
v-for="item in projectOptions"
:key="item.id"
:label="formatProjectLabel(item)"
:value="String(item.id)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="运输完成时限">
<div class="process-config-form__finish-days">
<span>默认</span>
<el-input
v-model="form.defaultFinishDays"
maxlength="3"
placeholder="请输入"
@input="handleFinishDaysInput"
/>
<span>天后完成运输</span>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input
v-model="form.remark"
type="textarea"
:rows="4"
maxlength="500"
show-word-limit
placeholder="请输入"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</section-card>
<section-card title="现行过程节点">
<div class="process-config-form__timeline" v-if="enabledNodes.length">
<div class="process-config-form__timeline-line"></div>
<div
v-for="node in enabledNodes"
:key="node.key"
class="process-config-form__timeline-item"
>
<span class="process-config-form__timeline-dot"></span>
<div class="process-config-form__timeline-name">{{ node.name }}</div>
<div class="process-config-form__timeline-desc">
<div v-for="text in timelineDescriptions(node)" :key="text">{{ text }}</div>
</div>
</div>
</div>
<el-empty v-else description="请在节点设置中勾选过程节点" :image-size="72" />
</section-card>
<section-card title="节点设置">
<el-table
class="process-config-form__node-table"
border
:data="nodeRows"
:span-method="nodeSpanMethod"
>
<el-table-column label="节点设置" width="136">
<template #default="{ row }">
<div class="process-config-form__node-cell">
<el-checkbox
v-model="row.enabled"
:disabled="dialogReadonly"
@change="syncNodeFields"
/>
<span class="process-config-form__node-name">{{ row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="操作要求" min-width="660">
<template #default="{ row }">
<div class="process-config-form__requirements">
<template v-if="row.key === 'accept'">
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">是否确认</span>
<el-radio-group
v-model="row.confirmMode"
:disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields"
>
<el-radio label="yes"></el-radio>
<el-radio label="no_confirm_accept">无需确认接单</el-radio>
</el-radio-group>
</div>
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">确认接单人</span>
<el-checkbox
v-model="row.confirmDriver"
:disabled="dialogReadonly || !row.enabled || row.confirmMode !== 'yes'"
@change="syncNodeFields"
>司机</el-checkbox
>
</div>
</template>
<template v-else-if="row.key === 'return'">
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">是否确认</span>
<el-radio-group
v-model="row.confirmMode"
:disabled="dialogReadonly || !row.enabled"
@change="handleReturnConfirmChange(row)"
>
<el-radio label="yes"></el-radio>
<el-radio label="no_confirm_complete">无需确认完成</el-radio>
</el-radio-group>
</div>
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">确认完成</span>
<el-checkbox
v-model="row.confirmInternal"
:disabled="dialogReadonly || !row.enabled || row.confirmMode !== 'yes'"
@change="syncNodeFields"
>内部人员</el-checkbox
>
</div>
</template>
<template v-else>
<div class="process-config-form__require-grid">
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">是否打卡</span>
<el-radio-group
v-model="row.punch"
:disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields"
>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</div>
<div class="process-config-form__require-line" v-if="row.supportCargo">
<span class="process-config-form__require-label">上传货量</span>
<el-radio-group
v-model="row.uploadCargo"
:disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields"
>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</div>
<div
class="process-config-form__require-line process-config-form__require-line--wide"
v-if="row.key === 'transit'"
>
<span class="process-config-form__require-label">打卡频次</span>
<el-input
v-model="row.frequencyDays"
:disabled="dialogReadonly || !row.enabled || !row.punch"
maxlength="3"
@input="value => handleFrequencyInput(row, value)"
/>
<span>天打卡1次</span>
</div>
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">打卡定位</span>
<el-radio-group
v-model="row.location"
:disabled="dialogReadonly || !row.enabled || !row.punch"
@change="syncNodeFields"
>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</div>
<div class="process-config-form__require-line" v-if="row.supportCargo">
<span class="process-config-form__require-label">上传货量类型</span>
<el-checkbox-group
v-model="row.cargoTypes"
:disabled="dialogReadonly || !row.enabled || !row.uploadCargo"
@change="syncNodeFields"
>
<el-checkbox label="重量">重量</el-checkbox>
<el-checkbox label="体积">体积</el-checkbox>
<el-checkbox label="数量">数量</el-checkbox>
</el-checkbox-group>
</div>
<div
class="process-config-form__require-line process-config-form__require-line--wide"
v-if="row.key === 'transit'"
>
<span class="process-config-form__require-label">打卡时段</span>
<el-time-picker
v-model="row.timeStart"
:disabled="dialogReadonly || !row.enabled || !row.punch"
format="HH:mm"
value-format="HH:mm"
placeholder="开始"
/>
<span>-</span>
<el-time-picker
v-model="row.timeEnd"
:disabled="dialogReadonly || !row.enabled || !row.punch"
format="HH:mm"
value-format="HH:mm"
placeholder="结束"
/>
</div>
</div>
</template>
</div>
</template>
</el-table-column>
<el-table-column label="附件要求" min-width="320">
<template #default="{ row }">
<div class="process-config-form__attachments" v-if="row.supportVoucher">
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">上传凭证</span>
<el-radio-group
v-model="row.uploadVoucher"
:disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields"
>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</div>
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">凭证类型</span>
<el-checkbox-group
v-model="row.voucherTypes"
:disabled="dialogReadonly || !row.enabled || !row.uploadVoucher"
@change="syncNodeFields"
>
<el-checkbox v-for="item in row.voucherOptions" :key="item" :label="item">
{{ item }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</template>
</el-table-column>
</el-table>
</section-card>
</div>
<template #footer>
<el-button @click="configBox = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button
plain
:loading="submitting"
v-if="!dialogReadonly"
@click="submitConfig('draft')"
>
保存草稿
</el-button>
<el-button
type="primary"
:loading="submitting"
v-if="!dialogReadonly"
@click="submitConfig('enabled')"
>
保存并启用
</el-button>
</template>
</el-dialog>
<empty-pagination
:page="page"
@size-change="sizeChange"
@@ -507,6 +506,8 @@ export default {
loading: true,
data: [],
dialogReadonly: false,
configBox: false,
submitting: false,
projectOptions: [],
projectLoading: false,
selectedProjectId: '',
@@ -518,7 +519,6 @@ export default {
total: 0,
},
selectionList: [],
submitStatus: 1,
};
},
computed: {
@@ -880,11 +880,6 @@ export default {
}
return true;
},
stopSubmitLoading(loading) {
if (typeof loading === 'function') {
loading();
}
},
needsDuplicateVoucherConfirm() {
const signNode = this.nodeRows.find(item => item.key === 'sign');
const returnNode = this.nodeRows.find(item => item.key === 'return');
@@ -914,14 +909,45 @@ export default {
}
);
},
submitRow(row, done, loading, status = this.submitStatus) {
this.submitStatus = 1;
this.syncNodeFields();
const submitRow = this.normalizeRow({ ...row, status });
if (!this.validateRow(submitRow)) {
this.stopSubmitLoading(loading);
openConfig(row, readonly = false) {
this.dialogReadonly = readonly;
if (!row || !row.id) {
this.form = {
configName: '',
projectIds: '',
projectNames: '',
defaultFinishDays: '',
remark: '',
status: 0,
};
this.selectedProjectId = '';
this.resetNodeRows();
this.configBox = true;
return;
}
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);
this.configBox = true;
});
},
handleDialogClosed() {
this.submitting = false;
this.dialogReadonly = false;
},
submitConfig(mode) {
if (this.submitting) return;
this.syncNodeFields();
const submitRow = this.normalizeRow({
...this.form,
status: mode === 'draft' ? 2 : 1,
});
if (!this.validateRow(submitRow)) {
return;
}
this.submitting = true;
this.checkEnabledProjectBeforeSubmit(submitRow)
.then(exists => {
if (exists) {
@@ -932,9 +958,9 @@ export default {
.then(() => this.confirmDuplicateVoucher())
.then(() => api.submit(submitRow))
.then(() => {
this.configBox = false;
this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' });
done();
})
.catch(error => {
if (
@@ -944,21 +970,11 @@ export default {
) {
window.console.log(error);
}
this.stopSubmitLoading(loading);
})
.finally(() => {
this.submitting = false;
});
},
rowSave(row, done, loading) {
this.submitRow(row, done, loading);
},
rowUpdate(row, index, done, loading) {
this.submitRow(row, done, loading);
},
handleProcessConfigSave(mode) {
this.submitStatus = mode === 'draft' ? 2 : 1;
if (this.$refs.crud && typeof this.$refs.crud.rowSave === 'function') {
this.$refs.crud.rowSave();
}
},
rowDel(row) {
this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定',
@@ -1001,35 +1017,6 @@ export default {
}
this.selectionClear();
},
beforeOpen(done, type) {
this.dialogReadonly = type === 'view';
this.submitStatus = 1;
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.applyDefaultDeptSearch();
@@ -1164,6 +1151,13 @@ export default {
word-break: break-all;
}
:deep(.avue-crud__header) {
margin-top: 12px;
background: transparent;
}
}
.process-config-form {
&__finish-days {
display: flex;
align-items: center;
@@ -1174,10 +1168,6 @@ export default {
}
}
&__custom-form {
width: 100%;
}
&__timeline {
position: relative;
display: flex;
@@ -1285,11 +1275,6 @@ export default {
text-align: right;
}
:deep(.avue-crud__header) {
margin-top: 12px;
background: transparent;
}
:deep(.el-table) {
--el-table-border-color: #eff1f7;
}
+9 -2
View File
@@ -106,7 +106,7 @@
:model="form"
:rules="formRules"
label-position="right"
label-width="auto"
label-width="150"
class="project-apply-form"
:disabled="dialogReadonly"
>
@@ -1964,7 +1964,7 @@ export default {
&__grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-columns: repeat(4, minmax(0, 1fr));
column-gap: 24px;
row-gap: 0;
margin-bottom: 12px;
@@ -1972,6 +1972,13 @@ export default {
border-radius: 0 0 6px 6px;
padding: 14px 16px 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
// label 固定宽度:可容纳 6 个汉字,超出自动换行(覆盖全局 min-width:160px 与 auto 行内宽度)
:deep(.el-form-item__label) {
width: 108px !important;
min-width: 0 !important;
white-space: normal;
}
}
&__picker-icon {
+2 -2
View File
@@ -364,7 +364,7 @@
:before-close="beforeProgressDialogClose"
@opened="loadProgress"
>
<section class="voucher-manage-page__progress-search">
<section-card class="voucher-manage-page__progress-search">
<el-form :model="progressQuery" label-position="right" label-width="160px"
><div class="voucher-manage-page__search-grid">
<el-form-item label="凭证批次号"
@@ -391,7 +391,7 @@
</div>
</div></el-form
>
</section>
</section-card>
<el-table :data="progressRows" border class="voucher-manage-page__table"
><el-table-column type="selection" width="52" /><el-table-column
type="index"
@@ -14,7 +14,7 @@
label-width="auto"
>
<el-row :gutter="24">
<el-col v-for="field in fields" :key="field.prop" :span="8">
<el-col v-for="field in fields" :key="field.prop" :span="6">
<el-form-item :label="field.label" :prop="field.prop">
<el-date-picker
v-if="field.type === 'date' && editable"
@@ -0,0 +1,216 @@
<template>
<div class="fs-table-panel">
<div class="fs-table-panel__toolbar">
<div>
<el-button
v-if="hasPermission('formal_settlement_add')"
type="primary"
@click="$emit('create')"
>新增</el-button
>
<el-button
v-if="hasPermission('formal_settlement_payment')"
type="primary"
plain
disabled
@click="$emit('payment')"
>付款申请</el-button
>
<el-button
v-if="hasPermission('formal_settlement_sync')"
type="primary"
plain
disabled
@click="$emit('sync')"
>同步金蝶</el-button
>
<el-button
v-if="hasPermission('formal_settlement_print')"
type="primary"
plain
@click="emitSingle('print')"
>打印结算单</el-button
>
<el-button
v-if="hasPermission('formal_settlement_export')"
type="primary"
plain
@click="$emit('export')"
>导出</el-button
>
</div>
<div class="fs-table-panel__toolbar-right">
<el-tooltip content="刷新" placement="top">
<el-button :icon="Refresh" text @click="$emit('refresh')" />
</el-tooltip>
</div>
</div>
<el-table
v-loading="loading"
:data="rows"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="52" fixed="left" align="center" />
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column
v-for="column in columns"
:key="column.prop"
v-bind="column"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
<el-link v-if="column.link && row[column.prop]" type="primary" @click="emitAction('view', row)">{{
row[column.prop]
}}</el-link>
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)">{{
displayValue(row[column.prop])
}}</el-tag>
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
<span v-else-if="column.prop === 'invoiceStatusName'">{{
invoiceName(row.invoiceStatus)
}}</span>
<span v-else-if="column.prop === 'paymentStatusName'">{{
paymentName(row.paymentStatus)
}}</span>
<span v-else>{{ displayValue(row[column.prop]) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right" align="center">
<template #default="{ row }"
><div class="fs-table-panel__links">
<el-link
v-for="action in rowActions(row)"
:key="action.type"
:type="action.danger ? 'danger' : 'primary'"
@click="emitAction(action.type, row)"
>{{ action.label }}</el-link
>
</div></template
>
</el-table-column>
</el-table>
<div class="fs-table-panel__pagination">
<el-pagination
:current-page="page.current"
:page-size="page.size"
:total="page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="current => $emit('page-change', { current, size: page.size })"
@size-change="size => $emit('page-change', { current: 1, size })"
/>
</div>
</div>
</template>
<script>
import { Refresh } from '@element-plus/icons-vue';
import {
invoiceStatusOptions,
paymentStatusOptions,
} from '@/option/settlement/formalSettlementSearch';
export default {
name: 'FormalSettlementTablePanel',
props: {
rows: { type: Array, default: () => [] },
columns: { type: Array, default: () => [] },
loading: { type: Boolean, default: false },
page: { type: Object, required: true },
hasPermission: { type: Function, required: true },
},
emits: ['create', 'payment', 'sync', 'print', 'export', 'refresh', 'page-change', 'action'],
data() {
return {
Refresh,
selection: [],
};
},
methods: {
handleSelectionChange(value) {
this.selection = value;
this.$emit('update:selection', value);
},
emitAction(type, row) {
this.$emit('action', { type, row });
},
emitSingle(type) {
if (this.selection.length !== 1) {
this.$message.warning(`${type === 'print' ? '打印' : '操作'}需选择一条正式结算单`);
return;
}
this.$emit('action', { type, row: this.selection[0] });
},
isEditable(row) {
return ['draft', 'returned'].includes(row.approvalStatus);
},
rowActions(row) {
const actions = [];
if (this.hasPermission('formal_settlement_view'))
actions.push({ type: 'view', label: '查看' });
if (this.hasPermission('formal_settlement_edit') && this.isEditable(row))
actions.push({ type: 'edit', label: '编辑' });
if (this.hasPermission('formal_settlement_delete') && row.approvalStatus === 'draft')
actions.push({ type: 'delete', label: '删除', danger: true });
if (this.hasPermission('formal_settlement_submit') && this.isEditable(row))
actions.push({ type: 'submit', label: '提交' });
if (this.hasPermission('formal_settlement_approve') && row.approvalStatus === 'reviewing') {
actions.push({ type: 'approve', label: '通过' });
actions.push({ type: 'return', label: '驳回', danger: true });
}
if (
this.hasPermission('formal_settlement_void') &&
row.approvalStatus === 'approved' &&
row.kingdeeSyncStatus !== 'synced'
)
actions.push({ type: 'void', label: '作废', danger: true });
return actions;
},
statusType(status) {
return (
{ approved: 'success', reviewing: 'warning', returned: 'danger', voided: 'info' }[status] ||
''
);
},
invoiceName(value) {
return invoiceStatusOptions.find(item => item.value === value)?.label || value || '-';
},
paymentName(value) {
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
},
displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value;
},
formatMoney(value, currency = 'RMB') {
return `${Number(value || 0).toFixed(2)} ${currency || 'RMB'}`;
},
},
};
</script>
<style scoped lang="scss">
.fs-table-panel__toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
}
.fs-table-panel__toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.fs-table-panel__links {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.fs-table-panel__pagination {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
</style>
@@ -10,7 +10,7 @@
label-width="auto"
>
<el-row :gutter="24">
<el-col v-for="field in fields" :key="field.prop" :span="8"
<el-col v-for="field in fields" :key="field.prop" :span="6"
><el-form-item :label="field.label" :prop="field.prop"
><el-select
v-if="field.prop === 'formalSettlementId' && editable"
@@ -10,7 +10,7 @@
label-width="auto"
>
<el-row :gutter="24">
<el-col v-for="field in formFields" :key="field.prop" :span="8">
<el-col v-for="field in formFields" :key="field.prop" :span="6">
<el-form-item :label="field.label" :prop="field.prop">
<el-input
v-if="field.prop === 'formalSettlementNo' && editable"
@@ -0,0 +1,148 @@
<template>
<div class="tr-table-panel">
<div class="tr-table-panel__toolbar">
<div>
<el-button
v-if="hasPermission('transport_reconciliation_add')"
type="primary"
@click="$emit('create')"
>新增</el-button
>
<el-button type="primary" plain @click="$emit('export')">导出</el-button>
</div>
<div class="tr-table-panel__toolbar-right">
<el-tooltip content="刷新" placement="top">
<el-button :icon="Refresh" text @click="$emit('refresh')" />
</el-tooltip>
</div>
</div>
<el-table
v-loading="loading"
:data="rows"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="52" fixed="left" align="center" />
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column
v-for="column in columns"
:key="column.prop"
v-bind="column"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
<el-link v-if="column.link" type="primary" @click="emitAction('view', row)">{{
row[column.prop] || '-'
}}</el-link>
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
<el-tag
v-else-if="column.prop === 'reconciliationStatusName'"
:type="row.reconciliationStatus === 'completed' ? 'success' : 'warning'"
>{{ row[column.prop] || '-' }}</el-tag
>
<span v-else>{{ displayValue(row[column.prop]) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="220" fixed="right" align="center">
<template #default="{ row }"
><div class="tr-table-panel__links">
<el-link
v-for="action in rowActions(row)"
:key="action.type"
:type="action.danger ? 'danger' : 'primary'"
@click="emitAction(action.type, row)"
>{{ action.label }}</el-link
>
</div></template
>
</el-table-column>
</el-table>
<div class="tr-table-panel__pagination">
<el-pagination
:current-page="page.current"
:page-size="page.size"
:total="page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="current => $emit('page-change', { current, size: page.size })"
@size-change="size => $emit('page-change', { current: 1, size })"
/>
</div>
</div>
</template>
<script>
import { Refresh } from '@element-plus/icons-vue';
export default {
name: 'TransportReconciliationTablePanel',
props: {
rows: { type: Array, default: () => [] },
columns: { type: Array, default: () => [] },
loading: { type: Boolean, default: false },
page: { type: Object, required: true },
hasPermission: { type: Function, required: true },
},
emits: ['create', 'export', 'refresh', 'page-change', 'update:selection', 'action'],
data() {
return {
Refresh,
selection: [],
};
},
methods: {
handleSelectionChange(value) {
this.selection = value;
this.$emit('update:selection', value);
},
emitAction(type, row) {
this.$emit('action', { type, row });
},
isEditable(row) {
return row.reconciliationStatus === 'unfinished';
},
rowActions(row) {
const actions = [{ type: 'view', label: '查看' }];
if (this.hasPermission('transport_reconciliation_edit') && this.isEditable(row))
actions.push({ type: 'edit', label: '编辑' });
if (this.hasPermission('transport_reconciliation_delete') && this.isEditable(row))
actions.push({ type: 'delete', label: '删除', danger: true });
if (this.hasPermission('transport_reconciliation_complete') && this.isEditable(row))
actions.push({ type: 'complete', label: '确认' });
return actions;
},
displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value;
},
formatMoney(value, currency = 'RMB') {
return `${Number(value || 0).toFixed(2)} ${currency || 'RMB'}`;
},
},
};
</script>
<style scoped lang="scss">
.tr-table-panel__toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
}
.tr-table-panel__toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.tr-table-panel__links {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.tr-table-panel__pagination {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
</style>
@@ -343,7 +343,6 @@ export default {
justify-content: space-between;
align-items: center;
min-height: 56px;
padding: 12px;
}
&__toolbar-right {
display: flex;
+46 -105
View File
@@ -31,91 +31,43 @@
</section>
<section class="reconciliation-page__table-panel">
<div class="reconciliation-page__toolbar">
<div>
<el-button
v-if="hasPermission('transport_reconciliation_add')"
type="primary"
@click="openCreate"
>新增</el-button
>
<el-button type="primary" plain @click="handleExport">导出</el-button>
</div>
<div class="reconciliation-page__toolbar-right">
<el-tooltip content="刷新" placement="top">
<el-button :icon="Refresh" text @click="loadTable" />
</el-tooltip>
</div>
</div>
<el-tabs
v-model="settlementType"
class="reconciliation-page__settlement-tabs"
type="card"
type="border-card"
@tab-change="handleTabChange"
>
<el-tab-pane label="应付" name="payable" />
<el-tab-pane label="应收" name="receivable" />
</el-tabs>
<el-table v-loading="loading" :data="rows" border @selection-change="selection = $event">
<el-table-column type="selection" width="52" fixed="left" align="center" />
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column
v-for="column in columns"
:key="column.prop"
v-bind="column"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
<el-link v-if="column.link" type="primary" @click="openView(row)">{{
row[column.prop] || '-'
}}</el-link>
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
<el-tag
v-else-if="column.prop === 'reconciliationStatusName'"
:type="row.reconciliationStatus === 'completed' ? 'success' : 'warning'"
>{{ row[column.prop] || '-' }}</el-tag
>
<span v-else>{{ displayValue(row[column.prop]) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="220" fixed="right" align="center">
<template #default="{ row }">
<div class="reconciliation-page__links">
<el-link type="primary" @click="openView(row)">查看</el-link>
<el-link
v-if="hasPermission('transport_reconciliation_edit') && isEditable(row)"
type="primary"
@click="openEdit(row)"
>编辑</el-link
>
<el-link
v-if="hasPermission('transport_reconciliation_delete') && isEditable(row)"
type="danger"
@click="handleDelete(row)"
>删除</el-link
>
<el-link
v-if="hasPermission('transport_reconciliation_complete') && isEditable(row)"
type="primary"
@click="handleComplete(row)"
>确认</el-link
>
</div>
</template>
</el-table-column>
</el-table>
<div class="reconciliation-page__pagination">
<el-pagination
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadTable"
@size-change="handleSizeChange"
<el-tab-pane label="应付" name="payable">
<transport-reconciliation-table-panel
:rows="rows"
:columns="columns"
:loading="loading"
:page="page"
:has-permission="hasPermission"
@create="openCreate"
@export="handleExport"
@refresh="loadTable"
@page-change="handlePageChange"
@update:selection="selection = $event"
@action="handleAction"
/>
</div>
</el-tab-pane>
<el-tab-pane label="应收" name="receivable">
<transport-reconciliation-table-panel
:rows="rows"
:columns="columns"
:loading="loading"
:page="page"
:has-permission="hasPermission"
@create="openCreate"
@export="handleExport"
@refresh="loadTable"
@page-change="handlePageChange"
@update:selection="selection = $event"
@action="handleAction"
/>
</el-tab-pane>
</el-tabs>
</section>
<transport-reconciliation-editor
@@ -136,6 +88,7 @@ import * as api from '@/api/settlement/transportReconciliation';
import { transportReconciliationSearchFields } from '@/option/settlement/transportReconciliationSearch';
import { transportReconciliationTableColumns } from '@/option/settlement/transportReconciliationTable';
import TransportReconciliationEditor from './components/transport-reconciliation-editor.vue';
import TransportReconciliationTablePanel from './components/transport-reconciliation-table-panel.vue';
const emptyQuery = () => ({
reconciliationNo: '',
@@ -151,7 +104,7 @@ const emptyQuery = () => ({
export default {
name: 'TransportReconciliation',
components: { TransportReconciliationEditor },
components: { TransportReconciliationEditor, TransportReconciliationTablePanel },
data() {
return {
ArrowDown,
@@ -210,10 +163,21 @@ export default {
this.page.current = 1;
this.loadTable();
},
handleSizeChange() {
this.page.current = 1;
handlePageChange({ current, size }) {
this.page.current = current;
this.page.size = size;
this.loadTable();
},
handleAction({ type, row }) {
const map = {
view: this.openView,
edit: this.openEdit,
delete: this.handleDelete,
complete: this.handleComplete,
};
const fn = map[type];
if (fn) fn(row);
},
openCreate() {
this.editor = { visible: true, id: null, readonly: false };
},
@@ -307,35 +271,12 @@ export default {
.reconciliation-page__table-panel {
margin-top: 8px;
}
.reconciliation-page__toolbar {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 56px;
//padding: 12px;
}
.reconciliation-page__toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.reconciliation-page__settlement-tabs {
margin-bottom: 0;
}
.reconciliation-page__settlement-tabs :deep(.el-tabs__header) {
margin: 0;
}
.reconciliation-page__links {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.reconciliation-page__pagination {
display: flex;
justify-content: flex-end;
padding: 12px;
}
.reconciliation-page :deep(.el-table) {
--el-table-border-color: #eff1f7;
background: #fff;
+3 -1
View File
@@ -1601,5 +1601,7 @@ export default {
color: #d40000;
font-weight: 600;
}
.el-form-item {
margin-bottom: 14px !important;
}
</style>
+20 -11
View File
@@ -137,13 +137,13 @@
:model="shipForm"
:rules="formRules"
label-position="right"
label-width="auto"
label-width="100"
:disabled="readonly"
class="ship-form archive-form"
>
<section-card title="基础船舶信息">
<el-row :gutter="18">
<el-col :span="8">
<el-col :span="6">
<el-form-item label="船舶所属组织" prop="organizationName">
<el-select
v-model="shipForm.organizationName"
@@ -160,12 +160,12 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="船名" prop="shipName">
<el-input v-model="shipForm.shipName" maxlength="20" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="安放龙骨日期/建造完工日期" prop="keelLayingDate">
<div class="date-range-inline">
<el-date-picker
@@ -186,7 +186,7 @@
</el-col>
</el-row>
<el-row :gutter="18">
<el-col :span="8">
<el-col :span="6">
<el-form-item label="总长" prop="totalLength">
<el-input
v-model="shipForm.totalLength"
@@ -196,7 +196,7 @@
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="船宽" prop="shipWidth">
<el-input
v-model="shipForm.shipWidth"
@@ -206,7 +206,7 @@
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="型深" prop="moldedDepth">
<el-input
v-model="shipForm.moldedDepth"
@@ -218,7 +218,7 @@
</el-col>
</el-row>
<el-row :gutter="18">
<el-col :span="8">
<el-col :span="6">
<el-form-item label="最大船高" prop="maxShipHeight">
<el-input
v-model="shipForm.maxShipHeight"
@@ -228,7 +228,7 @@
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="空载吃水" prop="lightDraft">
<el-input
v-model="shipForm.lightDraft"
@@ -238,7 +238,7 @@
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="6">
<el-form-item label="满载吃水" prop="fullLoadDraft">
<el-input
v-model="shipForm.fullLoadDraft"
@@ -250,7 +250,7 @@
</el-col>
</el-row>
<el-row :gutter="18">
<el-col :span="8">
<el-col :span="6">
<el-form-item label="航区" prop="navigationArea">
<div class="navigation-area-field">
<el-select v-model="shipForm.navigationArea" placeholder="请选择">
@@ -1226,6 +1226,12 @@ export default {
:deep(.el-input-number) {
width: 100%;
}
// label 固定宽度:可容纳 6 个汉字,超出自动换行
:deep(.el-form-item__label) {
white-space: normal;
line-height: 20px;
}
}
.date-range-inline {
@@ -1368,4 +1374,7 @@ export default {
color: #d40000;
font-weight: 600;
}
.el-form-item {
margin-bottom: 14px !important;
}
</style>
+4
View File
@@ -1536,4 +1536,8 @@ export default {
color: #d40000;
font-weight: 600;
}
.el-form-item {
margin-bottom: 14px !important;
}
</style>
@@ -1102,7 +1102,7 @@ export default {
padding: 4px 0 6px;
:deep(.el-form-item) {
margin-bottom: 18px;
margin-bottom: 14px;
}
:deep(.el-form-item__label) {
@@ -1141,7 +1141,7 @@ export default {
column-gap: 112px;
:deep(.el-form-item) {
margin-bottom: 18px;
margin-bottom: 14px;
}
}
}
+11 -9
View File
@@ -1079,7 +1079,7 @@
ref="scoreForm"
:model="currentScore"
label-position="right"
label-width="auto"
label-width="150"
:disabled="readonly"
class="score-detail-form"
>
@@ -4416,8 +4416,8 @@ export default {
flex-wrap: nowrap;
:deep(.el-date-editor) {
flex: 0 0 150px;
width: 150px;
flex: 0 0 140px;
width: 140px;
min-width: 0;
}
@@ -4501,7 +4501,7 @@ export default {
padding: 8px 36px 0;
:deep(.el-form-item) {
margin-bottom: 18px;
margin-bottom: 14px;
}
:deep(.el-form-item__label) {
@@ -4629,7 +4629,7 @@ export default {
max-width: 620px;
:deep(.el-form-item) {
margin-bottom: 16px;
margin-bottom: 14px;
}
:deep(.el-input) {
@@ -4856,15 +4856,11 @@ export default {
.archive-page-form {
min-height: 100%;
//padding: 20px 24px 92px;
//background: #fff;
}
.archive-form__footer {
display: flex;
justify-content: flex-end;
gap: 12px;
.archive-page-form & {
position: fixed;
right: 0;
@@ -4901,4 +4897,10 @@ export default {
max-height: 72vh;
overflow: auto;
}
.el-col{
margin-bottom: 0;
}
.el-form-item {
margin-bottom: 14px !important;
}
</style>
+13 -13
View File
@@ -50,26 +50,26 @@ export default ({
__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, './'),