Compare commits

..

230 Commits

Author SHA1 Message Date
刘泉佳 44cec23147 fix: 发票信息查看下载优先走金蝶票池版式文件 2026-11-08 14:01:21 +08:00
刘泉佳 b218fc2345 feat: 付款记录付款凭证查看下载 2026-11-06 17:39:34 +08:00
刘泉佳 eb74466c30 refactor: 抽取金蝶附件查看下载公共方法 2026-11-06 17:39:30 +08:00
刘泉佳 79e903bcd8 fix: 修复发票登记弹窗金蝶同步状态不显示 2026-11-05 18:05:58 +08:00
刘泉佳 119d71b4f5 fix: 收票管理列表金蝶状态增加颜色标签与其他页面一致 2026-11-05 16:49:01 +08:00
刘泉佳 d57d3f86da fix: 金蝶票据池弹框样式与批量转结算保持一致 2026-11-05 16:34:04 +08:00
刘泉佳 2fd927a90c fix: 预付结算单与结算调整单分页固定底部,搜索label不再换行 2026-11-05 15:01:28 +08:00
刘泉佳 8057e860de fix: 批量转结算弹窗居中限高并固定勾选列 2026-11-05 14:47:06 +08:00
刘泉佳 ffc2942d2b fix: 付款申请结算单号误跳预付结算单过滤-1占位id 2026-11-05 13:30:36 +08:00
刘泉佳 22bbcbb7aa fix: 应收转结算与开票申请点击结算单号跳转收款结算单 2026-11-05 13:30:36 +08:00
刘泉佳 4dd63fc5fa feat: 收票登记新增所属合同带出承办部门替代手选结算组织 2026-11-05 12:05:56 +08:00
刘泉佳 c525bffeca fix: 修复开票申请提交返回列表页未刷新 2026-11-05 11:44:19 +08:00
b2894lxlx 8f3da0e386 ✨ 预结算收款方与付款方改为下拉选择
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 23:47:00 +08:00
b2894lxlx d4ae6d521a ✨ 客商评分双模式与金蝶公司代码改回组织维护
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 23:46:20 +08:00
刘泉佳 b36fa18db5 Merge remote-tracking branch 'origin/master' 2026-11-04 22:19:13 +08:00
刘泉佳 f648285406 fix: 修复客商档案供应商编码查询无结果时显示[object Object] 2026-11-04 22:17:52 +08:00
b2894lxlx fff0a29947 ✨ 业务部门配置支持维护金蝶公司代码
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 22:15:48 +08:00
b2894lxlx 64b61c9c29 Merge pull request '付款结算 / 收票登记 / 发票认领模块' (#12) from feature/20261103-bfi-warehouse-web into master
Reviewed-on: #12
2026-11-04 21:22:51 +08:00
刘泉佳 5d56706eed Merge branch 'master' into feature/20261103-bfi-warehouse-web
# Conflicts:
#	src/option/settlement/formalSettlement.js
2026-11-04 17:18:51 +08:00
b2894lxlx 9efae11811 ✨ 合并远程收款流水列表改造 2026-11-04 16:56:30 +08:00
b2894lxlx 9a04757a9e ✨ 合并正式结算单金蝶冲销状态列
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 16:55:57 +08:00
b2894lxlx e88a27d01f Merge pull request '收款流水列表改造' (#10) from feature/20261027-hcs-bif-all-web-recBillSync into master
Reviewed-on: #10
2026-11-04 16:45:48 +08:00
b2894lxlx b9c367e028 Merge pull request '金蝶单据状态着色并悬浮展示失败原因+认领流水认领记录的改造' (#9) from feature/20261027-hcs-bif-all-web into master
Reviewed-on: #9
2026-11-04 16:33:35 +08:00
刘泉佳 7f694bad55 fix: 收款结算单不展示已指定应付列 2026-11-04 11:02:45 +08:00
b2894lxlx 014ee537a0 ✨ 组织编码/平台公司、合同计费客商角色与基础模块查看按钮
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 03:15:44 +08:00
b2894lxlx 5bde60d844 ✨ 编辑用户增加数据管理权限多选组织
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 03:14:45 +08:00
刘泉佳 68904b02a5 fix: 审核按钮统一超时设置60s 2026-11-03 09:54:14 +08:00
b2894lxlx c0ba9ba39b ✨ 计费/调度/结算支持多方客商与承运商区分
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 21:12:56 +08:00
b2894lxlx 8388a55481 ✨ 新增业务部门配置页并简化项目/合同部门选择
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 21:10:47 +08:00
刘泉佳 b492dd35ea fix: 结算单列表已指定应付列定义补充 2026-11-02 19:58:01 +08:00
刘泉佳 acb74cbc9a fix: 结算单列表指定失败标签补悬浮原因 2026-11-02 19:56:54 +08:00
刘泉佳 9a51bd2cfd feat: 结算单/付款申请单列表指定失败悬浮展示原因 2026-11-02 19:54:15 +08:00
刘泉佳 4dd54f7e4c feat: 付款申请单列表增加已指定应付列 2026-11-02 19:36:04 +08:00
刘泉佳 6705763951 feat: 查看页指定失败行支持编辑匹配金额随重推生效 2026-11-02 19:16:41 +08:00
刘泉佳 4ba18acd25 feat: 查看页重推认领收票单并放开付款匹配金额编辑 2026-11-02 18:38:18 +08:00
刘泉佳 c07371450f feat: 发票登记弹窗匹配结算单金额支持手动修改 2026-11-02 18:38:18 +08:00
刘泉佳 5fbbb5f88f feat: 结算单发票信息增加是否已指定应付列 2026-11-02 18:10:53 +08:00
刘泉佳 1ed7955764 feat: 发票登记选票暂存展示且确认后才保存推送 2026-11-02 18:10:53 +08:00
刘泉佳 daab1001db fix: 付款管理批量同步增加遮罩并统一金蝶接口30s超时常量 2026-11-02 17:15:50 +08:00
刘泉佳 f5735e6bad fix: 收票登记提交返回列表后刷新列表数据 2026-11-02 16:36:00 +08:00
刘泉佳 bd9da83175 feat: 发票登记弹窗回显已认领收票单并补齐结算基本信息 2026-11-02 16:13:00 +08:00
刘泉佳 b59e43c9b7 feat: 认领收票单候选弹窗改为分页并悬浮展示推送失败原因 2026-11-02 16:12:52 +08:00
刘泉佳 eae50ab909 feat: 发票登记移至工具栏并回显已认领收票单 2026-11-02 15:39:26 +08:00
b2894lxlx 8e1430491b ✨ 项目提交MK传projectType,并修复已撤回再提交
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 14:28:07 +08:00
b2894lxlx 9fed3785d9 ⚡ 流程配置取消默认按当前用户部门筛选
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 13:32:06 +08:00
b2894lxlx eaf431819e ✨ 客商档案营业执照OCR改接新接口并回填字段
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 13:31:33 +08:00
刘泉佳 c7bd4461e2 Merge remote-tracking branch 'refs/remotes/origin/master' into feature/20261030-kingdee-writeoff-status-web
# Conflicts:
#	src/views/business/contract-manage.vue
2026-11-02 09:54:00 +08:00
刘泉佳 ab7491e6d6 feat: 收票登记表单去税率展示字段 2026-11-02 09:36:41 +08:00
刘泉佳 d78d100824 feat: 全额认领不再校验匹配金额合计上限 2026-10-31 16:52:22 +08:00
刘泉佳 9908b543ea fix: 项目立项补充getRisk导入修复风险查询未定义 2026-10-31 16:37:01 +08:00
刘泉佳 46e7952e49 feat: 付款结算单选收票单全额认领并新增发票登记推送指定应付 2026-10-31 16:07:12 +08:00
刘泉佳 441d62a874 feat: 收票登记先票后款表单改造(手选结算组织/发票明细区块/去关联结算单) 2026-10-31 16:07:04 +08:00
刘泉佳 1a143aa05b fix: 合同管理删除hcsRepush重复导入修复构建 2026-10-31 16:07:04 +08:00
刘泉佳 f3d69d7df0 fix: 代码合并报错 2026-10-31 15:38:27 +08:00
刘泉佳 cd58e8ebc7 Merge branch 'feature/20261027-hcs-bif-all-web-recBillSync' into feature/20261031-bfi-收票收款改造-web 2026-10-31 14:18:38 +08:00
刘泉佳 11d33df46a Merge branch 'master' into feature/20261027-hcs-bif-all-web
# Conflicts:
#	src/views/business/contract-manage.vue
2026-10-31 14:12:05 +08:00
刘泉佳 06de9477e5 feat: 客商收款信息开户行失焦匹配金蝶带出联行号并保存前校验
- 开户行 label 加问号提示,失焦调 A905-3 按行名精确匹配自动回填联行号,未命中提示检查
- 保存前按联行号校验金蝶档案,查不到拦截保存,接口异常提示稍后重试
- 回填后清除联行号字段残留校验提示
2026-10-31 10:16:38 +08:00
刘泉佳 f7ee9273c6 feat: 合同与客商列表客商风险标签统一悬浮显示明细提示 2026-10-30 21:40:04 +08:00
刘泉佳 e0a420ad08 feat: 正式结算单列表新增金蝶冲销状态列
- 列表新增「金蝶冲销状态」单元格插槽:已同步显示 已冲销/未冲销/未校验 标签,未同步显示 -
- 新增 kingdeeWriteOffStatusOptions 选项与 kingdeeWriteOffTagType 颜色映射
2026-10-30 21:23:53 +08:00
刘泉佳 8388904916 fix: 合同列表客商风险列排序调整至签约类型之后 2026-10-30 21:02:27 +08:00
刘泉佳 24fec3c7d7 fix: 客商风险按编号查询透传客商名称 2026-10-30 20:53:41 +08:00
刘泉佳 b76ada9d6c fix: 修改信用代码时同步清空客商名称下风险提示 2026-10-30 20:53:22 +08:00
刘泉佳 2083754b16 feat: 新增客商填写信用代码后即时回显金蝶供应商编码 2026-10-30 20:41:26 +08:00
刘泉佳 3df09d7d45 feat: 客商页面新增供应商编码展示 2026-10-30 20:34:25 +08:00
刘泉佳 b878bc356e fix: 付款页风险客商提示改用后端明细去除重复等级黑名单前缀 2026-10-30 20:20:23 +08:00
刘泉佳 d76977c07c fix: 新增客商档案风险提示按等级与黑名单细分并优先按编号查询 2026-10-30 20:09:57 +08:00
b2894lxlx d95163663b ✨ immediate-enabled关闭时隐藏项目/合同推送合规入口
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 16:34:45 +08:00
刘泉佳 e95151ad51 feat: 合同列表新增客商风险列并抽取风险标签公共展示方法(客商页复用) 2026-10-30 14:41:49 +08:00
刘泉佳 5351911d50 refactor: 合同侧黑名单状态字段改kingdeeBlackStatus(与后端列名统一) 2026-10-30 14:10:30 +08:00
刘泉佳 e602eecee8 fix: 客商列表黑名单标签判定改为仅金蝶黑名单命中(与后端口径一致) 2026-10-30 13:42:20 +08:00
刘泉佳 70801096dd feat: 付款/收款结算单查看页面新增同步金蝶按钮,单张推送并提示结果与异常原因 2026-10-30 12:21:16 +08:00
刘泉佳 025cba317d fix: 修复客商档案清空客商风险后仍按上次条件查询
Avue 搜索事件只回传非空筛选项,被清空的字段不会出现在参数里,
与上一次查询条件合并会保留旧值,改为点击查询时整体重建查询条件。
2026-10-30 12:12:57 +08:00
b2894lxlx 5ce91ec5d5 🐛 总单选址弹窗固定label宽度,并兼容运输类型非字符串
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 11:47:51 +08:00
刘泉佳 26140aecb9 refactor: 客商列表风险筛选项改为纯字典加载(customer_risk)去除硬编码兜底 2026-10-30 11:47:37 +08:00
b2894lxlx 8d85311bb3 ✨ 司机身份证OCR支持正反面识别并回填有效期
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 11:47:21 +08:00
刘泉佳 8b2fa09cb9 fix: 客商列表风险列加宽至150适配组合文案 2026-10-30 11:46:07 +08:00
刘泉佳 4d78466824 fix: 客商列表风险列黑名单与任意等级组合展示(如低风险、黑名单) 2026-10-30 11:44:55 +08:00
b2894lxlx fe4255c59b 🐛 修复正式结算旧路径空白,按单号跳转付款/收款列表
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 11:40:32 +08:00
刘泉佳 c6c7c80d97 feat: 客商列表风险列内部组织客商默认无风险并组合展示高风险与黑名单 2026-10-30 11:35:46 +08:00
刘泉佳 3397bd6a7b feat: 客商列表风险列改读落库字段并支持字典筛选与后台明细悬浮展示 2026-10-30 11:33:38 +08:00
b2894lxlx 083d954765 🐛 放开本账号运单复制,并避免总单选地址时提前校验
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-29 22:44:01 +08:00
刘泉佳 78bb38e955 🐛 收款流水认领额度改用收款金额并新增剩余可认领金额列
认领只登记本地,额度由本地累计认领控制,金蝶未匹配金额不再作为上限;批量认领校验复用剩余可认领金额口径。
2026-10-29 20:07:53 +08:00
刘泉佳 4b060fc045 feat: 开票申请查看展示每张发票金蝶开票状态与结果备注 2026-10-29 18:31:35 +08:00
刘泉佳 2cf966d6e3 feat: 开票申请查看展示每张发票金蝶开票状态与结果备注 2026-10-29 18:17:42 +08:00
刘泉佳 d23a9b00de refactor: 收款流水未匹配金额字段名统一为unmatchAmountRec 2026-10-29 16:13:23 +08:00
刘泉佳 d89fd9db97 feat: 收款流水列表展示匹配与结算金额字段 2026-10-29 16:06:03 +08:00
刘泉佳 94ee14b683 feat: 收款流水列表展示收款人公司与核算组织编码 2026-10-29 15:05:28 +08:00
刘泉佳 a725d3078f fix: 收款流水详情收款金额显示名称修正 2026-10-29 14:12:55 +08:00
b2894lxlx 185a0f9aec Merge remote-tracking branch 'websoft/dev' 2026-10-29 14:05:12 +08:00
b2894lxlx a528788409 🐛 修复总单地址回填、结算费用英文项与项目部门提示
常用地址级联回填不再误清空;预结算/正式结算过滤 freightJson 杂项;项目查看态业务部门/平台公司支持悬停完整信息。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-29 14:04:55 +08:00
刘泉佳 1957a78c00 fix: 收款流水同步接口超时时间延长到30秒 2026-10-29 12:55:03 +08:00
刘泉佳 acfe9bdd74 feat: 收款流水页面适配A301-1数据源并下线推送金蝶相关交互 2026-10-29 11:20:18 +08:00
weicw 72471f48de 🔧 类型检查改用 jsconfig 并锁定 TypeScript 5.9,恢复 vue-tsc 可用 2026-10-29 09:45:31 +08:00
weicw 415e1fd58d ✨ 维修记录列表展示关联运单与关联异常 2026-10-29 08:58:21 +08:00
刘泉佳 698f808f0b fix: 结算行付款金额联动回填申请付款金额并校验合计一致
结算信息行改动付款比例或付款金额时,自动把行金额合计回填到上方申请付款金额并重算比例;
保存/提交前校验行金额合计与申请付款金额一致,避免推送金蝶金额与预付建档分摊不一致。
回显加载走默认不同步,避免改写历史单据。
2026-10-29 02:19:23 +08:00
刘泉佳 ea6ec13ea4 fix: 付款申请拦截已调整结算单并过滤候选列表 2026-10-29 02:15:57 +08:00
weicw 114c2fd2af 🔀 合并主干 master 到 dev 2026-10-29 00:16:39 +08:00
刘泉佳 6119c44394 fix: 金蝶单据状态标签移除 status-text 类恢复颜色展示 2026-10-28 19:04:09 +08:00
刘泉佳 9dab7f7495 fix: 结算列表插槽列被兜底 formatter 覆盖导致状态无颜色与悬浮 2026-10-28 17:27:10 +08:00
刘泉佳 f5697e111a feat: 结算单与客商档案金蝶状态着色并悬浮展示失败原因 2026-10-28 17:00:34 +08:00
刘泉佳 775307f5c0 feat: 付款与开票申请金蝶单据状态着色并悬浮展示失败原因 2026-10-28 17:00:34 +08:00
刘泉佳 4502881063 feat: 付款申请审批通过前增加确认弹框 2026-10-28 16:11:29 +08:00
刘泉佳 358add743e fix: 合同变更审批接口超时统一使用APPROVE_TIMEOUT 2026-10-28 14:36:03 +08:00
刘泉佳 f67a4036ed fix: 项目变更与合同管理审批接口超时统一使用APPROVE_TIMEOUT 2026-10-28 14:16:42 +08:00
b2894lxlx 7ba6620f06 Merge remote-tracking branch 'websoft/dev' 2026-10-27 18:38:25 +08:00
b2894lxlx 9a3652d767 🔧 构建完成后自动打包 dist.zip
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-27 18:34:11 +08:00
b2894lxlx 450505715a ✨ 新增合同变更菜单列表与MK审批对接
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-27 18:34:11 +08:00
weicw 9a603a3796 🔀 合并 master 回 dev,追平 HCS/BFI 与项目变更菜单 2026-10-27 17:44:08 +08:00
b2894lxlx 2b51619728 ✨ 新增项目变更菜单列表与MK审批对接
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-27 16:45:38 +08:00
b2894lxlx 5f167aae50 Merge pull request '收票管理/开票管理/正式结算单/预结算单/结算调整通过按钮增加表格遮罩防重复点击' (#7) from feature/20261019-bfi-all-web into master
Reviewed-on: #7
2026-10-27 15:29:12 +08:00
刘泉佳 0d8eb25253 Merge branch 'master' into feature/20261019-bfi-all-web
# Conflicts:
#	src/views/payment/invoice-application.vue
#	src/views/payment/invoice-receipt.vue
#	src/views/payment/payment-application.vue
#	src/views/settlement/formal-settlement.vue
#	src/views/settlement/pre-settlement.vue
2026-10-27 15:03:17 +08:00
b2894lxlx 74988c0b5e Merge pull request '项目+合同对接合同系统' (#8) from feature/hcs-api-p0-web into master
Reviewed-on: #8
2026-10-27 14:47:14 +08:00
weicw e53852b05f ⚡ 后台小程序用户列表不再提供删除入口
- 移除工具条「批量删除」与行内「删除」两个入口
- 一并删除 handleDelete / rowDel 与 permissionList.delBtn,不留无入口的死方法
- 删除是逻辑删除,且手机号唯一键不排除已删行,删过的手机号既登录不了也注册不了;终止访问改用「停用」
- 保留 api/system/mpUser.js 的 remove 封装与后端 /mp-user/remove 接口,本次只隐藏入口
- 保留表格多选列,批量「密码重置」仍依赖勾选
2026-10-27 11:12:21 +08:00
weicw 8d3e87d710 ⚡ 移除新增配载管理页面的配载条件说明文案 2026-10-27 10:56:22 +08:00
weicw c7b07f0c95 💄 运单管理待回单确认的操作文案改为完成 2026-10-27 08:17:52 +08:00
b2894lxlx 4c117d87a5 ✨ 角色数据权限配置并移除用户侧数据权限
角色权限弹窗支持按菜单批量设置数据范围;用户管理去掉组织/客商权限配置;角色别名改为角色标识。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-27 04:05:02 +08:00
weicw 59199397e0 🔀 合并票 03 前端:后台 openid 改为只读 2026-10-26 14:53:42 +08:00
weicw 70d49d022b 💄 后台小程序用户 openid 改为只读展示 2026-10-26 14:53:11 +08:00
weicw 7d16660bfc 🔀 合并主干 master 到 dev 2026-10-26 14:18:43 +08:00
b2894lxlx d6bcb12c20 Merge remote-tracking branch 'websoft/dev'
# Conflicts:
#	src/views/transportCapacity/vehicle.vue
2026-10-26 10:51:35 +08:00
b2894lxlx e1d87f5788 ✨ 结算汇率反算、开票合计填报、配载承运商交集与车辆组织悬停
外币结算按本位币/汇率反算;开票改填合计金额并反算不含税单价;配载展示承运商合同并按乙方交集限选;评分加减分封顶15分;车辆所有组织/使用部门悬停显示完整路径。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-26 10:50:58 +08:00
weicw 56b8f68449 ✨ 过程配置回单「内部人员」改为只读并按是否确认自动勾选 2026-10-25 23:10:24 +08:00
weicw 0ac17dea0b 🐛 fix(运单管理): 打卡记录里程回显按正整数归一
后端若仍下发 "200.00",编辑打卡记录时原样保存会被正整数校验拒掉。
2026-10-25 21:57:33 +08:00
weicw 39e3115fbe 🔀 合并「所属组织不是小程序注册与司机建档门槛」 2026-10-25 20:24:41 +08:00
weicw 9c0f7cb3f4 📝 所属组织可为空的注释改为事实句 2026-10-25 20:21:16 +08:00
weicw 7b478bdbf2 📝 修正未分配组织筛选的注释与接入步骤 2026-10-25 20:21:16 +08:00
weicw 87d6ff97a3 🔀 合并票 06 前端未分配组织筛选 2026-10-25 19:50:08 +08:00
weicw e3bd44e8fc 🔀 合并票 05 前端取消所属组织必填 2026-10-25 19:49:54 +08:00
weicw 324daf9d4d ✨ 司机与车辆列表支持按未分配组织筛选 2026-10-25 19:48:08 +08:00
weicw 37ac683fb4 🐛 前端取消所属组织必填 2026-10-25 19:46:42 +08:00
weicw c402d68aea 🔥 管理端打卡照片去掉主图角标
主图角标按分组下标标在第一张上,既没有独立字段也不能切换,与司机端一致删除;v-for 不再需要下标。
2026-10-24 22:19:23 +08:00
weicw e8f6311477 ✨ 编辑打卡记录支持里程采集与结算后冻结 2026-10-24 21:29:21 +08:00
b2894lxlx fa78a68df6 🐛 过程配置项目下拉兼容变更已通过状态
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 14:56:10 +08:00
b2894lxlx 320422856c 🔀 合并 websoft/dev,统一待回单确认与公路配载校验
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 14:55:04 +08:00
weicw-dshsa 0add4c5cdc ♻️ 派单候选司机不再按承运商组织过滤
- 新增 fetchDriverCandidates:按租户与岗位取司机,不再解析承运商联系人组织
- driverOptionText 只做展示(姓名+手机尾号后四位),写回表单的仍是姓名
- 删除 fetchDriversByCarrierOrganizations 与无调用者的 clearCarrierOrganizationCache
- 运单管理、总单派车、运输计划、装卸管理四页同步;去掉「请先选择承运商」前置
2026-10-23 20:03:49 +08:00
刘泉佳 f6621392b1 Merge remote-tracking branch 'origin/master' into feature/hcs-api-p0-web
# Conflicts:
#	src/views/business/contract-manage-change.vue
2026-10-23 19:20:12 +08:00
b2894lxlx 2a5eca8393 ✨ 评分减分累计与最高扣分、用户部门标签联动树,并修复对账导入与结算调整等
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-23 04:28:22 +08:00
刘泉佳 e4ae1d3c77 fix: 推送合规失败提示与拦截器重复弹出,合并为单条 2026-10-22 20:20:41 +08:00
刘泉佳 96783fcd7b feat: 推送合规按钮增加加载遮罩防重复操作 2026-10-22 20:08:02 +08:00
weicw-dshsa 372e8826a2 🔀 合并待回单确认功能 2026-10-22 16:51:06 +08:00
b2894lxlx 8d9fe998c0 ✨ 进行中配载可编辑、汇票选择与收款方展示优化,并支持业务部门与待确认回单
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 15:41:01 +08:00
weicw-dshsa 92a8d4aab1 🔀 合并待回单确认审查修整 2026-10-22 14:53:04 +08:00
weicw-dshsa 59a1ca8a0d ♻️ 删除待回单确认标签色死分支并订正注释 2026-10-22 14:39:50 +08:00
刘泉佳 b9de34aa0e feat: 审批通过按钮增加防重复点击,避免重复提示 2026-10-22 14:06:50 +08:00
weicw-dshsa 5544cfb3ca 🔀 合并待回单确认禁止补录打卡入口 2026-10-22 13:48:56 +08:00
weicw-dshsa fcd5ef7df9 🔀 合并待回单确认主分支 2026-10-22 13:44:19 +08:00
weicw-dshsa 489b406214 🔀 合并待回单确认管理端动作门禁 2026-10-22 13:44:10 +08:00
weicw-dshsa 353148eb7f 🐛 待回单确认下拒绝补录打卡 2026-10-22 13:44:06 +08:00
weicw-dshsa 293b4fc6ce 🔀 合并待回单确认前端状态名称 2026-10-22 13:44:03 +08:00
weicw-dshsa 53b588063e 🐛 待回单确认运单支持管理端完成与取消 2026-10-22 13:42:43 +08:00
weicw-dshsa 0889f29c59 🐛 待回单确认补齐前端状态名称与标签色 2026-10-22 13:40:46 +08:00
weicw-dshsa f1c004813e 🔀 合并待回单确认 web 状态与确认按钮 2026-10-22 13:36:32 +08:00
weicw-dshsa d67cf55491 ✨ 运单列表支持待回单确认状态 2026-10-22 13:32:35 +08:00
b2894lxlx d7969c191a ✨ 客商档案表单重构,合同流水号-1列表显示为空
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 12:56:50 +08:00
刘泉佳 2f64abfc83 feat: 审核按钮统一30秒超时并加遮罩 2026-10-22 12:06:27 +08:00
b2894lxlx a657b9d0ef ✨ 小程序用户管理支持所属组织与数据权限配置
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 10:59:22 +08:00
刘泉佳 9558426da1 Merge remote-tracking branch 'origin/master' into feature/hcs-api-p0-web 2026-10-22 09:43:02 +08:00
weicw-dshsa 6384e80758 🐛 司机档案住址与出生年月口径对齐小程序
- 住址只读态直接显示区划名称串,不再用级联反解(改名后会退化成灰字 placeholder)
- 身份证号联动改为失焦触发,且只在出生年月/性别为空时补,不覆盖手填值
- 认证审核页标签改为「住址」「详细地址」
- 详细地址 placeholder 补明采集范围,空住址提示改为「请选择省/市/区」
2026-10-22 00:17:55 +08:00
b2894lxlx e7c76c803f ✨ 结算单业务模式运输方式与编辑体验优化
预结算/收付款结算支持业务模式与运输方式展示导出;完善结算调整、对账导入与变更记录;组织合同初始流水号未填不再显示-1。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 16:42:02 +08:00
刘泉佳 eb32a275f3 Merge remote-tracking branch 'origin/master' into feature/20261019-bfi-all-web
# Conflicts:
#	src/views/settlement/formal-settlement.vue
2026-10-21 16:15:04 +08:00
刘泉佳 9150213b0f feat: 新增客商查询金蝶后回显客商编码 2026-10-21 15:49:44 +08:00
b2894lxlx 6a21b4ac8d ✨ 人员同步提示改为按工号入库并展示删除人数
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 15:36:34 +08:00
b2894lxlx 1c798046fd 🐛 修复收票付款与结算费用选择框长文本展示
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 11:53:56 +08:00
b2894lxlx 8d25e186b1 ⚡ 调度剩余数量按本次填写联动,公路运输司机手机号必填
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 11:53:56 +08:00
b2894lxlx c1863d0b6a ✨ 再次点击组织取消人员筛选
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 11:53:55 +08:00
b2894lxlx 4a0af452e8 ✨ 合同编号支持手工填写,组织可配置初始流水号
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 11:53:55 +08:00
gxwebsoft 5c97d3b5c5 Merge remote-tracking branch 'origin/master' 2026-10-20 19:08:26 +08:00
gxwebsoft 09ce6472b0 refactor(settlement): 全面重构结算相关页面为 avue-crud 组件
- formal-settlement.vue 中移除旧搜索区,移除多余样式,实现结算表格面板按激活标签条件渲染
- 替换 formal-settlement-table-panel.vue 为基于 avue-crud,简化表格和操作,优化分页和搜索事件处理
- 新版 formal-settlement 中统一使用 onLoad 替代 loadTable 方法,简化数据加载逻辑
- 删除不必要的样式代码,统一表格奇偶行背景颜色,调整整体样式提升视觉一致性
- 结算导入页面 settlement-import.vue 同样迁移至 avue-crud 组件实现,实现搜索、分页、刷新等功能事件解耦
- 优化分页数据结构,统一使用 currentPage 和 pageSize 命名,支持多页容量选择
- 调整部分接口调用及事件回调,保障新组件兼容已有业务流程及权限控制
- 移除废弃的内部变量与混入,针对权限控制和字段渲染进行明确封装和暴露
- 保持功能行为不变的前提下提升代码可维护性和复用性,增强组件封装与状态同步能力
2026-10-20 18:57:28 +08:00
b2894lxlx 5b435bef04 ✨ 导入运单支持未结算整批删除,结算导入运输方式取业务字典
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 14:43:52 +08:00
weicw-dshsa d3af455374 📝 修正配载候选运单组织范围注释 2026-10-20 11:54:49 +08:00
b2894lxlx 4c9bbf1eb7 🐛 修复预付驳回重提、预结算变更记录与收票多选结算单
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 01:50:54 +08:00
刘泉佳 4e8163dd18 feat: 开票申请选单放开未收票过滤并展示已申请/可开票金额
- 选单弹窗新增已申请开票金额列,分摊发票金额改为只读展示后端自动分摊结果
- 选单后分摊金额默认归零,由后端保存时按本次开票金额自动分摊
2026-10-19 23:30:18 +08:00
b2894lxlx 31794bf26a 🐛 结算导入去掉模板结算金额依赖并取消编辑回算
导入后按运输费与费用项自动合计结算金额,编辑费用项时不再覆盖结算金额。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 23:28:51 +08:00
刘泉佳 89bbc43f12 feat: 汇票付款审批通过增加确认弹窗与表格遮罩防重复点击 2026-10-19 20:08:51 +08:00
刘泉佳 4199318aa9 fix: 收票管理/开票管理/正式结算单/预结算单/结算调整通过按钮增加表格遮罩防重复点击 2026-10-19 20:08:37 +08:00
刘泉佳 555189177c fix: 付款管理审批通过改为表格遮罩防重复点击 2026-10-19 19:55:24 +08:00
weicw-dshsa 1622015724 🔀 合并 origin/master 到 dev
# Conflicts:
#	src/views/business/components/business-crud-page.vue
2026-10-19 19:06:34 +08:00
b2894lxlx 0e57de5c71 🐛 修复收款流水继续认领预填与付款申请预结算引用提交
部分认领打开时预填历史关联结算单,可认领上限按公告未认领与收款金额取小;付款申请提交前清理误存正式结算单引用。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 17:12:24 +08:00
b2894lxlx 5cb7eb16d5 ⚡ 修复运输计划/发货模板列表缓存不刷新,并优化结算导入查看与费用弹窗
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 16:56:03 +08:00
b2894lxlx ccaf944782 Merge pull request '结算调整单接入金蝶列表展示与冲销预检' (#6) from feature/20261016-settlement-adjustment-kingdee into master
Reviewed-on: #6
2026-10-19 15:00:17 +08:00
b2894lxlx aee661772b ✨ 完善付款申请多结算行、结算导入编辑与计费规则展示
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 12:06:51 +08:00
刘泉佳 bbe71e340a fix: 付款管理审批通过按钮增加防重复点击 2026-10-19 01:36:52 +08:00
b2894lxlx 1ac741f785 ✨ 完善导入结算单明细编辑、草稿刷新与历史运单选择
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 20:35:28 +08:00
weicw-dshsa f7d8fbf16b 🐛 配载候选列表不再按运输方式过滤并订正可配载注释 2026-10-18 15:49:38 +08:00
刘泉佳 912c1169a6 Merge remote-tracking branch 'origin/master' into feature/20261016-settlement-adjustment-kingdee 2026-10-18 15:14:38 +08:00
weicw-dshsa 847f09b14d ♻️ 运单管理公路配载改用可配载查询回查并清理自造判据 2026-10-18 14:51:33 +08:00
刘泉佳 6596722601 feat: 正式结算单新增预付汇总栏与付款记录合并展示
结算合计下新增汇总栏(结算合计原金额-已付金额含预付核销-剩余未付);只读付款信息合并展示进度预付-项目预付核销-结算尾款三类记录;预结算转正式自动预填来源预结算核销行;单据状态名提取公共 settlementStatus 工具与预结算编辑器共用
2026-10-18 14:32:15 +08:00
weicw-dshsa 2ae817ec38 ✨ 运单管理新增「确认回单」行操作
回单节点配成「需业务员确认」时,运单会一直停在进行中等人确认,此前后台没有这个入口。
在与「完成」并列的位置加一个「确认回单」行操作:只在进行中的运单上出现,沿用「完成」的
权限码(它比完成更严——后端还要校验司机打卡节点齐全且无未了结异常,故不会多开出任何权限)。
确认后不可撤销,提示里写明。
2026-10-18 12:15:07 +08:00
b2894lxlx 6f2383a7e5 ✨ 结算付款体验优化与合同/运单导入校验
付款申请汇票筛选与提示去重、结算菜单拆分与金蝶同步弹框、应收应付详情高度、合同计费与半月结调整,以及导入运单批量删除校验。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 05:12:26 +08:00
b2894lxlx 8db52ef168 🐛 修复变更审批通过后列表不显示再次变更入口
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-17 17:03:26 +08:00
b2894lxlx 38ca250242 ✨ 项目变更可编辑业务部门/平台公司,组织名称列左对齐
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-17 17:01:27 +08:00
刘泉佳 73c12cc1c7 fix: 修复带detailNo跳转正式结算单时缓存实例重复弹出查看弹框 2026-10-17 16:34:08 +08:00
b2894lxlx f4542cd0b8 ⚡ 组织同步按公司刷新树并加宽组织名称列
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-17 16:22:39 +08:00
刘泉佳 4b165471e3 feat: 结算调整单结算单号支持点击跳转正式结算单详情 2026-10-17 16:08:29 +08:00
b2894lxlx d1e779bbcc ⚡ 优化组织全量同步流程,部门按公司轮询并去掉确认弹窗
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-17 15:35:20 +08:00
weicw-dshsa 6d80ffde6c ✨ 认证审核对比补充出生年月与住址字段
审核对比的字段元数据加上 birthday / addressRegion / address:司机端本次新提交
的这几项若不登记,审核页既不展示也 diff 不出改动,审核员等于在不知情下通过。
2026-10-17 12:38:17 +08:00
b2894lxlx acfbb4c54e 🐛 修复运输对账匹配日期格式与完成对账疑似重复误判
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-17 08:31:27 +08:00
weicw-dshsa 44809ad303 Merge remote-tracking branch 'origin/master' into dev 2026-10-17 01:12:09 +08:00
刘泉佳 f440343830 style: 修复看板组件 sass @import 弃用警告,改用 @use 2026-10-17 00:56:04 +08:00
刘泉佳 c1bc55e133 refactor: 结算调整单推送金蝶与重新推送合并为单一按钮 2026-10-16 19:58:52 +08:00
刘泉佳 baec42f876 feat: 正式结算单展示调整标识与重推入口 2026-10-16 19:47:08 +08:00
刘泉佳 4d3b6cdaed feat: 结算调整单接入金蝶列表展示与冲销预检 2026-10-16 19:47:01 +08:00
b2894lxlx 96b675b3be Merge remote-tracking branch 'websoft/master' 2026-10-16 16:05:55 +08:00
b2894lxlx 6067d24e3b 🐛 修复列表二次搜索与结算导入回列表,并完善付款申请多结算单展示
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 16:03:26 +08:00
liuquanjia e051327f2d Merge pull request 'fix: 批量认领补充剩余可认领金额展示与流水维度分摊上限' (#5) from feature/bfi-api-rate into master
Reviewed-on: #5
2026-10-16 15:34:35 +08:00
刘泉佳 f7759fd9e7 fix: 批量认领补充剩余可认领金额展示与流水维度分摊上限 2026-10-16 15:26:56 +08:00
weicw-dshsa 91cb3ee8c2 🐛 修复运输计划调度确认生成仍强制校验司机 2026-10-16 12:57:14 +08:00
weicw-dshsa b2a6d73bac 🐛 修复导入运单明细重载后实际/预计时间列为空
明细接口返回运单实体字段 startDate/endDate/estimatedStartTime/estimatedEndTime,
明细表与编辑回填按导入列字段名 actualStartDate 等读取,重载后统一在 normalizeDetailRow 对齐。
2026-10-16 12:10:47 +08:00
b2894lxlx 4287133f84 Merge remote-tracking branch 'websoft/dev' 2026-10-16 11:48:10 +08:00
b2894lxlx 4b01316f64 ✨ 新增项目预付核销 UI,并修复常用线路新增不显示与付款提交后刷新
结算单附件增加项目预付核销;常用线路超管默认查全部组织并按筛选归属新增;付款申请提交后关页并刷新列表;平台公司选项 ID 按字符串绑定。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 02:02:00 +08:00
b2894lxlx 0b170fe5c1 Merge remote-tracking branch 'websoft/master' 2026-10-16 00:57:59 +08:00
b2894lxlx 4f29b1e0eb Merge pull request '客商审核通过后推送金蝶失败原因以警告提示展示、以及 付款申请/开票申请/正式结算单列表展示金蝶解锁终态标识' (#4) from feature/bfi-api-rate into master
Reviewed-on: #4
2026-10-16 00:57:51 +08:00
b2894lxlx 6db599400e Merge remote-tracking branch 'websoft/master' 2026-10-16 00:57:19 +08:00
b2894lxlx c5a74acd00 ✨ 新增导入结算单页面,并优化 MK 开关缓存与客商同步入口
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 00:56:55 +08:00
刘泉佳 f23d65e41e :fix: 客商审核通过后推送金蝶失败原因以警告提示展示 2026-10-15 21:14:06 +08:00
weicw-dshsa 6b0cb37699 ✨ 结算明细的单据号、运单号支持跳转应收应付明细与运单详情 2026-10-15 20:41:20 +08:00
weicw-dshsa c867c5d7a5 ⚡ 新增预结算与正式结算合同币种为人民币时隐藏汇率日期、结算汇率与本位币合计 2026-10-15 20:12:05 +08:00
weicw-dshsa 903e0a4d7b 📝 自检端口修正为 2888 2026-10-15 18:55:46 +08:00
weicw-dshsa 6461c3d80f 💄 正式结算单号改为蓝色显示 2026-10-15 18:04:56 +08:00
weicw-dshsa 8380351529 🐛 修复新增预结算页收付方、创建信息与预结算单号为空 2026-10-15 16:56:52 +08:00
weicw-dshsa f8a238d0c0 ⚡ 批量转结算弹窗去掉预结算单号与正式结算单号列 2026-10-15 15:32:06 +08:00
weicw-dshsa 5c6fcbe951 ⚡ 维修记录里程单位改由车船类型自动推导,表单去掉里程单位项
- 里程/航程数标签去掉「(公里)」,表头不再需要 renderHeader 覆盖
- 里程单位列不再出现在表单(display: false),无需人工选择
- 单位仍按车船类型推导:车辆=公里、船舶=海里,保存时再兜底推导一次
2026-10-15 15:14:03 +08:00
weicw-dshsa 7997089b16 🐛 总单列表悬浮气泡展示完整地址、创建时间只保留日期 2026-10-15 14:45:00 +08:00
gxwebsoft 089b74479b Merge remote-tracking branch 'origin/master' 2026-10-15 14:24:34 +08:00
gxwebsoft cf542313f7 style(contract-manage): 注释掉表单项底部外边距
- 将.el-form-item的margin-bottom属性注释,调整样式间距
- 保持对齐方式不变以确保布局稳定
- 便于后续样式调整和优化维护
2026-10-15 14:24:31 +08:00
b2894lxlx a19097d1be ✨ 组织同步文案与客商固定组织对齐白名单规则
更新部门全量同步说明为 id=1 与二级公司白名单,客商所属组织改为广西现代物流集团有限公司。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 12:47:23 +08:00
gxwebsoft 87aa64d0c1 fix(payment): 修复付款申请表单结算单号字段交互体验
- 结算单号由原来的 `el-select multiple` 改为不可编辑的 `el-input readonly`
- 点击输入框触发弹窗选择,保留右侧「选择」按钮
- 占位符改为「请输入」,纠正不可输入与提示不符的问题
- 根据已选单号数量动态显示单号或“已选 N 张结算单”,多张显示全称放 tooltip 提示
- 删除无用的选项选择及相关方法,简化组件逻辑和样式
- 调整样式避免宽度不足导致数据显示被截断问题
- 临时验证页面引入完整 Vue 编译版本确保字符串模板支持
- 更新依赖锁中的 TypeScript 版本等补丁信息
2026-10-15 11:33:10 +08:00
刘泉佳 fbad28b717 feat: 付款申请/开票申请/正式结算单列表展示金蝶解锁终态标识
- lockTag 增加解锁终态分支:金蝶删单红tag、金蝶解锁橙tag,tooltip 展示操作人与解锁时间
- 数据来自后端 fillUnlockMarks 回填的 kingdeeUnlockTime/kingdeeUnlockOperator/kingdeeUnlockOperationName
2026-10-15 11:29:29 +08:00
gxwebsoft 19972aa8d8 🐛 付款申请表单结算单号改为只读输入框+整框可点开选择弹窗,消除请输入歧义 2026-10-15 10:50:25 +08:00
b2894lxlx 5569f373c5 Merge pull request 'Tms对接金蝶接口-前端' (#3) from feature/bfi-api-rate into master
Reviewed-on: #3
2026-10-14 17:28:16 +08:00
171 changed files with 16702 additions and 20261 deletions
+1
View File
@@ -2,6 +2,7 @@
node_modules node_modules
pnpm-lock.yaml pnpm-lock.yaml
/dist /dist
/dist.zip
/tests/e2e/videos/ /tests/e2e/videos/
/tests/e2e/screenshots/ /tests/e2e/screenshots/
+16
View File
@@ -124,3 +124,19 @@
另:自定义组件接模板字符串时,切出的模板里 `row.content` 要替换成 `content`(插槽作用域不存在于独立组件里),否则报 `Cannot read properties of undefined (reading 'content')`。 另:自定义组件接模板字符串时,切出的模板里 `row.content` 要替换成 `content`(插槽作用域不存在于独立组件里),否则报 `Cannot read properties of undefined (reading 'content')`。
- 校验:编译三个 .vue 的 template + scss 全过;临时验证页 `__route-change-verify.html` 与 8899 端口静态服务已删除/停止。 - 校验:编译三个 .vue 的 template + scss 全过;临时验证页 `__route-change-verify.html` 与 8899 端口静态服务已删除/停止。
- 未处理(等用户确认):备注列的 `show-overflow-tooltip` 保持原样(属全站约定),用户若要求连备注一起去掉再单独改。 - 未处理(等用户确认):备注列的 `show-overflow-tooltip` 保持原样(属全站约定),用户若要求连备注一起去掉再单独改。
## 16:45 付款申请表单「结算单号」字段交互歧义修复(方案 A)
- 现象(用户截图圈出):`/payment/payment-application/form` 结算单号是 `el-select multiple`,placeholder 写「请输入」但**打不进去字**,必须先猜到要点右边「选择」才有效。
- 根因(双重的):
1. 该 `el-select` 的 `:model-value` 绑只读 computed `referenceSettlementNos`(= 已选单号本身),**options 就是它自己** → 点开下拉、点选项,值不变;代码里 `handleSettlementSelectVisible` 甚至主动 blur 掉下拉。真正改值的只有右侧「选择」按钮。
2. placeholder 写「请输入」但控件不可输入(未开 `filterable`),且 `.el-tag__close{display:none}` 藏了删除叉 → 视觉上更像输入框。全站规矩是「请输入/请选择」二选一,这里是选择语义。
- 落地(只改 `views/payment/payment-application-form.vue`,全站 `openReference`/`referenceSettlementNos` 无第二处引用):
- 模板:`el-select` → `el-input readonly` + 外层 `el-tooltip`,`:model-value="referenceDisplayText"`,`placeholder="请输入"`,`:disabled="readonly"`,**`@click="!readonly && openReference()"` 让整框可点开弹窗**;右侧「选择」按钮保留。
- 新增 computed:`referenceSettlementCount`、`referenceDisplayText`(单张显示单号;**多张只显示 `已选 N 张结算单`**,完整单号靠 tooltip,明细看下方「结算信息」表)。
- 删除 `handleSettlementSelectVisible` 方法与 `settlementNoSelectRef`;样式 `.payment-form-page__settlement-select{,:deep(...)}` → `.payment-form-page__settlement-input`(+ `cursor:pointer` / disabled 时 `not-allowed`)。
- ⚠️ 踩坑:第一版把「共 N 张」做成 `el-input` 的 `#suffix` 插槽 → span=6 的窄列里后缀挤占宽度,单号被截成 `JS202610`,比不改还差。**窄列(≤span6)不要在输入框内塞后缀/前缀,把信息编进 `:model-value` 文本本身**。
- 验证(项目根临时 html + `vue/dist/vue.esm-bundler.js`,用完即删):
- ⚠️ 临时页必须 import `vue/dist/vue.esm-bundler.js`,默认 `vue` 是 runtime-only,字符串 template 报 “runtime compilation is not supported”。
- 4 态实测:空值 placeholder 正确 / 单张显单号 / 多张显「已选 3 张结算单」且不截断 / 查看态 disabled 且按钮隐藏;输入框宽 132px、行高 32px。
- 点击探针验证:可编辑态点 `.el-input` 命中 1 次(el-tooltip 不拦截 click),查看态命中 0 次 ✓。
+7
View File
@@ -0,0 +1,7 @@
# 2026-10-15
## 10:46 付款申请表单「结算单号」修复丢失 → 重放并提交
- 现象:用户反馈昨天改好的「结算单号」(只读输入框 + 整框可点开选择弹窗)今天又变回旧的 `el-select` 写法。
- 根因:昨天的改动**一直没有 commit**,是纯工作区改动;用户 pull(HEAD 到 9919fb9,两个 settlement 样式提交包含该文件旧版)后被覆盖。教训同 2026-09-18「漏提交新文件」一役:**本仓库改动必须尽快显式 commit**,`git status` 干净 ≠ 改动还在。
- 处理:按昨天方案 A 原样重放 4 处修改(模板 el-select→el-input readonly+tooltip+整框可点、新增 `referenceSettlementCount`/`referenceDisplayText` computed、删 `handleSettlementSelectVisible`、样式类改名 `__settlement-input` + cursor 规则),curl 自检 vue/style 均 200。
- **这次只提交该单文件**:`git commit -m ":bug: ..." -- src/views/payment/payment-application-form.vue` → `db03ced`(`.workbuddy/memory` 不入库,留在工作区)。尚未 push,等用户确认。
+31
View File
@@ -0,0 +1,31 @@
# 2026-10-20
## settlement-import 列表页工具栏对齐 receipt-settlement
- `src/views/settlement/settlement-import.vue`:
- 左侧只保留「新增」(`type="primary"`),删掉原来的 `plain` 文字「刷新」按钮(刷新统一收进右侧图标组,与 receipt-settlement / settlement-adjustment 一致)。
- 右侧接入 `<settlement-table-tools>`(`./components/settlement-table-tools.vue`,即 `table-header-tools` 包装):刷新 / 列设置 / 搜索显隐 / 表格尺寸 4 键,`@refresh="refreshTable"`。
- 表格列改为配置化,新增 `src/option/settlement/settlementImportTable.js`(`settlementImportTableColumns`),页面加 `tableColumns / visibleColumnKeys / tableSize / columnSettingOptions / visibleTableColumns`,`el-table` 加 `:size="tableSize"`。
- 列渲染标志:`link / status / enum / billType / count`(注意**不能用 `type` 作 key**,`type` 是 el-table-column 真实 prop,会把列变成 selection/index 列)。
- 新增 `displayValue()`;`settlementBillTypeLabel('')` 返回 `-`。
- 序号列改 `fixed="left"`;数据列与操作列统一 `align: 'center'`,操作链接 `justify-content: center`。
- 校验方式(无 dev server 时):项目根目录临时 `sfc-check.mjs` 用 `vue/compiler-sfc` 的 `parse + compileTemplate + compileScript`;SCSS 用 `sass.compileString`。用完即删。
## 追加:用户要求「列显隐 + 显示方式」→ 两个页面改 avue-crud
用户反馈上面那版不对:右侧工具按钮必须是 **avue-crud 原生**的 4 键,第 2 键是「列显隐」抽屉(不是「列设置」勾选弹窗),第 4 键是「显示方式」(表格↔卡片),不是表格尺寸循环。确认后范围:`settlement-import` + `formal-settlement`(receipt/payment 共用)都改 avue-crud。
- 关键结论:**「列显隐 + 显示方式」只有 avue-crud 原生才有**,avue 的 `dialog-column` 依赖 `inject: ['crud']`,脱离 avue-crud 拿不出来;`table-header-tools` 只是仿制件,做不到。所以目标页只能整页改 avue-crud。
- avue 右侧按钮源码:`node_modules/@smallwei/avue/lib/packages/element-plus/crud/menu/header-menu3.js`(refreshBtn/columnBtn/searchBtn/gridBtn,`gridBtn` → `handleGridShow()` 切 `gridShow` 表格↔卡片);图标默认配置在同目录 `config.js`。
- 新增 `src/option/settlement/settlementImport.js`(`settlementImportOption`)、`src/option/settlement/formalSettlement.js`(`formalSettlementOption`,表格列复用 `formalSettlementTable.js` 的 `formalSettlementTableColumns` + `COLUMN_EXTRA` 增量配置,避免两处维护)。
- `settlement-import.vue` 整页改 avue-crud(删除手写的搜索区/表格/分页);`formal-settlement-table-panel.vue` 由 el-table 改成包一层 `<avue-crud>`,父页面保留数据加载与全部业务动作。
- ⚠️ **avue 的搜索自定义控件要用 `column.renderSearch`(`h()` 渲染),不能用 `searchslot`** —— avue 3.9.2 根本不读 `searchslot`(全库无此字段),而 `searchslot: true` 还会被 `header-search2.js` 的 `searchXxx → xxx` 映射成 `slot: true`。真正的搜索插槽是 avue-crud 上 `${prop}-search`(`crud.searchSlot`,见 `index2.js` `getSlotList(["-search"])`),但其作用域**没有 `row`**(只有 value/column/label/size/readonly/disabled/dic),`mileage-record.vue` 用 `{ row }` 是错的。已用于批次号下拉(filterable + allowCreate)。
- avue `formatter` 签名确认:`detail.js` → `column.formatter(row, row[column.prop], result, column)`。
- `on-load` 由 `table-page2.js` 在 created / 翻页 / 改页容量时触发,参数是内部 `defaultPage`(已 `Object.assign` 合并外部 page)。
- 分页透传写法(父持 page、子包 avue-crud):子组件用 computed `crudPage{get/set}`,setter 里 `Object.assign(this.page, value)` 原地回写,避免 prop 直接赋值告警。
- ⚠️ `src/option/settlement/formalSettlementTable.js` **不能删** —— `formal-settlement-editor.vue` 还从它导入 `candidateColumns/candidateDetailColumns/detailColumns/sourceColumns/summaryColumns`(我一度删掉,Vite 转换报错后从 HEAD 恢复)。
- 校验方式升级:沙箱里 curl 不通 dev server(502),改用 `vite.createServer({ middlewareMode:true })` + `server.transformRequest(path)` 就地转换,能同时暴露依赖解析失败(如上面误删文件)。脚本用完即删。
## 遗留未改(仍用 table-header-tools 仿制件)
pre-settlement、settlement-adjustment、receivable-payable-detail、transport-reconciliation-table-panel、business/{master-order,loading-manage,voucher-manage}、payment/* 手写页。若用户要全站一致,需逐页改 avue-crud。
+13 -1
View File
@@ -66,9 +66,21 @@
## 表单 / 自检 / 导出 ## 表单 / 自检 / 导出
- placeholder 只写 `请输入`/`请选择`;例外:`请输入或选择车辆`、日期区间 `起`/`止`、示例值。校验 message 须带字段名。 - placeholder 只写 `请输入`/`请选择`;例外:`请输入或选择车辆`、日期区间 `起`/`止`、示例值。校验 message 须带字段名。
- `business-crud-page.vue`:option 经 `cloneOption` 克隆,独立表单页走 `PageAvueForm`。 - `business-crud-page.vue`:option 经 `cloneOption` 克隆,独立表单页走 `PageAvueForm`。
- 自检:`curl "localhost:2889/src/xxx.vue"` 与 `"...?vue&type=style&index=0&scoped=true&lang.scss"`(200 / 500)。`vite build` 在 `facelogin.vue` 被沙箱中断,不能作唯一校验。dev `VITE_APP_API=/api` 代理 `172.16.203.228:8000`。 - 自检:`curl "localhost:2888/src/xxx.vue"` 与 `"...?vue&type=style&index=0&scoped=true&lang.scss"`(200 / 500)。`vite build` 在 `facelogin.vue` 被沙箱中断,不能作唯一校验。dev `VITE_APP_API=/api` 代理 `172.16.203.228:8000`。
- 导出:前端 `exportColumns` 后端未用,以 `XxxExportExcel.java` 为准;BladeX `BeanUtil` 类型不兼容静默跳过(`Date` → `LocalDateTime` 丢值,用 `DateUtil.fromDate`)。 - 导出:前端 `exportColumns` 后端未用,以 `XxxExportExcel.java` 为准;BladeX `BeanUtil` 类型不兼容静默跳过(`Date` → `LocalDateTime` 丢值,用 `DateUtil.fromDate`)。
## avue-crud 才是「右侧 4 键」的唯一来源(2026-10-20 定论)
用户要的右侧工具栏是 **avue-crud 原生**:刷新 / **列显隐**(el-drawer,含 隐藏/冻结/过滤/排序 + 行拖拽)/ 搜索显隐 / **显示方式**(表格↔卡片)。
- 源码:`node_modules/@smallwei/avue/lib/packages/element-plus/crud/menu/header-menu3.js`(refreshBtn/columnBtn/searchBtn/gridBtn),`gridBtn` → `handleGridShow()` 切 `gridShow`;图标默认在同目录 `config.js`。「列显隐」文案来自 avue 语言包 `crud.showTitle`(项目 `src/lang/zh.js` 已覆盖)。
- **列显隐抽屉依赖 `inject: ['crud']`,脱离 avue-crud 拿不出来**;`components/table-header-tools` 的「列设置勾选弹窗 + 表格尺寸循环」是仿制件,**做不到这两点**。所以「要和 avue 一致」= 整页改用 avue-crud,没有中间方案。
- ⚠️ 搜索自定义控件用 `column.renderSearch = scope => h(...)`(`scope.row` 可用,同 `mixins/organization-search.js`)。**不要用 `searchslot`**:avue 3.9.2 全库无此字段,且 `header-search2.js` 的 `searchXxx → xxx` 映射会把 `searchslot` 变成 `slot: true`。
- avue-crud 上的 `${prop}-search` 插槽确实存在(`index2.js` 的 `getSlotList(["-search"])`),但**作用域没有 `row`**(只有 value/column/label/size/readonly/disabled/dic)——`mileage-record.vue` 里 `{ row }` 是错的写法,别照抄。
- `formatter` 签名:`detail.js` → `column.formatter(row, row[column.prop], result, column)`。
- `on-load` 由 `table-page2.js` 在 created / 翻页 / 改页容量时触发,参数是内部 `defaultPage`(已合并外部 page)。分页若由父组件持有、子组件包一层 avue-crud,用 computed `crudPage{get/set}` + setter 里 `Object.assign(this.page, value)` 原地回写。
- 已改:settlement-import.vue、formal-settlement.vue + components/formal-settlement-table-panel.vue(receipt/payment 共用)。未改:pre-settlement、settlement-adjustment、receivable-payable-detail、transport-reconciliation-table-panel、business 与 payment 的手写页。
- ⚠️ `src/option/settlement/formalSettlementTable.js` 别删:`formal-settlement-editor.vue` 还依赖它的 candidate/detail/source/summary 列。
- 沙箱里 curl 打不通本地 dev server(一律 502)。改用 `vite.createServer({middlewareMode:true})` + `server.transformRequest(path)` 就地转换校验,还能暴露依赖解析失败。
## Element Plus 图标两种用法的机制差异(易误判) ## Element Plus 图标两种用法的机制差异(易误判)
`main.js` 里 `for (...ElementPlusIconsVue) app.component(key, component)` 全局注册了全部图标,因此模板里可以写标签形式 `<Location />`(靠全局注册解析)。 `main.js` 里 `for (...ElementPlusIconsVue) app.component(key, component)` 全局注册了全部图标,因此模板里可以写标签形式 `<Location />`(靠全局注册解析)。
但 `:icon="OfficeBuilding"` 是**表达式**,编译成 `_ctx.OfficeBuilding`,只会从 data / props / computed / setup 返回值里取 —— **组件对象必须放进 `data()`**;放进 `components: {}` 注册里模板取不到,会静默渲染不出图标(不报错,只是空白)。 但 `:icon="OfficeBuilding"` 是**表达式**,编译成 `_ctx.OfficeBuilding`,只会从 data / props / computed / setup 返回值里取 —— **组件对象必须放进 `data()`**;放进 `components: {}` 注册里模板取不到,会静默渲染不出图标(不报错,只是空白)。
+5 -4
View File
@@ -4,10 +4,11 @@
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"prod": "vite --mode production", "prod": "vite --mode production",
"build": "vite build", "build": "vite build && node scripts/zip-dist.mjs",
"build:prod": "vite build --mode production", "build:prod": "vite build --mode production && node scripts/zip-dist.mjs",
"zip:dist": "node scripts/zip-dist.mjs",
"serve": "vite preview --host", "serve": "vite preview --host",
"typecheck": "vue-tsc --noEmit" "typecheck": "vue-tsc -p jsconfig.json --noEmit"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
@@ -48,7 +49,7 @@
"prettier": "^2.8.7", "prettier": "^2.8.7",
"sass": "^1.85.1", "sass": "^1.85.1",
"terser": "^5.31.1", "terser": "^5.31.1",
"typescript": "^7.0.2", "typescript": "5.9.3",
"unplugin-auto-import": "^0.11.2", "unplugin-auto-import": "^0.11.2",
"vite": "^5.4.19", "vite": "^5.4.19",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
+206 -87
View File
@@ -4,40 +4,43 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
patchedDependencies:
'@smallwei/avue': 5ba15ad41b2c301250bf24adf39da2ed526b8375143da2c5a495361b29e862db
importers: importers:
.: .:
dependencies: dependencies:
'@element-plus/icons-vue': '@element-plus/icons-vue':
specifier: ^2.3.1 specifier: ^2.3.1
version: 2.3.2(vue@3.5.34) version: 2.3.2(vue@3.5.34(typescript@5.9.3))
'@open-file-viewer/core': '@open-file-viewer/core':
specifier: ^0.1.27 specifier: ^0.1.27
version: 0.1.27(pdfjs-dist@6.1.200) version: 0.1.27(pdfjs-dist@6.1.200)
'@open-file-viewer/vue': '@open-file-viewer/vue':
specifier: ^0.1.27 specifier: ^0.1.27
version: 0.1.27(@open-file-viewer/core@0.1.27(pdfjs-dist@6.1.200))(vue@3.5.34) version: 0.1.27(@open-file-viewer/core@0.1.27(pdfjs-dist@6.1.200))(vue@3.5.34(typescript@5.9.3))
'@saber/nf-design-base-elp': '@saber/nf-design-base-elp':
specifier: ^1.3.0 specifier: ^1.3.0
version: 1.3.0 version: 1.3.0
'@saber/nf-form-design-elp': '@saber/nf-form-design-elp':
specifier: ^1.6.1 specifier: ^1.6.1
version: 1.6.1(vue@3.5.34) version: 1.6.1(vue@3.5.34(typescript@5.9.3))
'@saber/nf-form-elp': '@saber/nf-form-elp':
specifier: ^1.6.3 specifier: ^1.6.3
version: 1.7.0 version: 1.7.0
'@smallwei/avue': '@smallwei/avue':
specifier: ~3.9.2 specifier: ~3.9.2
version: 3.9.2(@element-plus/icons-vue@2.3.2(vue@3.5.34))(element-plus@2.14.2(vue@3.5.34))(vue@3.5.34) version: 3.9.2(patch_hash=5ba15ad41b2c301250bf24adf39da2ed526b8375143da2c5a495361b29e862db)(@element-plus/icons-vue@2.3.2(vue@3.5.34(typescript@5.9.3)))(debug@4.4.3(supports-color@7.2.0))(element-plus@2.14.2(vue@3.5.34(typescript@5.9.3)))(vue@3.5.34(typescript@5.9.3))
animate.css: animate.css:
specifier: ^4.1.1 specifier: ^4.1.1
version: 4.1.1 version: 4.1.1
avue-plugin-ueditor: avue-plugin-ueditor:
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4(axios@1.16.0)(vue@3.5.34) version: 1.0.4(axios@1.16.0(debug@4.4.3(supports-color@7.2.0)))(vue@3.5.34(typescript@5.9.3))
axios: axios:
specifier: ^1.13.5 specifier: ^1.13.5
version: 1.16.0 version: 1.16.0(debug@4.4.3(supports-color@7.2.0))
codemirror: codemirror:
specifier: ^5.65.21 specifier: ^5.65.21
version: 5.65.21 version: 5.65.21
@@ -55,7 +58,7 @@ importers:
version: 6.1.0 version: 6.1.0
element-plus: element-plus:
specifier: ^2.14.2 specifier: ^2.14.2
version: 2.14.2(vue@3.5.34) version: 2.14.2(vue@3.5.34(typescript@5.9.3))
exceljs: exceljs:
specifier: ^4.4.0 specifier: ^4.4.0
version: 4.4.0 version: 4.4.0
@@ -82,29 +85,29 @@ importers:
version: 0.4.0 version: 0.4.0
vue: vue:
specifier: ^3.5.13 specifier: ^3.5.13
version: 3.5.34 version: 3.5.34(typescript@5.9.3)
vue-i18n: vue-i18n:
specifier: ^11.4.2 specifier: ^11.4.2
version: 11.4.2(vue@3.5.34) version: 11.4.2(vue@3.5.34(typescript@5.9.3))
vue-pdf-embed: vue-pdf-embed:
specifier: ^2.1.6 specifier: ^2.1.6
version: 2.1.6(vue@3.5.34) version: 2.1.6(vue@3.5.34(typescript@5.9.3))
vue-router: vue-router:
specifier: ^4.3.2 specifier: ^4.3.2
version: 4.6.4(vue@3.5.34) version: 4.6.4(vue@3.5.34(typescript@5.9.3))
vue3-clipboard: vue3-clipboard:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0(vue@3.5.34) version: 1.0.0(vue@3.5.34(typescript@5.9.3))
vuex: vuex:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0(vue@3.5.34) version: 4.1.0(vue@3.5.34(typescript@5.9.3))
xlsx: xlsx:
specifier: 0.18.5 specifier: 0.18.5
version: 0.18.5 version: 0.18.5
devDependencies: devDependencies:
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^5.2.4 specifier: ^5.2.4
version: 5.2.4(vite@5.4.21(sass@1.99.0)(terser@5.46.2))(vue@3.5.34) version: 5.2.4(vite@5.4.21(sass@1.99.0)(terser@5.46.2))(vue@3.5.34(typescript@5.9.3))
'@vue/compiler-sfc': '@vue/compiler-sfc':
specifier: ^3.4.27 specifier: ^3.4.27
version: 3.5.34 version: 3.5.34
@@ -117,18 +120,24 @@ importers:
terser: terser:
specifier: ^5.31.1 specifier: ^5.31.1
version: 5.46.2 version: 5.46.2
typescript:
specifier: 5.9.3
version: 5.9.3
unplugin-auto-import: unplugin-auto-import:
specifier: ^0.11.2 specifier: ^0.11.2
version: 0.11.5(@vueuse/core@14.3.0(vue@3.5.34))(rollup@4.60.3) version: 0.11.5(@vueuse/core@14.3.0(vue@3.5.34(typescript@5.9.3)))(rollup@4.60.3)
vite: vite:
specifier: ^5.4.19 specifier: ^5.4.19
version: 5.4.21(sass@1.99.0)(terser@5.46.2) version: 5.4.21(sass@1.99.0)(terser@5.46.2)
vite-plugin-compression: vite-plugin-compression:
specifier: ^0.5.1 specifier: ^0.5.1
version: 0.5.1(vite@5.4.21(sass@1.99.0)(terser@5.46.2)) version: 0.5.1(supports-color@7.2.0)(vite@5.4.21(sass@1.99.0)(terser@5.46.2))
vite-plugin-vue-setup-extend: vite-plugin-vue-setup-extend:
specifier: ^0.4.0 specifier: ^0.4.0
version: 0.4.0(vite@5.4.21(sass@1.99.0)(terser@5.46.2)) version: 0.4.0(vite@5.4.21(sass@1.99.0)(terser@5.46.2))
vue-tsc:
specifier: ^3.3.11
version: 3.3.13(typescript@5.9.3)
packages: packages:
@@ -404,30 +413,35 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-arm64-musl@1.0.2': '@napi-rs/canvas-linux-arm64-musl@1.0.2':
resolution: {integrity: sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA==} resolution: {integrity: sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@napi-rs/canvas-linux-riscv64-gnu@1.0.2': '@napi-rs/canvas-linux-riscv64-gnu@1.0.2':
resolution: {integrity: sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw==} resolution: {integrity: sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-gnu@1.0.2': '@napi-rs/canvas-linux-x64-gnu@1.0.2':
resolution: {integrity: sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g==} resolution: {integrity: sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-musl@1.0.2': '@napi-rs/canvas-linux-x64-musl@1.0.2':
resolution: {integrity: sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q==} resolution: {integrity: sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@napi-rs/canvas-win32-arm64-msvc@1.0.2': '@napi-rs/canvas-win32-arm64-msvc@1.0.2':
resolution: {integrity: sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ==} resolution: {integrity: sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ==}
@@ -503,36 +517,42 @@ packages:
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.6': '@parcel/watcher-linux-arm-musl@2.5.6':
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.6': '@parcel/watcher-linux-arm64-glibc@2.5.6':
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.6': '@parcel/watcher-linux-arm64-musl@2.5.6':
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.6': '@parcel/watcher-linux-x64-glibc@2.5.6':
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.6': '@parcel/watcher-linux-x64-musl@2.5.6':
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.6': '@parcel/watcher-win32-arm64@2.5.6':
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
@@ -599,66 +619,79 @@ packages:
resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.60.3': '@rollup/rollup-linux-arm-musleabihf@4.60.3':
resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.60.3': '@rollup/rollup-linux-arm64-gnu@4.60.3':
resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.60.3': '@rollup/rollup-linux-arm64-musl@4.60.3':
resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.60.3': '@rollup/rollup-linux-loong64-gnu@4.60.3':
resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.60.3': '@rollup/rollup-linux-loong64-musl@4.60.3':
resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.60.3': '@rollup/rollup-linux-ppc64-gnu@4.60.3':
resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.60.3': '@rollup/rollup-linux-ppc64-musl@4.60.3':
resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.60.3': '@rollup/rollup-linux-riscv64-gnu@4.60.3':
resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.60.3': '@rollup/rollup-linux-riscv64-musl@4.60.3':
resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.60.3': '@rollup/rollup-linux-s390x-gnu@4.60.3':
resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.60.3': '@rollup/rollup-linux-x64-gnu@4.60.3':
resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.60.3': '@rollup/rollup-linux-x64-musl@4.60.3':
resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.60.3': '@rollup/rollup-openbsd-x64@4.60.3':
resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==}
@@ -762,6 +795,20 @@ packages:
vite: ^5.0.0 || ^6.0.0 vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25 vue: ^3.2.25
'@volar/language-core@2.4.28':
resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==}
'@volar/source-map@2.4.28':
resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==}
'@volar/typescript@2.4.28':
resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@vue/compiler-core@3.5.34': '@vue/compiler-core@3.5.34':
resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==} resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==}
@@ -777,6 +824,9 @@ packages:
'@vue/devtools-api@6.6.4': '@vue/devtools-api@6.6.4':
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
'@vue/language-core@3.3.13':
resolution: {integrity: sha512-axA0Xemn5LuUKuKWob0/kVq7xQ2mJJM6VqA9Exvt35nM9yRC31goWVve/G/Zb2ZCLsXC8FHYjjfXlnshknSCew==}
'@vue/reactivity@3.5.34': '@vue/reactivity@3.5.34':
resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==}
@@ -990,6 +1040,9 @@ packages:
ag-psd@30.2.0: ag-psd@30.2.0:
resolution: {integrity: sha512-tSAWfNzLl5brFqKEer7egxASZ1a0LwHnReM/D5VVpvrgdwdsa8OVfCQpysK7tGaOuE2tPEqp3mAuH7aIvPhcig==} resolution: {integrity: sha512-tSAWfNzLl5brFqKEer7egxASZ1a0LwHnReM/D5VVpvrgdwdsa8OVfCQpysK7tGaOuE2tPEqp3mAuH7aIvPhcig==}
alien-signals@3.2.1:
resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==}
animate.css@4.1.1: animate.css@4.1.1:
resolution: {integrity: sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==} resolution: {integrity: sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==}
@@ -1701,6 +1754,9 @@ packages:
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
namespace-emitter@2.0.1: namespace-emitter@2.0.1:
resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==} resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==}
@@ -1751,6 +1807,9 @@ packages:
parsedbf@2.0.0: parsedbf@2.0.0:
resolution: {integrity: sha512-WNjKn/cwgGBkXqQLif+2VMEahcRHkBRU0/RfBWZ7Vj7snRNNW63yW1mVuuHRDyXTRxuGCzAHHBcr/Fn+U/bXjQ==} resolution: {integrity: sha512-WNjKn/cwgGBkXqQLif+2VMEahcRHkBRU0/RfBWZ7Vj7snRNNW63yW1mVuuHRDyXTRxuGCzAHHBcr/Fn+U/bXjQ==}
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
path-intersection@2.2.1: path-intersection@2.2.1:
resolution: {integrity: sha512-9u8xvMcSfuOiStv9bPdnRJQhGQXLKurew94n4GPQCdH1nj9QKC9ObbNoIpiRq8skiOBxKkt277PgOoFgAt3/rA==} resolution: {integrity: sha512-9u8xvMcSfuOiStv9bPdnRJQhGQXLKurew94n4GPQCdH1nj9QKC9ObbNoIpiRq8skiOBxKkt277PgOoFgAt3/rA==}
@@ -2015,6 +2074,11 @@ packages:
typedarray@0.0.6: typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
ufo@1.6.4: ufo@1.6.4:
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
@@ -2095,6 +2159,9 @@ packages:
terser: terser:
optional: true optional: true
vscode-uri@3.2.0:
resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==}
vue-component-type-helpers@3.3.6: vue-component-type-helpers@3.3.6:
resolution: {integrity: sha512-FkljacAwJ9BUoSUdpFe3VDy0sGigNlTH9+2zcXUWmZOjN8swiCkl3t48wOJun0OsUd2cEIda1l04tsxMiKIIrQ==} resolution: {integrity: sha512-FkljacAwJ9BUoSUdpFe3VDy0sGigNlTH9+2zcXUWmZOjN8swiCkl3t48wOJun0OsUd2cEIda1l04tsxMiKIIrQ==}
@@ -2114,6 +2181,12 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.5.0 vue: ^3.5.0
vue-tsc@3.3.13:
resolution: {integrity: sha512-jE4/bXdOMSHVxEGgQCqNllJy+9N3Gx6tfSAsg0yhFK/MAN6eXqISihupnDEKSCt12VVnjN2gI3AqLYcqoh5x/A==}
hasBin: true
peerDependencies:
typescript: '>=5.0.0'
vue3-clipboard@1.0.0: vue3-clipboard@1.0.0:
resolution: {integrity: sha512-GUqKh1oO79xDpq0z+cCv/NDVTpcJGNDzeNgT3PmTdTp/WJh3gcTrDqIYKycKhzMFOtIFJ7hO/+usgyWtT+fNhA==} resolution: {integrity: sha512-GUqKh1oO79xDpq0z+cCv/NDVTpcJGNDzeNgT3PmTdTp/WJh3gcTrDqIYKycKhzMFOtIFJ7hO/+usgyWtT+fNhA==}
peerDependencies: peerDependencies:
@@ -2215,9 +2288,9 @@ snapshots:
'@ctrl/tinycolor@4.2.0': {} '@ctrl/tinycolor@4.2.0': {}
'@element-plus/icons-vue@2.3.2(vue@3.5.34)': '@element-plus/icons-vue@2.3.2(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.21.5':
optional: true optional: true
@@ -2458,10 +2531,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- geotiff - geotiff
'@open-file-viewer/vue@0.1.27(@open-file-viewer/core@0.1.27(pdfjs-dist@6.1.200))(vue@3.5.34)': '@open-file-viewer/vue@0.1.27(@open-file-viewer/core@0.1.27(pdfjs-dist@6.1.200))(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
'@open-file-viewer/core': 0.1.27(pdfjs-dist@6.1.200) '@open-file-viewer/core': 0.1.27(pdfjs-dist@6.1.200)
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
'@parcel/watcher-android-arm64@2.5.6': '@parcel/watcher-android-arm64@2.5.6':
optional: true optional: true
@@ -2613,9 +2686,9 @@ snapshots:
codemirror: 5.65.21 codemirror: 5.65.21
randomcolor: 0.6.2 randomcolor: 0.6.2
'@saber/nf-form-design-elp@1.6.1(vue@3.5.34)': '@saber/nf-form-design-elp@1.6.1(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
vuedraggable: 4.1.0(vue@3.5.34) vuedraggable: 4.1.0(vue@3.5.34(typescript@5.9.3))
transitivePeerDependencies: transitivePeerDependencies:
- vue - vue
@@ -2627,16 +2700,16 @@ snapshots:
countup.js: 2.10.0 countup.js: 2.10.0
signature_pad: 5.1.3 signature_pad: 5.1.3
'@smallwei/avue@3.9.2(@element-plus/icons-vue@2.3.2(vue@3.5.34))(element-plus@2.14.2(vue@3.5.34))(vue@3.5.34)': '@smallwei/avue@3.9.2(patch_hash=5ba15ad41b2c301250bf24adf39da2ed526b8375143da2c5a495361b29e862db)(@element-plus/icons-vue@2.3.2(vue@3.5.34(typescript@5.9.3)))(debug@4.4.3(supports-color@7.2.0))(element-plus@2.14.2(vue@3.5.34(typescript@5.9.3)))(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
'@element-plus/icons-vue': 2.3.2(vue@3.5.34) '@element-plus/icons-vue': 2.3.2(vue@3.5.34(typescript@5.9.3))
axios: 0.21.4 axios: 0.21.4(debug@4.4.3(supports-color@7.2.0))
countup.js: 1.9.3 countup.js: 1.9.3
dayjs: 1.11.20 dayjs: 1.11.20
element-plus: 2.14.2(vue@3.5.34) element-plus: 2.14.2(vue@3.5.34(typescript@5.9.3))
lodash: 4.18.1 lodash: 4.18.1
lodash-es: 4.18.1 lodash-es: 4.18.1
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
@@ -2692,10 +2765,24 @@ snapshots:
'@uppy/utils': 4.1.3 '@uppy/utils': 4.1.3
nanoid: 3.3.12 nanoid: 3.3.12
'@vitejs/plugin-vue@5.2.4(vite@5.4.21(sass@1.99.0)(terser@5.46.2))(vue@3.5.34)': '@vitejs/plugin-vue@5.2.4(vite@5.4.21(sass@1.99.0)(terser@5.46.2))(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
vite: 5.4.21(sass@1.99.0)(terser@5.46.2) vite: 5.4.21(sass@1.99.0)(terser@5.46.2)
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
'@volar/language-core@2.4.28':
dependencies:
'@volar/source-map': 2.4.28
'@volar/source-map@2.4.28': {}
'@volar/typescript@2.4.28(typescript@5.9.3)':
dependencies:
'@volar/language-core': 2.4.28
path-browserify: 1.0.1
vscode-uri: 3.2.0
optionalDependencies:
typescript: 5.9.3
'@vue/compiler-core@3.5.34': '@vue/compiler-core@3.5.34':
dependencies: dependencies:
@@ -2729,6 +2816,16 @@ snapshots:
'@vue/devtools-api@6.6.4': {} '@vue/devtools-api@6.6.4': {}
'@vue/language-core@3.3.13':
dependencies:
'@volar/language-core': 2.4.28
'@vue/compiler-dom': 3.5.34
'@vue/shared': 3.5.34
alien-signals: 3.2.1
muggle-string: 0.4.1
path-browserify: 1.0.1
picomatch: 4.0.4
'@vue/reactivity@3.5.34': '@vue/reactivity@3.5.34':
dependencies: dependencies:
'@vue/shared': 3.5.34 '@vue/shared': 3.5.34
@@ -2745,51 +2842,51 @@ snapshots:
'@vue/shared': 3.5.34 '@vue/shared': 3.5.34
csstype: 3.2.3 csstype: 3.2.3
'@vue/server-renderer@3.5.34(vue@3.5.34)': '@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
'@vue/compiler-ssr': 3.5.34 '@vue/compiler-ssr': 3.5.34
'@vue/shared': 3.5.34 '@vue/shared': 3.5.34
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
'@vue/shared@3.5.34': {} '@vue/shared@3.5.34': {}
'@vueuse/core@14.3.0(vue@3.5.34)': '@vueuse/core@14.3.0(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
'@types/web-bluetooth': 0.0.21 '@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 14.3.0 '@vueuse/metadata': 14.3.0
'@vueuse/shared': 14.3.0(vue@3.5.34) '@vueuse/shared': 14.3.0(vue@3.5.34(typescript@5.9.3))
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
'@vueuse/metadata@14.3.0': {} '@vueuse/metadata@14.3.0': {}
'@vueuse/shared@14.3.0(vue@3.5.34)': '@vueuse/shared@14.3.0(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
'@wangeditor-next/basic-modules@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3)': '@wangeditor-next/basic-modules@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)':
dependencies: dependencies:
'@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
dom7: 3.0.0 dom7: 4.0.6
is-url: 1.2.4 is-url: 1.2.4
lodash.throttle: 4.1.1 lodash.throttle: 4.1.1
nanoid: 3.3.12 nanoid: 5.1.11
slate: 0.72.8 slate: 0.123.0
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wangeditor-next/code-highlight@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(slate@0.123.0)(snabbdom@3.6.3)': '@wangeditor-next/code-highlight@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(slate@0.123.0)(snabbdom@3.6.3)':
dependencies: dependencies:
'@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
dom7: 4.0.6 dom7: 4.0.6
prismjs: 1.30.0 prismjs: 1.30.0
slate: 0.123.0 slate: 0.123.0
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3)': '@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)':
dependencies: dependencies:
'@types/event-emitter': 0.3.5 '@types/event-emitter': 0.3.5
'@uppy/core': 2.3.4 '@uppy/core': 2.3.4
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
dom7: 3.0.0 dom7: 4.0.6
event-emitter: 0.3.5 event-emitter: 0.3.5
html-void-elements: 3.0.0 html-void-elements: 3.0.0
i18next: 23.16.8 i18next: 23.16.8
@@ -2800,9 +2897,9 @@ snapshots:
lodash.foreach: 4.5.0 lodash.foreach: 4.5.0
lodash.throttle: 4.1.1 lodash.throttle: 4.1.1
lodash.toarray: 4.4.0 lodash.toarray: 4.4.0
nanoid: 3.3.12 nanoid: 5.1.11
scroll-into-view-if-needed: 3.1.0 scroll-into-view-if-needed: 3.1.0
slate: 0.72.8 slate: 0.123.0
slate-history: 0.115.0(slate@0.123.0) slate-history: 0.115.0(slate@0.123.0)
snabbdom: 3.6.3 snabbdom: 3.6.3
@@ -2810,9 +2907,9 @@ snapshots:
dependencies: dependencies:
'@uppy/core': 2.3.4 '@uppy/core': 2.3.4
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
'@wangeditor-next/basic-modules': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/basic-modules': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
'@wangeditor-next/code-highlight': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(slate@0.123.0)(snabbdom@3.6.3) '@wangeditor-next/code-highlight': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(slate@0.123.0)(snabbdom@3.6.3)
'@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
'@wangeditor-next/list-module': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(slate@0.123.0)(snabbdom@3.6.3) '@wangeditor-next/list-module': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(slate@0.123.0)(snabbdom@3.6.3)
'@wangeditor-next/table-module': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.debounce@4.0.8)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3) '@wangeditor-next/table-module': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.debounce@4.0.8)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
'@wangeditor-next/upload-image-module': 2.0.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor-next/basic-modules@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.foreach@4.5.0)(slate@0.123.0)(snabbdom@3.6.3) '@wangeditor-next/upload-image-module': 2.0.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor-next/basic-modules@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.foreach@4.5.0)(slate@0.123.0)(snabbdom@3.6.3)
@@ -2831,14 +2928,14 @@ snapshots:
'@wangeditor-next/list-module@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(slate@0.123.0)(snabbdom@3.6.3)': '@wangeditor-next/list-module@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(slate@0.123.0)(snabbdom@3.6.3)':
dependencies: dependencies:
'@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
dom7: 4.0.6 dom7: 4.0.6
slate: 0.123.0 slate: 0.123.0
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wangeditor-next/table-module@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.debounce@4.0.8)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)': '@wangeditor-next/table-module@2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.debounce@4.0.8)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)':
dependencies: dependencies:
'@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
dom7: 4.0.6 dom7: 4.0.6
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
lodash.throttle: 4.1.1 lodash.throttle: 4.1.1
@@ -2850,8 +2947,8 @@ snapshots:
dependencies: dependencies:
'@uppy/core': 2.3.4 '@uppy/core': 2.3.4
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
'@wangeditor-next/basic-modules': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/basic-modules': 2.0.0(@wangeditor-next/core@1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3))(dom7@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
'@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
dom7: 4.0.6 dom7: 4.0.6
lodash.foreach: 4.5.0 lodash.foreach: 4.5.0
slate: 0.123.0 slate: 0.123.0
@@ -2861,7 +2958,7 @@ snapshots:
dependencies: dependencies:
'@uppy/core': 2.3.4 '@uppy/core': 2.3.4
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
'@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor-next/core': 1.8.0(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@4.0.6)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)
dom7: 4.0.6 dom7: 4.0.6
nanoid: 5.1.11 nanoid: 5.1.11
slate: 0.123.0 slate: 0.123.0
@@ -2908,10 +3005,10 @@ snapshots:
slate-history: 0.66.0(slate@0.72.8) slate-history: 0.66.0(slate@0.72.8)
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.34)': '@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.34(typescript@5.9.3))':
dependencies: dependencies:
'@wangeditor/editor': 5.1.23 '@wangeditor/editor': 5.1.23
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
'@wangeditor/editor@5.1.23': '@wangeditor/editor@5.1.23':
dependencies: dependencies:
@@ -2986,6 +3083,8 @@ snapshots:
base64-js: 1.5.1 base64-js: 1.5.1
pako: 2.1.0 pako: 2.1.0
alien-signals@3.2.1: {}
animate.css@4.1.1: {} animate.css@4.1.1: {}
ansi-styles@4.3.0: ansi-styles@4.3.0:
@@ -3038,22 +3137,22 @@ snapshots:
asynckit@0.4.0: {} asynckit@0.4.0: {}
avue-plugin-ueditor@1.0.4(axios@1.16.0)(vue@3.5.34): avue-plugin-ueditor@1.0.4(axios@1.16.0(debug@4.4.3(supports-color@7.2.0)))(vue@3.5.34(typescript@5.9.3)):
dependencies: dependencies:
'@wangeditor/editor': 5.1.23 '@wangeditor/editor': 5.1.23
'@wangeditor/editor-for-vue': 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.34) '@wangeditor/editor-for-vue': 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.34(typescript@5.9.3))
axios: 1.16.0 axios: 1.16.0(debug@4.4.3(supports-color@7.2.0))
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
axios@0.21.4: axios@0.21.4(debug@4.4.3(supports-color@7.2.0)):
dependencies: dependencies:
follow-redirects: 1.16.0 follow-redirects: 1.16.0(debug@4.4.3(supports-color@7.2.0))
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
axios@1.16.0: axios@1.16.0(debug@4.4.3(supports-color@7.2.0)):
dependencies: dependencies:
follow-redirects: 1.16.0 follow-redirects: 1.16.0(debug@4.4.3(supports-color@7.2.0))
form-data: 4.0.5 form-data: 4.0.5
proxy-from-env: 2.1.0 proxy-from-env: 2.1.0
transitivePeerDependencies: transitivePeerDependencies:
@@ -3221,9 +3320,11 @@ snapshots:
dayjs@1.11.20: {} dayjs@1.11.20: {}
debug@4.4.3: debug@4.4.3(supports-color@7.2.0):
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
optionalDependencies:
supports-color: 7.2.0
delayed-stream@1.0.0: {} delayed-stream@1.0.0: {}
@@ -3294,15 +3395,15 @@ snapshots:
tslib: 2.3.0 tslib: 2.3.0
zrender: 6.1.0 zrender: 6.1.0
element-plus@2.14.2(vue@3.5.34): element-plus@2.14.2(vue@3.5.34(typescript@5.9.3)):
dependencies: dependencies:
'@ctrl/tinycolor': 4.2.0 '@ctrl/tinycolor': 4.2.0
'@element-plus/icons-vue': 2.3.2(vue@3.5.34) '@element-plus/icons-vue': 2.3.2(vue@3.5.34(typescript@5.9.3))
'@floating-ui/dom': 1.7.6 '@floating-ui/dom': 1.7.6
'@popperjs/core': '@sxzz/popperjs-es@2.11.8' '@popperjs/core': '@sxzz/popperjs-es@2.11.8'
'@types/lodash': 4.17.24 '@types/lodash': 4.17.24
'@types/lodash-es': 4.17.12 '@types/lodash-es': 4.17.12
'@vueuse/core': 14.3.0(vue@3.5.34) '@vueuse/core': 14.3.0(vue@3.5.34(typescript@5.9.3))
async-validator: 4.2.5 async-validator: 4.2.5
dayjs: 1.11.20 dayjs: 1.11.20
lodash: 4.18.1 lodash: 4.18.1
@@ -3310,7 +3411,7 @@ snapshots:
lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1) lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1)
memoize-one: 6.0.0 memoize-one: 6.0.0
normalize-wheel-es: 1.2.0 normalize-wheel-es: 1.2.0
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
vue-component-type-helpers: 3.3.6 vue-component-type-helpers: 3.3.6
end-of-stream@1.4.5: end-of-stream@1.4.5:
@@ -3431,7 +3532,9 @@ snapshots:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
follow-redirects@1.16.0: {} follow-redirects@1.16.0(debug@4.4.3(supports-color@7.2.0)):
optionalDependencies:
debug: 4.4.3(supports-color@7.2.0)
form-data@4.0.5: form-data@4.0.5:
dependencies: dependencies:
@@ -3763,6 +3866,8 @@ snapshots:
ms@2.1.3: {} ms@2.1.3: {}
muggle-string@0.4.1: {}
namespace-emitter@2.0.1: {} namespace-emitter@2.0.1: {}
nanoid@3.3.12: {} nanoid@3.3.12: {}
@@ -3796,6 +3901,8 @@ snapshots:
parsedbf@2.0.0: {} parsedbf@2.0.0: {}
path-browserify@1.0.1: {}
path-intersection@2.2.1: {} path-intersection@2.2.1: {}
path-is-absolute@1.0.1: {} path-is-absolute@1.0.1: {}
@@ -4065,6 +4172,8 @@ snapshots:
typedarray@0.0.6: {} typedarray@0.0.6: {}
typescript@5.9.3: {}
ufo@1.6.4: {} ufo@1.6.4: {}
underscore@1.13.8: {} underscore@1.13.8: {}
@@ -4087,7 +4196,7 @@ snapshots:
universalify@2.0.1: {} universalify@2.0.1: {}
unplugin-auto-import@0.11.5(@vueuse/core@14.3.0(vue@3.5.34))(rollup@4.60.3): unplugin-auto-import@0.11.5(@vueuse/core@14.3.0(vue@3.5.34(typescript@5.9.3)))(rollup@4.60.3):
dependencies: dependencies:
'@antfu/utils': 0.7.10 '@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.3.0(rollup@4.60.3) '@rollup/pluginutils': 5.3.0(rollup@4.60.3)
@@ -4096,7 +4205,7 @@ snapshots:
unimport: 1.3.0(rollup@4.60.3) unimport: 1.3.0(rollup@4.60.3)
unplugin: 1.16.1 unplugin: 1.16.1
optionalDependencies: optionalDependencies:
'@vueuse/core': 14.3.0(vue@3.5.34) '@vueuse/core': 14.3.0(vue@3.5.34(typescript@5.9.3))
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
@@ -4126,10 +4235,10 @@ snapshots:
uuid@8.3.2: {} uuid@8.3.2: {}
vite-plugin-compression@0.5.1(vite@5.4.21(sass@1.99.0)(terser@5.46.2)): vite-plugin-compression@0.5.1(supports-color@7.2.0)(vite@5.4.21(sass@1.99.0)(terser@5.46.2)):
dependencies: dependencies:
chalk: 4.1.2 chalk: 4.1.2
debug: 4.4.3 debug: 4.4.3(supports-color@7.2.0)
fs-extra: 10.1.0 fs-extra: 10.1.0
vite: 5.4.21(sass@1.99.0)(terser@5.46.2) vite: 5.4.21(sass@1.99.0)(terser@5.46.2)
transitivePeerDependencies: transitivePeerDependencies:
@@ -4151,48 +4260,58 @@ snapshots:
sass: 1.99.0 sass: 1.99.0
terser: 5.46.2 terser: 5.46.2
vscode-uri@3.2.0: {}
vue-component-type-helpers@3.3.6: {} vue-component-type-helpers@3.3.6: {}
vue-i18n@11.4.2(vue@3.5.34): vue-i18n@11.4.2(vue@3.5.34(typescript@5.9.3)):
dependencies: dependencies:
'@intlify/core-base': 11.4.2 '@intlify/core-base': 11.4.2
'@intlify/devtools-types': 11.4.2 '@intlify/devtools-types': 11.4.2
'@intlify/shared': 11.4.2 '@intlify/shared': 11.4.2
'@vue/devtools-api': 6.6.4 '@vue/devtools-api': 6.6.4
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
vue-pdf-embed@2.1.6(vue@3.5.34): vue-pdf-embed@2.1.6(vue@3.5.34(typescript@5.9.3)):
dependencies: dependencies:
pdfjs-dist: 6.2.108 pdfjs-dist: 6.2.108
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
vue-router@4.6.4(vue@3.5.34): vue-router@4.6.4(vue@3.5.34(typescript@5.9.3)):
dependencies: dependencies:
'@vue/devtools-api': 6.6.4 '@vue/devtools-api': 6.6.4
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
vue3-clipboard@1.0.0(vue@3.5.34): vue-tsc@3.3.13(typescript@5.9.3):
dependencies:
'@volar/typescript': 2.4.28(typescript@5.9.3)
'@vue/language-core': 3.3.13
typescript: 5.9.3
vue3-clipboard@1.0.0(vue@3.5.34(typescript@5.9.3)):
dependencies: dependencies:
clipboard: 2.0.11 clipboard: 2.0.11
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
vue@3.5.34: vue@3.5.34(typescript@5.9.3):
dependencies: dependencies:
'@vue/compiler-dom': 3.5.34 '@vue/compiler-dom': 3.5.34
'@vue/compiler-sfc': 3.5.34 '@vue/compiler-sfc': 3.5.34
'@vue/runtime-dom': 3.5.34 '@vue/runtime-dom': 3.5.34
'@vue/server-renderer': 3.5.34(vue@3.5.34) '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@5.9.3))
'@vue/shared': 3.5.34 '@vue/shared': 3.5.34
optionalDependencies:
typescript: 5.9.3
vuedraggable@4.1.0(vue@3.5.34): vuedraggable@4.1.0(vue@3.5.34(typescript@5.9.3)):
dependencies: dependencies:
sortablejs: 1.14.0 sortablejs: 1.14.0
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
vuex@4.1.0(vue@3.5.34): vuex@4.1.0(vue@3.5.34(typescript@5.9.3)):
dependencies: dependencies:
'@vue/devtools-api': 6.6.4 '@vue/devtools-api': 6.6.4
vue: 3.5.34 vue: 3.5.34(typescript@5.9.3)
webpack-virtual-modules@0.6.2: {} webpack-virtual-modules@0.6.2: {}
+25
View File
@@ -0,0 +1,25 @@
import { execFileSync } from 'node:child_process';
import { existsSync, rmSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
const root = process.cwd();
const distDir = resolve(root, 'dist');
const zipPath = resolve(root, 'dist.zip');
if (!existsSync(distDir) || !statSync(distDir).isDirectory()) {
console.error('[zip-dist] dist 目录不存在,请先完成构建');
process.exit(1);
}
if (existsSync(zipPath)) {
rmSync(zipPath);
}
// 压缩 dist 内文件到根级(解压即 index.html / assets,便于直接上传部署)
execFileSync('zip', ['-qr', zipPath, '.', '-x', '*.DS_Store', '-x', '__MACOSX/*'], {
cwd: distDir,
stdio: 'inherit',
});
const sizeMb = (statSync(zipPath).size / (1024 * 1024)).toFixed(2);
console.log(`[zip-dist] 已生成 ${zipPath}(${sizeMb} MB)`);
+61
View File
@@ -0,0 +1,61 @@
import { createCrudApi } from './common';
import { APPROVE_TIMEOUT } from '@/api/common';
import request from '@/axios';
const baseUrl = '/blade-transport/contract-change';
const api = createCrudApi(baseUrl);
export const getList = api.getList;
export const getDetail = api.getDetail;
export const submit = api.submit;
export const remove = api.remove;
export const createFromContract = (contractId, changeType) =>
request({
url: `${baseUrl}/create-from-contract`,
method: 'post',
params: { contractId, changeType },
});
export const saveDraft = row =>
request({
url: `${baseUrl}/save-draft`,
method: 'post',
data: row,
});
export const submitApproval = id =>
request({
url: `${baseUrl}/submit-approval`,
method: 'post',
params: { id },
});
export const approve = id =>
request({
url: `${baseUrl}/approve`,
method: 'post',
params: { id },
timeout: APPROVE_TIMEOUT,
});
export const reject = id =>
request({
url: `${baseUrl}/reject`,
method: 'post',
params: { id },
});
export const withdraw = id =>
request({
url: `${baseUrl}/withdraw`,
method: 'post',
params: { id },
});
export const markSyncStatus = (ids, syncStatus) =>
request({
url: `${baseUrl}/mark-sync-status`,
method: 'post',
params: { ids, syncStatus },
});
+5 -5
View File
@@ -1,4 +1,5 @@
import { createCrudApi } from './common'; import { createCrudApi } from './common';
import { APPROVE_TIMEOUT } from '@/api/common';
import request from '@/axios'; import request from '@/axios';
const baseUrl = '/blade-transport/contract-manage'; const baseUrl = '/blade-transport/contract-manage';
@@ -36,8 +37,7 @@ export const approve = id =>
url: `${baseUrl}/approve`, url: `${baseUrl}/approve`,
method: 'post', method: 'post',
params: { id }, params: { id },
// 审批通过后同步推送合规(附件转传可达 20s+),覆盖全局 10s 超时 timeout: APPROVE_TIMEOUT,
timeout: 120000,
}); });
export const reject = id => export const reject = id =>
@@ -93,10 +93,10 @@ export const expireStats = params =>
params, params,
}); });
/** 按合同负责部门预览下一合同编号 */ /** 按平台公司 + 业务部门预览下一合同编号 */
export const nextContractNo = organizationId => export const nextContractNo = (organizationId, platformCompanyId) =>
request({ request({
url: `${baseUrl}/next-contract-no`, url: `${baseUrl}/next-contract-no`,
method: 'get', method: 'get',
params: { organizationId }, params: { organizationId, platformCompanyId },
}); });
+39 -2
View File
@@ -2,6 +2,43 @@ import request from '@/axios';
const baseUrl = '/blade-transport/hcs'; const baseUrl = '/blade-transport/hcs';
/** 进程内缓存,避免列表页重复请求推送开关 */
let immediateEnabledCache = null;
let immediateEnabledPromise = null;
/**
* 查询 HCS 即时推送开关(thirdParty.hcs.push.immediate-enabled)。
* 关闭时项目管理/合同管理隐藏「推送合规」「批量推送合规」。
*/
export const getPushConfig = () =>
request({
url: `${baseUrl}/push-config`,
method: 'get',
});
export const loadHcsImmediateEnabled = () => {
if (immediateEnabledCache !== null) {
return Promise.resolve(immediateEnabledCache);
}
if (immediateEnabledPromise) {
return immediateEnabledPromise;
}
immediateEnabledPromise = getPushConfig()
.then(res => {
const data = res?.data?.data ?? res?.data ?? {};
immediateEnabledCache = data.immediateEnabled === true || data.immediateEnabled === 'true';
return immediateEnabledCache;
})
.catch(() => {
immediateEnabledCache = false;
return false;
})
.finally(() => {
immediateEnabledPromise = null;
});
return immediateEnabledPromise;
};
// 合规推送涉及 HCS 远程多次调用(风险校验+附件转传+起草),可达 20s+,覆盖全局 10s 超时 // 合规推送涉及 HCS 远程多次调用(风险校验+附件转传+起草),可达 20s+,覆盖全局 10s 超时
export const repush = (bizType, id) => export const repush = (bizType, id) =>
request({ request({
@@ -32,11 +69,11 @@ export const refreshMasterData = () =>
timeout: 60000, timeout: 60000,
}); });
export const getRisk = customerCode => export const getRisk = (customerCode, creditCode, customerName) =>
request({ request({
url: `${baseUrl}/risk`, url: `${baseUrl}/risk`,
method: 'get', method: 'get',
params: { customerCode }, params: { customerCode, creditCode, customerName },
timeout: 30000, timeout: 30000,
}); });
+7 -2
View File
@@ -24,11 +24,16 @@ export const getDetail = id =>
}, },
}); });
export const getCarrierContracts = projectIds => export const getCarrierContracts = (projectIds, waybillIds = []) =>
request({ request({
url: `${baseUrl}/carrier-contracts`, url: `${baseUrl}/carrier-contracts`,
method: 'get', method: 'get',
params: { projectIds: (projectIds || []).join(',') }, params: {
projectIds: (projectIds || []).join(','),
...(waybillIds || []).filter(Boolean).length
? { waybillIds: (waybillIds || []).filter(Boolean).join(',') }
: {},
},
}); });
export const saveDraft = row => export const saveDraft = row =>
+54
View File
@@ -0,0 +1,54 @@
import { createCrudApi } from './common';
import { APPROVE_TIMEOUT } from '@/api/common';
import request from '@/axios';
const baseUrl = '/blade-transport/project-change';
const api = createCrudApi(baseUrl);
export const getList = api.getList;
export const getDetail = api.getDetail;
export const submit = api.submit;
export const remove = api.remove;
export const createFromProject = (projectId, changeType) =>
request({
url: `${baseUrl}/create-from-project`,
method: 'post',
params: { projectId, changeType },
});
export const saveDraft = row =>
request({
url: `${baseUrl}/save-draft`,
method: 'post',
data: row,
});
export const submitApproval = id =>
request({
url: `${baseUrl}/submit-approval`,
method: 'post',
params: { id },
});
export const approve = id =>
request({
url: `${baseUrl}/approve`,
method: 'post',
params: { id },
timeout: APPROVE_TIMEOUT,
});
export const reject = id =>
request({
url: `${baseUrl}/reject`,
method: 'post',
params: { id },
});
export const withdraw = id =>
request({
url: `${baseUrl}/withdraw`,
method: 'post',
params: { id },
});
+7
View File
@@ -14,6 +14,13 @@ export const cancel = api.cancel;
export const reassign = api.reassign; export const reassign = api.reassign;
export const complete = api.complete; export const complete = api.complete;
export const batchComplete = api.batchComplete; export const batchComplete = api.batchComplete;
/** 确认回单:回单节点配成「需业务员确认」时用;后端校验司机打卡节点齐全且无未了结异常后才置完成 */
export const confirmReturn = id =>
request({
url: `${baseUrl}/confirm-return`,
method: 'post',
params: { id },
});
export const changeRoute = data => export const changeRoute = data =>
request({ request({
url: `${baseUrl}/change-route`, url: `${baseUrl}/change-route`,
+3
View File
@@ -1,6 +1,9 @@
import request from '@/axios'; import request from '@/axios';
import { ElLoading, ElMessage } from 'element-plus'; import { ElLoading, ElMessage } from 'element-plus';
/** 审核按钮统一超时:审批通过后可能同步推送金蝶等耗时操作,覆盖全局 10s */
export const APPROVE_TIMEOUT = 60 * 1000;
/** /**
* 文件流返回 * 文件流返回
* @param url 接口地址 * @param url 接口地址
+4 -4
View File
@@ -6,17 +6,17 @@ export const getList = (current, size, params) =>
request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } }); request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } });
export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } }); export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } });
export const getExpiryCounts = () => request({ url: `${baseUrl}/expiry-counts`, method: 'get' }); export const getExpiryCounts = () => request({ url: `${baseUrl}/expiry-counts`, method: 'get' });
export const getAvailableOptions = (keyword, deptId, selectedId) => export const getAvailableOptions = (keyword, deptId, selectedId, payerName, payeeName) =>
request({ request({
url: `${baseUrl}/available-options`, url: `${baseUrl}/available-options`,
method: 'get', method: 'get',
params: { keyword, deptId, selectedId }, params: { keyword, deptId, selectedId, payerName, payeeName },
}); });
export const getAvailablePage = (current, size, keyword, deptId, selectedId) => export const getAvailablePage = (current, size, keyword, deptId, selectedId, payerName, payeeName) =>
request({ request({
url: `${baseUrl}/available-page`, url: `${baseUrl}/available-page`,
method: 'get', method: 'get',
params: { current, size, keyword, deptId, selectedId }, params: { current, size, keyword, deptId, selectedId, payerName, payeeName },
}); });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } }); export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
+3 -1
View File
@@ -1,4 +1,5 @@
import request from '@/axios'; import request from '@/axios';
import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/invoice-application'; const baseUrl = '/blade-transport/invoice-application';
@@ -18,7 +19,8 @@ export const getReceiverInformation = settlementIds =>
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data }); export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } }); export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); export const approve = data =>
request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
export const syncKingdee = id => export const syncKingdee = id =>
+12 -1
View File
@@ -1,4 +1,5 @@
import request from '@/axios'; import request from '@/axios';
import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/invoice-receipt'; const baseUrl = '/blade-transport/invoice-receipt';
@@ -31,11 +32,21 @@ export const getReferenceInformation = settlementIds =>
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data }); export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } }); export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); export const approve = data =>
request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
export const pushKingdee = ids => export const pushKingdee = ids =>
request({ url: `${baseUrl}/push-kingdee`, method: 'post', timeout: 30000, data: ids }); request({ url: `${baseUrl}/push-kingdee`, method: 'post', timeout: 30000, data: ids });
// 先票后款:发票明细查询(收票登记明细区块按票号带出发票明细行)
export const invoiceDetailQuery = invoiceNo =>
request({ url: `${baseUrl}/invoice-detail-query`, method: 'get', params: { invoiceNo } });
// 先票后款:结算单认领收票单候选分页(审批通过且未被有效应付结算单认领)
export const claimCandidates = params =>
request({ url: `${baseUrl}/claim-candidates`, method: 'get', params });
// 先票后款:结算单已认领收票登记列表(发票登记对话框回显已认领票)
export const claimedReceipts = formalSettlementId =>
request({ url: `${baseUrl}/claimed-list`, method: 'get', params: { formalSettlementId } });
export const approvalStatusOptions = [ export const approvalStatusOptions = [
{ label: '草稿', value: 'draft' }, { label: '草稿', value: 'draft' },
+22 -4
View File
@@ -1,4 +1,5 @@
import request from '@/axios'; import request from '@/axios';
import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/payment-application'; const baseUrl = '/blade-transport/payment-application';
export const getList = (current, size, params) => export const getList = (current, size, params) =>
@@ -10,21 +11,38 @@ export const getReferenceAmount = (paymentType, referenceId, excludeId) =>
method: 'get', method: 'get',
params: { paymentType, referenceId, excludeId: excludeId || undefined }, params: { paymentType, referenceId, excludeId: excludeId || undefined },
}); });
/** 按合同查询可选项目预付核销列表(审批通过且仍有可核销余额) */
export const getProjectAdvanceWriteOffOptions = params =>
request({
url: `${baseUrl}/project-advance-write-off-options`,
method: 'get',
params,
});
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data }); export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } }); export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); export const approve = data =>
request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
// 同步金蝶涉及多次金蝶远程接口调用(A103-2 预检 + A103-1/A103-3 推送),单笔可达 20s+,覆盖全局 10s 超时 // 同步金蝶涉及多次金蝶远程接口调用(A103-2 预检 + A103-1/A103-3 推送),单笔可达 20s+,覆盖全局 10s 超时
export const syncKingdee = id => export const syncKingdee = id =>
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: 30000, params: { id } }); request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: APPROVE_TIMEOUT, params: { id } });
// 批量同步逐单单据级事务串行推送,耗时随单量线性增长 // 批量同步逐单单据级事务串行推送,耗时随单量线性增长
export const syncKingdeeBatch = ids => export const syncKingdeeBatch = ids =>
request({ url: `${baseUrl}/sync-kingdee-batch`, method: 'post', timeout: 30000, data: ids }); request({ url: `${baseUrl}/sync-kingdee-batch`, method: 'post', timeout: APPROVE_TIMEOUT, data: ids });
// 回写任务按 paymentNo 分批远程查询 A103-2/A103-4,同样可能超过全局超时 // 回写任务按 paymentNo 分批远程查询 A103-2/A103-4,同样可能超过全局超时
export const syncKingdeeResult = () => export const syncKingdeeResult = () =>
request({ url: `${baseUrl}/sync-kingdee-result`, method: 'post', timeout: 30000 }); request({ url: `${baseUrl}/sync-kingdee-result`, method: 'post', timeout: APPROVE_TIMEOUT });
// 付款记录付款凭证下载(后端代理 A801-2 流式回源),耗时较普通接口长,放宽到 120s
export const downloadRecordVoucher = recordId =>
request({
url: `${baseUrl}/record/download-voucher`,
method: 'get',
params: { recordId },
responseType: 'blob',
timeout: 120000,
});
export const paymentTypeOptions = [ export const paymentTypeOptions = [
{ label: '项目预付', value: 'project_advance' }, { label: '项目预付', value: 'project_advance' },
-8
View File
@@ -8,20 +8,12 @@ export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get'
export const updateAttachments = data => export const updateAttachments = data =>
request({ url: `${baseUrl}/attachments`, method: 'post', data }); request({ url: `${baseUrl}/attachments`, method: 'post', data });
export const voidClaim = id => request({ url: `${baseUrl}/void`, method: 'post', params: { id } }); export const voidClaim = id => request({ url: `${baseUrl}/void`, method: 'post', params: { id } });
export const queryKingdeeResult = () =>
request({ url: `${baseUrl}/query-kingdee-result`, method: 'post' });
export const claimStatusOptions = [ export const claimStatusOptions = [
{ label: '已认领', value: 'claimed' }, { label: '已认领', value: 'claimed' },
{ label: '已作废', value: 'voided' }, { label: '已作废', value: 'voided' },
]; ];
export const pushStatusOptions = [
{ label: '未推送', value: 'none' },
{ label: '已推送', value: 'pushed' },
{ label: '推送失败', value: 'failed' },
];
export const kingdeeBillStatusOptions = [ export const kingdeeBillStatusOptions = [
{ label: '未生成', value: 'none' }, { label: '未生成', value: 'none' },
{ label: '已推送', value: 'pushed' }, { label: '已推送', value: 'pushed' },
+4 -9
View File
@@ -2,6 +2,9 @@ import request from '@/axios';
const baseUrl = '/blade-transport/receipt-flow'; const baseUrl = '/blade-transport/receipt-flow';
/** 收款流水同步需远程拉取金蝶收款单并逐条落库,覆盖全局 10s 超时 */
export const SYNC_TIMEOUT = 30000;
export const getList = (current, size, params) => export const getList = (current, size, params) =>
request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } }); request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } });
export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } }); export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } });
@@ -12,9 +15,7 @@ export const getSettlementCandidates = (keyword, flowId) =>
params: { keyword, flowId }, params: { keyword, flowId },
}); });
export const claim = data => request({ url: `${baseUrl}/claim`, method: 'post', data }); export const claim = data => request({ url: `${baseUrl}/claim`, method: 'post', data });
export const sync = () => request({ url: `${baseUrl}/sync`, method: 'post' }); export const sync = () => request({ url: `${baseUrl}/sync`, method: 'post', timeout: SYNC_TIMEOUT });
export const pushKingdee = flowId =>
request({ url: `${baseUrl}/push-kingdee`, method: 'post', params: { flowId } });
export const claimStatusOptions = [ export const claimStatusOptions = [
{ label: '未认领', value: 'unclaimed' }, { label: '未认领', value: 'unclaimed' },
@@ -22,12 +23,6 @@ export const claimStatusOptions = [
{ label: '认领完成', value: 'claimed' }, { label: '认领完成', value: 'claimed' },
]; ];
export const pushStatusOptions = [
{ label: '未推送', value: 'none' },
{ label: '已推送', value: 'pushed' },
{ label: '推送失败', value: 'failed' },
];
export const kingdeeBillStatusOptions = [ export const kingdeeBillStatusOptions = [
{ label: '已推送', value: 'pushed' }, { label: '已推送', value: 'pushed' },
{ label: '审核通过', value: 'approved' }, { label: '审核通过', value: 'approved' },
+28 -3
View File
@@ -1,4 +1,5 @@
import request from '@/axios'; import request from '@/axios';
import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/formal-settlement'; const baseUrl = '/blade-transport/formal-settlement';
@@ -27,12 +28,20 @@ export const getCandidateDetails = (current, size, params) =>
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data }); export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } }); export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); export const approve = data =>
request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
// 同步金蝶涉及多次金蝶远程接口调用(A102-4 预检 + A102-2 保存),可达 20s+,覆盖全局 10s 超时 // 同步金蝶涉及多次金蝶远程接口调用(A102-4 预检 + A102-2 保存),可达 20s+,覆盖全局 10s 超时
export const syncKingdee = id => // config.meta.suppressErrorMessage=true 时由页面弹框汇总结果,避免拦截器重复 toast
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: 30000, params: { id } }); export const syncKingdee = (id, config = {}) =>
request({
url: `${baseUrl}/sync-kingdee`,
method: 'post',
timeout: 30000,
params: { id },
...config,
});
export const getDetailFees = detailId => export const getDetailFees = detailId =>
request({ url: `${baseUrl}/detail-fees`, method: 'get', params: { detailId } }); request({ url: `${baseUrl}/detail-fees`, method: 'get', params: { detailId } });
export const getReceiptClaims = formalSettlementId => export const getReceiptClaims = formalSettlementId =>
@@ -49,5 +58,21 @@ export const applyPayments = data =>
request({ url: `${baseUrl}/apply-payments`, method: 'post', data }); request({ url: `${baseUrl}/apply-payments`, method: 'post', data });
export const claimInvoices = data => export const claimInvoices = data =>
request({ url: `${baseUrl}/claim-invoices`, method: 'post', data }); request({ url: `${baseUrl}/claim-invoices`, method: 'post', data });
// 先票后款:发票登记确认推送(认领收票单逐单 A302-6 指定应付),涉及多次金蝶远程调用,覆盖全局超时
export const invoiceAssignKingdee = id =>
request({
url: `${baseUrl}/invoice-assign-kingdee`,
method: 'post',
timeout: 30000,
params: { id },
});
// 先票后款:查看页重推认领收票单(收票单A303-2+指定应付A302-6 一步式),涉及多次金蝶远程调用,覆盖全局超时
export const repushClaimedInvoice = (id, invoiceNo, matchedAmount) =>
request({
url: `${baseUrl}/repush-claimed-invoice`,
method: 'post',
timeout: 30000,
params: { id, invoiceNo, matchedAmount },
});
export const queryInvoicePool = params => export const queryInvoicePool = params =>
request({ url: `${baseUrl}/invoice-pool`, method: 'get', params }); request({ url: `${baseUrl}/invoice-pool`, method: 'get', params });
+5 -1
View File
@@ -1,4 +1,5 @@
import request from '@/axios'; import request from '@/axios';
import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/pre-settlement'; const baseUrl = '/blade-transport/pre-settlement';
@@ -24,6 +25,8 @@ export const getContractList = (current, size, params) =>
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' }); export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
export const getNextNo = () => request({ url: `${baseUrl}/next-no`, method: 'get' });
export const getCandidateDetails = (current, size, params) => export const getCandidateDetails = (current, size, params) =>
request({ request({
url: `${baseUrl}/candidate-details`, url: `${baseUrl}/candidate-details`,
@@ -40,7 +43,8 @@ export const removeDetail = (id, detailId) =>
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); export const approve = data =>
request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
@@ -17,6 +17,10 @@ export const approve = id => request({ url: `${baseUrl}/approve`, method: 'post'
export const returnBill = (id, reason) => export const returnBill = (id, reason) =>
request({ url: `${baseUrl}/return`, method: 'post', data: { id, reason } }); request({ url: `${baseUrl}/return`, method: 'post', data: { id, reason } });
export const repush = id => request({ url: `${baseUrl}/repush`, method: 'post', params: { id } }); export const repush = id => request({ url: `${baseUrl}/repush`, method: 'post', params: { id } });
export const syncKingdee = id =>
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: 30000, params: { id } });
export const kingdeeWriteOffPrecheck = formalSettlementId =>
request({ url: `${baseUrl}/kingdee-writeoff-precheck`, method: 'get', params: { formalSettlementId } });
export const approvalStatusOptions = [ export const approvalStatusOptions = [
{ label: '草稿', value: 'draft' }, { label: '草稿', value: 'draft' },
+65
View File
@@ -0,0 +1,65 @@
import request from '@/axios';
const baseUrl = '/blade-transport/settlement-import';
export const getOptions = () => request({ url: `${baseUrl}/options`, method: 'get' });
export const getNextCode = () => request({ url: `${baseUrl}/next-code`, method: 'get' });
export const getList = (current, size, params) =>
request({
url: `${baseUrl}/list`,
method: 'get',
params: { current, size, ...params },
});
export const getDetail = id =>
request({ url: `${baseUrl}/detail`, method: 'get', params: { id } });
export const exportTemplate = () =>
request({
url: `${baseUrl}/export-template`,
method: 'get',
responseType: 'blob',
});
export const parseFile = file => {
const data = new FormData();
data.append('file', file);
return request({
url: `${baseUrl}/parse`,
method: 'post',
data,
timeout: 120000,
});
};
export const validate = data =>
request({ url: `${baseUrl}/validate`, method: 'post', data, timeout: 120000 });
export const exportFailures = data =>
request({
url: `${baseUrl}/export-failures`,
method: 'post',
data,
responseType: 'blob',
timeout: 120000,
});
export const saveDraft = data =>
request({ url: `${baseUrl}/draft`, method: 'post', data, timeout: 120000 });
export const confirm = data =>
request({ url: `${baseUrl}/confirm`, method: 'post', data, timeout: 120000 });
export const voidBatch = data => request({ url: `${baseUrl}/void`, method: 'post', data });
export const removeDraft = id =>
request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const getHistoryWaybills = params =>
request({
url: `${baseUrl}/history-waybills`,
method: 'get',
params,
});
+42
View File
@@ -0,0 +1,42 @@
import request from '@/axios';
const baseUrl = '/blade-system/business-dept-config';
export const getList = (current, size, params) =>
request({
url: `${baseUrl}/list`,
method: 'get',
params: {
...params,
current,
size,
},
});
export const getDetail = id =>
request({
url: `${baseUrl}/detail`,
method: 'get',
params: { id },
});
export const submit = row =>
request({
url: `${baseUrl}/submit`,
method: 'post',
data: row,
});
export const remove = ids =>
request({
url: `${baseUrl}/remove`,
method: 'post',
params: { ids },
});
export const getByPlatformDept = (platformCompanyId, businessDeptId) =>
request({
url: `${baseUrl}/by-platform-dept`,
method: 'get',
params: { platformCompanyId, businessDeptId },
});
+1
View File
@@ -12,6 +12,7 @@ export const MK_SUBMIT_TIMEOUT = 30000;
* @param {string} data.bizId 业务主键(后端生成/复用 formInstanceId) * @param {string} data.bizId 业务主键(后端生成/复用 formInstanceId)
* @param {string} [data.subject] 流程标题 * @param {string} [data.subject] 流程标题
* @param {string} [data.bizType] 业务类型 * @param {string} [data.bizType] 业务类型
* @param {Object} [data.tempVarData] MK 临时变量(如 { projectType })
*/ */
export const processSubmit = data => { export const processSubmit = data => {
return request({ return request({
+10 -1
View File
@@ -77,7 +77,8 @@ export const syncOaDepartment = (
size = 20, size = 20,
signal, signal,
incremental = false, incremental = false,
modifiedSince modifiedSince,
companyOaId
) => { ) => {
return request({ return request({
url: '/blade-system/dept/sync-oa-department', url: '/blade-system/dept/sync-oa-department',
@@ -87,6 +88,7 @@ export const syncOaDepartment = (
size, size,
incremental: !!incremental, incremental: !!incremental,
modifiedSince: modifiedSince || undefined, modifiedSince: modifiedSince || undefined,
companyOaId: companyOaId || undefined,
}, },
timeout: 60000, timeout: 60000,
signal, signal,
@@ -137,3 +139,10 @@ export const getPlatformCompanySelect = () => {
method: 'get', method: 'get',
}); });
}; };
export const getBusinessDeptSelect = () => {
return request({
url: '/blade-system/dept/business-dept-select',
method: 'get',
});
};
+19
View File
@@ -0,0 +1,19 @@
import request from '@/axios';
export const getRoleMenuDataScopeTree = roleId => {
return request({
url: '/blade-system/role-menu-data-scope/tree',
method: 'get',
params: {
roleId,
},
});
};
export const submitRoleMenuDataScope = data => {
return request({
url: '/blade-system/role-menu-data-scope/submit',
method: 'post',
data,
});
};
+65 -2
View File
@@ -152,6 +152,34 @@ export const getScoreTemplate = quantificationId => {
}); });
}; };
/** 线下评分 Excel 模板下载 */
export const downloadScoreExcelTemplate = quantificationId => {
return request({
url: '/blade-transport/customer-archive/score-excel-template',
method: 'get',
responseType: 'blob',
params: {
quantificationId,
},
});
};
/** 线下评分 Excel 导入(成功返回评分明细 JSON;失败返回失败明细 Excel 流) */
export const importScoreExcel = (quantificationId, file) => {
const formData = new FormData();
formData.append('file', file);
return request({
url: '/blade-transport/customer-archive/score-import',
method: 'post',
data: formData,
responseType: 'blob',
timeout: 60000,
params: {
quantificationId,
},
});
};
export const queryKingdeeCustomer = creditCode => { export const queryKingdeeCustomer = creditCode => {
return request({ return request({
url: '/blade-transport/bfi/customer/query-by-credit-code', url: '/blade-transport/bfi/customer/query-by-credit-code',
@@ -163,6 +191,42 @@ export const queryKingdeeCustomer = creditCode => {
}); });
}; };
// 金蝶供应商编码查询(A901-1):新增客商填写信用代码后即时回显
export const querySupplierCode = creditCode => {
return request({
url: '/blade-transport/customer-archive/query-supplier-code',
method: 'get',
timeout: 30000,
params: {
creditCode,
},
});
};
// 金蝶开户行查询(A905-3):收款信息填写开户行失焦后精确匹配带出联行号
export const queryKingdeeBank = bankName => {
return request({
url: '/blade-transport/customer-archive/query-bank',
method: 'get',
timeout: 30000,
params: {
bankName,
},
});
};
// 金蝶联行号校验(A905-3):收款信息保存前按联行号精确匹配,查不到不允许保存
export const queryKingdeeBankByNumber = cnapsCode => {
return request({
url: '/blade-transport/customer-archive/query-bank-by-number',
method: 'get',
timeout: 30000,
params: {
cnapsCode,
},
});
};
export const syncKingdee = id => { export const syncKingdee = id => {
return request({ return request({
url: '/blade-transport/customer-archive/sync-kingdee', url: '/blade-transport/customer-archive/sync-kingdee',
@@ -176,12 +240,11 @@ export const syncKingdee = id => {
export const recognizeBusinessLicenseOcr = imageUrl => { export const recognizeBusinessLicenseOcr = imageUrl => {
return request({ return request({
url: '/blade-transport/baidu-ocr/recognize-url', url: '/blade-transport/baidu-ocr/business-license',
method: 'post', method: 'post',
timeout: 60000, timeout: 60000,
params: { params: {
imageUrl, imageUrl,
type: 'business_license',
}, },
}); });
}; };
+46 -18
View File
@@ -74,12 +74,15 @@ const redirectToLogin = () => {
* 统一弹出错误提示并返回带标记的 Error。 * 统一弹出错误提示并返回带标记的 Error。
* `_notified` 标记用于让业务层(批量导入等)识别「拦截器已经提示过」, * `_notified` 标记用于让业务层(批量导入等)识别「拦截器已经提示过」,
* 避免同一条错误被弹两次。 * 避免同一条错误被弹两次。
* meta.suppressErrorMessage=true 时由业务层自行弹框汇总,拦截器不再 toast。
*/ */
const notifiedError = message => { const notifiedError = (message, config) => {
ElMessage({ if (config?.meta?.suppressErrorMessage !== true) {
message: message, ElMessage({
type: 'error', message: message,
}); type: 'error',
});
}
const error = new Error(message); const error = new Error(message);
error._notified = true; error._notified = true;
return error; return error;
@@ -102,12 +105,27 @@ const isJsonContentType = res => {
return contentType.includes('application/json') || contentType.includes('text/json'); return contentType.includes('application/json') || contentType.includes('text/json');
}; };
/**
* 是否为可按文本读取的 Blob / Blob-like(含跨 realm)。
* 禁止对普通业务 JSON 对象调用 .text()。
*/
const isBlobLikeData = value =>
value != null &&
typeof value === 'object' &&
typeof value.size === 'number' &&
typeof value.type === 'string' &&
typeof value.text === 'function';
/** /**
* 判断响应体是否已是解析好的对象(无需再解析)。 * 判断响应体是否已是解析好的对象(无需再解析)。
* 注意 Blob 也是 object,必须排除,否则会把待解析的文件流误判为已完成。 * 注意 Blob 也是 object,必须排除,否则会把待解析的文件流误判为已完成。
*/ */
const isPlainObject = value => const isPlainObject = value =>
value !== null && typeof value === 'object' && !(value instanceof Blob); value !== null &&
typeof value === 'object' &&
!(value instanceof Blob) &&
!isBlobLikeData(value) &&
!(value instanceof ArrayBuffer);
//跨域请求,允许保存cookie //跨域请求,允许保存cookie
axios.defaults.withCredentials = true; axios.defaults.withCredentials = true;
// NProgress Configuration // NProgress Configuration
@@ -187,15 +205,20 @@ axios.interceptors.response.use(
// 用户就会以为系统崩了——而后端其实返回了明确的业务提示(如「未读取到数据行…」)。 // 用户就会以为系统崩了——而后端其实返回了明确的业务提示(如「未读取到数据行…」)。
// 这里按 Content-Type 判断:JSON 才解析回对象;真·文件流(Excel 等)保持原样不动。 // 这里按 Content-Type 判断:JSON 才解析回对象;真·文件流(Excel 等)保持原样不动。
// 兼容 Blob(浏览器 responseType='blob')与 string(非浏览器环境)两种形态。 // 兼容 Blob(浏览器 responseType='blob')与 string(非浏览器环境)两种形态。
// 仅对真正的 Blob/Blob-like 调 .text(),避免业务 JSON 已解析后出现
// 「e.data.text is not a function」。
if (isJsonContentType(res) && !isPlainObject(res.data)) { if (isJsonContentType(res) && !isPlainObject(res.data)) {
try { try {
let text = ''; let text = '';
if (typeof res.data?.text === 'function') { const raw = res.data;
text = await res.data.text(); if (isBlobLikeData(raw) || (typeof Blob !== 'undefined' && raw instanceof Blob)) {
} else if (typeof res.data === 'string') { text = await raw.text();
text = res.data; } else if (typeof raw === 'string') {
} else if (typeof res.data?.arrayBuffer === 'function') { text = raw;
text = new TextDecoder().decode(await res.data.arrayBuffer()); } else if (raw && typeof raw.arrayBuffer === 'function') {
text = new TextDecoder().decode(await raw.arrayBuffer());
} else if (raw instanceof ArrayBuffer) {
text = new TextDecoder().decode(raw);
} }
const parsed = typeof text === 'string' && text ? JSON.parse(text) : null; const parsed = typeof text === 'string' && text ? JSON.parse(text) : null;
if (parsed && typeof parsed === 'object') { if (parsed && typeof parsed === 'object') {
@@ -205,10 +228,11 @@ axios.interceptors.response.use(
// 声明是 JSON 却解析失败:保持原样,交给下游按文件处理 // 声明是 JSON 却解析失败:保持原样,交给下游按文件处理
} }
} }
//获取状态信息 // 获取状态信息:文件流没有 code/msg,必须退回 HTTP status,禁止读 Blob 上的幽灵字段
const status = res.data.error_code || res.data.code || res.status; const payload = isPlainObject(res.data) ? res.data : {};
const status = Number(payload.error_code || payload.code || res.status);
const statusWhiteList = website.statusWhiteList || []; const statusWhiteList = website.statusWhiteList || [];
const message = res.data.msg || res.data.error_description || '系统错误'; const message = payload.msg || payload.error_description || '系统错误';
//如果在白名单里则自行catch逻辑处理 //如果在白名单里则自行catch逻辑处理
if (statusWhiteList.includes(status)) return Promise.reject(res); if (statusWhiteList.includes(status)) return Promise.reject(res);
@@ -260,12 +284,12 @@ axios.interceptors.response.use(
// OAuth2 业务错误码提示 // OAuth2 业务错误码提示
if (status > 2000 && !validatenull(res.data.error_description)) { if (status > 2000 && !validatenull(res.data.error_description)) {
return Promise.reject(notifiedError(message)); return Promise.reject(notifiedError(message, config));
} }
// 其余非 200 响应统一提示 // 其余非 200 响应统一提示
if (status !== 200) { if (status !== 200) {
return Promise.reject(notifiedError(message)); return Promise.reject(notifiedError(message, config));
} }
return res; return res;
}, },
@@ -273,7 +297,11 @@ axios.interceptors.response.use(
NProgress.done(); NProgress.done();
// 网络级失败(HTTP 状态 >500、网关错误、超时、断网等 validateStatus 放行之外的响应) // 网络级失败(HTTP 状态 >500、网关错误、超时、断网等 validateStatus 放行之外的响应)
// 原为静默 reject,接口报错页面无任何感知;统一补错误提示,取消请求与刷新令牌请求除外 // 原为静默 reject,接口报错页面无任何感知;统一补错误提示,取消请求与刷新令牌请求除外
if (!axios.isCancel(error) && !isRefreshTokenRequest(error?.config)) { if (
!axios.isCancel(error) &&
!isRefreshTokenRequest(error?.config) &&
error?.config?.meta?.suppressErrorMessage !== true
) {
ElMessage({ ElMessage({
message: error?.response?.data?.msg || '请求失败,请稍后重试', message: error?.response?.data?.msg || '请求失败,请稍后重试',
type: 'error', type: 'error',
@@ -1,8 +1,8 @@
<template> <template>
<el-dialog v-model="visible" :title="title" width="92%" append-to-body destroy-on-close @open="handleOpen"> <el-dialog v-model="visible" :title="title" width="92%" append-to-body align-center destroy-on-close @open="handleOpen">
<el-form :model="query" inline label-width="130px" @submit.prevent> <el-form class="kingdee-pool-dialog__form" :model="query" inline label-position="right" label-width="120px" @submit.prevent>
<el-form-item label="发票号码"> <el-form-item label="发票号码">
<el-input v-model="query.invoiceNo" clearable @keyup.enter="handleQuery" /> <el-input v-model="query.invoiceNo" clearable placeholder="请输入" @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item label="开票日期"> <el-form-item label="开票日期">
<el-date-picker <el-date-picker
@@ -10,15 +10,16 @@
type="daterange" type="daterange"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
format="YYYY-MM-DD" format="YYYY-MM-DD"
start-placeholder="开始日期" range-separator="-"
end-placeholder="结束日期" start-placeholder="请选择"
end-placeholder="请选择"
/> />
</el-form-item> </el-form-item>
<el-form-item label="开票单位"> <el-form-item label="开票单位">
<el-input v-model="query.salerName" clearable @keyup.enter="handleQuery" /> <el-input v-model="query.salerName" clearable placeholder="请输入" @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item label="受票单位"> <el-form-item label="受票单位">
<el-input v-model="query.buyerName" clearable @keyup.enter="handleQuery" /> <el-input v-model="query.buyerName" clearable placeholder="请输入" @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item label="开票单位(销方)"> <el-form-item label="开票单位(销方)">
<el-select <el-select
@@ -93,6 +94,9 @@
<el-form-item> <el-form-item>
<el-button :disabled="loading" @click="resetQuery">重置</el-button> <el-button :disabled="loading" @click="resetQuery">重置</el-button>
<el-button type="primary" :disabled="loading" @click="handleQuery">查询</el-button> <el-button type="primary" :disabled="loading" @click="handleQuery">查询</el-button>
<el-tooltip content="按名称检索范围为已同步票,未找到请改用发票号码或税号精确查找" placement="top">
<el-icon class="kingdee-pool-dialog__query-tip"><QuestionFilled /></el-icon>
</el-tooltip>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table <el-table
@@ -101,7 +105,7 @@
:data="rows" :data="rows"
:row-key="rowKey" :row-key="rowKey"
border border
height="520" max-height="50vh"
:highlight-current-row="!multiple" :highlight-current-row="!multiple"
@selection-change="selection => (selected = selection)" @selection-change="selection => (selected = selection)"
@row-dblclick="handleRowDblclick" @row-dblclick="handleRowDblclick"
@@ -118,6 +122,7 @@
</el-radio> </el-radio>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column type="index" label="序号" width="70" align="center" :index="indexMethod" />
<el-table-column prop="invoiceNo" label="发票号码" min-width="180" /> <el-table-column prop="invoiceNo" label="发票号码" min-width="180" />
<el-table-column prop="invoiceDate" label="开票日期" min-width="120" /> <el-table-column prop="invoiceDate" label="开票日期" min-width="120" />
<el-table-column prop="invoiceType" label="发票类型" min-width="140" /> <el-table-column prop="invoiceType" label="发票类型" min-width="140" />
@@ -172,8 +177,8 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="kingdee-pool-dialog__pager"> <div class="kingdee-pool-dialog__pagination">
<span class="kingdee-pool-dialog__pager-tip">按名称检索范围为已同步票,未找到请改用发票号码或税号精确查找</span> <span class="kingdee-pool-dialog__pagination-tip">按名称检索范围为已同步票,未找到请改用发票号码或税号精确查找</span>
<el-pagination <el-pagination
v-model:current-page="page.current" v-model:current-page="page.current"
v-model:page-size="page.size" v-model:page-size="page.size"
@@ -323,6 +328,9 @@ export default {
this.page.current = 1; this.page.current = 1;
this.load(); this.load();
}, },
indexMethod(index) {
return (this.page.current - 1) * this.page.size + index + 1;
},
resetQuery() { resetQuery() {
this.query = this.emptyQuery(); this.query = this.emptyQuery();
this.handleQuery(); this.handleQuery();
@@ -420,15 +428,28 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.kingdee-pool-dialog__pager { .kingdee-pool-dialog__form {
margin-bottom: 16px;
padding: 12px 12px 4px;
background: #fff;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.kingdee-pool-dialog__pagination {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-top: 12px; padding: 16px 0;
gap: 16px; gap: 16px;
} }
.kingdee-pool-dialog__pager-tip { .kingdee-pool-dialog__pagination-tip {
color: #909399; color: #909399;
font-size: 12px; font-size: 12px;
white-space: nowrap;
}
.kingdee-pool-dialog__query-tip {
margin-left: 8px;
color: #909399;
cursor: pointer;
} }
</style> </style>
+27 -2
View File
@@ -12,6 +12,8 @@ import { h } from 'vue';
* 1. 混入本 mixin:mixins: [organizationSearch] * 1. 混入本 mixin:mixins: [organizationSearch]
* 2. option 中「所属组织」列加上 searchslot: true(prop 为 organizationName 或 deptName 均可) * 2. option 中「所属组织」列加上 searchslot: true(prop 为 organizationName 或 deptName 均可)
* 3. 在 created / 初始化时调用 this.loadOrganizationOptions() * 3. 在 created / 初始化时调用 this.loadOrganizationOptions()
* 4. 需要「未分配组织」筛选项的页面在 data 里置 enableUnassignedOrgFilter: true;
* 该项只追加进搜索树 organizationSearchTreeOptions,表单级联 organizationTreeOptions 不含哨兵
* *
* 说明: * 说明:
* - 自动识别 organizationName / deptName 两种 prop,并挂载 column.renderSearch * - 自动识别 organizationName / deptName 两种 prop,并挂载 column.renderSearch
@@ -26,6 +28,12 @@ import { h } from 'vue';
*/ */
const ORG_PROPS = ['organizationName', 'deptName']; const ORG_PROPS = ['organizationName', 'deptName'];
// 「未分配组织」筛选项的哨兵值:不是真实组织名,选中后提交给后端的就是这串字面量。
// 后端 TransportBusiConstant.UNASSIGNED_ORG_FILTER、DriverMapper.xml / TransportVehicleMapper.xml
// 的 organizationName 分支以及司机、车辆导出查询都按它改判为 organization_name = '';
// 后端换成空串会让整段组织条件不生效,筛选静默失效。前端与后端这几处必须同步修改。
const UNASSIGNED_ORG_FILTER = '__UNASSIGNED_ORG__';
export default { export default {
data() { data() {
return { return {
@@ -34,9 +42,18 @@ export default {
organizationTreeOptions: [], organizationTreeOptions: [],
organizationTreeFlatOptions: [], organizationTreeFlatOptions: [],
organizationLoading: false, organizationLoading: false,
// 组织筛选是否追加「未分配组织」一项;页面在 data 里覆盖为 true 才生效
enableUnassignedOrgFilter: false,
}; };
}, },
computed: { computed: {
// 搜索栏级联的选项:开关打开时在真实组织最前面追加「未分配组织」。
// 不能直接改 organizationTreeOptions——司机/车辆页的编辑表单也用这棵树选组织。
organizationSearchTreeOptions() {
return this.enableUnassignedOrgFilter
? [this.unassignedOrgOption(), ...this.organizationTreeOptions]
: this.organizationTreeOptions;
},
// 所属组织级联配置:checkStrictly 允许选中任意层级,不必选到末级 // 所属组织级联配置:checkStrictly 允许选中任意层级,不必选到末级
organizationCascaderProps() { organizationCascaderProps() {
return { return {
@@ -61,7 +78,7 @@ export default {
? this.excludeExternalOrganization(source) ? this.excludeExternalOrganization(source)
: source; : source;
this.organizationTreeOptions = this.normalizeOrgTree(tree); this.organizationTreeOptions = this.normalizeOrgTree(tree);
this.organizationTreeFlatOptions = this.flattenOrgTree(this.organizationTreeOptions); this.organizationTreeFlatOptions = this.flattenOrgTree(this.organizationSearchTreeOptions);
const option = this.tableOption || this.option; const option = this.tableOption || this.option;
if (!option) return; if (!option) return;
ORG_PROPS.forEach(prop => { ORG_PROPS.forEach(prop => {
@@ -100,12 +117,20 @@ export default {
}; };
}); });
}, },
// 哨兵节点的 id 与 rawLabel 都取哨兵值,resolveOrgName 取到的仍是哨兵而不是某个真实组织名
unassignedOrgOption() {
return { id: UNASSIGNED_ORG_FILTER, label: '未分配组织', rawLabel: UNASSIGNED_ORG_FILTER };
},
flattenOrgTree(tree = []) { flattenOrgTree(tree = []) {
return (tree || []).flatMap(item => [ return (tree || []).flatMap(item => [
{ id: String(item.id), label: item.label, rawLabel: item.rawLabel }, { id: String(item.id), label: item.label, rawLabel: item.rawLabel },
...this.flattenOrgTree(item.children || []), ...this.flattenOrgTree(item.children || []),
]); ]);
}, },
// 组织列渲染用:空串或纯空白即「未分配组织」(组织列 NOT NULL,无组织存空串)
isBlankOrganizationName(value) {
return String(value ?? '').trim() === '';
},
// 兼容两种入参:级联路径数组(取末级)或单个部门 id // 兼容两种入参:级联路径数组(取末级)或单个部门 id
findOrgById(id) { findOrgById(id) {
const value = Array.isArray(id) ? id.at(-1) : id; const value = Array.isArray(id) ? id.at(-1) : id;
@@ -155,7 +180,7 @@ export default {
// 级联单选:modelValue 必须是 id 路径数组,否则控件内部状态会错乱导致无法再次选中 // 级联单选:modelValue 必须是 id 路径数组,否则控件内部状态会错乱导致无法再次选中
if (scope.row) scope.row[prop] = Array.isArray(value) ? value : []; if (scope.row) scope.row[prop] = Array.isArray(value) ? value : [];
}, },
options: this.organizationTreeOptions, options: this.organizationSearchTreeOptions,
props: { props: {
label: 'label', label: 'label',
value: 'id', value: 'id',
+1
View File
@@ -38,6 +38,7 @@ export const createOption = () => ({
delBtn: false, delBtn: false,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 320,
menuFixed: 'right', menuFixed: 'right',
column: [ column: [
{ {
+1
View File
@@ -42,6 +42,7 @@ export const waybillStatusOptions = [
{ label: '草稿', value: 'draft' }, { label: '草稿', value: 'draft' },
{ label: '待执行', value: 'pending' }, { label: '待执行', value: 'pending' },
{ label: '进行中', value: 'running' }, { label: '进行中', value: 'running' },
{ label: '待回单确认', value: 'pending_return_confirm' },
{ label: '已完成', value: 'completed' }, { label: '已完成', value: 'completed' },
{ label: '已取消', value: 'cancelled' }, { label: '已取消', value: 'cancelled' },
]; ];
+221
View File
@@ -0,0 +1,221 @@
import {
approvalStatusOptions,
contractCategoryOptions,
createCrudOption,
hcsSyncStatusOptions,
signTypeOptions,
} from './common';
const listDicFormatter = res => {
const data = res?.data || res;
if (Array.isArray(data)) return data;
if (Array.isArray(data?.records)) return data.records;
if (Array.isArray(data?.data)) return data.data;
if (Array.isArray(data?.data?.records)) return data.data.records;
return [];
};
const projectNameDicFormatter = res => {
const list = listDicFormatter(res);
return list.filter(item => item && item.projectName && String(item.projectName).trim());
};
export const changeTypeOptions = [
{ label: '合同信息变更', value: '合同信息变更' },
{ label: '结算规则变更', value: '结算规则变更' },
];
export const config = {
title: '合同变更',
permission: 'contract_change',
statusProp: 'approvalStatus',
statusTextProp: 'approvalStatusName',
deleteStatus: ['draft', 'withdrawn', 'rejected'],
editStatus: ['draft', 'withdrawn', 'rejected'],
viewStatus: ['approved'],
operations: [
{
action: 'submitApproval',
label: '提交',
statusProp: 'approvalStatus',
status: ['draft'],
permission: 'contract_change_submit',
},
{
action: 'withdraw',
label: '撤回',
statusProp: 'approvalStatus',
status: ['reviewing'],
permission: 'contract_change_withdraw',
},
{
action: 'mkAudit',
label: '流程',
statusProp: 'approvalStatus',
status: ['reviewing', 'rejected', 'approved'],
permission: 'contract_change_approve',
mkOnly: true,
},
{
action: 'approve',
label: '通过',
type: 'success',
statusProp: 'approvalStatus',
status: ['reviewing'],
permission: 'contract_change_approve',
localOnly: true,
},
{
action: 'reject',
label: '驳回',
type: 'warning',
statusProp: 'approvalStatus',
status: ['reviewing'],
permission: 'contract_change_reject',
localOnly: true,
},
],
};
export const option = createCrudOption([
{
label: '合同编号',
prop: 'contractNo',
search: true,
searchPlaceholder: '请输入',
searchOrder: 20,
minWidth: 160,
},
{
label: '合同名称',
prop: 'contractName',
search: true,
searchPlaceholder: '请输入',
searchOrder: 30,
minWidth: 180,
},
{
label: '所属项目',
prop: 'projectName',
search: true,
searchType: 'select',
searchPlaceholder: '请选择',
searchOrder: 60,
dicUrl: '/blade-transport/project-apply/list?current=1&size=9999&contractSelectable=true',
dicFormatter: projectNameDicFormatter,
props: {
label: 'projectName',
value: 'projectName',
},
filterable: true,
minWidth: 160,
},
{
label: '所属组织',
prop: 'organizationName',
search: true,
searchPlaceholder: '请输入',
searchOrder: 10,
minWidth: 150,
},
{
label: '合同类别',
prop: 'contractCategory',
type: 'select',
search: true,
searchPlaceholder: '请选择',
searchOrder: 70,
dicData: contractCategoryOptions,
minWidth: 130,
},
{
label: '签约类型',
prop: 'signType',
type: 'select',
search: true,
searchPlaceholder: '请选择',
searchOrder: 50,
dicData: signTypeOptions,
minWidth: 120,
},
{
label: '甲方',
prop: 'partyA',
minWidth: 160,
},
{
label: '乙方',
prop: 'partyB',
minWidth: 160,
},
{
label: '开始日期',
prop: 'startDate',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
minWidth: 130,
},
{
label: '结束日期',
prop: 'endDate',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
minWidth: 130,
},
{
label: '临时效力起',
prop: 'temporaryStartDate',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
minWidth: 130,
},
{
label: '临时效力止',
prop: 'temporaryEndDate',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
minWidth: 130,
},
{
label: '经办人',
prop: 'handlerUserName',
minWidth: 120,
},
{
label: '创建时间',
prop: 'createTime',
type: 'datetime',
minWidth: 170,
},
{
label: '审核状态',
prop: 'approvalStatus',
type: 'select',
search: true,
searchPlaceholder: '请选择',
searchOrder: 40,
dicData: approvalStatusOptions,
minWidth: 110,
},
{
label: '当前节点',
prop: 'currentNode',
minWidth: 120,
},
{
label: '当前处理人',
prop: 'currentProcessor',
minWidth: 120,
},
{
label: '同步状态',
prop: 'syncStatus',
type: 'select',
dicData: hcsSyncStatusOptions,
minWidth: 110,
},
]);
+42 -16
View File
@@ -65,10 +65,12 @@ export const config = {
['contractName', '合同名称'], ['contractName', '合同名称'],
['projectName', '所属项目'], ['projectName', '所属项目'],
['organizationName', '合同负责部门'], ['organizationName', '合同负责部门'],
['platformCompanyName', '平台公司'],
['contractCategory', '合同类别'], ['contractCategory', '合同类别'],
['signType', '签约类型'], ['signType', '签约类型'],
['partyA', '甲方'], ['partyA', '甲方'],
['partyB', '乙方'], ['partyB', '乙方'],
['partyC', '丙方'],
['handlerUserName', '经办人'], ['handlerUserName', '经办人'],
['signDate', '签订日期'], ['signDate', '签订日期'],
['applyDate', '申请日期'], ['applyDate', '申请日期'],
@@ -247,6 +249,15 @@ export const option = createCrudOption([
hide: true, hide: true,
labelWidth: 0, labelWidth: 0,
}, },
{
label: '客商',
prop: 'partyName',
search: true,
searchOrder: 11,
searchPlaceholder: '请输入',
hide: true,
display: false,
},
{ {
label: '合同编号', label: '合同编号',
prop: 'contractNo', prop: 'contractNo',
@@ -254,8 +265,8 @@ export const option = createCrudOption([
searchOrder: 6, searchOrder: 6,
searchPlaceholder: '请输入', searchPlaceholder: '请输入',
order: 185, order: 185,
disabled: true, maxlength: 100,
placeholder: '不填写,则系统自动生成', placeholder: '不填写则按部门规则自动生成',
minWidth: 200, minWidth: 200,
}, },
{ {
@@ -264,7 +275,7 @@ export const option = createCrudOption([
align: 'left', align: 'left',
headerAlign: 'left', headerAlign: 'left',
search: true, search: true,
searchOrder: 7, searchOrder: 1,
searchPlaceholder: '请输入', searchPlaceholder: '请输入',
order: 180, order: 180,
slot: true, slot: true,
@@ -311,6 +322,13 @@ export const option = createCrudOption([
minWidth: 150, minWidth: 150,
rules: selectRule('合同负责部门'), rules: selectRule('合同负责部门'),
}, },
{
label: '平台公司',
prop: 'platformCompanyName',
minWidth: 180,
order: 115,
display: false,
},
{ {
label: '合同类别', label: '合同类别',
prop: 'contractCategory', prop: 'contractCategory',
@@ -337,7 +355,7 @@ export const option = createCrudOption([
rules: selectRule('签约类型'), rules: selectRule('签约类型'),
}, },
{ {
label: '生效类型', label: '合同状态',
prop: 'effectiveType', prop: 'effectiveType',
type: 'select', type: 'select',
search: true, search: true,
@@ -350,24 +368,23 @@ export const option = createCrudOption([
{ {
label: '甲方', label: '甲方',
prop: 'partyA', prop: 'partyA',
type: 'select',
formslot: true,
disabled: true,
order: 160,
filterable: true,
minWidth: 160, minWidth: 160,
rules: selectRule('甲方'), addDisplay: false,
editDisplay: false,
}, },
{ {
label: '乙方', label: '乙方',
prop: 'partyB', prop: 'partyB',
type: 'select',
formslot: true,
disabled: true,
order: 150,
filterable: true,
minWidth: 160, minWidth: 160,
rules: selectRule('乙方'), addDisplay: false,
editDisplay: false,
},
{
label: '丙方',
prop: 'partyC',
minWidth: 160,
addDisplay: false,
editDisplay: false,
}, },
{ {
label: '合同期限', label: '合同期限',
@@ -621,6 +638,15 @@ export const option = createCrudOption([
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{
label: '客商风险',
prop: 'customerRisk',
slot: true,
order: 189,
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
{ {
label: '金蝶合同ID', label: '金蝶合同ID',
prop: 'kingdeeId', prop: 'kingdeeId',
+2 -1
View File
@@ -99,7 +99,8 @@ export const config = {
action: 'startChange', action: 'startChange',
label: '变更', label: '变更',
statusProp: 'approvalStatus', statusProp: 'approvalStatus',
status: ['approved'], // 立项通过、变更通过后均可再次发起变更
status: ['approved', 'change_approved'],
permission: 'project_apply_change', permission: 'project_apply_change',
}, },
], ],
+161
View File
@@ -0,0 +1,161 @@
import {
approvalStatusOptions,
createCrudOption,
hcsSyncStatusOptions,
projectTypeOptions,
} from './common';
export const changeTypeOptions = [
{ label: '项目变更', value: '项目变更' },
{ label: '项目备案调整', value: '项目备案调整' },
];
export const config = {
title: '项目变更',
permission: 'project_change',
statusProp: 'approvalStatus',
statusTextProp: 'approvalStatusName',
deleteStatus: ['draft', 'withdrawn', 'rejected'],
editStatus: ['draft', 'withdrawn', 'rejected'],
viewStatus: ['approved', 'rejected', 'withdrawn'],
operations: [
{
action: 'submitApproval',
label: '提交',
statusProp: 'approvalStatus',
status: ['draft'],
permission: 'project_change_submit',
},
{
action: 'withdraw',
label: '撤回',
statusProp: 'approvalStatus',
status: ['reviewing'],
permission: 'project_change_withdraw',
},
{
action: 'mkAudit',
label: '流程',
statusProp: 'approvalStatus',
status: ['reviewing'],
permission: 'project_change_approve',
mkOnly: true,
},
{
action: 'approve',
label: '通过',
type: 'success',
statusProp: 'approvalStatus',
status: ['reviewing'],
permission: 'project_change_approve',
localOnly: true,
},
{
action: 'reject',
label: '驳回',
type: 'warning',
statusProp: 'approvalStatus',
status: ['reviewing'],
permission: 'project_change_reject',
localOnly: true,
},
],
};
export const option = createCrudOption([
{
label: '项目编号',
prop: 'projectCode',
search: true,
searchPlaceholder: '请输入',
searchOrder: 70,
minWidth: 160,
},
{
label: '项目名称',
prop: 'projectName',
search: true,
searchPlaceholder: '请输入',
searchOrder: 60,
minWidth: 180,
},
{
label: '变更类型',
prop: 'changeType',
type: 'select',
search: true,
searchPlaceholder: '请选择',
searchOrder: 10,
dicData: changeTypeOptions,
minWidth: 130,
},
{
label: '项目类型',
prop: 'projectType',
type: 'select',
search: true,
searchPlaceholder: '请选择',
searchOrder: 20,
dicData: projectTypeOptions,
minWidth: 110,
},
{
label: '客户名称',
prop: 'customerNames',
minWidth: 160,
},
{
label: '下游承运商',
prop: 'carrierNames',
minWidth: 160,
},
{
label: '承办部门',
prop: 'undertakeDeptName',
search: true,
searchPlaceholder: '请输入',
searchOrder: 50,
minWidth: 140,
},
{
label: '项目负责人',
prop: 'principalUserName',
search: true,
searchPlaceholder: '请输入',
searchOrder: 40,
minWidth: 120,
},
{
label: '审批状态',
prop: 'approvalStatus',
type: 'select',
search: true,
searchPlaceholder: '请选择',
searchOrder: 30,
dicData: approvalStatusOptions,
minWidth: 110,
},
{
label: '当前节点',
prop: 'currentNode',
minWidth: 120,
},
{
label: '当前处理人',
prop: 'currentProcessor',
minWidth: 120,
},
{
label: '审核通过时间',
prop: 'approvedTime',
type: 'datetime',
minWidth: 170,
},
{
label: '同步状态',
prop: 'syncStatus',
type: 'select',
dicData: hcsSyncStatusOptions,
minWidth: 110,
},
]);
+4 -2
View File
@@ -471,7 +471,7 @@ export const config = {
endDateRange: ['endDateStart', 'endDateEnd'], endDateRange: ['endDateStart', 'endDateEnd'],
createTimeRange: ['createTimeStart', 'createTimeEnd'], createTimeRange: ['createTimeStart', 'createTimeEnd'],
}, },
actions: ['copy', 'cancel', 'reassign', 'complete', 'batchComplete'], actions: ['copy', 'cancel', 'reassign', 'complete', 'batchComplete', 'confirmReturn'],
detailButton: true, detailButton: true,
statusProp: 'businessStatus', statusProp: 'businessStatus',
statusTextProp: 'businessStatusName', statusTextProp: 'businessStatusName',
@@ -483,6 +483,8 @@ export const config = {
row.requireAccept === true || row.requireAccept === true ||
row.requireAccept === 1 || row.requireAccept === 1 ||
requiresDriverAcceptConfirmation(row); requiresDriverAcceptConfirmation(row);
// 待回单确认由末位打卡产生,司机必然已接单
if (status === 'pending_return_confirm') return '待回单确认';
// 需司机接单且尚未接单 → 待执行(含库中仍为 running 的历史数据展示校正) // 需司机接单且尚未接单 → 待执行(含库中仍为 running 的历史数据展示校正)
if (needAccept && !hasDriverAcceptRecord(row) && !terminalStatuses.includes(status)) { if (needAccept && !hasDriverAcceptRecord(row) && !terminalStatuses.includes(status)) {
return '待执行'; return '待执行';
@@ -504,7 +506,7 @@ export const config = {
return !isDriverRejectedWaybill(row); return !isDriverRejectedWaybill(row);
}, },
canCancel(row) { canCancel(row) {
// 进行中的运单不允许取消 // 进行中的运单不允许取消;待回单确认仍可取消(与页面 canCancel 一致)
return !isInProgressStatus(row, row.businessStatusName); return !isInProgressStatus(row, row.businessStatusName);
}, },
deleteStatus: ['draft'], deleteStatus: ['draft'],
+1 -1
View File
@@ -12,7 +12,7 @@ export const invoiceReceiptTableColumns = [
{ prop: 'createUserName', label: '创建人', minWidth: 110 }, { prop: 'createUserName', label: '创建人', minWidth: 110 },
{ prop: 'createTime', label: '创建时间', minWidth: 170 }, { prop: 'createTime', label: '创建时间', minWidth: 170 },
{ prop: 'kingdeeReceivedInvoiceNo', label: '金蝶单据号', minWidth: 150 }, { prop: 'kingdeeReceivedInvoiceNo', label: '金蝶单据号', minWidth: 150 },
{ prop: 'kingdeePushStatusName', label: '金蝶状态', minWidth: 130 }, { prop: 'kingdeePushStatusName', label: '金蝶状态', minWidth: 130, kingdeeStatus: true },
{ prop: 'kingdeePushMsg', label: '失败原因', minWidth: 160 }, { prop: 'kingdeePushMsg', label: '失败原因', minWidth: 160 },
{ prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 }, { prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 },
]; ];
+24 -14
View File
@@ -52,7 +52,8 @@ export const paymentApplicationOption = ({
searchShow: true, searchShow: true,
searchIcon: true, searchIcon: true,
searchLabelWidth: 88, searchLabelWidth: 88,
searchMenuSpan: 24, // 查询/重置紧跟首行 4 个条件(4×5 + 菜单 4 = 24),不再独占下一整行
searchMenuSpan: 4,
searchIndex: 4, searchIndex: 4,
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
@@ -78,6 +79,7 @@ export const paymentApplicationOption = ({
prop: 'paymentNo', prop: 'paymentNo',
slot: true, slot: true,
search: true, search: true,
searchSpan: 5,
searchOrder: 9, searchOrder: 9,
searchPlaceholder: '请输入', searchPlaceholder: '请输入',
minWidth: 150, minWidth: 150,
@@ -88,6 +90,7 @@ export const paymentApplicationOption = ({
type: 'daterange', type: 'daterange',
searchType: 'daterange', searchType: 'daterange',
search: true, search: true,
searchSpan: 5,
searchOrder: 8, searchOrder: 8,
hide: true, hide: true,
showColumn: false, showColumn: false,
@@ -95,18 +98,24 @@ export const paymentApplicationOption = ({
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
}, },
searchOnlyColumn({ {
label: '客户名称', ...searchOnlyColumn({
prop: 'payeeNameSearch', label: '客户名称',
searchOrder: 7, prop: 'payeeNameSearch',
searchProp: 'payeeName', searchOrder: 7,
}), searchProp: 'payeeName',
searchOnlyColumn({ }),
label: '项目名称', searchSpan: 5,
prop: 'projectNameSearch', },
searchOrder: 6, {
searchProp: 'projectName', ...searchOnlyColumn({
}), label: '项目名称',
prop: 'projectNameSearch',
searchOrder: 6,
searchProp: 'projectName',
}),
searchSpan: 5,
},
searchOnlyColumn({ searchOnlyColumn({
label: '关联结算单', label: '关联结算单',
prop: 'settlementNo', prop: 'settlementNo',
@@ -160,7 +169,8 @@ export const paymentApplicationOption = ({
{ label: '当前节点', prop: 'currentNode', minWidth: 120, formatter: textFormatter }, { label: '当前节点', prop: 'currentNode', minWidth: 120, formatter: textFormatter },
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 120, formatter: textFormatter }, { label: '当前处理人', prop: 'currentProcessor', minWidth: 120, formatter: textFormatter },
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 130, formatter: textFormatter }, { label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 130, formatter: textFormatter },
{ label: '金蝶单据状态', prop: 'kingdeeStatusName', minWidth: 130, formatter: textFormatter }, { label: '金蝶单据状态', prop: 'kingdeeStatusName', slot: true, minWidth: 130 },
{ label: '已指定应付', prop: 'kingdeeAssignStatus', slot: true, minWidth: 120 },
{ label: '金蝶锁状态', prop: 'kingdeeLockStatus', slot: true, minWidth: 150 }, { label: '金蝶锁状态', prop: 'kingdeeLockStatus', slot: true, minWidth: 150 },
{ label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100, formatter: textFormatter }, { label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100, formatter: textFormatter },
], ],
+2 -2
View File
@@ -1,7 +1,7 @@
export const receiptClaimRecordTableColumns = [ export const receiptClaimRecordTableColumns = [
{ prop: 'receiptNoticeNo', label: '认领通知单', minWidth: 150, link: true }, { prop: 'receiptNoticeNo', label: '收款单号', minWidth: 150, link: true },
{ prop: 'payerName', label: '付款人', minWidth: 130 }, { prop: 'payerName', label: '付款人', minWidth: 130 },
{ prop: 'receiptAmount', label: '付款金额', minWidth: 130, money: true }, { prop: 'receiptAmount', label: '收款金额', minWidth: 130, money: true },
{ prop: 'counterpartyName', label: '对方户名', minWidth: 150 }, { prop: 'counterpartyName', label: '对方户名', minWidth: 150 },
{ prop: 'counterpartyAccount', label: '对方账号', minWidth: 170 }, { prop: 'counterpartyAccount', label: '对方账号', minWidth: 170 },
{ prop: 'counterpartyBank', label: '对方开户行', minWidth: 150 }, { prop: 'counterpartyBank', label: '对方开户行', minWidth: 150 },
+12 -7
View File
@@ -1,18 +1,23 @@
export const receiptFlowTableColumns = [ export const receiptFlowTableColumns = [
{ prop: 'receiptNoticeNo', label: '认领通知单', minWidth: 150, link: true }, { prop: 'receiptNoticeNo', label: '收款单号', minWidth: 150, link: true },
{ prop: 'payerName', label: '付款人', minWidth: 130 }, { prop: 'payerName', label: '付款人', minWidth: 130 },
{ prop: 'receiptAmount', label: '付款金额', minWidth: 130, money: true }, { prop: 'receiptAmount', label: '收款金额', minWidth: 130, money: true },
{ prop: 'counterpartyName', label: '对方户名', minWidth: 150 }, { prop: 'counterpartyName', label: '对方户名', minWidth: 150 },
{ prop: 'counterpartyAccount', label: '对方账号', minWidth: 170 }, { prop: 'counterpartyAccount', label: '对方账号', minWidth: 170 },
{ prop: 'counterpartyBank', label: '对方开户行', minWidth: 150 }, { prop: 'counterpartyBank', label: '对方开户行', minWidth: 150 },
{ prop: 'payeeName', label: '收款人(公司)', minWidth: 150 },
//{ prop: 'orgNumber', label: '收款公司编码', minWidth: 130 },
//{ prop: 'openOrgNumber', label: '核算组织编码', minWidth: 130 },
{ prop: 'summary', label: '摘要', minWidth: 160 }, { prop: 'summary', label: '摘要', minWidth: 160 },
{ prop: 'transactionTime', label: '交易时间', minWidth: 170 }, { prop: 'transactionTime', label: '交易时间', minWidth: 170 },
{ prop: 'claimStatusName', label: '认领状态', minWidth: 110, status: true }, { prop: 'claimStatusName', label: '认领状态', minWidth: 110, status: true },
{ prop: 'kingdeeClaimStatusName', label: '公告状态', minWidth: 110 },
{ prop: 'claimedAmount', label: '已认领金额', minWidth: 130, money: true }, { prop: 'claimedAmount', label: '已认领金额', minWidth: 130, money: true },
{ prop: 'unclaimedAmount', label: '金蝶未认领金额', minWidth: 140, money: true }, { prop: 'remainingAmount', label: '剩余可认领金额', minWidth: 140, money: true },
{ prop: 'pushStatusName', label: '推送状态', minWidth: 110, status: true }, { prop: 'unmatchAmountRec', label: '未匹配金额', minWidth: 140, money: true },
{ prop: 'pushFailReason', label: '推送失败原因', minWidth: 160 }, //{ prop: 'matchAmountRec', label: '已匹配收款金额', minWidth: 140, money: true },
{ prop: 'kingdeeBillNo', label: '金蝶收款单号', minWidth: 150 }, //{ prop: 'matchAmountPay', label: '已匹配付款金额', minWidth: 140, money: true },
//{ prop: 'unmatchAmountPay', label: '未匹配付款金额', minWidth: 140, money: true },
//{ prop: 'settleAmount', label: '已结算金额', minWidth: 130, money: true },
//{ prop: 'unsettleAmount', label: '未结算金额', minWidth: 130, money: true },
{ prop: 'kingdeeBillStatusName', label: '金蝶状态', minWidth: 110 }, { prop: 'kingdeeBillStatusName', label: '金蝶状态', minWidth: 110 },
]; ];
+181
View File
@@ -0,0 +1,181 @@
/**
* 正式结算单列表(avue-crud)配置
* ------------------------------------------------------------------
* 列表页与其它模块统一使用 avue-crud,头部 4 个按钮(刷新 / 列显隐 / 搜索显隐 /
* 显示方式)由 avue-crud 自带,不做二次封装。
*
* 表格列复用 formalSettlementTable.js 的 formalSettlementTableColumns(与弹窗内
* 明细表共用同一份列定义),此处只补充 avue 需要的展示/搜索属性,避免两处维护。
*
* ⚠️ searchOrder 为降序排序,值越大越靠前。
* ⚠️ 仅用于搜索的列必须 hide + showColumn: false,否则会混进「列显隐」抽屉。
* ⚠️ column 数组顺序 = 表格列顺序,搜索专用列统一放在数组最前面。
*/
import {
approvalStatusOptions,
invoiceStatusOptions,
kingdeeSyncStatusOptions,
kingdeeWriteOffStatusOptions,
paymentStatusOptions,
} from './formalSettlementSearch';
import { formalSettlementTableColumns } from './formalSettlementTable';
const textFormatter = (row, value) =>
value === null || value === undefined || value === '' ? '-' : value;
/** 金额统一展示:两位小数 + 币种 */
const moneyFormatter = (row, value) =>
`${Number(value || 0).toFixed(2)} ${row?.currency || 'RMB'}`;
const pickLabel = (options, value) =>
options.find(item => item.value === value)?.label || value || '-';
const searchOnlyColumn = ({ label, prop, searchOrder, type = 'input', dicData }) => {
const column = {
label,
prop,
search: true,
searchOrder,
searchSpan: 5,
hide: true,
showColumn: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
};
if (type === 'select') {
column.type = 'select';
column.searchPlaceholder = '请选择';
column.dicData = dicData || [];
} else if (type === 'daterange') {
column.type = 'daterange';
column.searchType = 'daterange';
column.searchPlaceholder = '请选择';
} else {
column.searchPlaceholder = '请输入';
}
return column;
};
/** 表格列的 avue 增量配置:搜索项、插槽、覆盖宽度等 */
const COLUMN_EXTRA = {
formalSettlementNo: {
slot: true,
search: true,
searchSpan: 5,
searchOrder: 12,
searchPlaceholder: '请输入',
},
payerName: { search: true, searchSpan: 5, searchOrder: 7, searchPlaceholder: '请输入' },
payeeName: { search: true, searchSpan: 5, searchOrder: 6, searchPlaceholder: '请输入' },
projectName: { search: true, searchSpan: 5, searchOrder: 11, searchPlaceholder: '请输入' },
deptName: { search: true, searchslot: true, searchSpan: 5, searchOrder: 10 },
contractNo: { search: true, searchSpan: 5, searchOrder: 9, searchPlaceholder: '请输入' },
invoiceStatusName: { slot: true },
paymentStatusName: { slot: true },
approvalStatusName: { slot: true },
adjustFlag: {
formatter: row => row.adjustStatusName || (row.originFormalSettlementNo ? '调整后单' : '-'),
},
kingdeeSyncStatus: {
slot: true,
search: true,
type: 'select',
dicData: kingdeeSyncStatusOptions,
searchSpan: 5,
searchOrder: 2,
searchPlaceholder: '请选择',
},
kingdeeLockStatus: { slot: true },
kingdeeWriteOffStatus: { slot: true },
kingdeeAssignStatus: { slot: true },
createTime: { width: 180 },
};
// 已指定应付仅付款结算单(先票后款 A302-6)有,收款结算单不展示
const buildTableColumns = settlementType =>
formalSettlementTableColumns
.filter(column => !(settlementType === 'receivable' && column.prop === 'kingdeeAssignStatus'))
.map(column => {
const extra = { ...(COLUMN_EXTRA[column.prop] || {}) };
if (column.money) {
extra.formatter = moneyFormatter;
extra.align = 'right';
}
// link / status 标记的列统一走单元格插槽
if (column.link || column.status) extra.slot = true;
// 插槽列不挂兜底 formatter:avue 中 formatter 会覆盖单元格插槽渲染
if (!extra.slot && !extra.formatter) extra.formatter = textFormatter;
return { ...column, ...extra };
});
export const formalSettlementOption = (settlementType = '') => ({
height: 'auto',
calcHeight: 32,
tip: false,
searchBtnText: '查询',
emptyBtnText: '重置',
searchShow: true,
searchIcon: true,
searchLabelWidth: 100,
// 查询/重置紧跟首行 4 个条件(4×5 + 菜单 4 = 24),不再独占下一整行
searchMenuSpan: 4,
searchIndex: 4,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
indexWidth: 64,
indexFixed: 'left',
selection: true,
selectionWidth: 52,
selectionFixed: 'left',
addBtn: false,
viewBtn: false,
delBtn: false,
editBtn: false,
menuWidth: 320,
menuFixed: 'right',
column: [
// ---------- 仅搜索列 ----------
searchOnlyColumn({
label: '预付结算单号',
prop: 'preSettlementNo',
searchOrder: 8,
}),
searchOnlyColumn({
label: '发票状态',
prop: 'invoiceStatus',
searchOrder: 5,
type: 'select',
dicData: invoiceStatusOptions,
}),
searchOnlyColumn({
label: '收付款状态',
prop: 'paymentStatus',
searchOrder: 4,
type: 'select',
dicData: paymentStatusOptions,
}),
searchOnlyColumn({
label: '审核状态',
prop: 'approvalStatus',
searchOrder: 3,
type: 'select',
dicData: approvalStatusOptions,
}),
searchOnlyColumn({
label: '生成日期',
prop: 'createDateRange',
searchOrder: 1,
type: 'daterange',
}),
// ---------- 表格列(部分兼搜索项) ----------
...buildTableColumns(settlementType),
],
});
export const kingdeeSyncName = value => pickLabel(kingdeeSyncStatusOptions, value);
export const kingdeeWriteOffName = value => pickLabel(kingdeeWriteOffStatusOptions, value);
export const paymentStatusName = value => pickLabel(paymentStatusOptions, value);
@@ -8,6 +8,8 @@ export const formalSettlementFormFields = [
{ label: '收款方', prop: 'payeeName', readonly: true }, { label: '收款方', prop: 'payeeName', readonly: true },
{ label: '结算类型', prop: 'settlementTypeName', readonly: true }, { label: '结算类型', prop: 'settlementTypeName', readonly: true },
{ label: '结算金额', prop: 'settlementAmount', readonly: true, money: true }, { label: '结算金额', prop: 'settlementAmount', readonly: true, money: true },
{ label: '业务模式', prop: 'businessModel', type: 'select', optionsKey: 'businessModelOptions' },
{ label: '运输方式', prop: 'transportType', type: 'select', optionsKey: 'transportTypeOptions' },
{ label: '汇率日期', prop: 'exchangeRateDate', type: 'date', required: true }, { label: '汇率日期', prop: 'exchangeRateDate', type: 'date', required: true },
{ label: '结算汇率', prop: 'exchangeRate', type: 'number', required: true }, { label: '结算汇率', prop: 'exchangeRate', type: 'number', required: true },
{ label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true }, { label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true },
@@ -25,6 +27,8 @@ export const createFormalSettlementForm = () => ({
settlementType: '', settlementType: '',
sourcePreSettlementIds: [], sourcePreSettlementIds: [],
sourceDetailIds: [], sourceDetailIds: [],
businessModel: '',
transportType: '',
exchangeRateDate: '', exchangeRateDate: '',
exchangeRate: 1, exchangeRate: 1,
createUserName: '', createUserName: '',
@@ -34,9 +34,14 @@ export const kingdeeSyncStatusOptions = [
{ label: '同步失败', value: 'failed' }, { label: '同步失败', value: 'failed' },
]; ];
export const kingdeeWriteOffStatusOptions = [
{ label: '未冲销', value: 'unwrittenoff' },
{ label: '已冲销', value: 'writtenoff' },
];
export const formalSettlementSearchFields = [ export const formalSettlementSearchFields = [
{ label: '结算单号', prop: 'formalSettlementNo', type: 'input' }, { label: '结算单号', prop: 'formalSettlementNo', type: 'input' },
{ label: '预结算单号', prop: 'preSettlementNo', type: 'input' }, { label: '预付结算单号', prop: 'preSettlementNo', type: 'input' },
{ label: '项目', prop: 'projectName', type: 'input' }, { label: '项目', prop: 'projectName', type: 'input' },
{ label: '所属组织', prop: 'deptName', type: 'cascader' }, { label: '所属组织', prop: 'deptName', type: 'cascader' },
{ label: '合同编号', prop: 'contractNo', type: 'input' }, { label: '合同编号', prop: 'contractNo', type: 'input' },
+13 -6
View File
@@ -8,6 +8,8 @@ export const formalSettlementTableColumns = [
{ label: '合同编号', prop: 'contractNo', minWidth: 150 }, { label: '合同编号', prop: 'contractNo', minWidth: 150 },
{ label: '合同名称', prop: 'contractName', minWidth: 170 }, { label: '合同名称', prop: 'contractName', minWidth: 170 },
{ label: '原币结算金额', prop: 'settlementAmount', minWidth: 145, money: true }, { label: '原币结算金额', prop: 'settlementAmount', minWidth: 145, money: true },
{ label: '业务模式', prop: 'businessModelName', minWidth: 120 },
{ label: '运输方式', prop: 'transportTypeName', minWidth: 120 },
{ label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 155, money: true }, { label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 155, money: true },
{ label: '结算汇率', prop: 'exchangeRate', minWidth: 110 }, { label: '结算汇率', prop: 'exchangeRate', minWidth: 110 },
{ label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', minWidth: 185, money: true }, { label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', minWidth: 185, money: true },
@@ -19,24 +21,28 @@ export const formalSettlementTableColumns = [
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true }, { label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
{ label: '当前节点', prop: 'currentNode', minWidth: 130 }, { label: '当前节点', prop: 'currentNode', minWidth: 130 },
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 }, { label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
{ label: '调整标识', prop: 'adjustFlag', minWidth: 110 },
{ label: '来源原单号', prop: 'originFormalSettlementNo', minWidth: 170 },
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 }, { label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
{ label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 }, { label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 },
{ label: '金蝶冲销状态', prop: 'kingdeeWriteOffStatus', minWidth: 135 },
{ label: '金蝶锁状态', prop: 'kingdeeLockStatus', minWidth: 150 }, { label: '金蝶锁状态', prop: 'kingdeeLockStatus', minWidth: 150 },
{ label: '已指定应付', prop: 'kingdeeAssignStatus', minWidth: 120 },
{ label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100 }, { label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100 },
{ label: '创建人', prop: 'createUserName', minWidth: 110 }, { label: '创建人', prop: 'createUserName', minWidth: 110 },
{ label: '创建时间', prop: 'createTime', minWidth: 170 }, { label: '创建时间', prop: 'createTime', minWidth: 170 },
]; ];
export const sourceColumns = [ export const sourceColumns = [
{ label: '预结算单号', prop: 'preSettlementNo', minWidth: 160 }, { label: '预付结算单号', prop: 'preSettlementNo', minWidth: 160 },
{ label: '结算金额', prop: 'settlementAmount', minWidth: 130, money: true }, { label: '结算金额', prop: 'settlementAmount', minWidth: 130, money: true },
{ label: '申请预付金额', prop: 'advanceAppliedAmount', minWidth: 140, money: true }, { label: '申请预付金额', prop: 'advanceAppliedAmount', minWidth: 140, money: true },
{ label: '已付款金额', prop: 'advancePaidAmount', minWidth: 130, money: true }, { label: '已付款金额', prop: 'advancePaidAmount', minWidth: 130, money: true },
]; ];
export const detailColumns = [ export const detailColumns = [
{ label: '单据号', prop: 'documentNo', minWidth: 180 }, { label: '单据号', prop: 'documentNo', minWidth: 180, link: true },
{ label: '运单号', prop: 'waybillNo', minWidth: 160 }, { label: '运单号', prop: 'waybillNo', minWidth: 160, link: true },
{ label: '车号', prop: 'vehicleNo', minWidth: 130 }, { label: '车号', prop: 'vehicleNo', minWidth: 130 },
{ label: '发货地址', prop: 'departureAddress', minWidth: 180 }, { label: '发货地址', prop: 'departureAddress', minWidth: 180 },
{ label: '到货地址', prop: 'arrivalAddress', minWidth: 180 }, { label: '到货地址', prop: 'arrivalAddress', minWidth: 180 },
@@ -48,11 +54,11 @@ export const detailColumns = [
{ label: '运输总量', prop: 'transportQuantity', minWidth: 120 }, { label: '运输总量', prop: 'transportQuantity', minWidth: 120 },
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 }, { label: '里程(KM)', prop: 'mileage', minWidth: 120 },
{ label: '批次号', prop: 'batchNo', minWidth: 120 }, { label: '批次号', prop: 'batchNo', minWidth: 120 },
{ label: '计费方式', prop: 'billingType', minWidth: 130 },
{ label: '运输单价', prop: 'unitPrice', minWidth: 120, money: true }, { label: '运输单价', prop: 'unitPrice', minWidth: 120, money: true },
{ label: '运费', prop: 'freightAmount', minWidth: 120, money: true }, { label: '运费', prop: 'freightAmount', minWidth: 120, money: true },
{ label: '调整金额', prop: 'adjustAmount', minWidth: 120, money: true }, { label: '调整金额', prop: 'adjustAmount', minWidth: 120, money: true },
{ label: '结算金额(含税)', prop: 'settlementAmountTax', minWidth: 155, money: true }, { label: '结算金额(含税)', prop: 'settlementAmountTax', minWidth: 155, money: true },
{ label: '结算金额(不含税)', prop: 'settlementAmountNoTax', minWidth: 170, money: true },
{ label: '备注', prop: 'remark', minWidth: 180 }, { label: '备注', prop: 'remark', minWidth: 180 },
]; ];
@@ -66,7 +72,7 @@ export const summaryColumns = [
]; ];
export const candidateColumns = [ export const candidateColumns = [
{ label: '预结算单号', prop: 'preSettlementNo', minWidth: 170 }, { label: '预付结算单号', prop: 'preSettlementNo', minWidth: 170 },
{ label: '项目名称', prop: 'projectName', minWidth: 140 }, { label: '项目名称', prop: 'projectName', minWidth: 140 },
{ label: '所属组织', prop: 'deptName', minWidth: 140 }, { label: '所属组织', prop: 'deptName', minWidth: 140 },
{ label: '合同编号', prop: 'contractNo', minWidth: 150 }, { label: '合同编号', prop: 'contractNo', minWidth: 150 },
@@ -86,7 +92,7 @@ export const candidateDetailColumns = [
{ label: '合同编号', prop: 'contractNo', minWidth: 150 }, { label: '合同编号', prop: 'contractNo', minWidth: 150 },
{ label: '合同名称', prop: 'contractName', minWidth: 170 }, { label: '合同名称', prop: 'contractName', minWidth: 170 },
{ label: '来源', prop: 'sourceType', minWidth: 110 }, { label: '来源', prop: 'sourceType', minWidth: 110 },
{ label: '预结算单号', prop: 'preSettlementNo', minWidth: 160 }, { label: '预付结算单号', prop: 'preSettlementNo', minWidth: 160 },
{ label: '正式结算单号', prop: 'formalSettlementNo', minWidth: 160 }, { label: '正式结算单号', prop: 'formalSettlementNo', minWidth: 160 },
{ label: '运单号', prop: 'waybillNo', minWidth: 150 }, { label: '运单号', prop: 'waybillNo', minWidth: 150 },
{ label: '车号', prop: 'vehicleNo', minWidth: 130 }, { label: '车号', prop: 'vehicleNo', minWidth: 130 },
@@ -94,6 +100,7 @@ export const candidateDetailColumns = [
{ label: '货物名称', prop: 'cargoName', minWidth: 140 }, { label: '货物名称', prop: 'cargoName', minWidth: 140 },
{ label: '货物类型', prop: 'cargoType', minWidth: 130 }, { label: '货物类型', prop: 'cargoType', minWidth: 130 },
{ label: '批次号', prop: 'batchNo', minWidth: 120 }, { label: '批次号', prop: 'batchNo', minWidth: 120 },
{ label: '计费方式', prop: 'billingType', minWidth: 130 },
{ label: '应收应付合计', prop: 'totalAmount', minWidth: 140, money: true }, { label: '应收应付合计', prop: 'totalAmount', minWidth: 140, money: true },
{ label: '状态', prop: 'settlementStatusName', minWidth: 110 }, { label: '状态', prop: 'settlementStatusName', minWidth: 110 },
]; ];
+21 -3
View File
@@ -4,14 +4,28 @@ export const settlementTypeOptions = [
]; ];
export const preSettlementFormFields = [ export const preSettlementFormFields = [
{ label: '预结算单号', prop: 'preSettlementNo', readonly: true }, { label: '预付结算单号', prop: 'preSettlementNo', readonly: true },
{ label: '合同名称', prop: 'contractId', type: 'contract', required: true }, { label: '合同名称', prop: 'contractId', type: 'contract', required: true },
{ label: '合同编号', prop: 'contractNo', readonly: true }, { label: '合同编号', prop: 'contractNo', readonly: true },
{ label: '项目名称', prop: 'projectName', readonly: true }, { label: '项目名称', prop: 'projectName', readonly: true },
{ label: '收款方', prop: 'payeeName', readonly: true }, {
{ label: '付款方', prop: 'payerName', readonly: true }, label: '收款方',
prop: 'payeeName',
type: 'select',
optionsKey: 'payeeOptions',
required: true,
},
{
label: '付款方',
prop: 'payerName',
type: 'select',
optionsKey: 'payerOptions',
required: true,
},
{ label: '结算类型', prop: 'settlementType', readonly: true }, { label: '结算类型', prop: 'settlementType', readonly: true },
{ label: '结算金额', prop: 'settlementAmount', readonly: true, money: true }, { label: '结算金额', prop: 'settlementAmount', readonly: true, money: true },
{ label: '业务模式', prop: 'businessModel', type: 'select', optionsKey: 'businessModelOptions' },
{ label: '运输方式', prop: 'transportType', type: 'select', optionsKey: 'transportTypeOptions' },
{ label: '汇率日期', prop: 'exchangeRateDate', type: 'date' }, { label: '汇率日期', prop: 'exchangeRateDate', type: 'date' },
{ label: '结算汇率', prop: 'exchangeRate', type: 'number', class: 'exchange-rate-input' }, { label: '结算汇率', prop: 'exchangeRate', type: 'number', class: 'exchange-rate-input' },
{ label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true }, { label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true },
@@ -22,6 +36,8 @@ export const preSettlementFormFields = [
export const preSettlementFormRules = { export const preSettlementFormRules = {
contractId: [{ required: true, message: '请选择合同名称', trigger: 'change' }], contractId: [{ required: true, message: '请选择合同名称', trigger: 'change' }],
payeeName: [{ required: true, message: '请选择收款方', trigger: 'change' }],
payerName: [{ required: true, message: '请选择付款方', trigger: 'change' }],
exchangeRateDate: [ exchangeRateDate: [
{ {
validator: (_rule, value, callback, source) => { validator: (_rule, value, callback, source) => {
@@ -64,6 +80,8 @@ export const emptyPreSettlementForm = () => ({
settlementType: 'payable', settlementType: 'payable',
currency: 'RMB', currency: 'RMB',
settlementAmount: 0, settlementAmount: 0,
businessModel: '',
transportType: '',
localCurrency: 'RMB', localCurrency: 'RMB',
localSettlementAmount: 0, localSettlementAmount: 0,
exchangeRateDate: '', exchangeRateDate: '',
+1 -1
View File
@@ -7,7 +7,7 @@ export const approvalStatusOptions = [
]; ];
export const preSettlementSearchFields = [ export const preSettlementSearchFields = [
{ label: '预结算单号', prop: 'preSettlementNo', type: 'input' }, { label: '预付结算单号', prop: 'preSettlementNo', type: 'input' },
{ label: '预付单号', prop: 'advanceNo', type: 'input' }, { label: '预付单号', prop: 'advanceNo', type: 'input' },
{ label: '项目名称', prop: 'projectName', type: 'input' }, { label: '项目名称', prop: 'projectName', type: 'input' },
{ label: '所属组织', prop: 'deptName', type: 'cascader' }, { label: '所属组织', prop: 'deptName', type: 'cascader' },
+6 -2
View File
@@ -1,5 +1,5 @@
export const preSettlementTableColumns = [ export const preSettlementTableColumns = [
{ label: '预结算单号', prop: 'preSettlementNo', minWidth: 150, link: true, fixed: 'left' }, { label: '预付结算单号', prop: 'preSettlementNo', minWidth: 150, link: true, fixed: 'left' },
{ label: '来源', prop: 'sourceType', minWidth: 100 }, { label: '来源', prop: 'sourceType', minWidth: 100 },
{ label: '付款方', prop: 'payerName', minWidth: 160 }, { label: '付款方', prop: 'payerName', minWidth: 160 },
{ label: '收款方', prop: 'payeeName', minWidth: 160 }, { label: '收款方', prop: 'payeeName', minWidth: 160 },
@@ -8,6 +8,8 @@ export const preSettlementTableColumns = [
{ label: '合同编号', prop: 'contractNo', minWidth: 140 }, { label: '合同编号', prop: 'contractNo', minWidth: 140 },
{ label: '合同名称', prop: 'contractName', minWidth: 160 }, { label: '合同名称', prop: 'contractName', minWidth: 160 },
{ label: '原币结算金额', prop: 'settlementAmount', minWidth: 140, money: true, precision: 2 }, { label: '原币结算金额', prop: 'settlementAmount', minWidth: 140, money: true, precision: 2 },
{ label: '业务模式', prop: 'businessModelName', minWidth: 120 },
{ label: '运输方式', prop: 'transportTypeName', minWidth: 120 },
{ label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 150, money: true, precision: 2 }, { label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 150, money: true, precision: 2 },
{ label: '结算汇率', prop: 'exchangeRate', minWidth: 110, precision: 2 }, { label: '结算汇率', prop: 'exchangeRate', minWidth: 110, precision: 2 },
{ {
@@ -40,7 +42,7 @@ export const settlementSummaryColumns = [
export const settlementDetailColumns = [ export const settlementDetailColumns = [
{ label: '单据号', prop: 'documentNo', minWidth: 180, link: true }, { label: '单据号', prop: 'documentNo', minWidth: 180, link: true },
{ label: '运单号', prop: 'waybillNo', minWidth: 160 }, { label: '运单号', prop: 'waybillNo', minWidth: 160, link: true },
{ label: '车号', prop: 'vehicleNo', minWidth: 130 }, { label: '车号', prop: 'vehicleNo', minWidth: 130 },
{ label: '发货地址', prop: 'departureAddress', minWidth: 180 }, { label: '发货地址', prop: 'departureAddress', minWidth: 180 },
{ label: '到货地址', prop: 'arrivalAddress', minWidth: 180 }, { label: '到货地址', prop: 'arrivalAddress', minWidth: 180 },
@@ -52,6 +54,7 @@ export const settlementDetailColumns = [
{ label: '运输总量', prop: 'transportQuantityText', minWidth: 120 }, { label: '运输总量', prop: 'transportQuantityText', minWidth: 120 },
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 }, { label: '里程(KM)', prop: 'mileage', minWidth: 120 },
{ label: '批次号', prop: 'batchNo', minWidth: 130 }, { label: '批次号', prop: 'batchNo', minWidth: 130 },
{ label: '计费方式', prop: 'billingType', minWidth: 130 },
{ label: '运输单价', prop: 'unitPrice', minWidth: 120, money: true }, { label: '运输单价', prop: 'unitPrice', minWidth: 120, money: true },
{ label: '运费', prop: 'freightAmount', minWidth: 120, money: true }, { label: '运费', prop: 'freightAmount', minWidth: 120, money: true },
{ label: '原金额', prop: 'originalAmount', minWidth: 120, money: true }, { label: '原金额', prop: 'originalAmount', minWidth: 120, money: true },
@@ -94,5 +97,6 @@ export const candidateDetailColumns = [
{ label: '货物名称', prop: 'cargoName', minWidth: 140 }, { label: '货物名称', prop: 'cargoName', minWidth: 140 },
{ label: '货物类型', prop: 'cargoType', minWidth: 130 }, { label: '货物类型', prop: 'cargoType', minWidth: 130 },
{ label: '批次号', prop: 'batchNo', minWidth: 120 }, { label: '批次号', prop: 'batchNo', minWidth: 120 },
{ label: '计费方式', prop: 'billingType', minWidth: 130 },
{ label: '结算金额', prop: 'totalAmount', minWidth: 130, money: true }, { label: '结算金额', prop: 'totalAmount', minWidth: 130, money: true },
]; ];
@@ -20,7 +20,7 @@ export const searchFields = [
{ label: '货物名称', prop: 'cargoName', type: 'input' }, { label: '货物名称', prop: 'cargoName', type: 'input' },
{ label: '合同编号', prop: 'contractNo', type: 'input' }, { label: '合同编号', prop: 'contractNo', type: 'input' },
{ label: '合同名称', prop: 'contractName', type: 'input' }, { label: '合同名称', prop: 'contractName', type: 'input' },
{ label: '预结算单号', prop: 'preSettlementNo', type: 'input' }, { label: '预付结算单号', prop: 'preSettlementNo', type: 'input' },
{ label: '正式结算单号', prop: 'formalSettlementNo', type: 'input' }, { label: '正式结算单号', prop: 'formalSettlementNo', type: 'input' },
{ label: '批次号', prop: 'batchNo', type: 'input' }, { label: '批次号', prop: 'batchNo', type: 'input' },
{ label: '车号', prop: 'vehicleNo', type: 'input' }, { label: '车号', prop: 'vehicleNo', type: 'input' },
@@ -35,7 +35,7 @@ export const tableColumns = [
{ label: '合同编号', prop: 'contractNo', minWidth: 140 }, { label: '合同编号', prop: 'contractNo', minWidth: 140 },
{ label: '合同名称', prop: 'contractName', minWidth: 140 }, { label: '合同名称', prop: 'contractName', minWidth: 140 },
{ label: '来源', prop: 'sourceType', minWidth: 110 }, { label: '来源', prop: 'sourceType', minWidth: 110 },
{ label: '预结算单号', prop: 'preSettlementNo', minWidth: 140, link: true }, { label: '预付结算单号', prop: 'preSettlementNo', minWidth: 140, link: true },
{ label: '正式结算单号', prop: 'formalSettlementNo', minWidth: 140, link: true }, { label: '正式结算单号', prop: 'formalSettlementNo', minWidth: 140, link: true },
{ label: '运单号', prop: 'waybillNo', minWidth: 140, link: true }, { label: '运单号', prop: 'waybillNo', minWidth: 140, link: true },
{ label: '车号', prop: 'vehicleNo', minWidth: 120 }, { label: '车号', prop: 'vehicleNo', minWidth: 120 },
@@ -1,6 +1,6 @@
export const settlementAdjustmentTableColumns = [ export const settlementAdjustmentTableColumns = [
{ label: '单据号', prop: 'adjustmentNo', minWidth: 160, link: true, fixed: 'left' }, { label: '单据号', prop: 'adjustmentNo', minWidth: 160, link: true, fixed: 'left' },
{ label: '结算单号', prop: 'formalSettlementNo', minWidth: 160 }, { label: '结算单号', prop: 'formalSettlementNo', minWidth: 160, link: true },
{ label: '结算单类型', prop: 'settlementTypeName', minWidth: 110 }, { label: '结算单类型', prop: 'settlementTypeName', minWidth: 110 },
{ label: '项目名称', prop: 'projectName', minWidth: 140 }, { label: '项目名称', prop: 'projectName', minWidth: 140 },
{ label: '所属组织', prop: 'deptName', minWidth: 140 }, { label: '所属组织', prop: 'deptName', minWidth: 140 },
@@ -9,6 +9,8 @@ export const settlementAdjustmentTableColumns = [
{ label: '合同名称', prop: 'contractName', minWidth: 170 }, { label: '合同名称', prop: 'contractName', minWidth: 170 },
{ label: '调整金额', prop: 'adjustmentAmount', minWidth: 130, money: true }, { label: '调整金额', prop: 'adjustmentAmount', minWidth: 130, money: true },
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true }, { label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
{ label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 130, kingdee: true },
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
{ label: '当前节点', prop: 'currentNode', minWidth: 120 }, { label: '当前节点', prop: 'currentNode', minWidth: 120 },
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 }, { label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
{ label: '创建人', prop: 'createUserName', minWidth: 110 }, { label: '创建人', prop: 'createUserName', minWidth: 110 },
+157
View File
@@ -0,0 +1,157 @@
/**
* 导入结算单列表(avue-crud)配置
* ------------------------------------------------------------------
* 列表页与其它模块统一使用 avue-crud,头部 4 个按钮(刷新 / 列显隐 / 搜索显隐 /
* 显示方式)由 avue-crud 自带,不做二次封装。
*
* ⚠️ searchOrder 为降序排序,值越大越靠前。
* ⚠️ 仅用于搜索的列必须 hide + showColumn: false,否则会混进「列显隐」抽屉。
*/
const IMPORT_STATUS_OPTIONS = [
{ label: '草稿', value: 'draft' },
{ label: '已完成', value: 'completed' },
{ label: '已作废', value: 'voided' },
];
const SETTLEMENT_TYPE_OPTIONS = [
{ label: '应收', value: 'receivable' },
{ label: '应付', value: 'payable' },
];
const textFormatter = (row, value) =>
value === null || value === undefined || value === '' ? '-' : value;
const pickLabel = (options, value) =>
options.find(item => item.value === value)?.label || value || '-';
const settlementTypeLabel = value => pickLabel(SETTLEMENT_TYPE_OPTIONS, value);
const billTypeLabel = value => (value === 'pre' ? '预结算单' : value ? '正式结算单' : '-');
export const importStatusLabel = value => pickLabel(IMPORT_STATUS_OPTIONS, value);
export const settlementImportOption = () => ({
height: 'auto',
calcHeight: 32,
tip: false,
searchBtnText: '查询',
emptyBtnText: '重置',
searchShow: true,
searchIcon: true,
searchLabelWidth: 88,
// 查询/重置紧跟首行 4 个条件(4×5 + 菜单 4 = 24),不再独占下一整行
searchMenuSpan: 4,
searchIndex: 4,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
indexWidth: 64,
indexFixed: 'left',
selection: false,
addBtn: false,
viewBtn: false,
delBtn: false,
editBtn: false,
menuWidth: 220,
menuFixed: 'right',
column: [
// ---------- 搜索项(前 4 项默认展示,其余折叠;searchOrder 越大越靠前) ----------
{
label: '状态',
prop: 'importStatus',
type: 'select',
search: true,
searchSpan: 5,
searchOrder: 6,
searchPlaceholder: '请选择',
dicData: IMPORT_STATUS_OPTIONS,
hide: true,
showColumn: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '导入类型',
prop: 'settlementType',
type: 'select',
search: true,
searchSpan: 5,
searchOrder: 5,
searchPlaceholder: '请选择',
dicData: SETTLEMENT_TYPE_OPTIONS,
hide: true,
showColumn: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
// ---------- 表格列 ----------
{
label: '导入批次号',
prop: 'batchNo',
slot: true,
search: true,
searchSpan: 5,
searchOrder: 7,
searchPlaceholder: '请选择',
minWidth: 160,
},
{ label: '状态', prop: 'importStatusName', slot: true, width: 100, align: 'center' },
{
label: '导入类型',
prop: 'settlementTypeName',
minWidth: 100,
align: 'center',
formatter: (row, value) => value || settlementTypeLabel(row.settlementType),
},
{
label: '结算单',
prop: 'settlementBillTypeName',
minWidth: 110,
align: 'center',
formatter: (row, value) => value || billTypeLabel(row.settlementBillType),
},
{
label: '项目',
prop: 'projectName',
search: true,
searchSpan: 5,
searchOrder: 4,
searchPlaceholder: '请输入',
minWidth: 140,
formatter: textFormatter,
},
{
label: '客户',
prop: 'customerName',
search: true,
searchSpan: 5,
searchOrder: 3,
searchPlaceholder: '请输入',
minWidth: 140,
formatter: textFormatter,
},
{
label: '承运商',
prop: 'carrierName',
search: true,
searchSpan: 5,
searchOrder: 2,
searchPlaceholder: '请输入',
minWidth: 140,
formatter: textFormatter,
},
{
label: '明细行数',
prop: 'detailCount',
width: 100,
align: 'center',
formatter: row => row.detailCount ?? row.waybillCount ?? 0,
},
{ label: '创建时间', prop: 'createTime', width: 180, align: 'center', formatter: textFormatter },
{ label: '创建人', prop: 'createUserName', minWidth: 110, formatter: textFormatter },
],
});
@@ -13,7 +13,7 @@ export const reconciliationStatusOptions = [
export const transportReconciliationSearchFields = [ export const transportReconciliationSearchFields = [
{ prop: 'reconciliationNo', label: '对账单号', type: 'input' }, { prop: 'reconciliationNo', label: '对账单号', type: 'input' },
{ prop: 'preSettlementNos', label: '预结算单号', type: 'input' }, { prop: 'preSettlementNos', label: '预付结算单号', type: 'input' },
{ prop: 'projectName', label: '项目', type: 'input' }, { prop: 'projectName', label: '项目', type: 'input' },
{ prop: 'deptName', label: '所属组织', type: 'cascader' }, { prop: 'deptName', label: '所属组织', type: 'cascader' },
{ prop: 'contractNo', label: '合同编号', type: 'input' }, { prop: 'contractNo', label: '合同编号', type: 'input' },
+62 -2
View File
@@ -14,6 +14,11 @@ const validatePhone = (rule, value, callback) => {
callback(new Error('请输入手机号')); callback(new Error('请输入手机号'));
return; return;
} }
// 详情回显为脱敏手机号(含 *)时放行,未修改则提交时剔除该字段
if (phone.includes('*')) {
callback();
return;
}
if (!/^1\d{10}$/.test(phone)) { if (!/^1\d{10}$/.test(phone)) {
callback(new Error('请输入正确的手机号')); callback(new Error('请输入正确的手机号'));
return; return;
@@ -121,6 +126,62 @@ export default {
order: 60, order: 60,
rules: [{ required: true, message: '请选择小程序身份', trigger: 'change' }], rules: [{ required: true, message: '请选择小程序身份', trigger: 'change' }],
}, },
{
label: '所属组织',
prop: 'deptId',
formslot: true,
hide: true,
span: 24,
order: 55,
// 所属组织可为空:为空表示未分配组织,账号的数据权限为空集
},
{
label: '所属组织',
prop: 'deptName',
minWidth: 180,
overHidden: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
order: 54,
},
{
label: '数据权限范围',
prop: 'dataScopeRange',
type: 'radio',
span: 24,
hide: true,
order: 53,
value: 1,
dicData: [
{ label: '仅所属组织', value: 1 },
{ label: '全部组织', value: 2 },
{ label: '自定义', value: 3 },
],
rules: [{ required: true, message: '请选择数据权限范围', trigger: 'change' }],
},
{
label: '数据层级范围',
prop: 'dataLevelRange',
type: 'radio',
span: 24,
hide: true,
order: 52,
value: 1,
dicData: [
{ label: '包含下级', value: 1 },
{ label: '仅本级', value: 2 },
],
rules: [{ required: true, message: '请选择数据层级范围', trigger: 'change' }],
},
{
label: '数据权限',
prop: 'dataScopeDeptIds',
formslot: true,
span: 24,
hide: true,
order: 51,
},
{ {
label: '性别', label: '性别',
prop: 'sex', prop: 'sex',
@@ -142,10 +203,9 @@ export default {
overHidden: true, overHidden: true,
hide: true, hide: true,
addDisplay: false, addDisplay: false,
editDisplay: true, editDisplay: false,
viewDisplay: true, viewDisplay: true,
order: 40, order: 40,
placeholder: '可清空以解绑微信',
}, },
{ {
label: '状态', label: '状态',
+1 -23
View File
@@ -72,6 +72,7 @@ export const userOption = safe => {
}, },
{ label: '岗位', prop: 'postName', overHidden: true, display: false }, { label: '岗位', prop: 'postName', overHidden: true, display: false },
{ label: '性别', prop: 'sexName', width: 80, display: false }, { label: '性别', prop: 'sexName', width: 80, display: false },
{ label: 'OA登录名', prop: 'oaLoginId', minWidth: 140, overHidden: true, display: false },
{ label: 'OA人员id', prop: 'oaPersonId', minWidth: 120, overHidden: true, display: false }, { label: 'OA人员id', prop: 'oaPersonId', minWidth: 120, overHidden: true, display: false },
{ label: '备注', prop: 'remark', minWidth: 160, overHidden: true, display: false }, { label: '备注', prop: 'remark', minWidth: 160, overHidden: true, display: false },
{ {
@@ -314,29 +315,6 @@ export const userOption = safe => {
{ label: '承运商', value: 2 }, { label: '承运商', value: 2 },
], ],
}, },
{
label: '数据权限范围',
prop: 'dataScopeRange',
type: 'radio',
span: 24,
value: 1,
dicData: [
{ label: '仅所属组织', value: 1 },
{ label: '全部组织', value: 2 },
{ label: '自定义', value: 3 },
],
},
{
label: '数据层级范围',
prop: 'dataLevelRange',
type: 'radio',
span: 24,
value: 1,
dicData: [
{ label: '包含下级', value: 1 },
{ label: '仅本级', value: 2 },
],
},
{ {
label: '备注', label: '备注',
prop: 'remark', prop: 'remark',
@@ -48,6 +48,7 @@ export const option = {
searchOrder: 1, searchOrder: 1,
searchslot: true, searchslot: true,
slot: true, slot: true,
overHidden: true,
minWidth: 150, minWidth: 150,
}, },
{ {
@@ -55,6 +56,8 @@ export const option = {
prop: 'useDepartment', prop: 'useDepartment',
search: true, search: true,
searchOrder: 3, searchOrder: 3,
slot: true,
overHidden: true,
minWidth: 140, minWidth: 140,
}, },
{ {
+24 -2
View File
@@ -95,6 +95,17 @@ export default [
bizType: 'project-apply', bizType: 'project-apply',
}, },
}, },
{
path: '/business/project-change/public-view',
name: '查看项目变更信息',
component: () => import('@/views/business/project-apply-public-view.vue'),
meta: {
keepAlive: false,
isTab: false,
isAuth: false,
bizType: 'project-change',
},
},
{ {
path: '/business/contract-manage/public-view', path: '/business/contract-manage/public-view',
name: '查看合同信息', name: '查看合同信息',
@@ -106,9 +117,20 @@ export default [
bizType: 'contract-manage', bizType: 'contract-manage',
}, },
}, },
{
path: '/business/contract-change/public-view',
name: '查看合同变更信息',
component: () => import('@/views/business/contract-change-public-view.vue'),
meta: {
keepAlive: false,
isTab: false,
isAuth: false,
bizType: 'contract-change',
},
},
{ {
path: '/settlement/pre-settlement/public-view', path: '/settlement/pre-settlement/public-view',
name: '查看预结算信息', name: '查看预付结算单信息',
component: () => import('@/views/settlement/pre-settlement-public-view.vue'), component: () => import('@/views/settlement/pre-settlement-public-view.vue'),
meta: { meta: {
keepAlive: false, keepAlive: false,
@@ -119,7 +141,7 @@ export default [
}, },
{ {
path: '/settlement/formal-settlement/public-view', path: '/settlement/formal-settlement/public-view',
name: '查看正式结算信息', name: '查看结算单信息',
component: () => import('@/views/settlement/formal-settlement-public-view.vue'), component: () => import('@/views/settlement/formal-settlement-public-view.vue'),
meta: { meta: {
keepAlive: false, keepAlive: false,
+91 -3
View File
@@ -95,7 +95,7 @@ export default [
children: [ children: [
{ {
path: '', path: '',
name: '新增编辑预结算', name: '新增编辑预付结算单',
meta: { meta: {
keepAlive: false, keepAlive: false,
activeMenu: '/settlement/pre-settlement', activeMenu: '/settlement/pre-settlement',
@@ -104,21 +104,85 @@ export default [
}, },
], ],
}, },
{
// 兼容旧菜单路径:按 detailNo 解析应付/应收后跳转到拆分后的列表页
path: '/settlement/formal-settlement',
component: Layout,
children: [
{
path: '',
name: '正式结算单兼容跳转',
meta: {
keepAlive: false,
activeMenu: '/settlement/payment-settlement',
},
component: loadView(() => import('@/views/settlement/formal-settlement-redirect.vue')),
},
],
},
{ {
path: '/settlement/formal-settlement/form', path: '/settlement/formal-settlement/form',
component: Layout, component: Layout,
children: [ children: [
{ {
path: '', path: '',
name: '新增编辑正式结算', name: '新增编辑结算单',
meta: { meta: {
keepAlive: false, keepAlive: false,
activeMenu: '/settlement/formal-settlement', // 付款结算单表单入口,高亮付款结算单菜单
activeMenu: '/settlement/payment-settlement',
}, },
component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')), component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')),
}, },
], ],
}, },
{
// 收款结算单表单入口:与付款结算单共用表单组件,仅高亮菜单不同
path: '/settlement/receipt-settlement/form',
component: Layout,
children: [
{
path: '',
name: '新增编辑收款结算单',
meta: {
keepAlive: false,
activeMenu: '/settlement/receipt-settlement',
},
component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')),
},
],
},
{
// 付款结算单表单入口:与 settlementListPath('payable') + '/form' 对应
path: '/settlement/payment-settlement/form',
component: Layout,
children: [
{
path: '',
name: '新增编辑付款结算单',
meta: {
keepAlive: false,
activeMenu: '/settlement/payment-settlement',
},
component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')),
},
],
},
{
path: '/settlement/settlement-import/form',
component: Layout,
children: [
{
path: '',
name: '新增导入结算单',
meta: {
keepAlive: false,
activeMenu: '/settlement/settlement-import',
},
component: loadView(() => import('@/views/settlement/settlement-import-form.vue')),
},
],
},
{ {
path: '/settlement/transport-reconciliation/form', path: '/settlement/transport-reconciliation/form',
component: Layout, component: Layout,
@@ -182,6 +246,18 @@ export default [
}, },
], ],
}, },
{
path: '/business/project-change/form',
component: Layout,
children: [
{
path: '',
name: '项目变更表单',
meta: { keepAlive: false, activeMenu: '/business/project-change' },
component: loadView(() => import('@/views/business/project-apply.vue')),
},
],
},
{ {
path: '/business/waybill-manage/form', path: '/business/waybill-manage/form',
component: Layout, component: Layout,
@@ -326,6 +402,18 @@ export default [
}, },
], ],
}, },
{
path: '/business/contract-change/form',
component: Layout,
children: [
{
path: '',
name: '合同变更表单',
meta: { keepAlive: false, activeMenu: '/business/contract-change' },
component: loadView(() => import('@/views/business/contract-manage-change.vue')),
},
],
},
{ {
path: '/wel', path: '/wel',
component: () => component: () =>
+1 -1
View File
@@ -622,7 +622,7 @@
background: #fff; background: #fff;
} }
// 合同管理新增/编辑弹窗(business-crud-page 封装 → avue-crud,弹窗带 avue-crud__dialog class) // 合同管理新增/编辑弹窗(avue-crud,弹窗带 avue-crud__dialog class)
// 复刻 waybill-manage 方案:接近全屏、压过库内置 .avue-dialog 的 calc(100% - 200px), // 复刻 waybill-manage 方案:接近全屏、压过库内置 .avue-dialog 的 calc(100% - 200px),
// 并解开 body 的 200px 上限、抵消全局 footer 的 -30px 上移 // 并解开 body 的 200px 上限、抵消全局 footer 的 -30px 上移
.contract-manage-dialog.el-dialog { .contract-manage-dialog.el-dialog {
+30 -33
View File
@@ -26,6 +26,35 @@ const uniqueOrgNames = (values = []) => {
return result; return result;
}; };
// 候选司机取数上限:超出后只能靠关键字缩小范围
const DRIVER_CANDIDATE_MAX_SIZE = 200;
/**
* 派单候选司机:租户内的司机档案,按岗位等条件过滤。
*
* 与组织、承运商都无关——司机档案与组织树、承运商之间没有外键,`organization_name`
* 只是自由文本,用它做匹配会让小程序自动建档的司机永远进不了候选。
*/
export const fetchDriverCandidates = async (query = {}) => {
const params = { ...query };
const size = Math.min(Math.max(Number(params.size) || 50, 50), DRIVER_CANDIDATE_MAX_SIZE);
delete params.size;
const res = await getDriverList(1, size, params);
return extractRecords(res).slice(0, size);
};
/**
* 司机候选项的展示文本:姓名(手机尾号后 4 位)。
*
* 只用于下拉渲染。写回表单的仍是姓名本身,别把带尾号的文本当值用。
*/
export const driverOptionText = (item = {}) => {
const name = normalizeOrgName(item.driverName || item.name);
const mobile = String(item.mobile || item.phone || item.driverPhone || '').trim();
if (!name) return '';
return mobile.length >= 4 ? `${name}(${mobile.slice(-4)})` : name;
};
/** /**
* 解析承运商客商档案上联系人的所属组织(branchName),无联系人时回退客商所属组织。 * 解析承运商客商档案上联系人的所属组织(branchName),无联系人时回退客商所属组织。
*/ */
@@ -72,13 +101,6 @@ export const resolveCarrierOrganizationNames = async ({ carrierId, carrierName }
return orgNames; return orgNames;
}; };
export const clearCarrierOrganizationCache = (carrier = {}) => {
const id = String(carrier.carrierId || '').trim();
const name = normalizeOrgName(carrier.carrierName);
if (id) orgNameCache.delete(id);
if (name) orgNameCache.delete(name);
};
export const matchOrganizationName = (value, orgNames = []) => { export const matchOrganizationName = (value, orgNames = []) => {
const text = normalizeOrgName(value); const text = normalizeOrgName(value);
if (!text || !orgNames.length) return false; if (!text || !orgNames.length) return false;
@@ -90,34 +112,9 @@ const filterByOrganizations = (records = [], orgNames = []) => {
return records.filter(item => matchOrganizationName(item.organizationName, orgNames)); return records.filter(item => matchOrganizationName(item.organizationName, orgNames));
}; };
/**
* 按承运商联系人所属组织筛选司机。
* 未选择承运商时返回空列表(需先选承运商)。
*/
export const fetchDriversByCarrierOrganizations = async (
query = {},
{ carrierId, carrierName } = {}
) => {
if (!String(carrierId || '').trim() && !normalizeOrgName(carrierName)) {
return [];
}
const orgNames = await resolveCarrierOrganizationNames({ carrierId, carrierName });
if (!orgNames.length) return [];
const size = Math.max(Number(query.size) || 50, 50);
const params = { ...query };
delete params.size;
// 单组织时用后端模糊条件缩小范围;多组织再前端精确过滤
if (orgNames.length === 1) {
params.organizationName = orgNames[0];
}
const res = await getDriverList(1, Math.min(size * 5, 200), params);
return filterByOrganizations(extractRecords(res), orgNames).slice(0, size);
};
/** /**
* 按承运商联系人所属组织筛选车辆。 * 按承运商联系人所属组织筛选车辆。
* 未选择承运商时返回空列表。 * 未选择承运商时返回空列表(需先选承运商)。
*/ */
export const fetchVehiclesByCarrierOrganizations = async ( export const fetchVehiclesByCarrierOrganizations = async (
query = {}, query = {},
+3
View File
@@ -106,6 +106,9 @@ const CERT_SNAPSHOT_GROUPS = {
fields: [ fields: [
{ key: 'realName', label: '真实姓名' }, { key: 'realName', label: '真实姓名' },
{ key: 'idCardNo', label: '身份证号' }, { key: 'idCardNo', label: '身份证号' },
{ key: 'birthday', label: '出生年月', type: TYPE_DATE },
{ key: 'addressRegion', label: '住址' },
{ key: 'address', label: '详细地址' },
{ key: 'idCardFrontUrl', label: '身份证正面照', type: TYPE_IMAGE }, { key: 'idCardFrontUrl', label: '身份证正面照', type: TYPE_IMAGE },
{ key: 'idCardBackUrl', label: '身份证反面照', type: TYPE_IMAGE }, { key: 'idCardBackUrl', label: '身份证反面照', type: TYPE_IMAGE },
{ key: 'idCardValidStart', label: '身份证有效期起', type: TYPE_DATE }, { key: 'idCardValidStart', label: '身份证有效期起', type: TYPE_DATE },
+26 -6
View File
@@ -7,7 +7,7 @@ import 'nprogress/nprogress.css';
const excelPattern = /\.(xls|xlsx)$/; const excelPattern = /\.(xls|xlsx)$/;
/** 是否为浏览器 Blob(含跨 realm 的 Blob-like) */ /** 是否为浏览器 Blob(含跨 realm 的 Blob-like) */
const isBlobLike = value => export const isBlobLike = value =>
value != null && value != null &&
typeof value === 'object' && typeof value === 'object' &&
typeof value.size === 'number' && typeof value.size === 'number' &&
@@ -15,14 +15,14 @@ const isBlobLike = value =>
(typeof value.text === 'function' || typeof value.arrayBuffer === 'function'); (typeof value.text === 'function' || typeof value.arrayBuffer === 'function');
/** axios 拦截器已把 JSON Blob 解析成的业务对象 */ /** axios 拦截器已把 JSON Blob 解析成的业务对象 */
const isBusinessJson = value => export const isBusinessJson = value =>
value != null && value != null &&
typeof value === 'object' && typeof value === 'object' &&
!isBlobLike(value) && !isBlobLike(value) &&
!(value instanceof ArrayBuffer) && !(value instanceof ArrayBuffer) &&
('code' in value || 'success' in value || 'msg' in value); ('code' in value || 'success' in value || 'msg' in value);
const isExcelResponse = res => { export const isExcelResponse = res => {
// 已是业务 JSON(导入成功/失败提示)时,绝不能当失败明细 Excel 处理 // 已是业务 JSON(导入成功/失败提示)时,绝不能当失败明细 Excel 处理
if (isBusinessJson(res?.data)) { if (isBusinessJson(res?.data)) {
return false; return false;
@@ -54,7 +54,7 @@ const isExcelResponse = res => {
* 提前解析成普通对象;此处需同时兼容 Blob / 字符串 / 已解析对象,避免再调 * 提前解析成普通对象;此处需同时兼容 Blob / 字符串 / 已解析对象,避免再调
* `blob.text()` 时出现「blob.text is not a function」。 * `blob.text()` 时出现「blob.text is not a function」。
*/ */
const parseBlobJson = async data => { export const parseBlobJson = async data => {
if (data == null) { if (data == null) {
return {}; return {};
} }
@@ -88,7 +88,7 @@ const parseBlobJson = async data => {
* *
* 这里补上这一步:从错误响应的 Blob 里取出后端 `msg`,取不到再退回通用文案。 * 这里补上这一步:从错误响应的 Blob 里取出后端 `msg`,取不到再退回通用文案。
*/ */
const resolveErrorMessage = async error => { export const resolveImportErrorMessage = async error => {
const payload = error?.response?.data ?? error?.data; const payload = error?.response?.data ?? error?.data;
if (isBusinessJson(payload) && payload.msg) { if (isBusinessJson(payload) && payload.msg) {
return payload.msg; return payload.msg;
@@ -109,6 +109,26 @@ const resolveErrorMessage = async error => {
return fallback; return fallback;
}; };
/**
* 统一消费导入接口响应:成功 JSON / 失败 Excel 流。
* 兼容 axios 已把 JSON Blob 解析成对象的情况,禁止再对普通对象调 .text()。
*/
export const consumeImportResponse = async (res, options = {}) => {
const { businessName = '导入', dayjsFormat } = options;
if (isExcelResponse(res)) {
return {
kind: 'excel',
blob: res?.data,
fileName: `${businessName}导入失败明细${dayjsFormat || ''}.xlsx`,
};
}
const result = isBusinessJson(res?.data) ? res.data : await parseBlobJson(res?.data);
if (Number(result?.code) !== 200) {
throw new Error(result?.msg || '导入失败');
}
return { kind: 'success', result };
};
/** /**
* 加工后端返回的导入失败明细 Excel。 * 加工后端返回的导入失败明细 Excel。
* 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流, * 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流,
@@ -235,7 +255,7 @@ export const handleImportExcel = async (vm, option, column, businessName, onSucc
// 拦截器已提示过的错误(如后端 400 返回的业务提示)不再重复弹一次, // 拦截器已提示过的错误(如后端 400 返回的业务提示)不再重复弹一次,
// 只保留一次清晰的中文提示。 // 只保留一次清晰的中文提示。
if (!error?._notified) { if (!error?._notified) {
vm.$message.error(await resolveErrorMessage(error)); vm.$message.error(await resolveImportErrorMessage(error));
} }
notifyUploadError(option, error); notifyUploadError(option, error);
} finally { } finally {
+47 -20
View File
@@ -21,16 +21,17 @@ export const isKingdeePathAttachment = attachment => {
}; };
/** /**
* 请求金蝶文件流:成功返回 Blob;业务失败为 HTTP 200 + R.fail JSON(响应通道未写流时后端即失败), * 请求金蝶文件流(公共核):成功返回 Blob;业务失败为 HTTP 200 + R.fail JSON
* 按 Content-Type 识别并抛出带 msg 的错误;网络级失败拦截器已提示,标记 silent 避免重复提示。 * (响应通道未写流时后端即失败),按 Content-Type 识别并抛出带 msg 的错误;
* 网络级失败拦截器已提示,标记 silent 避免重复提示。
* 供查看(弹窗内预览)/下载两类入口复用 * 供查看(弹窗内预览)/下载两类入口复用
* @param {String} invoiceNo 发票号码 * @param {Function} fetcher 文件流请求函数(返回 axios blob 响应)
* @returns {Promise<Blob>} * @returns {Promise<Blob>}
*/ */
export const requestKingdeeFile = async invoiceNo => { const fetchKingdeeBlob = async fetcher => {
let res; let res;
try { try {
res = await downloadInvoicePoolAttachment(invoiceNo); res = await fetcher();
} catch (error) { } catch (error) {
throw Object.assign(new Error(error?.message || '附件下载失败'), { silent: true }); throw Object.assign(new Error(error?.message || '附件下载失败'), { silent: true });
} }
@@ -51,6 +52,45 @@ export const requestKingdeeFile = async invoiceNo => {
return blob; return blob;
}; };
/**
* 请求金蝶文件流:票池发票按发票号回源(后端代理 A801-2)
* @param {String} invoiceNo 发票号码
* @returns {Promise<Blob>}
*/
export const requestKingdeeFile = invoiceNo => fetchKingdeeBlob(() => downloadInvoicePoolAttachment(invoiceNo));
/**
* 下载金蝶侧附件(经 A801-2 后端代理,公共入口),失败统一 ElMessage 提示
* @param {Function} fetcher 文件流请求函数
* @param {String} fileName 下载文件名
*/
export const downloadKingdeeFile = async (fetcher, fileName) => {
try {
const blob = await fetchKingdeeBlob(fetcher);
downloadFile(blob, fileName);
} catch (error) {
if (!error?.silent) ElMessage.error(error?.message || '金蝶附件下载失败');
}
};
/**
* 新窗口预览金蝶侧附件(经 A801-2 后端代理,公共入口):拉取 Blob 转 objectURL
* (PDF 可预览;OFD 等浏览器不可预览的格式由浏览器触发下载)
* @param {Function} fetcher 文件流请求函数
* @param {String} fileName 预览文件名
*/
export const viewKingdeeFile = async (fetcher, fileName) => {
try {
const blob = await fetchKingdeeBlob(fetcher);
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
// 延迟释放 objectURL:立即回收可能导致新窗口尚未加载完成即失效
setTimeout(() => URL.revokeObjectURL(url), 60000);
} catch (error) {
if (!error?.silent) ElMessage.error(error?.message || '金蝶附件预览失败');
}
};
/** /**
* 下载金蝶侧发票附件(经 A801-2 后端代理),失败统一 ElMessage 提示 * 下载金蝶侧发票附件(经 A801-2 后端代理),失败统一 ElMessage 提示
* @param {String} invoiceNo 发票号码 * @param {String} invoiceNo 发票号码
@@ -61,12 +101,7 @@ export const downloadKingdeeInvoiceFile = async (invoiceNo, fileName) => {
ElMessage.warning('当前发票缺少发票号码,无法下载金蝶侧附件'); ElMessage.warning('当前发票缺少发票号码,无法下载金蝶侧附件');
return; return;
} }
try { await downloadKingdeeFile(() => downloadInvoicePoolAttachment(invoiceNo), fileName || `${invoiceNo}.pdf`);
const blob = await requestKingdeeFile(invoiceNo);
downloadFile(blob, fileName || `${invoiceNo}.pdf`);
} catch (error) {
if (!error?.silent) ElMessage.error(error?.message || '金蝶附件下载失败');
}
}; };
/** /**
@@ -79,13 +114,5 @@ export const viewKingdeeInvoiceFile = async (invoiceNo, fileName) => {
ElMessage.warning('当前发票缺少发票号码,无法预览金蝶侧附件'); ElMessage.warning('当前发票缺少发票号码,无法预览金蝶侧附件');
return; return;
} }
try { await viewKingdeeFile(() => downloadInvoicePoolAttachment(invoiceNo), fileName || `${invoiceNo}.pdf`);
const blob = await requestKingdeeFile(invoiceNo);
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
// 延迟释放 objectURL:立即回收可能导致新窗口尚未加载完成即失效
setTimeout(() => URL.revokeObjectURL(url), 60000);
} catch (error) {
if (!error?.silent) ElMessage.error(error?.message || '金蝶附件预览失败');
}
}; };
+35
View File
@@ -0,0 +1,35 @@
/**
* 金蝶单据状态公共映射
*/
/** 金蝶单据状态 → el-tag 颜色类型(failed 状态列表页悬浮展示失败原因) */
export const KINGDEE_STATUS_TAG_TYPES = {
synced: 'success',
paid: 'success',
invoiced: 'success',
paying: 'warning',
failed: 'danger',
};
/** 金蝶单据状态 → el-tag 颜色类型,未映射状态返回 info */
export const kingdeeStatusType = status => KINGDEE_STATUS_TAG_TYPES[status] || 'info';
/** 收票登记推送状态 → el-tag 颜色类型(已指定应付=绿、已保存收票单=黄、推送失败=红、未推送=灰) */
export const KINGDEE_PUSH_STATUS_TAG_TYPES = {
unsynced: 'info',
pushed: 'warning',
assigned: 'success',
failed: 'danger',
};
/** 收票登记推送状态 → el-tag 颜色类型,未映射状态返回 info */
export const kingdeePushStatusType = status => KINGDEE_PUSH_STATUS_TAG_TYPES[status] || 'info';
/** 金蝶冲销状态 → el-tag 颜色类型(已冲销=绿、未冲销=黄) */
export const KINGDEE_WRITEOFF_TAG_TYPES = {
writtenoff: 'success',
unwrittenoff: 'warning',
};
/** 金蝶冲销状态 → el-tag 颜色类型,未映射状态(null=未校验)返回 info */
export const kingdeeWriteOffTagType = status => KINGDEE_WRITEOFF_TAG_TYPES[status] || 'info';
+85 -15
View File
@@ -15,15 +15,27 @@ export const MK_BIZ = {
rejected: ['rejected', 'change_rejected', 'withdrawn'], rejected: ['rejected', 'change_rejected', 'withdrawn'],
approved: ['approved', 'change_approved'], approved: ['approved', 'change_approved'],
}, },
'project-change': {
dictName: '提交项目变更审核流',
subjectPrefix: '项目变更审批',
rejected: ['rejected', 'withdrawn'],
approved: ['approved'],
},
'contract-manage': { 'contract-manage': {
dictName: '提交合同审核流', dictName: '提交合同审核流',
subjectPrefix: '合同审批', subjectPrefix: '合同审批',
rejected: ['rejected', 'change_rejected', 'withdrawn'], rejected: ['rejected', 'change_rejected', 'withdrawn'],
approved: ['approved', 'change_approved'], approved: ['approved', 'change_approved'],
}, },
'contract-change': {
dictName: '提交合同变更审核流',
subjectPrefix: '合同变更审批',
rejected: ['rejected', 'withdrawn'],
approved: ['approved'],
},
'pre-settlement': { 'pre-settlement': {
dictName: '提交预结算审核流', dictName: '提交预结算审核流',
subjectPrefix: '预结算审批', subjectPrefix: '预付结算审批',
rejected: ['returned'], rejected: ['returned'],
approved: ['approved'], approved: ['approved'],
}, },
@@ -61,9 +73,18 @@ export const MK_BIZ = {
const MK_SWITCH_NAME = '开启MK'; const MK_SWITCH_NAME = '开启MK';
async function loadMkTemplateList() { /** 进程内缓存,避免每次提交都打字典;登录后同一会话开关一般不变 */
let mkEnabledCache = null;
let mkEnabledPromise = null;
let mkTemplateListCache = null;
async function loadMkTemplateList(force = false) {
if (!force && mkTemplateListCache) {
return mkTemplateListCache;
}
const res = await getDictionary({ code: 'mk_template' }); const res = await getDictionary({ code: 'mk_template' });
return res?.data?.data || []; mkTemplateListCache = res?.data?.data || [];
return mkTemplateListCache;
} }
/** 业务字典 mk_template:名称「开启MK」且键值为 1 时才请求 MK */ /** 业务字典 mk_template:名称「开启MK」且键值为 1 时才请求 MK */
@@ -72,10 +93,37 @@ export function isMkEnabled(list = []) {
return String(matched?.dictKey ?? '').trim() === '1'; return String(matched?.dictKey ?? '').trim() === '1';
} }
/** 加载业务字典并判断是否开启 MK */ /** 清除 MK 开关缓存(字典变更后可调用) */
export async function loadMkEnabled() { export function clearMkEnabledCache() {
const list = await loadMkTemplateList(); mkEnabledCache = null;
return isMkEnabled(list); mkEnabledPromise = null;
mkTemplateListCache = null;
}
/**
* 加载业务字典并判断是否开启 MK
* @param {boolean} [force=false] 是否强制刷新缓存
*/
export async function loadMkEnabled(force = false) {
if (!force && mkEnabledCache !== null) {
return mkEnabledCache;
}
if (!force && mkEnabledPromise) {
return mkEnabledPromise;
}
mkEnabledPromise = loadMkTemplateList(force)
.then(list => {
mkEnabledCache = isMkEnabled(list);
return mkEnabledCache;
})
.catch(error => {
mkEnabledCache = false;
throw error;
})
.finally(() => {
mkEnabledPromise = null;
});
return mkEnabledPromise;
} }
export async function resolveMkTemplateCode(dictName, list) { export async function resolveMkTemplateCode(dictName, list) {
@@ -99,19 +147,24 @@ export function isMkApprovedStatus(bizType, approvalStatus) {
} }
/** /**
* 提交 MK 审核流 * 提交 MK 审核流。未开启 MK 时直接跳过,不请求流程实例相关接口。
* @param {Object} options * @param {Object} options
* @param {string} options.bizType 业务类型 * @param {string} options.bizType 业务类型
* @param {string|number} options.bizId 业务主键(后端据此生成/复用 formInstanceId) * @param {string|number} options.bizId 业务主键
* @param {string} [options.subjectName] 标题后缀 * @param {string} [options.subjectName] 标题后缀
* @param {string} [options.approvalStatus] 提交前的审批状态(须在业务 submit 改状态前取值;通过态强制新建实例) * @param {string} [options.approvalStatus] 提交前的审批状态
* @param {boolean} [options.mkEnabled] 调用方已知开关时传入,false 时立即跳过
* @param {Object} [options.tempVarData] 传给 MK 的临时变量(如项目类型 projectType)
* @returns {Promise<boolean>} true=已发起/复用 MK 流程,false=未开启或已跳过
*/ */
export async function submitMkApprovalFlow({ export async function submitMkApprovalFlow({
bizType, bizType,
bizId, bizId,
subjectName = '', subjectName = '',
approvalStatus = '', approvalStatus = '',
}) { mkEnabled,
tempVarData,
} = {}) {
const conf = MK_BIZ[bizType]; const conf = MK_BIZ[bizType];
if (!conf) { if (!conf) {
return Promise.reject(new Error(`不支持的MK业务类型:${bizType}`)); return Promise.reject(new Error(`不支持的MK业务类型:${bizType}`));
@@ -119,17 +172,29 @@ export async function submitMkApprovalFlow({
if (bizId == null || bizId === '') { if (bizId == null || bizId === '') {
return Promise.reject(new Error('业务id不能为空')); return Promise.reject(new Error('业务id不能为空'));
} }
// 调用方显式关闭,或字典未开启:不请求 hasMkProcessInstance / processSubmit 等 MK 接口
if (mkEnabled === false) {
return false;
}
const enabled = mkEnabled === true ? true : await loadMkEnabled();
if (!enabled) {
return false;
}
const list = await loadMkTemplateList(); const list = await loadMkTemplateList();
if (!isMkEnabled(list)) { if (!isMkEnabled(list)) {
return; mkEnabledCache = false;
return false;
} }
const bizIdText = String(bizId); const bizIdText = String(bizId);
const forceNewFormInstance = isMkApprovedStatus(bizType, approvalStatus); const forceNewFormInstance = isMkApprovedStatus(bizType, approvalStatus);
// 审核通过后再提交必须新建流程;其余场景已有有效实例则跳过 // 审核通过后再提交必须新建流程;其余场景已有有效实例则跳过
if (!forceNewFormInstance) { if (!forceNewFormInstance) {
const existsRes = await hasMkProcessInstance({ bizId: bizIdText }); const existsRes = await hasMkProcessInstance({ bizId: bizIdText });
if (existsRes?.data?.data) { if (existsRes?.data?.data) {
return; return true;
} }
} }
if ((conf.rejected || []).includes(approvalStatus)) { if ((conf.rejected || []).includes(approvalStatus)) {
@@ -139,11 +204,16 @@ export async function submitMkApprovalFlow({
const subject = subjectName const subject = subjectName
? `${conf.subjectPrefix}:${subjectName}` ? `${conf.subjectPrefix}:${subjectName}`
: `${conf.subjectPrefix}:${bizIdText}`; : `${conf.subjectPrefix}:${bizIdText}`;
return processSubmit({ const submitPayload = {
templateCode, templateCode,
bizId: bizIdText, bizId: bizIdText,
subject, subject,
bizType, bizType,
forceNewFormInstance, forceNewFormInstance,
}); };
if (tempVarData != null && typeof tempVarData === 'object') {
submitPayload.tempVarData = tempVarData;
}
await processSubmit(submitPayload);
return true;
} }
+28
View File
@@ -0,0 +1,28 @@
/**
* 客商/合同风险标签公共展示逻辑(黑名单=金蝶黑名单 A801-5 命中,与后端 HcsCreditCheckSupport 口径一致):
* 等级与黑名单组合展示、等级在前;内部组织客商默认无风险
*/
/** 金蝶黑名单命中判定(客商档案行:失信类别/列入事由任一非空) */
export function isKingdeeBlack(category, reason) {
return !!(category || reason);
}
/** 合同行黑名单判定(推送链路按金蝶命中口径落库 kingdeeBlackStatus=yes) */
export function isContractBlack(blackStatus) {
return String(blackStatus || '').toLowerCase() === 'yes';
}
/** 风险标签文案:riskLevel=C101-1 等级(0/1/2);isBlack=金蝶黑名单命中;customerKind=internal 显示"无风险" */
export function resolveRiskTagName(riskLevel, isBlack, customerKind) {
const levelName = { 2: '高风险', 1: '中风险', 0: '低风险' }[riskLevel] || '';
if (isBlack) return levelName ? `${levelName}、黑名单` : '黑名单';
if (levelName) return levelName;
if (customerKind === 'internal') return '无风险';
return '';
}
/** 是否红色风险标签(黑名单命中或高风险) */
export function isRiskyTag(riskLevel, isBlack) {
return isBlack || riskLevel === '2';
}
+32
View File
@@ -1,3 +1,35 @@
/**
* 合并列表搜索条件:新条件覆盖旧条件,本次显式清空的字段从查询中移除。
*/
export const applyIncomingSearch = (current = {}, incoming = {}) => {
const merged = { ...current, ...incoming };
Object.keys(incoming || {}).forEach(key => {
const value = incoming[key];
if (
value === '' ||
value === null ||
value === undefined ||
(Array.isArray(value) && value.length === 0)
) {
delete merged[key];
}
});
return merged;
};
/**
* 启停状态下拉「全部」在数字类型下会被转成 0/-1,不能当作查询条件。
* 状态 0 表示草稿等有效值的页面不要调用。
*/
export const omitInvalidListStatus = (params = {}) => {
const values = { ...params };
const status = values.status;
if (status === 0 || status === '0' || status === -1 || status === '-1') {
delete values.status;
}
return values;
};
export const normalizeSearchRangeParams = (params = {}, rangeMap = {}) => { export const normalizeSearchRangeParams = (params = {}, rangeMap = {}) => {
const values = { ...params }; const values = { ...params };
+47
View File
@@ -0,0 +1,47 @@
export function isFreightFeeName(name) {
const text = String(name || '');
return text.includes('运费') || text.includes('运输费');
}
function parseBillingRules(row = {}) {
const rule = row.billingRule || {};
const nested = Array.isArray(rule.rules) ? rule.rules : [];
if (nested.length) return nested;
const raw = row.billingRulesJson ?? row.billingRules;
if (Array.isArray(raw)) return raw.filter(item => item && typeof item === 'object');
if (typeof raw === 'string' && raw.trim()) {
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.filter(item => item && typeof item === 'object') : [];
} catch (error) {
return [];
}
}
return [];
}
/** 运输单价对应的运费计费类型。 */
export function freightBillingType(row = {}) {
const rule = row.billingRule || {};
const rules = parseBillingRules(row);
const freightRule = rules.find(
item => isFreightFeeName(item?.feeItem) || isFreightFeeName(item?.feeType)
);
return freightRule?.billingType || row.billingType || rule.billingType || '';
}
export function isTierUnitPrice(row) {
return freightBillingType(row) === '阶梯单价';
}
export function isIntervalBilling(row) {
const type = freightBillingType(row);
return type === '区间单价' || (type.includes('区间') && type.includes('一口价'));
}
/** 阶梯单价、区间单价、区间一口价的运输单价不开放手工调整。 */
export function isUnitPriceAdjustable(row) {
const type = freightBillingType(row);
if (!type) return true;
return !isTierUnitPrice(row) && !isIntervalBilling(row);
}
+6
View File
@@ -0,0 +1,6 @@
/** 审批通过、驳回等审批流转类变更内容,不在结算单变更记录中展示。 */
export function isApprovalSettlementChange(content) {
const text = String(content || '').trim();
if (!text) return false;
return text.includes('审批通过') || text.includes('审批驳回') || text === '已驳回';
}
+51
View File
@@ -0,0 +1,51 @@
import { getList as getReceivablePayableDetailList } from '@/api/settlement/receivable-payable-detail';
import { getList as getWaybillList } from '@/api/business/waybill-manage';
// 结算明细的「单据号」是应收应付明细单据号(应收 YS…/应付 YF…),按结算类型落到对应菜单页
const SETTLEMENT_DETAIL_PATHS = {
receivable: '/settlement/receivable-detail',
payable: '/settlement/payable-detail',
};
const unwrapPage = response => {
const body = response?.data || response || {};
return body?.data || body;
};
const firstRecordId = data => (Array.isArray(data) ? data : data?.records || [])[0]?.id || '';
export const settlementDetailPath = settlementType =>
SETTLEMENT_DETAIL_PATHS[settlementType] || SETTLEMENT_DETAIL_PATHS.payable;
/**
* 结算明细行「单据号」对应的应收应付明细ID:已落库的明细自带 sourceDetailId,未落库的行按单据号回查。
*/
export const resolveReceivablePayableDetailId = async (row = {}, settlementType) => {
if (row.sourceDetailId) return row.sourceDetailId;
if (!row.documentNo) return '';
try {
const data = unwrapPage(
await getReceivablePayableDetailList(1, 1, {
documentNo: row.documentNo,
settlementType,
})
);
return firstRecordId(data);
} catch (error) {
return '';
}
};
/**
* 结算明细行「运单号」对应的运单ID:总单来源的明细没有 waybillId(运单号列存的是总单号),按运单号回查。
*/
export const resolveWaybillId = async (row = {}) => {
if (row.waybillId) return row.waybillId;
if (!row.waybillNo) return '';
try {
const data = unwrapPage(await getWaybillList(1, 1, { waybillNo: row.waybillNo }));
return firstRecordId(data);
} catch (error) {
return '';
}
};
+39
View File
@@ -0,0 +1,39 @@
/** 结算菜单拆分后的展示名与权限前缀 */
export const SETTLEMENT_TYPE_PAYABLE = 'payable';
export const SETTLEMENT_TYPE_RECEIVABLE = 'receivable';
export function settlementBillLabel(settlementType) {
return settlementType === SETTLEMENT_TYPE_RECEIVABLE ? '收款结算单' : '付款结算单';
}
export function settlementPermissionPrefix(settlementType) {
return settlementType === SETTLEMENT_TYPE_RECEIVABLE
? 'receipt_settlement'
: 'payment_settlement';
}
export function settlementListPath(settlementType) {
return settlementType === SETTLEMENT_TYPE_RECEIVABLE
? '/settlement/receipt-settlement'
: '/settlement/payment-settlement';
}
/**
* 权限判断:优先新菜单码,兼容旧 formal_settlement_*。
* @param {Object} ctx 组件实例(需含 permission、isAdmin、validData)
* @param {string} settlementType payable|receivable
* @param {string} action add|edit|view...
*/
export function hasSettlementPermission(ctx, settlementType, action) {
if (ctx?.isAdmin) return true;
const prefix = settlementPermissionPrefix(settlementType);
const codes = [`${prefix}_${action}`, `formal_settlement_${action}`];
return codes.some(code => {
const value = ctx?.permission?.[code];
if (typeof ctx?.validData === 'function') {
return ctx.validData(value, false);
}
return value === true;
});
}
+23
View File
@@ -0,0 +1,23 @@
/**
* 结算单预付记录单据状态名(预结算编辑器与正式结算编辑器共用,避免两页状态名不一致)
*/
export function settlementStatusName(status, displayValue) {
const labels = {
reviewing: '审批中',
approved: '审批通过',
paid: '已付款',
returned: '已驳回',
voided: '已作废',
draft: '草稿',
};
const name = labels[status];
if (name) return name;
return typeof displayValue === 'function' ? displayValue(status) : status || '-';
}
/**
* 统一预付付款记录类型名
*/
export function settlementRecordTypeName(recordType) {
return { progress_advance: '进度预付', project_advance: '项目预付核销' }[recordType] || '';
}
+190 -100
View File
@@ -50,7 +50,7 @@
title="角色权限配置" title="角色权限配置"
append-to-body append-to-body
v-model="box" v-model="box"
width="345px" width="960px"
@close="handleDialogClose" @close="handleDialogClose"
> >
<el-tabs type="border-card"> <el-tabs type="border-card">
@@ -100,41 +100,26 @@
align="middle" align="middle"
style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px" style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px"
> >
<span style="display: inline-flex; align-items: center"> <span style="color: #606266; font-size: 13px"
<el-switch >勾选多个菜单后点击「设置」,可批量配置数据权限;设置后菜单旁展示当前范围</span
v-model="dataScopeLinked" >
active-text="节点联动" <el-button type="primary" size="small" @click="openDataScopeSetting">设置</el-button>
size="small"
@change="handleLinkedChange('treeDataScope')"
/>
<el-tooltip content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点" placement="top">
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
</el-tooltip>
</span>
<el-button-group>
<el-button
size="small"
plain
@click="handleSelectAll('treeDataScope', dataScopeGrantList)"
>全选</el-button
>
<el-button
size="small"
plain
@click="handleInvertSelect('treeDataScope', dataScopeGrantList, dataScopeLinked)"
>反选</el-button
>
</el-button-group>
</el-row> </el-row>
<el-tree <el-tree
:data="dataScopeGrantList" :data="roleDataScopeMenuTree"
show-checkbox show-checkbox
:check-strictly="!dataScopeLinked"
node-key="id" node-key="id"
ref="treeDataScope" ref="treeRoleDataScope"
:default-checked-keys="dataScopeTreeObj"
:props="props" :props="props"
> >
<template #default="{ data }">
<span class="role-data-scope-node">
<span>{{ data.title }}</span>
<span v-if="getMenuScopeLabel(data.id)" class="role-data-scope-tag"
>【{{ getMenuScopeLabel(data.id) }}】</span
>
</span>
</template>
</el-tree> </el-tree>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="接口权限"> <el-tab-pane label="接口权限">
@@ -188,6 +173,45 @@
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog
title="数据权限设置"
append-to-body
v-model="dataScopeSettingBox"
width="560px"
class="role-data-scope-setting-dialog"
>
<el-form label-position="right" label-width="auto">
<el-form-item label="角色名称">
<el-input v-model="dataScopeSetting.roleName" disabled />
</el-form-item>
<el-form-item label="数据权限">
<el-radio-group v-model="dataScopeSetting.scopeType">
<el-radio :value="1">所在部门及下级</el-radio>
<el-radio :value="2">全部组织</el-radio>
<el-radio :value="3">本人创建</el-radio>
<el-radio :value="4">自定义组织</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="Number(dataScopeSetting.scopeType) === 4" label="组织">
<el-tree
ref="treeCustomDept"
:data="deptTree"
show-checkbox
node-key="id"
default-expand-all
:props="{ label: 'title', children: 'children' }"
style="width: 100%; max-height: 320px; overflow: auto; border: 1px solid #ebeef5; padding: 8px"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dataScopeSettingBox = false">取消</el-button>
<el-button type="primary" @click="submitDataScopeSetting">保存</el-button>
</span>
</template>
</el-dialog>
</basic-container> </basic-container>
</template> </template>
@@ -196,37 +220,44 @@ import {
add, add,
getList, getList,
getRole, getRole,
getRoleAlias,
getRoleTreeById, getRoleTreeById,
grant, grant,
grantTree, grantTree,
remove, remove,
update, update,
} from '@/api/system/role'; } from '@/api/system/role';
import { getRoleMenuDataScopeTree, submitRoleMenuDataScope } from '@/api/system/roleDataScope';
import { getDeptTree } from '@/api/system/dept';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import website from '@/config/website'; import website from '@/config/website';
import { validatenull } from '@/utils/validate';
export default { export default {
data() { data() {
return { return {
form: {}, form: {},
box: false, box: false,
dataScopeSettingBox: false,
props: { props: {
label: 'title', label: 'title',
value: 'key', value: 'key',
}, },
menuGrantList: [], menuGrantList: [],
dataScopeGrantList: [],
apiScopeGrantList: [], apiScopeGrantList: [],
apiGrantList: [], apiGrantList: [],
menuTreeObj: [], menuTreeObj: [],
dataScopeTreeObj: [],
apiScopeTreeObj: [], apiScopeTreeObj: [],
menuLinked: false, menuLinked: false,
dataScopeLinked: false,
apiScopeLinked: false, apiScopeLinked: false,
currentRoleIds: [], currentRoleIds: [],
currentRoleName: '',
roleDataScopeMenuTree: [],
roleDataScopeMap: {},
deptTree: [],
dataScopeSetting: {
roleName: '',
scopeType: 2,
menuIds: [],
},
selectionList: [], selectionList: [],
query: {}, query: {},
loading: true, loading: true,
@@ -309,32 +340,19 @@ export default {
], ],
}, },
{ {
label: '角色别名', label: '角色标识',
labelTip: '用于权限校验与标注的属性,可手动录入也可点击现有别名快速录入', labelTip: '用于权限校验与标注的属性',
prop: 'roleAlias', prop: 'roleAlias',
search: true, search: true,
span: 24, span: 24,
rules: [ rules: [
{ {
required: true, required: true,
message: '请输入角色别名', message: '请输入角色标识',
trigger: 'blur', trigger: 'blur',
}, },
], ],
}, },
{
label: '现有别名',
labelTip: '当前系统已存在的角色别名,点击可快速录入',
prop: 'currentAlias',
type: 'radio',
dicData: [],
props: {
label: 'roleName',
value: 'roleAlias',
},
hide: true,
span: 24,
},
{ {
label: '角色排序', label: '角色排序',
prop: 'sort', prop: 'sort',
@@ -378,14 +396,6 @@ export default {
return ids; return ids;
}, },
}, },
watch: {
'form.currentAlias'() {
const alias = this.form.currentAlias;
if (!validatenull(alias)) {
this.form.roleAlias = alias;
}
},
},
methods: { methods: {
initData(roleId) { initData(roleId) {
getRoleTreeById(roleId).then(res => { getRoleTreeById(roleId).then(res => {
@@ -452,9 +462,9 @@ export default {
}, },
submit() { submit() {
const menuList = this.$refs.treeMenu.getCheckedKeys(); const menuList = this.$refs.treeMenu.getCheckedKeys();
const dataScopeList = this.$refs.treeDataScope.getCheckedKeys();
const apiScopeList = this.$refs.treeApiScope.getCheckedKeys(); const apiScopeList = this.$refs.treeApiScope.getCheckedKeys();
grant(this.currentRoleIds, menuList, dataScopeList, apiScopeList).then(() => { // 数据权限已改为角色菜单配置;传 null 跳过原生 dataScope 更新,避免误清历史配置
grant(this.currentRoleIds, menuList, null, apiScopeList).then(() => {
this.box = false; this.box = false;
this.$message({ this.$message({
type: 'success', type: 'success',
@@ -465,6 +475,84 @@ export default {
this.onLoad(this.page); this.onLoad(this.page);
}); });
}, },
getMenuScopeLabel(menuId) {
const scope = this.roleDataScopeMap[String(menuId)];
return scope ? scope.scopeTypeName : '';
},
loadRoleDataScopeTree(roleId) {
return getRoleMenuDataScopeTree(roleId).then(res => {
const data = res.data.data || {};
this.currentRoleName = data.roleName || '';
this.roleDataScopeMenuTree = data.menuTree || [];
const scopeMap = {};
(data.scopes || []).forEach(item => {
scopeMap[String(item.menuId)] = item;
});
this.roleDataScopeMap = scopeMap;
});
},
openDataScopeSetting() {
const tree = this.$refs.treeRoleDataScope;
if (!tree) {
return;
}
const menuIds = tree.getCheckedKeys();
if (!menuIds || menuIds.length === 0) {
this.$message.warning('请先勾选要设置的菜单');
return;
}
this.dataScopeSetting = {
roleName: this.currentRoleName,
scopeType: 2,
menuIds,
};
const loadDept =
this.deptTree && this.deptTree.length
? Promise.resolve()
: getDeptTree(this.userInfo.tenantId).then(res => {
this.deptTree = res.data.data || [];
});
loadDept.then(() => {
this.dataScopeSettingBox = true;
this.$nextTick(() => {
if (this.$refs.treeCustomDept) {
this.$refs.treeCustomDept.setCheckedKeys([]);
}
});
});
},
submitDataScopeSetting() {
const roleId = this.currentRoleIds && this.currentRoleIds[0];
if (!roleId) {
this.$message.warning('角色信息异常,请重新打开权限配置');
return;
}
const scopeType = Number(this.dataScopeSetting.scopeType || 2);
let deptIds = [];
if (scopeType === 4) {
deptIds = this.$refs.treeCustomDept ? this.$refs.treeCustomDept.getCheckedKeys() : [];
if (!deptIds.length) {
this.$message.warning('自定义组织时请勾选组织');
return;
}
}
submitRoleMenuDataScope({
roleId,
menuIds: this.dataScopeSetting.menuIds,
scopeType,
deptIds,
}).then(() => {
this.$message.success('数据权限设置成功');
this.dataScopeSettingBox = false;
this.loadRoleDataScopeTree(roleId).then(() => {
this.$nextTick(() => {
if (this.$refs.treeRoleDataScope) {
this.$refs.treeRoleDataScope.setCheckedKeys([]);
}
});
});
});
},
rowSave(row, done, loading) { rowSave(row, done, loading) {
add(this.withTenantId(row)).then( add(this.withTenantId(row)).then(
() => { () => {
@@ -547,10 +635,6 @@ export default {
this.form.parentId = ''; this.form.parentId = '';
} }
} }
getRoleAlias().then(res => {
const column = this.findColumn(this.option.column, 'currentAlias');
column.dicData = res.data.data;
});
done(); done();
}, },
handleRole() { handleRole() {
@@ -563,58 +647,52 @@ export default {
return; return;
} }
this.menuTreeObj = []; this.menuTreeObj = [];
this.dataScopeTreeObj = [];
this.apiScopeTreeObj = []; this.apiScopeTreeObj = [];
// 保存当前要配置权限的角色ID
this.currentRoleIds = this.idsArray; this.currentRoleIds = this.idsArray;
grantTree().then(res => { this.currentRoleName = (this.selectionList[0] && this.selectionList[0].roleName) || '';
this.menuGrantList = res.data.data.menu; const roleId = this.currentRoleIds[0];
this.dataScopeGrantList = res.data.data.dataScope; Promise.all([grantTree(), getRole(this.ids), this.loadRoleDataScopeTree(roleId)]).then(results => {
this.apiScopeGrantList = res.data.data.apiScope; const grantRes = results[0];
getRole(this.ids).then(res => { const roleRes = results[1];
this.menuTreeObj = res.data.data.menu; this.menuGrantList = grantRes.data.data.menu;
this.dataScopeTreeObj = res.data.data.dataScope; this.apiScopeGrantList = grantRes.data.data.apiScope;
this.apiScopeTreeObj = res.data.data.apiScope; this.menuTreeObj = roleRes.data.data.menu;
this.box = true; this.apiScopeTreeObj = roleRes.data.data.apiScope;
this.$nextTick(() => { this.box = true;
this.applyCheckedKeys('treeMenu', this.menuTreeObj); this.$nextTick(() => {
this.applyCheckedKeys('treeDataScope', this.dataScopeTreeObj); this.applyCheckedKeys('treeMenu', this.menuTreeObj);
this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj); this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
});
}); });
}); });
}, },
handleRowRole(row) { handleRowRole(row) {
this.menuTreeObj = []; this.menuTreeObj = [];
this.dataScopeTreeObj = [];
this.apiScopeTreeObj = []; this.apiScopeTreeObj = [];
// 保存当前要配置权限的角色ID(单个行操作)
this.currentRoleIds = [row.id]; this.currentRoleIds = [row.id];
grantTree().then(res => { this.currentRoleName = row.roleName || '';
this.menuGrantList = res.data.data.menu; Promise.all([grantTree(), getRole(row.id), this.loadRoleDataScopeTree(row.id)]).then(results => {
this.dataScopeGrantList = res.data.data.dataScope; const grantRes = results[0];
this.apiScopeGrantList = res.data.data.apiScope; const roleRes = results[1];
getRole(row.id).then(res => { this.menuGrantList = grantRes.data.data.menu;
this.menuTreeObj = res.data.data.menu; this.apiScopeGrantList = grantRes.data.data.apiScope;
this.dataScopeTreeObj = res.data.data.dataScope; this.menuTreeObj = roleRes.data.data.menu;
this.apiScopeTreeObj = res.data.data.apiScope; this.apiScopeTreeObj = roleRes.data.data.apiScope;
this.box = true; this.box = true;
this.$nextTick(() => { this.$nextTick(() => {
this.applyCheckedKeys('treeMenu', this.menuTreeObj); this.applyCheckedKeys('treeMenu', this.menuTreeObj);
this.applyCheckedKeys('treeDataScope', this.dataScopeTreeObj); this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
});
}); });
}); });
}, },
handleDialogClose() { handleDialogClose() {
// 对话框关闭时清空选择状态和权限树数据
this.menuTreeObj = []; this.menuTreeObj = [];
this.dataScopeTreeObj = [];
this.apiScopeTreeObj = []; this.apiScopeTreeObj = [];
this.menuLinked = false; this.menuLinked = false;
this.dataScopeLinked = false;
this.apiScopeLinked = false; this.apiScopeLinked = false;
this.roleDataScopeMenuTree = [];
this.roleDataScopeMap = {};
this.currentRoleName = '';
this.dataScopeSettingBox = false;
this.selectionClear(); this.selectionClear();
}, },
handleDelete() { handleDelete() {
@@ -659,3 +737,15 @@ export default {
}, },
}; };
</script> </script>
<style scoped>
.role-data-scope-node {
display: inline-flex;
align-items: center;
gap: 4px;
}
.role-data-scope-tag {
color: #409eff;
font-size: 12px;
}
</style>
+10 -2
View File
@@ -60,6 +60,13 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-link
type="primary"
v-if="hasPermission('airport_master_view')"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-link>
<el-link <el-link
type="primary" type="primary"
v-if="hasPermission('airport_master_edit')" v-if="hasPermission('airport_master_edit')"
@@ -150,6 +157,7 @@ import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel'; import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit'; import { formatUpdateUserName } from '@/utils/audit';
import { omitInvalidListStatus } from '@/utils/search-range';
import { getToken, isAdministrator } from '@/utils/auth'; import { getToken, isAdministrator } from '@/utils/auth';
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate'; import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
import AddressMapPicker from '@/components/address-map-picker/main.vue'; import AddressMapPicker from '@/components/address-map-picker/main.vue';
@@ -278,7 +286,7 @@ export default {
editBtn: false, editBtn: false,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 180, menuWidth: 220,
column: [ column: [
{ {
label: '编码', label: '编码',
@@ -1182,7 +1190,7 @@ export default {
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
this.loading = true; this.loading = true;
getList(page.currentPage, page.pageSize, { ...params, ...this.query }) getList(page.currentPage, page.pageSize, omitInvalidListStatus({ ...params, ...this.query }))
.then(res => { .then(res => {
const data = res.data.data; const data = res.data.data;
this.page.total = data.total; this.page.total = data.total;
+10 -2
View File
@@ -68,6 +68,13 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-link
type="primary"
v-if="permission.currency_view"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-link>
<el-link <el-link
type="primary" type="primary"
v-if="permission.currency_edit" v-if="permission.currency_edit"
@@ -104,6 +111,7 @@ import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel'; import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit'; import { formatUpdateUserName } from '@/utils/audit';
import { omitInvalidListStatus } from '@/utils/search-range';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
@@ -170,7 +178,7 @@ export default {
editBtn: false, editBtn: false,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 180, menuWidth: 220,
menuFixed: 'right', menuFixed: 'right',
column: [ column: [
{ {
@@ -507,7 +515,7 @@ export default {
this.onLoad(this.page, this.query); this.onLoad(this.page, this.query);
}, },
buildQuery(params = {}) { buildQuery(params = {}) {
const values = { ...params, ...this.query }; const values = omitInvalidListStatus({ ...params, ...this.query });
const rangeMap = { const rangeMap = {
effectiveDateRange: ['effectiveDateStart', 'effectiveDateEnd'], effectiveDateRange: ['effectiveDateStart', 'effectiveDateEnd'],
expiryDateRange: ['expiryDateStart', 'expiryDateEnd'], expiryDateRange: ['expiryDateStart', 'expiryDateEnd'],
+10 -2
View File
@@ -36,6 +36,13 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-link
type="primary"
v-if="permission.customer_type_view"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-link>
<el-link <el-link
type="primary" type="primary"
v-if="permission.customer_type_edit" v-if="permission.customer_type_edit"
@@ -63,6 +70,7 @@
<script> <script>
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/customer-type'; import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/customer-type';
import { omitInvalidListStatus } from '@/utils/search-range';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
export default { export default {
@@ -101,7 +109,7 @@ export default {
editBtn: false, editBtn: false,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 180, menuWidth: 220,
column: [ column: [
{ {
label: '中文名称', label: '中文名称',
@@ -290,7 +298,7 @@ export default {
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
this.loading = true; this.loading = true;
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => { getList(page.currentPage, page.pageSize, omitInvalidListStatus(Object.assign(params, this.query))).then(res => {
const data = res.data.data; const data = res.data.data;
this.page.total = data.total; this.page.total = data.total;
this.data = data.records; this.data = data.records;
+10 -2
View File
@@ -76,6 +76,13 @@
</el-input> </el-input>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-link
type="primary"
v-if="permission.fee_item_view"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-link>
<el-link <el-link
type="primary" type="primary"
v-if="permission.fee_item_edit" v-if="permission.fee_item_edit"
@@ -97,6 +104,7 @@ import { exportBlob } from '@/api/common';
import { getDictionaryAll } from '@/api/system/dictbiz'; import { getDictionaryAll } from '@/api/system/dictbiz';
import { getDeptTree } from '@/api/system/dept'; import { getDeptTree } from '@/api/system/dept';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { omitInvalidListStatus } from '@/utils/search-range';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { Download } from '@element-plus/icons-vue'; import { Download } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
@@ -140,7 +148,7 @@ export default {
editBtn: false, editBtn: false,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 180, menuWidth: 220,
column: [ column: [
{ {
label: '费用类型', label: '费用类型',
@@ -569,7 +577,7 @@ export default {
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
this.loading = true; this.loading = true;
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => { getList(page.currentPage, page.pageSize, omitInvalidListStatus(Object.assign(params, this.query))).then(res => {
const data = res.data.data; const data = res.data.data;
this.page.total = data.total; this.page.total = data.total;
this.data = data.records; this.data = data.records;
+9 -1
View File
@@ -46,6 +46,13 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-link
v-if="hasPermission('measurement_unit_view')"
type="primary"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-link>
<el-link <el-link
v-if="hasPermission('measurement_unit_edit')" v-if="hasPermission('measurement_unit_edit')"
type="primary" type="primary"
@@ -82,6 +89,7 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { changeStatus, getDetail, getList, remove, submit } from '@/api/base/measurement-unit'; import { changeStatus, getDetail, getList, remove, submit } from '@/api/base/measurement-unit';
import { createOption } from '@/option/base/measurement-unit'; import { createOption } from '@/option/base/measurement-unit';
import { omitInvalidListStatus } from '@/utils/search-range';
import { isAdministrator } from '@/utils/auth'; import { isAdministrator } from '@/utils/auth';
export default { export default {
@@ -218,7 +226,7 @@ export default {
}, },
onLoad(page = this.page, params = this.query) { onLoad(page = this.page, params = this.query) {
this.loading = true; this.loading = true;
return getList(page.currentPage, page.pageSize, params) return getList(page.currentPage, page.pageSize, omitInvalidListStatus(params))
.then(res => { .then(res => {
const pageData = res.data.data || {}; const pageData = res.data.data || {};
this.data = pageData.records || []; this.data = pageData.records || [];
+10 -2
View File
@@ -134,6 +134,13 @@
</div> </div>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-link
type="primary"
v-if="hasPermission('port_terminal_view')"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-link>
<el-link <el-link
type="primary" type="primary"
v-if="hasPermission('port_terminal_edit')" v-if="hasPermission('port_terminal_edit')"
@@ -270,6 +277,7 @@ import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel'; import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit'; import { formatUpdateUserName } from '@/utils/audit';
import { omitInvalidListStatus } from '@/utils/search-range';
import { getToken, isAdministrator } from '@/utils/auth'; import { getToken, isAdministrator } from '@/utils/auth';
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate'; import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
import AddressMapPicker from '@/components/address-map-picker/main.vue'; import AddressMapPicker from '@/components/address-map-picker/main.vue';
@@ -369,7 +377,7 @@ export default {
editBtn: false, editBtn: false,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 180, menuWidth: 220,
column: [ column: [
{ {
label: '编码', label: '编码',
@@ -1636,7 +1644,7 @@ export default {
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
this.loading = true; this.loading = true;
getList(page.currentPage, page.pageSize, { ...params, ...this.query }) getList(page.currentPage, page.pageSize, omitInvalidListStatus({ ...params, ...this.query }))
.then(res => { .then(res => {
const data = res.data.data; const data = res.data.data;
this.page.total = data.total; this.page.total = data.total;
+10 -2
View File
@@ -60,6 +60,13 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-link
type="primary"
v-if="hasPermission('railway_station_view')"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-link>
<el-link <el-link
type="primary" type="primary"
v-if="hasPermission('railway_station_edit')" v-if="hasPermission('railway_station_edit')"
@@ -150,6 +157,7 @@ import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel'; import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit'; import { formatUpdateUserName } from '@/utils/audit';
import { omitInvalidListStatus } from '@/utils/search-range';
import { getToken, isAdministrator } from '@/utils/auth'; import { getToken, isAdministrator } from '@/utils/auth';
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate'; import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
import AddressMapPicker from '@/components/address-map-picker/main.vue'; import AddressMapPicker from '@/components/address-map-picker/main.vue';
@@ -249,7 +257,7 @@ export default {
editBtn: false, editBtn: false,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 180, menuWidth: 220,
column: [ column: [
{ {
label: '编码', label: '编码',
@@ -1157,7 +1165,7 @@ export default {
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
this.loading = true; this.loading = true;
getList(page.currentPage, page.pageSize, { ...params, ...this.query }) getList(page.currentPage, page.pageSize, omitInvalidListStatus({ ...params, ...this.query }))
.then(res => { .then(res => {
const data = res.data.data; const data = res.data.data;
this.page.total = data.total; this.page.total = data.total;
+19 -1
View File
@@ -280,6 +280,23 @@ export default {
.filter(Boolean)[0] || ''; .filter(Boolean)[0] || '';
}, },
applyDefaultDeptSearch() { applyDefaultDeptSearch() {
// 超管默认不限组织,避免预填当前组织后看不到其他组织/无组织数据。
if (this.isAdmin) {
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
if (deptColumn) {
deptColumn.searchValue = undefined;
}
const nextQuery = { ...this.query };
delete nextQuery.deptId;
delete nextQuery.searchDeptId;
this.query = nextQuery;
this.$nextTick(() => {
if (this.$refs.crud?.searchForm) {
this.$refs.crud.searchForm.searchDeptId = undefined;
}
});
return;
}
const deptId = this.defaultDeptId(); const deptId = this.defaultDeptId();
const deptColumn = this.findColumn(this.option.column, 'searchDeptId'); const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
if (!deptId || !deptColumn) return; if (!deptId || !deptColumn) return;
@@ -298,7 +315,8 @@ export default {
if (deptColumn) { if (deptColumn) {
deptColumn.dicData = this.deptOptions; deptColumn.dicData = this.deptOptions;
} }
// dicData 异步到位后再回填,避免下拉选项为空时清空已选组织,导致请求丢失 deptId。 // 超管不回填默认组织;普通用户在 dicData 到位后再回填,避免下拉清空已选组织。
if (this.isAdmin) return;
const deptId = String(this.query.deptId || this.defaultDeptId() || ''); const deptId = String(this.query.deptId || this.defaultDeptId() || '');
if (!deptId) return; if (!deptId) return;
if (deptColumn) { if (deptColumn) {
+52 -7
View File
@@ -396,8 +396,11 @@ export default {
return this.selectionList.map(item => item.id).join(','); return this.selectionList.map(item => item.id).join(',');
}, },
}, },
created() { created() {
this.applyDefaultDeptSearch(); // 超管默认查全部组织,不预填组织筛选;普通用户预填当前组织。
if (!this.isAdmin) {
this.applyDefaultDeptSearch();
}
this.initDeptTree(); this.initDeptTree();
}, },
methods: { methods: {
@@ -455,10 +458,23 @@ export default {
normalizeListQuery(params = {}) { normalizeListQuery(params = {}) {
// 搜索条件优先于缓存的 query,避免默认组织 ID 覆盖用户新选择的组织。 // 搜索条件优先于缓存的 query,避免默认组织 ID 覆盖用户新选择的组织。
const query = { ...this.query, ...params }; const query = { ...this.query, ...params };
const rawDeptId = query.deptId ?? query.searchDeptId; // 本次搜索显式带了组织字段时,以本次为准(含清空);否则沿用缓存。
const hasDeptInParams =
Object.prototype.hasOwnProperty.call(params, 'deptId') ||
Object.prototype.hasOwnProperty.call(params, 'searchDeptId');
const rawDeptId = hasDeptInParams
? params.deptId ?? params.searchDeptId
: query.deptId ?? query.searchDeptId;
delete query.searchDeptId; delete query.searchDeptId;
if (rawDeptId !== undefined && rawDeptId !== null && String(rawDeptId).trim() !== '') { if (rawDeptId !== undefined && rawDeptId !== null && String(rawDeptId).trim() !== '') {
query.deptId = String(rawDeptId).split(',')[0].trim(); // 级联可能回传路径数组,统一取末级并保持字符串,避免雪花 ID 精度丢失。
const deptValue = Array.isArray(rawDeptId) ? rawDeptId.at(-1) : rawDeptId;
query.deptId = String(deptValue ?? '')
.split(',')[0]
.trim();
if (!query.deptId) {
delete query.deptId;
}
} else { } else {
delete query.deptId; delete query.deptId;
} }
@@ -466,6 +482,23 @@ export default {
return query; return query;
}, },
applyDefaultDeptSearch() { applyDefaultDeptSearch() {
// 超管默认不限组织,避免预填组织后新增落在其他组织时列表看不见。
if (this.isAdmin) {
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
if (deptColumn) {
deptColumn.searchValue = undefined;
}
const nextQuery = { ...this.query };
delete nextQuery.deptId;
delete nextQuery.searchDeptId;
this.query = nextQuery;
this.$nextTick(() => {
if (this.$refs.crud?.searchForm) {
this.$refs.crud.searchForm.searchDeptId = undefined;
}
});
return;
}
const deptId = this.defaultDeptId(); const deptId = this.defaultDeptId();
const deptColumn = this.findColumn(this.option.column, 'searchDeptId'); const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
if (!deptId || !deptColumn) return; if (!deptId || !deptColumn) return;
@@ -477,6 +510,11 @@ export default {
} }
}); });
}, },
resolveDeptName(deptId) {
if (!deptId) return '';
const matched = this.deptOptions.find(item => String(item.value) === String(deptId));
return matched ? String(matched.label || '').trim() : '';
},
resetAddressQuery() { resetAddressQuery() {
this.addressQuery = { this.addressQuery = {
addressName: '', addressName: '',
@@ -933,7 +971,8 @@ export default {
} }
api.submit(submitRow).then( api.submit(submitRow).then(
() => { () => {
this.onLoad(this.page); this.page.currentPage = 1;
this.onLoad(this.page, this.query);
this.$message({ type: 'success', message: '操作成功!' }); this.$message({ type: 'success', message: '操作成功!' });
done(); done();
}, },
@@ -951,7 +990,7 @@ export default {
} }
api.submit(submitRow).then( api.submit(submitRow).then(
() => { () => {
this.onLoad(this.page); this.onLoad(this.page, this.query);
this.$message({ type: 'success', message: '操作成功!' }); this.$message({ type: 'success', message: '操作成功!' });
done(); done();
}, },
@@ -1006,7 +1045,13 @@ export default {
beforeOpen(done, type) { beforeOpen(done, type) {
this.dialogReadonly = type === 'view'; this.dialogReadonly = type === 'view';
if (type === 'add') { if (type === 'add') {
this.form = {}; const form = {};
// 超管若当前按某组织筛选,新增默认归属该组织,避免保存后被筛选条件滤掉。
if (this.isAdmin && this.query.deptId) {
form.deptId = String(this.query.deptId);
form.deptName = this.resolveDeptName(form.deptId);
}
this.form = form;
done(); done();
return; return;
} }
@@ -12,12 +12,12 @@
<el-descriptions-item label="方案名称">{{ <el-descriptions-item label="方案名称">{{
displayValue(draft.planName) displayValue(draft.planName)
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="适用客商">{{
displayValue(draft.customerName || partyOptionLabel(draft.partyRowId))
}}</el-descriptions-item>
<el-descriptions-item label="运输方式">{{ <el-descriptions-item label="运输方式">{{
transportModeLabel(draft.transportMode) transportModeLabel(draft.transportMode)
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="默认方案">{{
isDefaultPlan(draft) ? '是' : '否'
}}</el-descriptions-item>
<el-descriptions-item label="备注">{{ displayValue(draft.remark) }}</el-descriptions-item> <el-descriptions-item label="备注">{{ displayValue(draft.remark) }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-form <el-form
@@ -37,6 +37,20 @@
:disabled="readonly" /></el-form-item :disabled="readonly" /></el-form-item
></el-col> ></el-col>
<el-col :span="8" <el-col :span="8"
><el-form-item label="适用客商" prop="partyRowId"
><el-select
v-model="draft.partyRowId"
clearable
filterable
placeholder="请选择适用客商"
@change="handlePartyChange"
><el-option
v-for="item in partyOptions"
:key="item.value"
:label="item.label"
:value="item.value" /></el-select></el-form-item
></el-col>
<el-col :span="8"
><el-form-item label="运输方式" prop="transportMode" ><el-form-item label="运输方式" prop="transportMode"
><el-select ><el-select
v-model="draft.transportMode" v-model="draft.transportMode"
@@ -51,16 +65,6 @@
:label="item.label" :label="item.label"
:value="item.value" /></el-select></el-form-item :value="item.value" /></el-select></el-form-item
></el-col> ></el-col>
<el-col :span="8"
><el-form-item
><el-checkbox v-model="draft.defaultPlan" :disabled="readonly">默认方案</el-checkbox
><el-tooltip content="同一运输方式仅支持配置一个默认计费方案" placement="top">
<el-icon class="billing-plan-editor__default-tip"
><InfoFilled
/></el-icon> </el-tooltip
></el-form-item
></el-col
>
<el-col :span="24" <el-col :span="24"
><el-form-item label="备注" ><el-form-item label="备注"
><el-input ><el-input
@@ -380,6 +384,7 @@ import { InfoFilled } from '@element-plus/icons-vue';
import { getDictionary } from '@/api/system/dictbiz'; import { getDictionary } from '@/api/system/dictbiz';
import SectionCard from '@/components/section-card/main.vue'; import SectionCard from '@/components/section-card/main.vue';
import { currencyUnit } from '@/utils/currency'; import { currencyUnit } from '@/utils/currency';
// InfoFilled 仅用于保底计费重量列头提示
const clone = value => JSON.parse(JSON.stringify(value)); const clone = value => JSON.parse(JSON.stringify(value));
/** 计费要素与计量单位维度的对应关系(这些要素可选计费单位) */ /** 计费要素与计量单位维度的对应关系(这些要素可选计费单位) */
@@ -433,6 +438,7 @@ export default {
index: { type: Number, default: -1 }, index: { type: Number, default: -1 },
readonly: Boolean, readonly: Boolean,
settlementCurrency: { type: String, default: 'RMB' }, settlementCurrency: { type: String, default: 'RMB' },
partyOptions: { type: Array, default: () => [] },
}, },
emits: ['update:modelValue', 'save'], emits: ['update:modelValue', 'save'],
data() { data() {
@@ -476,6 +482,7 @@ export default {
cargoRequest: null, cargoRequest: null,
formRules: { formRules: {
planName: [{ required: true, message: '请输入方案名称', trigger: 'blur' }], planName: [{ required: true, message: '请输入方案名称', trigger: 'blur' }],
partyRowId: [{ required: true, message: '请选择适用客商', trigger: 'change' }],
transportMode: [{ required: true, message: '请选择运输方式', trigger: 'change' }], transportMode: [{ required: true, message: '请选择运输方式', trigger: 'change' }],
}, },
}; };
@@ -516,10 +523,24 @@ export default {
handler(value) { handler(value) {
if (this.modelValue) { if (this.modelValue) {
this.draft = this.normalizePlan(value); this.draft = this.normalizePlan(value);
this.applyDefaultPartySelection();
this.loadRuleItems(); this.loadRuleItems();
} }
}, },
}, },
modelValue(visible) {
if (visible) {
this.$nextTick(() => this.applyDefaultPartySelection());
}
},
partyOptions: {
deep: true,
handler() {
if (this.modelValue) {
this.applyDefaultPartySelection();
}
},
},
}, },
methods: { methods: {
displayValue(value) { displayValue(value) {
@@ -539,27 +560,62 @@ export default {
); );
return this.displayValue(option?.label || this.draft.transportModeLabel || value); return this.displayValue(option?.label || this.draft.transportModeLabel || value);
}, },
isDefaultPlan(plan) {
return (
plan?.defaultPlan === true ||
plan?.defaultPlan === 1 ||
['true', '1'].includes(String(plan?.defaultPlan).toLowerCase())
);
},
normalizePlan(value = {}) { normalizePlan(value = {}) {
const plan = { const plan = {
planName: '', planName: '',
partyRowId: '',
partyRole: '',
customerId: '',
customerName: '',
transportMode: '', transportMode: '',
transportModeLabel: '', transportModeLabel: '',
defaultPlan: false,
remark: '', remark: '',
...clone(value || {}), ...clone(value || {}),
}; };
// 一个客商对应一个方案,不再维护默认方案字段
delete plan.defaultPlan;
plan.rules = (value?.rules?.length ? value.rules : [defaultRule()]).map(rule => plan.rules = (value?.rules?.length ? value.rules : [defaultRule()]).map(rule =>
this.normalizeRule(rule) this.normalizeRule(rule)
); );
return plan; return plan;
}, },
partyOptionLabel(partyRowId) {
const matched = (this.partyOptions || []).find(
item => String(item.value) === String(partyRowId)
);
return matched?.label || '';
},
/**
* 仅一个可选客商时默认选中(客户合同仅一个甲方的常见场景)
*/
applyDefaultPartySelection() {
if (this.readonly) {
return;
}
const options = this.partyOptions || [];
if (options.length !== 1) {
return;
}
const current = String(this.draft.partyRowId || '');
const only = options[0];
const onlyValue = String(only?.value || only?.partyRowId || '');
if (!onlyValue) {
return;
}
if (current && current === onlyValue) {
return;
}
this.handlePartyChange(only.value || only.partyRowId);
},
handlePartyChange(partyRowId) {
const matched = (this.partyOptions || []).find(
item => String(item.value) === String(partyRowId)
);
this.draft.partyRowId = matched?.partyRowId || matched?.value || '';
this.draft.partyRole = matched?.partyRole || '';
this.draft.customerId = matched?.customerId || '';
this.draft.customerName = matched?.customerName || '';
},
normalizeRule(rule = {}) { normalizeRule(rule = {}) {
const next = { ...defaultRule(), ...clone(rule) }; const next = { ...defaultRule(), ...clone(rule) };
if (next.billingType === '阶梯单价' && next.minimumBillingWeight === '') { if (next.billingType === '阶梯单价' && next.minimumBillingWeight === '') {
@@ -1032,6 +1088,11 @@ export default {
this.$refs.formRef.validate(valid => { this.$refs.formRef.validate(valid => {
if (!valid || !this.validateRules()) return; if (!valid || !this.validateRules()) return;
const plan = clone(this.draft); const plan = clone(this.draft);
this.handlePartyChange(plan.partyRowId);
plan.partyRowId = this.draft.partyRowId;
plan.partyRole = this.draft.partyRole;
plan.customerId = this.draft.customerId;
plan.customerName = this.draft.customerName;
const transportMode = this.transportModeOptions.find( const transportMode = this.transportModeOptions.find(
item => String(item.value) === String(plan.transportMode) item => String(item.value) === String(plan.transportMode)
); );
File diff suppressed because it is too large Load Diff
@@ -38,7 +38,7 @@
contract.hcsFdId || '-' contract.hcsFdId || '-'
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="客商风险"> <el-descriptions-item label="客商风险">
<template v-if="contract.hcsRiskLevel || contract.hcsBlackStatus"> <template v-if="contract.hcsRiskLevel || contract.kingdeeBlackStatus">
<el-tag <el-tag
v-if="isBlacklist" v-if="isBlacklist"
type="danger" type="danger"
@@ -130,7 +130,7 @@ export default {
name: 'HcsContractPanel', name: 'HcsContractPanel',
components: { UploadFilled }, components: { UploadFilled },
props: { props: {
// 合同详情行(须含 id/hcsSyncStatus/hcsFdId/hcsRiskLevel/hcsBlackStatus/hcsSpecStatus 等) // 合同详情行(须含 id/hcsSyncStatus/hcsFdId/hcsRiskLevel/kingdeeBlackStatus/hcsSpecStatus 等)
contract: { type: Object, required: true }, contract: { type: Object, required: true },
}, },
emits: ['refresh'], emits: ['refresh'],
@@ -152,7 +152,7 @@ export default {
return this.contract.hcsSyncMsg || ''; return this.contract.hcsSyncMsg || '';
}, },
isBlacklist() { isBlacklist() {
return String(this.contract.hcsBlackStatus || '').toLowerCase() === 'yes'; return String(this.contract.kingdeeBlackStatus || '').toLowerCase() === 'yes';
}, },
specLabel() { specLabel() {
return SPEC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSpecStatus)?.label || ''; return SPEC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSpecStatus)?.label || '';
@@ -294,8 +294,8 @@ export default {
query: { id: String(customerId), name: '查看客商档案', view: '1' }, query: { id: String(customerId), name: '查看客商档案', view: '1' },
}); });
}, },
waybillStatusName(value) { return ({ pending: '待执行', waiting: '待执行', running: '进行中', processing: '进行中', completed: '已完成', cancelled: '已取消' })[value] || value || '-'; }, waybillStatusName(value) { return ({ pending: '待执行', waiting: '待执行', running: '进行中', processing: '进行中', pending_return_confirm: '待回单确认', completed: '已完成', cancelled: '已取消' })[value] || value || '-'; },
waybillStatusType(value) { return ({ pending: 'info', waiting: 'info', running: 'warning', processing: 'warning', completed: 'success', cancelled: 'danger' })[value] || 'info'; }, waybillStatusType(value) { return ({ pending: 'info', waiting: 'info', running: 'warning', processing: 'warning', pending_return_confirm: 'warning', completed: 'success', cancelled: 'danger' })[value] || 'info'; },
statusName(value) { return ({ waiting_dispatch: '待调度', dispatching: '调度中', completed: '调度完成', closed: '调度关闭' })[value] || value || '-'; }, statusName(value) { return ({ waiting_dispatch: '待调度', dispatching: '调度中', completed: '调度完成', closed: '调度关闭' })[value] || value || '-'; },
statusType(value) { return ({ waiting_dispatch: 'warning', dispatching: 'success', completed: 'primary', closed: 'danger' })[value] || 'info'; }, statusType(value) { return ({ waiting_dispatch: 'warning', dispatching: 'success', completed: 'primary', closed: 'danger' })[value] || 'info'; },
}, },
@@ -195,7 +195,7 @@
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-form"> <el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-form">
<el-row :gutter="16"> <el-row :gutter="16">
<template v-if="isRoad(route)"> <template v-if="isRoad(route)">
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col> <el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? carrierSelectedValue(route) : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
<el-col :span="6" <el-col :span="6"
><el-form-item label="司机" :required="route.carrierType !== '承运商'" ><el-form-item label="司机" :required="route.carrierType !== '承运商'"
><el-autocomplete ><el-autocomplete
@@ -210,7 +210,11 @@
@mousedown="() => handleDriverFocus(route)" @mousedown="() => handleDriverFocus(route)"
@focus="() => handleDriverFocus(route)" @focus="() => handleDriverFocus(route)"
@select="handleDriverSuggestionSelect" @select="handleDriverSuggestionSelect"
/></el-form-item >
<template #default="{ item }">
<span>{{ driverOptionText(item) }}</span>
</template>
</el-autocomplete></el-form-item
></el-col> ></el-col>
<el-col :span="6" <el-col :span="6"
><el-form-item ><el-form-item
@@ -239,7 +243,11 @@
@mousedown="() => handleDriverFocus(route)" @mousedown="() => handleDriverFocus(route)"
@focus="() => handleDriverFocus(route)" @focus="() => handleDriverFocus(route)"
@select="handleEscortSuggestionSelect" @select="handleEscortSuggestionSelect"
/></el-form-item >
<template #default="{ item }">
<span>{{ driverOptionText(item) }}</span>
</template>
</el-autocomplete></el-form-item
></el-col> ></el-col>
<el-col v-if="route.carrierType !== '承运商'" :span="6" <el-col v-if="route.carrierType !== '承运商'" :span="6"
><el-form-item label="押运人手机号" ><el-form-item label="押运人手机号"
@@ -247,7 +255,7 @@
></el-col> ></el-col>
</template> </template>
<template v-else> <template v-else>
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col> <el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? carrierSelectedValue(route) : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="船班号/船次号/班次号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col> <el-col :span="6"><el-form-item label="船班号/船次号/班次号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="船长"><el-input v-model="route.captainName" placeholder="请输入" /></el-form-item></el-col> <el-col :span="6"><el-form-item label="船长"><el-input v-model="route.captainName" placeholder="请输入" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="联系电话"><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col> <el-col :span="6"><el-form-item label="联系电话"><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
@@ -316,7 +324,7 @@
<script> <script>
import * as api from '@/api/business/master-order'; import * as api from '@/api/business/master-order';
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource'; import { fetchDriverCandidates, driverOptionText } from '@/utils/carrier-org-resource';
import { formatContractOptionLabel } from '@/utils/contract'; import { formatContractOptionLabel } from '@/utils/contract';
import { import {
currencyUnit, currencyUnit,
@@ -403,6 +411,7 @@ export default {
this.applySelfOperatedCarrierDefaults(); this.applySelfOperatedCarrierDefaults();
}, },
methods: { methods: {
driverOptionText,
loadQuantityUnitOptions() { loadQuantityUnitOptions() {
if (this.quantityUnitLoading) return this.quantityUnitLoadPromise; if (this.quantityUnitLoading) return this.quantityUnitLoadPromise;
this.quantityUnitLoading = true; this.quantityUnitLoading = true;
@@ -911,6 +920,8 @@ export default {
route.carrierName = ''; route.carrierName = '';
route.carrierId = ''; route.carrierId = '';
route.carrierContractId = ''; route.carrierContractId = '';
route.carrierPartyId = '';
route.carrierPartyRole = '';
this.clearRouteDriverVehicleFields(route); this.clearRouteDriverVehicleFields(route);
if (value === '承运商') { if (value === '承运商') {
route.trailerVehicleNo = ''; route.trailerVehicleNo = '';
@@ -921,15 +932,18 @@ export default {
if (value === '自运') this.applySelfOperatedCarrierDefaults(route); if (value === '自运') this.applySelfOperatedCarrierDefaults(route);
}); });
}, },
handleCarrierContractChange(route, contractId) { handleCarrierContractChange(route, selectedValue) {
const contract = this.carrierOptions.find( const contract = this.carrierOptions.find(
item => item =>
this.carrierOptionValue(item) === String(selectedValue || '') ||
[item.contractId, item.carrierName, item.id].some( [item.contractId, item.carrierName, item.id].some(
value => String(value || '') === String(contractId || '') value => String(value || '') === String(selectedValue || '')
) )
); );
route.carrierContractId = route.carrierType !== '自运' ? contract?.contractId || '' : ''; route.carrierContractId = route.carrierType !== '自运' ? contract?.contractId || '' : '';
route.carrierId = route.carrierType !== '自运' ? contract?.carrierId || '' : ''; route.carrierPartyId = route.carrierType !== '自运' ? contract?.carrierPartyId || '' : '';
route.carrierPartyRole = route.carrierType !== '自运' ? contract?.carrierPartyRole || '' : '';
route.carrierId = route.carrierType !== '自运' ? contract?.carrierId || contract?.customerId || '' : '';
route.carrierName = contract?.carrierName || ''; route.carrierName = contract?.carrierName || '';
this.clearRouteDriverVehicleFields(route); this.clearRouteDriverVehicleFields(route);
}, },
@@ -944,22 +958,24 @@ export default {
route.escortPhone = ''; route.escortPhone = '';
this.driverOptions = []; this.driverOptions = [];
}, },
getRouteCarrierFilter(route = {}) {
return {
carrierId: route.carrierId || '',
carrierName: route.carrierName || '',
};
},
carrierOptionValue(item = {}) { carrierOptionValue(item = {}) {
return item.contractId || item.id || item.value || item.carrierName || ''; return [item.contractId || item.id || '', item.carrierPartyId || '', item.carrierName || ''].join(
'__'
);
},
carrierSelectedValue(route = {}) {
return this.carrierOptionValue({
contractId: route.carrierContractId,
carrierPartyId: route.carrierPartyId,
carrierName: route.carrierName,
});
}, },
carrierOptionLabel(item) { carrierOptionLabel(item) {
const duplicateCount = this.carrierOptions.filter(option => option.carrierName === item.carrierName).length; const roleLabel = item.carrierPartyRole === 'C' ? '丙方' : item.carrierPartyRole === 'B' ? '乙方' : '';
if (duplicateCount <= 1) { const nameWithRole =
return item.carrierName; roleLabel && item.carrierName ? `${item.carrierName}(${roleLabel})` : item.carrierName;
}
const contractLabel = formatContractOptionLabel(item); const contractLabel = formatContractOptionLabel(item);
return contractLabel ? `${item.carrierName}(${contractLabel})` : item.carrierName; return contractLabel ? `${nameWithRole} - ${contractLabel}` : nameWithRole;
}, },
driverOptionLabel(item) { return item.driverName || item.name || ''; }, driverOptionLabel(item) { return item.driverName || item.name || ''; },
driverOptionKey(item) { return item.id || item.driverId || this.driverOptionLabel(item) || item.mobile; }, driverOptionKey(item) { return item.id || item.driverId || this.driverOptionLabel(item) || item.mobile; },
@@ -980,19 +996,22 @@ export default {
...item, ...item,
contractId: item.contractId || item.carrierContractId || item.id || '', contractId: item.contractId || item.carrierContractId || item.id || '',
carrierName: carrierName:
item.carrierName ||
item.partyB || item.partyB ||
item.partyBName || item.partyBName ||
item.contractPartyB || item.contractPartyB ||
item.customerContractPartyB || item.customerContractPartyB ||
item.carrierName ||
'', '',
carrierId: carrierId:
item.customerId ||
item.partyBId || item.partyBId ||
item.partyBUserId || item.partyBUserId ||
item.contractPartyBId || item.contractPartyBId ||
item.customerContractPartyBId || item.customerContractPartyBId ||
item.carrierId || item.carrierId ||
'', '',
carrierPartyId: item.carrierPartyId || '',
carrierPartyRole: item.carrierPartyRole || '',
})) }))
.filter(item => item.carrierName); .filter(item => item.carrierName);
} finally { } finally {
@@ -1026,36 +1045,22 @@ export default {
this.activeSuggestRoute = route; this.activeSuggestRoute = route;
}, },
async loadDriverOptions(route = {}, keyword = '') { async loadDriverOptions(route = {}, keyword = '') {
const carrier = this.getRouteCarrierFilter(route);
if (!carrier.carrierId && !carrier.carrierName) {
this.driverOptions = [];
return [];
}
this.driverLoading = true; this.driverLoading = true;
try { try {
this.driverOptions = await fetchDriversByCarrierOrganizations( this.driverOptions = await fetchDriverCandidates({
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' }, size: 20,
carrier ...(keyword ? { driverName: keyword } : {}),
); posts: '司机',
});
return this.driverOptions; return this.driverOptions;
} finally { } finally {
this.driverLoading = false; this.driverLoading = false;
} }
}, },
fetchDriverSuggestions(queryString, callback) { fetchDriverSuggestions(queryString, callback) {
const route = this.activeSuggestRoute || {};
const carrier = this.getRouteCarrierFilter(route);
const keyword = String(queryString || '').trim(); const keyword = String(queryString || '').trim();
if (!carrier.carrierId && !carrier.carrierName) {
callback([]);
if (!keyword) this.$message.warning('请先选择承运商');
return;
}
this.driverLoading = true; this.driverLoading = true;
fetchDriversByCarrierOrganizations( fetchDriverCandidates({ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
carrier
)
.then(records => { .then(records => {
this.driverOptions = records; this.driverOptions = records;
callback( callback(
@@ -1080,19 +1085,13 @@ export default {
if (drivingVehicle) route.vehicleNo = drivingVehicle; if (drivingVehicle) route.vehicleNo = drivingVehicle;
}, },
fetchEscortSuggestions(queryString, callback) { fetchEscortSuggestions(queryString, callback) {
const route = this.activeSuggestRoute || {};
const keyword = String(queryString || '').trim(); const keyword = String(queryString || '').trim();
const carrier = this.getRouteCarrierFilter(route);
if (!carrier.carrierId && !carrier.carrierName) {
callback([]);
if (!keyword) this.$message.warning('请先选择承运商');
return;
}
this.escortLoading = true; this.escortLoading = true;
fetchDriversByCarrierOrganizations( fetchDriverCandidates({
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' }, size: 20,
carrier ...(keyword ? { driverName: keyword } : {}),
) posts: '押运员',
})
.then(records => { .then(records => {
callback( callback(
records.map(item => ({ records.map(item => ({
@@ -1154,7 +1153,7 @@ export default {
id: `${route.segmentNo}-${item.cargoName}-${Date.now()}-${Math.random()}`, id: `${route.segmentNo}-${item.cargoName}-${Date.now()}-${Math.random()}`,
batchNo, batchNo,
segmentNo: route.segmentNo, relationNo: route.segmentNo, documentType: route.documentType, transportType: route.transportType, carrierType: route.carrierType, segmentNo: route.segmentNo, relationNo: route.segmentNo, documentType: route.documentType, transportType: route.transportType, carrierType: route.carrierType,
carrierContractId: route.carrierContractId, carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, captainName: route.captainName, cabinNo: route.cabinNo, containerNo: route.containerNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: this.normalizeMileage(route.mileage), unitPrice: this.freightItemsForGoods(route, item).unitPrice || '', priceUnit: this.freightItemsForGoods(route, item).priceUnit || '', currency: route.currency || 'RMB', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), routeRemark: route.remark, carrierContractId: route.carrierContractId, carrierPartyId: route.carrierPartyId, carrierPartyRole: route.carrierPartyRole, carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, captainName: route.captainName, cabinNo: route.cabinNo, containerNo: route.containerNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: this.normalizeMileage(route.mileage), unitPrice: this.freightItemsForGoods(route, item).unitPrice || '', priceUnit: this.freightItemsForGoods(route, item).priceUnit || '', currency: route.currency || 'RMB', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), routeRemark: route.remark,
departureName: route.departureName, departureAddress: route.departureAddress, departureContact: route.departureContact, departurePhone: route.departurePhone, departureName: route.departureName, departureAddress: route.departureAddress, departureContact: route.departureContact, departurePhone: route.departurePhone,
arrivalName: route.arrivalName, arrivalAddress: route.arrivalAddress, arrivalContact: route.arrivalContact, arrivalPhone: route.arrivalPhone, arrivalName: route.arrivalName, arrivalAddress: route.arrivalAddress, arrivalContact: route.arrivalContact, arrivalPhone: route.arrivalPhone,
estimatedStartTime: route.estimatedStartTime, estimatedEndTime: route.estimatedEndTime, estimatedStartTime: route.estimatedStartTime, estimatedEndTime: route.estimatedEndTime,
@@ -94,7 +94,7 @@
<div class="route-node-form"> <div class="route-node-form">
<el-form-item label="途经地" required class="route-address-form-item" <el-form-item label="途经地" required class="route-address-form-item"
><div class="route-address-inputs" ><div class="route-address-inputs"
><el-input v-if="isNonRoadAddress(`route-${index}`)" v-model="route.departureName" class="route-address-name" readonly :disabled="isBasicRouteReadonly || !route.transportType" placeholder="请先选择运输方式" /><el-cascader v-else v-model="addressRegionPaths[`route-${index}`]" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="isBasicRouteReadonly || !route.transportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange(`route-${index}`, value)" /><el-input v-model="route.departureAddress" class="route-address-detail" readonly :disabled="isBasicRouteReadonly || !route.transportType" placeholder="请先选择运输方式" @click="handleAddressDetailClick(`route-${index}`)" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="isBasicRouteReadonly || !route.transportType" @click="openCommonAddress(`route-${index}`)" /></el-tooltip></div ><el-input v-if="isNonRoadAddress(`route-${index}`)" v-model="route.departureName" class="route-address-name" readonly :disabled="isBasicRouteReadonly" placeholder="请选择站点" /><el-cascader v-else v-model="addressRegionPaths[`route-${index}`]" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="isBasicRouteReadonly" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange(`route-${index}`, value)" /><el-input v-model="route.departureAddress" class="route-address-detail" readonly :disabled="isBasicRouteReadonly" placeholder="请输入详细地址" @click="handleAddressDetailClick(`route-${index}`)" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="isBasicRouteReadonly" @click="openCommonAddress(`route-${index}`)" /></el-tooltip></div
></el-form-item> ></el-form-item>
<el-form-item label="联系人" class="route-contact-form-item"><el-input v-model="route.departureContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item> <el-form-item label="联系人" class="route-contact-form-item"><el-input v-model="route.departureContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item>
<el-form-item label="联系方式" class="route-phone-form-item"><div class="route-contact-actions"><el-input v-model="route.departurePhone" :disabled="isBasicRouteReadonly" placeholder="请输入" /><template v-if="!isBasicRouteReadonly"><el-tooltip v-if="index === form.routes.length - 1" content="新增途经点" placement="top"><el-button class="route-action-button" type="primary" link @click="addRoute"><el-icon><CirclePlus /></el-icon></el-button></el-tooltip><el-tooltip content="删除途经点" placement="top"><el-button class="route-action-button" type="danger" link @click="removeRoute(index)"><el-icon><Remove /></el-icon></el-button></el-tooltip></template></div></el-form-item> <el-form-item label="联系方式" class="route-phone-form-item"><div class="route-contact-actions"><el-input v-model="route.departurePhone" :disabled="isBasicRouteReadonly" placeholder="请输入" /><template v-if="!isBasicRouteReadonly"><el-tooltip v-if="index === form.routes.length - 1" content="新增途经点" placement="top"><el-button class="route-action-button" type="primary" link @click="addRoute"><el-icon><CirclePlus /></el-icon></el-button></el-tooltip><el-tooltip content="删除途经点" placement="top"><el-button class="route-action-button" type="danger" link @click="removeRoute(index)"><el-icon><Remove /></el-icon></el-button></el-tooltip></template></div></el-form-item>
@@ -111,7 +111,7 @@
<div class="route-node-form"> <div class="route-node-form">
<el-form-item label="收货地" required class="route-address-form-item" <el-form-item label="收货地" required class="route-address-form-item"
><div class="route-address-inputs" ><div class="route-address-inputs"
><el-input v-if="isNonRoadAddress('arrival')" v-model="form.arrivalName" class="route-address-name" readonly :disabled="isBasicRouteReadonly || !form.finalTransportType" placeholder="请先选择运输方式" /><el-cascader v-else v-model="addressRegionPaths.arrival" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="isBasicRouteReadonly || !form.finalTransportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('arrival', value)" /><el-input v-model="form.arrivalAddress" class="route-address-detail" readonly :disabled="isBasicRouteReadonly || !form.finalTransportType" placeholder="请先选择运输方式" @click="handleAddressDetailClick('arrival')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="isBasicRouteReadonly || !form.finalTransportType" @click="openCommonAddress('arrival')" /></el-tooltip></div ><el-input v-if="isNonRoadAddress('arrival')" v-model="form.arrivalName" class="route-address-name" readonly :disabled="isBasicRouteReadonly" placeholder="请选择站点" /><el-cascader v-else v-model="addressRegionPaths.arrival" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="isBasicRouteReadonly" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('arrival', value)" /><el-input v-model="form.arrivalAddress" class="route-address-detail" readonly :disabled="isBasicRouteReadonly" placeholder="请输入详细地址" @click="handleAddressDetailClick('arrival')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="isBasicRouteReadonly" @click="openCommonAddress('arrival')" /></el-tooltip></div
></el-form-item> ></el-form-item>
<el-form-item label="联系人" class="route-contact-form-item" <el-form-item label="联系人" class="route-contact-form-item"
><el-input v-model="form.arrivalContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item> ><el-input v-model="form.arrivalContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item>
@@ -352,7 +352,12 @@
@opened="loadAddressList" @opened="loadAddressList"
> >
<div class="route-dialog__search common-address-dialog__search"> <div class="route-dialog__search common-address-dialog__search">
<el-form :model="addressQuery" label-position="right" label-width="auto"> <el-form
class="dialog-form-label-fixed"
:model="addressQuery"
label-position="right"
label-width="96px"
>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="6"> <el-col :span="6">
<el-form-item label="地址名称"> <el-form-item label="地址名称">
@@ -446,7 +451,12 @@
@opened="loadStationList" @opened="loadStationList"
> >
<div class="route-dialog__search"> <div class="route-dialog__search">
<el-form :model="stationQuery" label-position="right" label-width="auto"> <el-form
class="dialog-form-label-fixed"
:model="stationQuery"
label-position="right"
label-width="96px"
>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="8"><el-form-item label="站点名称"><el-input v-model="stationQuery.name" clearable placeholder="请输入" /></el-form-item></el-col> <el-col :span="8"><el-form-item label="站点名称"><el-input v-model="stationQuery.name" clearable placeholder="请输入" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="详细地址"><el-input v-model="stationQuery.detailAddress" clearable placeholder="请输入" /></el-form-item></el-col> <el-col :span="8"><el-form-item label="详细地址"><el-input v-model="stationQuery.detailAddress" clearable placeholder="请输入" /></el-form-item></el-col>
@@ -652,6 +662,8 @@ export default {
addressQuery: {}, addressQuery: {},
addressPage: { current: 1, size: 10, total: 0 }, addressPage: { current: 1, size: 10, total: 0 },
addressRegionPaths: { departure: [], arrival: [] }, addressRegionPaths: { departure: [], arrival: [] },
// 程序回填省市区时抑制 cascader @change,避免空值回调把已填地址清空
suppressAddressRegionChange: false,
regionOptions: [], regionOptions: [],
regionLoading: false, regionLoading: false,
regionRequest: null, regionRequest: null,
@@ -714,7 +726,7 @@ export default {
'msg', 'msg',
'zip', 'zip',
], ],
transports: ['公路运输', '铁路运输', '水路运输', '航空运输'], transports: ['铁路运输', '水路运输', '航空运输', '公路运输'],
quantityUnitOptions: [], quantityUnitOptions: [],
quantityUnitLoading: false, quantityUnitLoading: false,
form: { form: {
@@ -798,6 +810,7 @@ export default {
methods: { methods: {
formatContractOptionLabel, formatContractOptionLabel,
defaultQuantityUnit() { defaultQuantityUnit() {
if (this.quantityUnitOptions.includes('吨')) return '吨';
return this.quantityUnitOptions[0] || '吨'; return this.quantityUnitOptions[0] || '吨';
}, },
loadQuantityUnitOptions() { loadQuantityUnitOptions() {
@@ -888,26 +901,63 @@ export default {
findRegionPathByName(options = [], regionName = '', parents = []) { findRegionPathByName(options = [], regionName = '', parents = []) {
const target = String(regionName || '').replace(/\s+/g, ''); const target = String(regionName || '').replace(/\s+/g, '');
if (!target) return []; if (!target) return [];
let suffixMatch = [];
for (const item of options) { for (const item of options) {
const nextParents = [...parents, item]; const nextParents = [...parents, item];
const name = nextParents.map(region => region.title || region.name || '').join(''); const name = nextParents.map(region => region.title || region.name || '').join('');
if (name === target) return nextParents.map(region => region.id); if (name === target) return nextParents.map(region => region.id);
// 常用地址可能缺省名,允许「市+区」后缀命中完整省市区路径
if (!suffixMatch.length && name.endsWith(target) && target.length >= 2) {
suffixMatch = nextParents.map(region => region.id);
}
const path = this.findRegionPathByName(item.children, target, nextParents); const path = this.findRegionPathByName(item.children, target, nextParents);
if (path.length) return path; if (path.length) return path;
} }
return []; return suffixMatch;
}, },
findRegionPathByCode(options = [], regionCode = '', parents = []) { findRegionPathByCode(options = [], regionCode = '', parents = []) {
const target = String(regionCode || '').trim(); const target = String(regionCode || '').trim();
if (!target) return []; if (!target) return [];
for (const item of options) { for (const item of options) {
const nextParents = [...parents, item]; const nextParents = [...parents, item];
if (String(item.id) === target) return nextParents.map(region => region.id); if (String(item.id) === target || String(item.value || '') === target) {
return nextParents.map(region => region.id);
}
const path = this.findRegionPathByCode(item.children, target, nextParents); const path = this.findRegionPathByCode(item.children, target, nextParents);
if (path.length) return path; if (path.length) return path;
} }
return []; return [];
}, },
resolveRegionPath(field) {
if (!field) return [];
const codePath = this.findRegionPathByCode(
this.regionOptions,
field.model[field.regionCode]
);
if (codePath.length) return codePath;
const namePath = this.findRegionPathByName(this.regionOptions, field.model[field.name]);
if (namePath.length) return namePath;
const composed = [field.model[field.provinceName], field.model[field.cityName], field.model[field.districtName]]
.filter(Boolean)
.join('');
return this.findRegionPathByName(this.regionOptions, composed);
},
setAddressRegionPath(target, path = []) {
const nextPath = Array.isArray(path) ? path.map(item => String(item)) : [];
const current = this.addressRegionPaths[target] || [];
if (
current.length === nextPath.length &&
current.every((item, index) => String(item) === String(nextPath[index]))
) {
return;
}
this.suppressAddressRegionChange = true;
// Vue3 直接写动态 key,避免整体替换对象导致其它 cascader 重建并触发空 @change
this.addressRegionPaths[target] = nextPath;
this.$nextTick(() => {
this.suppressAddressRegionChange = false;
});
},
regionLabels(path = []) { regionLabels(path = []) {
let options = this.regionOptions; let options = this.regionOptions;
const labels = []; const labels = [];
@@ -946,10 +996,22 @@ export default {
this.setAddressRegionNames(field, [provinceMatch?.[0], cityMatch?.[0], districtMatch?.[0]]); this.setAddressRegionNames(field, [provinceMatch?.[0], cityMatch?.[0], districtMatch?.[0]]);
}, },
handleAddressRegionChange(target, value) { handleAddressRegionChange(target, value) {
if (this.suppressAddressRegionChange) return;
const field = this.addressModel(target); const field = this.addressModel(target);
const path = Array.isArray(value) ? value.map(item => String(item)) : []; const path = Array.isArray(value) ? value.map(item => String(item)) : [];
this.addressRegionPaths = { ...this.addressRegionPaths, [target]: path }; this.setAddressRegionPath(target, path);
if (!field) return; if (!field) return;
// 程序回填/重建过程中 cascader 可能短暂抛出空值,不能据此清空已选常用地址
if (!path.length) {
const hasExisting =
String(field.model[field.name] || '').trim() ||
String(field.model[field.address] || '').trim() ||
String(field.model[field.regionCode] || '').trim();
if (hasExisting) {
this.$nextTick(() => this.syncAddressRegionPath(target));
return;
}
}
const labels = this.regionLabels(path); const labels = this.regionLabels(path);
field.model[field.name] = labels.join(''); field.model[field.name] = labels.join('');
field.model[field.regionCode] = path.at(-1) || ''; field.model[field.regionCode] = path.at(-1) || '';
@@ -958,19 +1020,29 @@ export default {
}, },
syncAddressRegionPath(target) { syncAddressRegionPath(target) {
const field = this.addressModel(target); const field = this.addressModel(target);
if (!field) return; if (!field || !this.regionOptions.length) return;
const codePath = this.findRegionPathByCode(this.regionOptions, field.model[field.regionCode]); const path = this.resolveRegionPath(field);
const path = codePath.length this.setAddressRegionPath(target, path);
? codePath
: this.findRegionPathByName(this.regionOptions, field.model[field.name]);
this.addressRegionPaths = { ...this.addressRegionPaths, [target]: path };
if (path.length) this.setAddressRegionNames(field, this.regionLabels(path)); if (path.length) this.setAddressRegionNames(field, this.regionLabels(path));
}, },
syncAddressRegionPaths() { syncAddressRegionPaths() {
if (!this.regionOptions.length) return; if (!this.regionOptions.length) return;
['departure', 'arrival', ...this.form.routes.map((route, index) => `route-${index}`)].forEach(target => { this.suppressAddressRegionChange = true;
this.syncAddressRegionPath(target); try {
}); ['departure', 'arrival', ...this.form.routes.map((route, index) => `route-${index}`)].forEach(
target => {
const field = this.addressModel(target);
if (!field) return;
const path = this.resolveRegionPath(field);
this.addressRegionPaths[target] = path;
if (path.length) this.setAddressRegionNames(field, this.regionLabels(path));
}
);
} finally {
this.$nextTick(() => {
this.suppressAddressRegionChange = false;
});
}
}, },
async loadDetail(id) { async loadDetail(id) {
const res = await api.getDetail(id); const res = await api.getDetail(id);
@@ -1144,13 +1216,15 @@ export default {
}; };
}, },
addressTransportType(target) { addressTransportType(target) {
if (target.startsWith('route-')) { const key = String(target || '');
return this.form.routes[Number(target.slice(6))]?.transportType || ''; if (key.startsWith('route-')) {
return String(this.form.routes[Number(key.slice(6))]?.transportType || '');
} }
return target === 'arrival' ? this.form.finalTransportType : this.form.routes[0]?.transportType || ''; if (key === 'arrival') return String(this.form.finalTransportType || '');
return String(this.form.routes[0]?.transportType || '');
}, },
addressTransportMode(target) { addressTransportMode(target) {
const type = this.addressTransportType(target); const type = String(this.addressTransportType(target) || '');
if (type.includes('铁路')) return 'rail'; if (type.includes('铁路')) return 'rail';
if (type.includes('水路') || type.includes('水运') || type.includes('海运')) return 'water'; if (type.includes('水路') || type.includes('水运') || type.includes('海运')) return 'water';
if (type.includes('航空') || type.includes('空运')) return 'air'; if (type.includes('航空') || type.includes('空运')) return 'air';
@@ -1164,8 +1238,7 @@ export default {
const field = this.addressModel(target); const field = this.addressModel(target);
if (!field) return false; if (!field) return false;
const siteCode = String(field.model[field.siteCode] || '').trim(); const siteCode = String(field.model[field.siteCode] || '').trim();
// 地址展示模式由实际选择的数据决定:有站点编码表示非公路站点名称, // 展示模式由已选数据决定:有站点编码则显示站点名,否则省市区;暂不按运输方式强制站点限制。
// 没有站点编码则保留省市区级联,避免切换运输方式时改写已有地址。
return Boolean(siteCode && siteCode !== '/'); return Boolean(siteCode && siteCode !== '/');
}, },
resolveAddressRegionCode(row = {}) { resolveAddressRegionCode(row = {}) {
@@ -1174,12 +1247,7 @@ export default {
openCommonAddress(target) { openCommonAddress(target) {
if (this.isBasicRouteReadonly) return; if (this.isBasicRouteReadonly) return;
this.addressTarget = target; this.addressTarget = target;
if (this.addressTransportMode(target) !== 'road') { // 暂不按运输方式限制站点:统一走常用地址选择。
this.stationQuery = {};
this.stationPage.current = 1;
this.stationDialogVisible = true;
return;
}
this.addressQuery = {}; this.addressQuery = {};
this.addressPage.current = 1; this.addressPage.current = 1;
this.addressDialogVisible = true; this.addressDialogVisible = true;
@@ -1224,8 +1292,9 @@ export default {
this.stationPage.current = 1; this.stationPage.current = 1;
this.loadStationList(); this.loadStationList();
}, },
selectStation(row) { async selectStation(row) {
const target = this.addressModel(this.addressTarget); const targetKey = this.addressTarget;
const target = this.addressModel(targetKey);
if (!target) return; if (!target) return;
const station = this.normalizeStation(row); const station = this.normalizeStation(row);
target.model[target.name] = station.stationName || station.regionName; target.model[target.name] = station.stationName || station.regionName;
@@ -1236,7 +1305,9 @@ export default {
target.model[target.siteCode] = station.stationCode; target.model[target.siteCode] = station.stationCode;
this.setAddressRegionNamesFromRow(target, station); this.setAddressRegionNamesFromRow(target, station);
this.stationDialogVisible = false; this.stationDialogVisible = false;
this.loadRegionOptions().then(() => this.syncAddressRegionPath(this.addressTarget)); await this.loadRegionOptions();
await this.$nextTick();
this.syncAddressRegionPath(targetKey);
}, },
async loadAddressList() { async loadAddressList() {
this.addressLoading = true; this.addressLoading = true;
@@ -1262,8 +1333,9 @@ export default {
this.addressPage.current = 1; this.addressPage.current = 1;
this.loadAddressList(); this.loadAddressList();
}, },
selectAddress(row) { async selectAddress(row) {
const target = this.addressModel(this.addressTarget); const targetKey = this.addressTarget;
const target = this.addressModel(targetKey);
if (!target) return; if (!target) return;
target.model[target.name] = row.regionName || row.addressName || ''; target.model[target.name] = row.regionName || row.addressName || '';
target.model[target.address] = row.detailAddress || row.address || ''; target.model[target.address] = row.detailAddress || row.address || '';
@@ -1275,22 +1347,17 @@ export default {
target.model[target.siteCode] = ''; target.model[target.siteCode] = '';
this.setAddressRegionNamesFromRow(target, row); this.setAddressRegionNamesFromRow(target, row);
this.addressDialogVisible = false; this.addressDialogVisible = false;
this.loadRegionOptions().then(() => this.syncAddressRegionPath(this.addressTarget)); await this.loadRegionOptions();
// nextTick 确保途经点动态 key 已挂载 cascader 后再回填路径
await this.$nextTick();
this.syncAddressRegionPath(targetKey);
}, },
handleAddressDetailClick(target) { handleAddressDetailClick(target) {
if (this.isBasicRouteReadonly) return; if (this.isBasicRouteReadonly) return;
if (this.addressTransportMode(target) !== 'road') {
this.openCommonAddress(target);
return;
}
this.openAddressMap(target); this.openAddressMap(target);
}, },
openAddressMap(target) { openAddressMap(target) {
if (this.isBasicRouteReadonly) return; if (this.isBasicRouteReadonly) return;
if (this.addressTransportMode(target) !== 'road') {
this.$message.info('非公路运输地址不能使用地图选择');
return;
}
const field = this.addressModel(target); const field = this.addressModel(target);
if (!field) return; if (!field) return;
this.mapTarget = target; this.mapTarget = target;
@@ -1690,7 +1757,11 @@ export default {
'', '',
cargoTypePath, cargoTypePath,
quantity: row.quantity || '', quantity: row.quantity || '',
quantityUnit: row.quantityUnit || this.defaultQuantityUnit(), // 常用货物单位若为空或「个」,新建总单统一默认「吨」
quantityUnit:
!row.quantityUnit || String(row.quantityUnit).trim() === '个'
? this.defaultQuantityUnit()
: row.quantityUnit,
packageType: row.packageType || row.package || '', packageType: row.packageType || row.package || '',
brand: row.brand || '', brand: row.brand || '',
specification: row.specification || row.spec || '', specification: row.specification || row.spec || '',
@@ -1775,17 +1846,21 @@ export default {
return formatUpdateUserName(row); return formatUpdateUserName(row);
}, },
selectRoute(route) { selectRoute(route) {
// 先写入运输方式(会触发地点清空),再回填线路地址,避免被清空逻辑覆盖。
this.form.finalTransportType = '公路运输';
if (!this.form.routes.length) this.addRoute();
if (this.form.routes[0]) this.form.routes[0].transportType = '公路运输';
this.form.departureName = route.departureName || ''; this.form.departureName = route.departureName || '';
this.form.departureAddress = route.departureAddress || ''; this.form.departureAddress = route.departureAddress || '';
this.form.departureContact = route.departureContact || ''; this.form.departureContact = route.departureContact || '';
this.form.departurePhone = route.departurePhone || ''; this.form.departurePhone = route.departurePhone || '';
this.form.departureSiteCode = '';
this.form.arrivalName = route.arrivalName || ''; this.form.arrivalName = route.arrivalName || '';
this.form.arrivalAddress = route.arrivalAddress || ''; this.form.arrivalAddress = route.arrivalAddress || '';
this.form.arrivalContact = route.arrivalContact || ''; this.form.arrivalContact = route.arrivalContact || '';
this.form.arrivalPhone = route.arrivalPhone || ''; this.form.arrivalPhone = route.arrivalPhone || '';
this.form.arrivalSiteCode = '';
this.syncAddressRegionPaths(); this.syncAddressRegionPaths();
this.form.finalTransportType = '公路运输';
if (!this.form.routes.length) this.addRoute();
this.routeDialogVisible = false; this.routeDialogVisible = false;
}, },
createRoute(index = this.form.routes.length) { createRoute(index = this.form.routes.length) {
@@ -1793,12 +1868,23 @@ export default {
}, },
addRoute() { addRoute() {
if (this.isBasicRouteReadonly) return; if (this.isBasicRouteReadonly) return;
this.form.routes.push(this.createRoute()); const index = this.form.routes.length;
this.form.routes.push(this.createRoute(index));
this.addressRegionPaths[`route-${index}`] = [];
}, },
removeRoute(index) { removeRoute(index) {
if (this.isBasicRouteReadonly) return; if (this.isBasicRouteReadonly) return;
this.form.routes.splice(index, 1); this.form.routes.splice(index, 1);
this.syncAddressRegionPaths(); const nextPaths = { departure: this.addressRegionPaths.departure || [], arrival: this.addressRegionPaths.arrival || [] };
this.form.routes.forEach((route, routeIndex) => {
nextPaths[`route-${routeIndex}`] = this.addressRegionPaths[`route-${routeIndex >= index ? routeIndex + 1 : routeIndex}`] || [];
});
this.suppressAddressRegionChange = true;
this.addressRegionPaths = nextPaths;
this.$nextTick(() => {
this.suppressAddressRegionChange = false;
this.syncAddressRegionPaths();
});
}, },
goodsKey(row = {}) { goodsKey(row = {}) {
return `${row.cargoName || ''}\u0000${row.cargoType || ''}`; return `${row.cargoName || ''}\u0000${row.cargoType || ''}`;
@@ -10,7 +10,7 @@
:class="{ 'punch-record-photos__grid--edit': editable }" :class="{ 'punch-record-photos__grid--edit': editable }"
> >
<div <div
v-for="(item, groupIndex) in group.items" v-for="item in group.items"
:key="`${item.photo.url}-${item.index}`" :key="`${item.photo.url}-${item.index}`"
class="punch-record-photos__item" class="punch-record-photos__item"
@click="handlePreview(item.index)" @click="handlePreview(item.index)"
@@ -23,8 +23,6 @@
:lazy="lazy" :lazy="lazy"
fit="cover" fit="cover"
/> />
<!-- 主图 = 该类型的第一张,按上传顺序取;没有独立字段,也不可切换 -->
<div v-if="groupIndex === 0" class="punch-record-photos__badge">主图</div>
<el-link <el-link
v-if="editable" v-if="editable"
class="punch-record-photos__remove" class="punch-record-photos__remove"
@@ -59,7 +57,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
// 打卡照片是每个打卡节点必有的一个照片类型,不由过程配置的凭证类型决定; // 打卡照片是每个打卡节点必有的一个照片类型,不由过程配置的凭证类型决定;
// 它与各凭证类型各自最多 9 张,同一类型下第一张即界面上的「主图」。 // 它与各凭证类型各自最多 9 张。
const PUNCH_PHOTO_TYPE = '打卡照片'; const PUNCH_PHOTO_TYPE = '打卡照片';
// 照片没带类型时的兜底名称,与后端落库时的兜底口径一致 // 照片没带类型时的兜底名称,与后端落库时的兜底口径一致
const PHOTO_TYPE_FALLBACK = '凭证'; const PHOTO_TYPE_FALLBACK = '凭证';
@@ -252,18 +250,6 @@ const countText = group =>
} }
} }
&__badge {
position: absolute;
top: 0;
left: 0;
padding: 0 6px;
border-radius: 4px 0 4px 0;
background: rgba(64, 158, 255, 0.9);
font-size: 12px;
line-height: 18px;
color: #fff;
}
&__remove { &__remove {
align-self: center; align-self: center;
font-size: 12px; font-size: 12px;
@@ -388,7 +388,7 @@
:disabled="dialogReadonly" :disabled="dialogReadonly"
@visible-change="visible => visible && loadFeeUnitOptions()" @visible-change="visible => visible && loadFeeUnitOptions()"
><el-option ><el-option
v-for="unit in feeUnitOptions" v-for="unit in feeUnitSelectOptions"
:key="unit.value" :key="unit.value"
:label="unit.label" :label="unit.label"
:value="unit.value" /></el-select :value="unit.value" /></el-select
@@ -1124,6 +1124,10 @@ import { getDictionary as getSystemDictionary } from '@/api/system/dict';
import { getDictionary } from '@/api/system/dictbiz'; import { getDictionary } from '@/api/system/dictbiz';
import { packageOptions } from '@/option/business/common'; import { packageOptions } from '@/option/business/common';
import { formatContractOptionLabel } from '@/utils/contract'; import { formatContractOptionLabel } from '@/utils/contract';
import {
mapFeeUnitOptions,
resolvePriceUnitByQuantityUnit,
} from '@/utils/currency';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit'; import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search'; import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { downloadFileByUrl, downloadXls } from '@/utils/util'; import { downloadFileByUrl, downloadXls } from '@/utils/util';
@@ -1264,6 +1268,10 @@ const PageAvueForm = {
}, },
}; };
const shippingTemplateListRoute = '/business/shipping-template';
const shippingTemplateFormRoute = '/business/shipping-template/form';
const shippingTemplateListRefreshKey = 'shipping-template-list-refresh';
export default { export default {
name: 'ShippingTemplatePage', name: 'ShippingTemplatePage',
components: { PageAvueForm, ElImageViewer, OpenFileViewer }, components: { PageAvueForm, ElImageViewer, OpenFileViewer },
@@ -1280,6 +1288,9 @@ export default {
return { return {
Location, Location,
OfficeBuilding, OfficeBuilding,
// 实例所属路由快照,避免 keep-alive 列表实例被全局 $route 误判成表单页。
routePathLocked: this.$route.path,
isFormPageInstance: this.$route.path === shippingTemplateFormRoute,
form: {}, form: {},
query: {}, query: {},
loading: true, loading: true,
@@ -1401,13 +1412,18 @@ export default {
isStandaloneBusinessPage() { isStandaloneBusinessPage() {
return ( return (
this.standaloneFormPage && this.standaloneFormPage &&
['/business/shipping-template', '/business/shipping-template/form'].includes( (this.isFormPageInstance ||
this.$route.path [shippingTemplateListRoute, shippingTemplateFormRoute].includes(this.routePathLocked) ||
) [shippingTemplateListRoute, shippingTemplateFormRoute].includes(this.$route.path))
); );
}, },
isStandaloneFormPage() { isStandaloneFormPage() {
return this.isStandaloneBusinessPage && ['add', 'edit'].includes(this.$route.query.mode); // 用实例锁定标志,避免列表 keep-alive 实例被全局路由误判成表单页。
return (
this.standaloneFormPage &&
this.isFormPageInstance &&
['add', 'edit'].includes(this.$route.query.mode)
);
}, },
formPageTitle() { formPageTitle() {
return this.isStandaloneFormPage return this.isStandaloneFormPage
@@ -1464,6 +1480,12 @@ export default {
); );
return this.currencyUnit(option?.label || this.shippingTemplateFreight.currency); return this.currencyUnit(option?.label || this.shippingTemplateFreight.currency);
}, },
feeUnitSelectOptions() {
return mapFeeUnitOptions(
this.feeUnitOptions,
this.shippingTemplateFreight.currency || 'RMB'
);
},
roadFreightTotal() { roadFreightTotal() {
const total = this.shippingTemplateFreight.freightItems.reduce( const total = this.shippingTemplateFreight.freightItems.reduce(
(sum, item) => sum + Number(this.freightAmount(item) || 0), (sum, item) => sum + Number(this.freightAmount(item) || 0),
@@ -1530,6 +1552,18 @@ export default {
}, },
watch: { watch: {
$route(to, from) { $route(to, from) {
// 列表实例:从其他页进入列表时刷新(activated 负责标签切回)。
if (!this.isFormPageInstance) {
const enteringList =
(to.path === this.routePathLocked || to.path === shippingTemplateListRoute) &&
from?.path &&
from.path !== to.path;
if (enteringList) {
sessionStorage.removeItem(shippingTemplateListRefreshKey);
this.$nextTick(() => this.onLoad(this.page, this.query));
}
return;
}
if ( if (
this.isStandaloneFormPage && this.isStandaloneFormPage &&
(to.query.mode !== from?.query?.mode || (to.query.mode !== from?.query?.mode ||
@@ -1550,6 +1584,10 @@ export default {
this.handleTransportTypeChange(value); this.handleTransportTypeChange(value);
this.syncFreightItems(); this.syncFreightItems();
}, },
'form.templateType'() {
// 切换到运单模板时立即按货物数量单位生成公路运费明细。
this.$nextTick(() => this.syncFreightItems());
},
}, },
created() { created() {
this.loadProjectOptions(); this.loadProjectOptions();
@@ -1559,8 +1597,16 @@ export default {
this.loadCurrencyOptions(); this.loadCurrencyOptions();
if (this.isStandaloneFormPage) this.$nextTick(() => this.initStandaloneFormPage()); if (this.isStandaloneFormPage) this.$nextTick(() => this.initStandaloneFormPage());
}, },
activated() {
if (this.isFormPageInstance) return;
sessionStorage.removeItem(shippingTemplateListRefreshKey);
this.$nextTick(() => this.onLoad(this.page, this.query));
},
methods: { methods: {
formatContractOptionLabel, formatContractOptionLabel,
markShippingTemplateListNeedRefresh() {
sessionStorage.setItem(shippingTemplateListRefreshKey, '1');
},
loadQuantityUnitOptions() { loadQuantityUnitOptions() {
if (this.quantityUnitLoading) return this.quantityUnitLoadPromise; if (this.quantityUnitLoading) return this.quantityUnitLoadPromise;
this.quantityUnitLoading = true; this.quantityUnitLoading = true;
@@ -1591,12 +1637,8 @@ export default {
}); });
} }
if (this.menuWidth != null) next.menuWidth = this.menuWidth; if (this.menuWidth != null) next.menuWidth = this.menuWidth;
if ( // 仅表单页实例套用全屏表单弹窗样式,避免列表页误用后表格区域异常。
this.standaloneFormPage && if (this.standaloneFormPage && this.$route.path === shippingTemplateFormRoute) {
['/business/shipping-template', '/business/shipping-template/form'].includes(
this.$route.path
)
) {
next.dialogAppendToBody = false; next.dialogAppendToBody = false;
next.dialogModal = false; next.dialogModal = false;
next.dialogCustomClass = 'shipping-template-form-page-dialog'; next.dialogCustomClass = 'shipping-template-form-page-dialog';
@@ -1776,13 +1818,22 @@ export default {
this.onLoad(this.page, this.query); this.onLoad(this.page, this.query);
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
if (this.isFormPageInstance) return;
this.loading = true; this.loading = true;
const currentPage = page?.currentPage || this.page.currentPage || 1;
const pageSize = page?.pageSize || this.page.pageSize || 10;
this.api this.api
.getList(page.currentPage, page.pageSize, { ...this.query, ...params }) .getList(currentPage, pageSize, { ...this.query, ...params })
.then(res => { .then(res => {
const data = res?.data?.data || {}; const data = res?.data?.data || res?.data || {};
this.data = extractRecords(res); const records = extractRecords(res);
this.page.total = data.total || this.data.length; this.data = records;
this.page.total = Number(data.total ?? records.length) || 0;
})
.catch(error => {
window.console.error(error);
this.data = [];
this.page.total = 0;
}) })
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
@@ -1821,9 +1872,10 @@ export default {
.submit(submit) .submit(submit)
.then(() => { .then(() => {
this.$message.success('操作成功'); this.$message.success('操作成功');
this.markShippingTemplateListNeedRefresh();
done?.(); done?.();
this.closeCrudDialog(); this.closeCrudDialog();
this.onLoad(this.page, this.query); if (!this.isFormPageInstance) this.onLoad(this.page, this.query);
}) })
.catch(() => loading?.()); .catch(() => loading?.());
}, },
@@ -1837,9 +1889,10 @@ export default {
.submit(submit) .submit(submit)
.then(() => { .then(() => {
this.$message.success('操作成功'); this.$message.success('操作成功');
this.markShippingTemplateListNeedRefresh();
done?.(); done?.();
this.closeCrudDialog(); this.closeCrudDialog();
this.onLoad(this.page, this.query); if (!this.isFormPageInstance) this.onLoad(this.page, this.query);
}) })
.catch(() => loading?.()); .catch(() => loading?.());
}, },
@@ -1883,6 +1936,8 @@ export default {
this.attachmentRows = []; this.attachmentRows = [];
this.shippingTemplateFreight = defaultFreight(); this.shippingTemplateFreight = defaultFreight();
this.loadCurrencyOptions(); this.loadCurrencyOptions();
this.loadFeeUnitOptions();
this.$nextTick(() => this.syncFreightItems());
if (typeof this.api.nextCode === 'function') if (typeof this.api.nextCode === 'function')
this.api this.api
.nextCode() .nextCode()
@@ -2005,20 +2060,47 @@ export default {
return false; return false;
} }
if (this.transportMode === 'road') { if (this.transportMode === 'road') {
const invalid = this.shippingTemplateFreight.freightItems.findIndex( if (!this.shippingTemplateFreight.freightItems.length) {
item => this.$message.warning('请先完善货物数量单位,以便生成运费明细');
Number(item.unitPrice || 0) < 0 ||
!item.priceUnit ||
(!this.isFreightAutoCalculable(item) &&
(item.freightAmount === '' ||
item.freightAmount === null ||
item.freightAmount === undefined)) ||
Number(item.freightAmount || 0) < 0
);
if (invalid > -1) {
this.$message.warning(`第${invalid + 1}条货物的单价、计价单位或运费无效`);
return false; return false;
} }
for (const [index, item] of this.shippingTemplateFreight.freightItems.entries()) {
const label = `第${index + 1}条运费明细`;
if (
item.unitPrice === '' ||
item.unitPrice === null ||
item.unitPrice === undefined
) {
this.$message.warning(`请填写${label}的单价`);
return false;
}
if (Number(item.unitPrice) < 0) {
this.$message.warning(`${label}的单价不能小于0`);
return false;
}
if (!String(item.priceUnit || '').trim()) {
this.$message.warning(`请选择${label}的计价单位`);
return false;
}
if (this.isFreightAutoCalculable(item)) {
if (!item.freightAmountManual) {
item.freightAmount = this.freightAmount(item);
}
} else if (
item.freightAmount === '' ||
item.freightAmount === null ||
item.freightAmount === undefined
) {
this.$message.warning(
`${label}的计价单位与货物数量单位不匹配,无法自动计算运费,请手动填写运费`
);
return false;
}
if (Number(item.freightAmount || 0) < 0) {
this.$message.warning(`${label}的运费不能小于0`);
return false;
}
}
} else if (Number(this.shippingTemplateFreight.freightAmount || 0) < 0) { } else if (Number(this.shippingTemplateFreight.freightAmount || 0) < 0) {
this.$message.warning('运费不能小于0'); this.$message.warning('运费不能小于0');
return false; return false;
@@ -2033,8 +2115,11 @@ export default {
return true; return true;
}, },
closeCrudDialog() { closeCrudDialog() {
if (this.isStandaloneBusinessPage) { if (this.isStandaloneBusinessPage || this.isFormPageInstance) {
this.$router.push({ path: '/business/shipping-template', query: {} }); // 关闭独立表单页时标记列表需刷新(含直接点关闭未保存的场景)
this.markShippingTemplateListNeedRefresh();
this.$router.$avueRouter?.closeTag?.();
this.$router.push({ path: shippingTemplateListRoute, query: {} });
return; return;
} }
this.$refs.crud?.closeDialog?.(); this.$refs.crud?.closeDialog?.();
@@ -2294,7 +2379,16 @@ export default {
}, },
loadFeeUnitOptions() { loadFeeUnitOptions() {
getDictionary({ code: 'unit_fee' }).then(res => { getDictionary({ code: 'unit_fee' }).then(res => {
this.feeUnitOptions = this.normalizeDictOptions(res?.data?.data || []); // 计价单位必须保存可读文案(如 元/吨),才能与货物数量单位匹配并自动计算运费。
this.feeUnitOptions = (res?.data?.data || []).map(item => {
const label = String(item.dictValue || item.label || item.value || '').trim();
const key = String(item.dictKey || item.value || '').trim();
return {
label: label || key,
value: label.includes('/') ? label : key || label,
remark: item.remark || '',
};
});
}); });
}, },
loadCurrencyOptions() { loadCurrencyOptions() {
@@ -3098,24 +3192,53 @@ export default {
: []; : [];
const quantityUnits = []; const quantityUnits = [];
this.transportCargoRows.forEach(row => { this.transportCargoRows.forEach(row => {
const quantityUnit = row.quantityUnit || ''; const quantityUnit = String(row.quantityUnit || '').trim();
if (!quantityUnit) return;
if (!quantityUnits.some(unit => unit === quantityUnit)) quantityUnits.push(quantityUnit); if (!quantityUnits.some(unit => unit === quantityUnit)) quantityUnits.push(quantityUnit);
}); });
const currency = this.shippingTemplateFreight.currency || 'RMB';
this.shippingTemplateFreight.freightItems = quantityUnits.map((quantityUnit, index) => { this.shippingTemplateFreight.freightItems = quantityUnits.map((quantityUnit, index) => {
const matched = const matched =
previousItems.find(item => String(item.quantityUnit || '') === String(quantityUnit)) || previousItems.find(item => String(item.quantityUnit || '') === String(quantityUnit)) ||
previousItems[index] || previousItems[index] ||
{}; {};
const defaultPriceUnit = resolvePriceUnitByQuantityUnit(quantityUnit, currency);
const priceUnit = this.normalizeFreightPriceUnit(
matched.priceUnit || defaultPriceUnit || '',
quantityUnit,
currency
);
return { return {
quantityUnit, quantityUnit,
unitPrice: matched.unitPrice || '', unitPrice: matched.unitPrice || '',
priceUnit: matched.priceUnit || '', priceUnit,
freightAmount: matched.freightAmount ?? '', freightAmount: matched.freightAmount ?? '',
freightAmountManual: freightAmountManual:
matched.freightAmountManual === true || matched.manualFreightAmount === true, matched.freightAmountManual === true || matched.manualFreightAmount === true,
}; };
}); });
}, },
normalizeFreightPriceUnit(priceUnit, quantityUnit, currency = 'RMB') {
const text = this.resolvePriceUnitText(priceUnit);
if (text && this.priceUnitMatchesQuantity(text, quantityUnit)) return text;
return (
resolvePriceUnitByQuantityUnit(quantityUnit, currency) ||
text ||
String(priceUnit || '').trim()
);
},
resolvePriceUnitText(priceUnit) {
const raw = String(priceUnit || '').trim();
if (!raw) return '';
if (raw.includes('/')) return raw;
const options = this.feeUnitSelectOptions.length
? this.feeUnitSelectOptions
: this.feeUnitOptions;
const matched = options.find(
item => String(item.value) === raw || String(item.label) === raw
);
return matched?.label || raw;
},
quantityUnits() { quantityUnits() {
return [ return [
...new Set( ...new Set(
@@ -3131,7 +3254,7 @@ export default {
.replace(/\s+/g, '') .replace(/\s+/g, '')
.replace(/立方米|立方公尺|方/g, '立方'); .replace(/立方米|立方公尺|方/g, '立方');
const unit = normalizeUnit(quantityUnit).replace(/[·*.]/g, ''); const unit = normalizeUnit(quantityUnit).replace(/[·*.]/g, '');
const value = String(priceUnit || '') const value = String(this.resolvePriceUnitText(priceUnit) || '')
.trim() .trim()
.toLowerCase() .toLowerCase()
.replace(/\s+/g, ''); .replace(/\s+/g, '');
@@ -3163,6 +3286,10 @@ export default {
return this.formatFreightNumber(item.freightAmount); return this.formatFreightNumber(item.freightAmount);
} }
if (!this.isFreightAutoCalculable(item)) { if (!this.isFreightAutoCalculable(item)) {
// 无法自动计算且未手工填写时返回空,避免把空值显示成 0 造成“已填写”的错觉。
if (item.freightAmount === '' || item.freightAmount === null || item.freightAmount === undefined) {
return '';
}
return this.formatFreightNumber(item.freightAmount); return this.formatFreightNumber(item.freightAmount);
} }
return this.formatFreightNumber( return this.formatFreightNumber(
@@ -1677,7 +1677,7 @@
{{ {{
isDispatchPlanQuantityUnlimited(row) isDispatchPlanQuantityUnlimited(row)
? '-' ? '-'
: formatDispatchQuantity(dispatchItemRemainingQuantity(row)) : formatDispatchQuantity(dispatchItemDisplayRemainingQuantity(row))
}} }}
</template> </template>
</el-table-column> </el-table-column>
@@ -1861,7 +1861,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="司机" :required="dispatchItemForm.carrierType !== '承运商'"> <el-form-item label="司机" required>
<el-autocomplete <el-autocomplete
v-model="dispatchItemForm.driverName" v-model="dispatchItemForm.driverName"
placeholder="请输入" placeholder="请输入"
@@ -1870,11 +1870,15 @@
:fetch-suggestions="fetchTaskDriverSuggestions" :fetch-suggestions="fetchTaskDriverSuggestions"
:loading="taskDriverLoading" :loading="taskDriverLoading"
@select="item => handleTaskDriverSelect('dispatch', item)" @select="item => handleTaskDriverSelect('dispatch', item)"
/> >
<template #default="{ item }">
<span>{{ driverOptionText(item) }}</span>
</template>
</el-autocomplete>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
:label="dispatchItemForm.carrierType === '承运商' ? '手机号' : '司机手机号'" :label="dispatchItemForm.carrierType === '承运商' ? '手机号' : '司机手机号'"
:required="dispatchItemForm.carrierType !== '承运商'" required
> >
<el-input v-model="dispatchItemForm.driverPhone" placeholder="请输入" /> <el-input v-model="dispatchItemForm.driverPhone" placeholder="请输入" />
</el-form-item> </el-form-item>
@@ -1893,7 +1897,11 @@
:fetch-suggestions="fetchTaskEscortSuggestions" :fetch-suggestions="fetchTaskEscortSuggestions"
:loading="taskEscortLoading" :loading="taskEscortLoading"
@select="handleTaskEscortSelect" @select="handleTaskEscortSelect"
/> >
<template #default="{ item }">
<span>{{ driverOptionText(item) }}</span>
</template>
</el-autocomplete>
</el-form-item> </el-form-item>
<el-form-item v-if="dispatchItemForm.carrierType !== '承运商'" label="押运人手机号"> <el-form-item v-if="dispatchItemForm.carrierType !== '承运商'" label="押运人手机号">
<el-input v-model="dispatchItemForm.escortPhone" placeholder="请输入" /> <el-input v-model="dispatchItemForm.escortPhone" placeholder="请输入" />
@@ -1955,7 +1963,7 @@
class="transport-plan-page__dispatch-quantity-hint" class="transport-plan-page__dispatch-quantity-hint"
> >
剩余数量:{{ 剩余数量:{{
formatDispatchQuantity(dispatchItemHintRemainingQuantity(dispatchItemForm)) formatDispatchQuantity(dispatchItemDisplayRemainingQuantity(dispatchItemForm))
}} }}
{{ dispatchItemForm.quantityUnit || '吨' }} {{ dispatchItemForm.quantityUnit || '吨' }}
</div> </div>
@@ -2096,11 +2104,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="dispatchIsRoadTransport" label="司机" required>
v-if="dispatchIsRoadTransport"
label="司机"
:required="dispatchItemForm.carrierType === '自运'"
>
<el-autocomplete <el-autocomplete
v-model="dispatchItemForm.driverName" v-model="dispatchItemForm.driverName"
placeholder="请输入" placeholder="请输入"
@@ -2111,11 +2115,7 @@
@select="item => handleTaskDriverSelect('dispatch', item)" @select="item => handleTaskDriverSelect('dispatch', item)"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="dispatchIsRoadTransport" label="手机号" required>
v-if="dispatchIsRoadTransport"
label="手机号"
:required="dispatchItemForm.carrierType === '自运'"
>
<el-input v-model="dispatchItemForm.driverPhone" placeholder="请输入" /> <el-input v-model="dispatchItemForm.driverPhone" placeholder="请输入" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
@@ -2768,7 +2768,7 @@ import {
getList as getProjectList, getList as getProjectList,
} from '@/api/business/project-apply'; } from '@/api/business/project-apply';
import { getList as getCommonRouteList } from '@/api/business/common-route'; import { getList as getCommonRouteList } from '@/api/business/common-route';
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource'; import { fetchDriverCandidates, driverOptionText } from '@/utils/carrier-org-resource';
import { getDictionary } from '@/api/system/dictbiz'; import { getDictionary } from '@/api/system/dictbiz';
import { getDictionary as getSystemDictionary } from '@/api/system/dict'; import { getDictionary as getSystemDictionary } from '@/api/system/dict';
import { addressTypeOptions } from '@/option/base/common-address'; import { addressTypeOptions } from '@/option/base/common-address';
@@ -2807,8 +2807,11 @@ import 'nprogress/nprogress.css';
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315'; const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a'; const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
let amapLoader; let amapLoader;
const transportPlanListRoute = '/business/transport-plan';
const transportPlanFormRoute = '/business/transport-plan/form';
const transportPlanListRefreshKey = 'transport-plan-list-refresh';
const standaloneTransportPlanFormRoutes = { const standaloneTransportPlanFormRoutes = {
'/business/transport-plan': '/business/transport-plan/form', [transportPlanListRoute]: transportPlanFormRoute,
}; };
const standaloneTransportPlanFormPaths = [ const standaloneTransportPlanFormPaths = [
...Object.keys(standaloneTransportPlanFormRoutes), ...Object.keys(standaloneTransportPlanFormRoutes),
@@ -3072,6 +3075,10 @@ export default {
// 必须放 data:Options API 的顺序是 data → computed → watch(immediate) → created, // 必须放 data:Options API 的顺序是 data → computed → watch(immediate) → created,
// 放 created 的话首个 immediate watcher 会读到空值,把首次加载也挡掉。 // 放 created 的话首个 immediate watcher 会读到空值,把首次加载也挡掉。
routePathLocked: this.$route.path, routePathLocked: this.$route.path,
// 表单页实例在创建时锁定,避免 keep-alive 列表实例随全局 $route 误切到表单态。
isFormPageInstance: Object.values(standaloneTransportPlanFormRoutes).includes(
this.$route.path
),
form: {}, form: {},
suppressTransportTypeClear: false, suppressTransportTypeClear: false,
query: {}, query: {},
@@ -3112,10 +3119,11 @@ export default {
option: (() => { option: (() => {
const opt = this.cloneOption(this.crudOption, estimateMenuButtonCount(this.config)); const opt = this.cloneOption(this.crudOption, estimateMenuButtonCount(this.config));
if (this.menuWidth != null) opt.menuWidth = this.menuWidth; if (this.menuWidth != null) opt.menuWidth = this.menuWidth;
if ( // 仅表单页实例套用全屏表单弹窗样式,避免列表页误用后表格区域异常。
this.standaloneFormPage && const isFormInstance = Object.values(standaloneTransportPlanFormRoutes).includes(
standaloneTransportPlanFormPaths.includes(this.$route.path) this.$route.path
) { );
if (this.standaloneFormPage && isFormInstance) {
opt.dialogAppendToBody = false; opt.dialogAppendToBody = false;
opt.dialogModal = false; opt.dialogModal = false;
opt.dialogCustomClass = 'transport-plan-form-page-dialog'; opt.dialogCustomClass = 'transport-plan-form-page-dialog';
@@ -3150,6 +3158,8 @@ export default {
dispatchItemIndex: -1, dispatchItemIndex: -1,
dispatchItemForm: defaultDispatchRow(), dispatchItemForm: defaultDispatchRow(),
dispatchItemCargoRows: [], dispatchItemCargoRows: [],
// 本次数量变更版本号:驱动同表其他行「剩余数量」同步重算
dispatchCargoQuantityVersion: 0,
dispatchItemAttachmentRows: [], dispatchItemAttachmentRows: [],
selectedDispatchItemAttachmentRows: [], selectedDispatchItemAttachmentRows: [],
attachmentRows: [], attachmentRows: [],
@@ -3289,6 +3299,12 @@ export default {
} }
this.loadTaskQuantityUnitOptions(); this.loadTaskQuantityUnitOptions();
}, },
// keep-alive 重新激活列表时强制刷新,避免缓存脏数据;标记位用于兼容旧跳转逻辑。
activated() {
if (this.isFormPageInstance) return;
sessionStorage.removeItem(transportPlanListRefreshKey);
this.$nextTick(() => this.onLoad(this.page, this.query));
},
computed: { computed: {
...mapGetters(['permission', 'userInfo']), ...mapGetters(['permission', 'userInfo']),
permissionList() { permissionList() {
@@ -3300,10 +3316,20 @@ export default {
return this.config.detailSections || []; return this.config.detailSections || [];
}, },
isStandaloneBusinessPage() { isStandaloneBusinessPage() {
return this.standaloneFormPage && standaloneTransportPlanFormPaths.includes(this.$route.path); return (
this.standaloneFormPage &&
(this.isFormPageInstance ||
standaloneTransportPlanFormPaths.includes(this.routePathLocked) ||
standaloneTransportPlanFormPaths.includes(this.$route.path))
);
}, },
isStandaloneFormPage() { isStandaloneFormPage() {
return this.isStandaloneBusinessPage && ['add', 'edit'].includes(this.$route.query.mode); // 用实例锁定标志,避免列表 keep-alive 实例被全局路由误判成表单页。
return (
this.standaloneFormPage &&
this.isFormPageInstance &&
['add', 'edit'].includes(this.$route.query.mode)
);
}, },
formPageTitle() { formPageTitle() {
if (this.isStandaloneFormPage) { if (this.isStandaloneFormPage) {
@@ -3752,6 +3778,18 @@ export default {
}, },
watch: { watch: {
$route(to, from) { $route(to, from) {
// 列表实例:从其他页进入列表时刷新(activated 负责标签切回)。
if (!this.isFormPageInstance) {
const enteringList =
(to.path === this.routePathLocked || to.path === transportPlanListRoute) &&
from?.path &&
from.path !== to.path;
if (enteringList) {
sessionStorage.removeItem(transportPlanListRefreshKey);
this.$nextTick(() => this.onLoad(this.page, this.query));
}
return;
}
if (!this.isStandaloneFormPage) return; if (!this.isStandaloneFormPage) return;
if ( if (
to.query.mode !== from?.query?.mode || to.query.mode !== from?.query?.mode ||
@@ -3779,6 +3817,7 @@ export default {
}, },
methods: { methods: {
formatContractOptionLabel, formatContractOptionLabel,
driverOptionText,
initStandaloneFormPage() { initStandaloneFormPage() {
if (!this.isStandaloneFormPage) return; if (!this.isStandaloneFormPage) return;
const mode = this.$route.query.mode === 'edit' ? 'edit' : 'add'; const mode = this.$route.query.mode === 'edit' ? 'edit' : 'add';
@@ -4148,6 +4187,7 @@ export default {
waiting: ['待执行', 'is-pending'], waiting: ['待执行', 'is-pending'],
processing: ['进行中', 'is-processing'], processing: ['进行中', 'is-processing'],
running: ['进行中', 'is-processing'], running: ['进行中', 'is-processing'],
pending_return_confirm: ['待回单确认', 'is-pending'],
completed: ['已完成', 'is-completed'], completed: ['已完成', 'is-completed'],
cancelled: ['已取消', 'is-cancelled'], cancelled: ['已取消', 'is-cancelled'],
}; };
@@ -4484,6 +4524,9 @@ export default {
loading(); loading();
} }
}, },
markTransportPlanListNeedRefresh() {
sessionStorage.setItem(transportPlanListRefreshKey, '1');
},
rowSave(row, done, loading) { rowSave(row, done, loading) {
const submitRow = this.normalizeRow(row); const submitRow = this.normalizeRow(row);
if (!submitRow) { if (!submitRow) {
@@ -4492,6 +4535,7 @@ export default {
} }
this.getSaveRequest()(submitRow).then( this.getSaveRequest()(submitRow).then(
() => { () => {
this.markTransportPlanListNeedRefresh();
this.onLoad(this.page); this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' }); this.$message({ type: 'success', message: '操作成功!' });
done(); done();
@@ -4511,6 +4555,7 @@ export default {
} }
this.getSaveRequest()(submitRow).then( this.getSaveRequest()(submitRow).then(
() => { () => {
this.markTransportPlanListNeedRefresh();
this.onLoad(this.page); this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' }); this.$message({ type: 'success', message: '操作成功!' });
done(); done();
@@ -4540,7 +4585,7 @@ export default {
.submit(submitRow) .submit(submitRow)
.then(() => { .then(() => {
this.$message.success(action === 'draft' ? '暂存成功' : '创建成功'); this.$message.success(action === 'draft' ? '暂存成功' : '创建成功');
this.onLoad(this.page, this.query); this.markTransportPlanListNeedRefresh();
this.closeCrudDialog(); this.closeCrudDialog();
}) })
.finally(() => { .finally(() => {
@@ -4548,12 +4593,14 @@ export default {
}); });
}, },
closeCrudDialog() { closeCrudDialog() {
if (this.isStandaloneBusinessPage) { if (this.isStandaloneBusinessPage || this.isFormPageInstance) {
// 关闭独立表单页时标记列表需刷新(含直接点关闭未保存的场景)
this.markTransportPlanListNeedRefresh();
this.$router.$avueRouter?.closeTag?.(); this.$router.$avueRouter?.closeTag?.();
const listPath = const listPath =
Object.entries(standaloneTransportPlanFormRoutes).find( Object.entries(standaloneTransportPlanFormRoutes).find(
([, formPath]) => formPath === this.$route.path ([, formPath]) => formPath === this.$route.path || formPath === this.routePathLocked
)?.[0] || this.$route.path; )?.[0] || transportPlanListRoute;
this.$router.push({ path: listPath, query: {} }); this.$router.push({ path: listPath, query: {} });
return; return;
} }
@@ -4951,16 +4998,8 @@ export default {
}, },
fetchTaskDriverSuggestions(queryString, callback) { fetchTaskDriverSuggestions(queryString, callback) {
const keyword = String(queryString || '').trim(); const keyword = String(queryString || '').trim();
const carrier = this.getDispatchCarrierFilter();
if (!carrier.carrierId && !carrier.carrierName) {
callback([]);
return;
}
this.taskDriverLoading = true; this.taskDriverLoading = true;
fetchDriversByCarrierOrganizations( fetchDriverCandidates({ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
carrier
)
.then(records => { .then(records => {
this.taskDriverOptions = records; this.taskDriverOptions = records;
callback( callback(
@@ -4980,16 +5019,12 @@ export default {
}, },
fetchTaskEscortSuggestions(queryString, callback) { fetchTaskEscortSuggestions(queryString, callback) {
const keyword = String(queryString || '').trim(); const keyword = String(queryString || '').trim();
const carrier = this.getDispatchCarrierFilter();
if (!carrier.carrierId && !carrier.carrierName) {
callback([]);
return;
}
this.taskEscortLoading = true; this.taskEscortLoading = true;
fetchDriversByCarrierOrganizations( fetchDriverCandidates({
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' }, size: 20,
carrier ...(keyword ? { driverName: keyword } : {}),
) posts: '押运员',
})
.then(records => { .then(records => {
this.taskEscortOptions = records; this.taskEscortOptions = records;
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' }))); callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
@@ -6634,20 +6669,33 @@ export default {
return query; return query;
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
if (this.isFormPageInstance) return;
this.loading = true; this.loading = true;
const currentPage = page?.currentPage || this.page.currentPage || 1;
const pageSize = page?.pageSize || this.page.pageSize || 10;
const query = this.normalizeQueryParams({ const query = this.normalizeQueryParams({
...params, ...params,
...this.query, ...this.query,
allDept: 0, allDept: 0,
}); });
this.api this.api
.getList(page.currentPage, page.pageSize, query) .getList(currentPage, pageSize, query)
.then(res => { .then(res => {
const result = res.data.data; const result = res?.data?.data || res?.data || {};
this.page.total = result.total; const records = Array.isArray(result.records)
this.data = result.records; ? result.records
: Array.isArray(result)
? result
: [];
this.page.total = Number(result.total ?? records.length) || 0;
this.data = records;
this.selectionClear(); this.selectionClear();
}) })
.catch(error => {
window.console.error(error);
this.data = [];
this.page.total = 0;
})
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
}); });
@@ -7652,6 +7700,7 @@ export default {
row.cargoName = ''; row.cargoName = '';
row.specification = ''; row.specification = '';
row.model = ''; row.model = '';
this.dispatchCargoQuantityVersion += 1;
}, },
handleDispatchConfiguredCargoNameChange(row, value) { handleDispatchConfiguredCargoNameChange(row, value) {
const nextValue = String(value ?? row.cargoName ?? '').trim(); const nextValue = String(value ?? row.cargoName ?? '').trim();
@@ -7660,6 +7709,7 @@ export default {
); );
if (!option) { if (!option) {
row.cargoName = nextValue; row.cargoName = nextValue;
this.dispatchCargoQuantityVersion += 1;
return; return;
} }
const previousType = String(row.cargoType || '').trim(); const previousType = String(row.cargoType || '').trim();
@@ -7674,6 +7724,7 @@ export default {
} }
row.specification = option.specification || row.specification || ''; row.specification = option.specification || row.specification || '';
row.model = option.model || row.model || ''; row.model = option.model || row.model || '';
this.dispatchCargoQuantityVersion += 1;
}, },
openDispatchItemDialog(index = -1, row = defaultDispatchRow()) { openDispatchItemDialog(index = -1, row = defaultDispatchRow()) {
const defaultSummaryItem = const defaultSummaryItem =
@@ -7991,17 +8042,12 @@ export default {
this.taskDriverOptions = []; this.taskDriverOptions = [];
this.taskEscortOptions = []; this.taskEscortOptions = [];
}, },
getDispatchCarrierFilter() {
return {
carrierId: this.dispatchItemForm.carrierId || '',
carrierName: this.dispatchItemForm.carrierName || '',
};
},
handleDispatchCargoQuantityInput(row, value) { handleDispatchCargoQuantityInput(row, value) {
const text = String(value || '').replace(/[^\d.]/g, ''); const text = String(value || '').replace(/[^\d.]/g, '');
const parts = text.split('.'); const parts = text.split('.');
row.quantity = row.quantity =
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 3)}` : parts[0]; parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 3)}` : parts[0];
this.dispatchCargoQuantityVersion += 1;
}, },
handleDispatchCargoNumberInput(row, prop, value) { handleDispatchCargoNumberInput(row, prop, value) {
this.handleFreightNumberInput(row, prop, value); this.handleFreightNumberInput(row, prop, value);
@@ -8021,12 +8067,14 @@ export default {
const nextRow = this.normalizeTransportCargoRow({ priceUnit: this.defaultPriceUnit }); const nextRow = this.normalizeTransportCargoRow({ priceUnit: this.defaultPriceUnit });
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow); if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
else this.dispatchItemCargoRows.push(nextRow); else this.dispatchItemCargoRows.push(nextRow);
this.dispatchCargoQuantityVersion += 1;
}, },
removeDispatchCargoRow(index) { removeDispatchCargoRow(index) {
this.dispatchItemCargoRows.splice(index, 1); this.dispatchItemCargoRows.splice(index, 1);
if (!this.dispatchItemCargoRows.length) { if (!this.dispatchItemCargoRows.length) {
this.dispatchItemCargoRows.push(this.normalizeTransportCargoRow()); this.dispatchItemCargoRows.push(this.normalizeTransportCargoRow());
} }
this.dispatchCargoQuantityVersion += 1;
}, },
dispatchCargoFreightAmount(cargo = {}) { dispatchCargoFreightAmount(cargo = {}) {
if (cargo.freightAmount !== undefined && cargo.freightAmount !== null) { if (cargo.freightAmount !== undefined && cargo.freightAmount !== null) {
@@ -8176,11 +8224,50 @@ export default {
}, 0) }, 0)
); );
}, },
/** 精简录入提示:可调度余量扣减当前本次数量后的剩余 */ /**
dispatchItemHintRemainingQuantity(row = {}) { * 当前编辑表单中,与指定行同货物(有身份)或同单位(无身份)已占用的本次数量合计。
* 用于剩余数量展示联动,不参与提交校验(校验仍按整单合计对比计划余量)。
*/
getDispatchFormOccupiedQuantity(row = {}) {
const goodsRows =
this.dispatchItemForm?.taskEntryMode === 'full'
? this.dispatchItemCargoRows || []
: [this.dispatchItemForm || row];
const hasCargoIdentity = Boolean(
String(row.cargoName || row.goodsName || row.name || '').trim() ||
String(row.cargoType || row.secondCargoTypeName || row.goodsType || row.type || '').trim()
);
const unit = this.getDispatchQuantityUnit(row);
const cargoKey = this.getDispatchCargoIdentity(row);
return goodsRows.reduce((sum, goods) => {
if (!goods) return sum;
if (hasCargoIdentity) {
const goodsHasIdentity = Boolean(
String(goods.cargoName || goods.goodsName || goods.name || '').trim() ||
String(
goods.cargoType || goods.secondCargoTypeName || goods.goodsType || goods.type || ''
).trim()
);
if (!goodsHasIdentity || this.getDispatchCargoIdentity(goods) !== cargoKey) {
return sum;
}
} else if (this.getDispatchQuantityUnit(goods) !== unit) {
return sum;
}
return sum + this.parseDispatchQuantity(goods.quantity);
}, 0);
},
/** 展示用剩余数量:计划可调度余量扣减本单已填本次数量(含当前行) */
dispatchItemDisplayRemainingQuantity(row = {}) {
// 依赖版本号,确保一行本次数量变化后其他行剩余数量一并刷新
void this.dispatchCargoQuantityVersion;
const available = this.dispatchItemRemainingQuantity(row); const available = this.dispatchItemRemainingQuantity(row);
const current = this.parseDispatchQuantity(row.quantity); const occupied = this.getDispatchFormOccupiedQuantity(row);
return Math.max(available - current, 0); return Math.max(available - occupied, 0);
},
/** 精简录入提示:与完整录入剩余数量展示规则一致 */
dispatchItemHintRemainingQuantity(row = {}) {
return this.dispatchItemDisplayRemainingQuantity(row);
}, },
pruneDispatchPendingRows(rows = []) { pruneDispatchPendingRows(rows = []) {
const planQuantities = new Map(); const planQuantities = new Map();
@@ -8278,13 +8365,17 @@ export default {
const fullEntry = row.taskEntryMode === 'full'; const fullEntry = row.taskEntryMode === 'full';
const roadTransport = this.resolveTransportMode(row.transportType) === 'road'; const roadTransport = this.resolveTransportMode(row.transportType) === 'road';
if (fullEntry) { if (fullEntry) {
if (roadTransport && row.carrierType === '自运') { // 公路运输时司机/手机号一律必填(含承运类型为承运商)
if (roadTransport && row.carrierType) {
requiredFields.push(['driverName', '司机']); requiredFields.push(['driverName', '司机']);
requiredFields.push(['driverPhone', '手机号']); requiredFields.push(['driverPhone', '手机号']);
} }
} else if (row.carrierType && row.carrierType !== '承运商') { } else if (row.carrierType) {
requiredFields.push(['driverName', '司机']); requiredFields.push(['driverName', '司机']);
requiredFields.push(['driverPhone', '司机手机号']); requiredFields.push([
'driverPhone',
row.carrierType === '承运商' ? '手机号' : '司机手机号',
]);
} }
const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop])); const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
if (emptyField) return requiredMessage(emptyField[1]); if (emptyField) return requiredMessage(emptyField[1]);
@@ -96,22 +96,12 @@
prop="carrierName" prop="carrierName"
label="承运商" label="承运商"
min-width="140" min-width="140"
/><el-table-column
v-if="columnVisible.carrierType"
prop="carrierType"
label="承运类型"
min-width="120"
/> />
<el-table-column <el-table-column
v-if="columnVisible.waybillCount" v-if="columnVisible.waybillCount"
prop="waybillCount" prop="waybillCount"
label="运单数" label="运单数"
width="90" width="90"
/> <el-table-column
v-if="columnVisible.importTypeName"
prop="importTypeName"
label="导入方式"
width="100"
/> />
<el-table-column <el-table-column
v-if="columnVisible.statusName" v-if="columnVisible.statusName"
@@ -119,6 +109,12 @@
label="状态" label="状态"
width="100" width="100"
/> />
<el-table-column
v-if="columnVisible.settlementStatusName"
prop="settlementStatusName"
label="结算状态"
width="110"
/>
<el-table-column <el-table-column
v-if="columnVisible.createUserName" v-if="columnVisible.createUserName"
prop="createUserName" prop="createUserName"
@@ -159,7 +155,7 @@
>编辑</el-link >编辑</el-link
> >
<el-link <el-link
v-if="row.importStatus === 'draft'" v-if="canRemoveBatch(row)"
type="danger" type="danger"
@click="removeBatch(row)" @click="removeBatch(row)"
>删除</el-link >删除</el-link
@@ -641,10 +637,9 @@ const columnOptions = [
{ prop: 'batchNo', label: '运单批次号' }, { prop: 'batchNo', label: '运单批次号' },
{ prop: 'projectName', label: '项目名称' }, { prop: 'projectName', label: '项目名称' },
{ prop: 'carrierName', label: '承运商' }, { prop: 'carrierName', label: '承运商' },
{ prop: 'carrierType', label: '承运类型' },
{ prop: 'waybillCount', label: '运单数' }, { prop: 'waybillCount', label: '运单数' },
{ prop: 'importTypeName', label: '导入方式' },
{ prop: 'statusName', label: '状态' }, { prop: 'statusName', label: '状态' },
{ prop: 'settlementStatusName', label: '结算状态' },
{ prop: 'createUserName', label: '创建人' }, { prop: 'createUserName', label: '创建人' },
{ prop: 'createTime', label: '创建时间' }, { prop: 'createTime', label: '创建时间' },
{ prop: 'updateTime', label: '更新时间' }, { prop: 'updateTime', label: '更新时间' },
@@ -1266,9 +1261,47 @@ const fillEditForm = async row => {
if (carrierValue) carrierChange(carrierValue); if (carrierValue) carrierChange(carrierValue);
await loadBatchRows(snapshot.id); await loadBatchRows(snapshot.id);
}; };
const removeBatches = () => /** 与后端一致:仅未结算批次可整批删除 */
api.removeImportBatches(selected.value.map(item => item.id).join(',')).then(loadBatches); const canRemoveBatch = row => String(row?.settlementStatus || 'unsettled') === 'unsettled';
const removeBatch = row => api.removeImportBatches(row.id).then(loadBatches); const removeBatches = async () => {
if (!selected.value.length) {
ElMessage.warning('请选择至少一条数据');
return;
}
// 选中含部分结算/已结算时整批拦截,不静默跳过
if (selected.value.some(row => !canRemoveBatch(row))) {
ElMessage.warning('仅未结算的运单批次可以删除');
return;
}
try {
await ElMessageBox.confirm('确认删除选中的运单批次?删除后不可恢复。', '提示', {
type: 'warning',
});
} catch {
return;
}
await api.removeImportBatches(selected.value.map(item => item.id).join(','));
ElMessage.success('删除成功');
return loadBatches();
};
const removeBatch = async row => {
if (!canRemoveBatch(row)) {
ElMessage.warning('仅未结算的运单批次可以删除');
return;
}
try {
await ElMessageBox.confirm(
`确认删除运单批次「${row.batchNo || ''}」?删除后不可恢复。`,
'提示',
{ type: 'warning' }
);
} catch {
return;
}
await api.removeImportBatches(row.id);
ElMessage.success('删除成功');
return loadBatches();
};
const projectChange = async id => { const projectChange = async id => {
const requestId = ++projectRequestId; const requestId = ++projectRequestId;
const item = projects.value.find(row => String(row.id) === String(id)); const item = projects.value.find(row => String(row.id) === String(id));
@@ -1348,11 +1381,23 @@ const formatOptionalNumber = value => {
const normalized = normalizeOptionalImportNumber(value); const normalized = normalizeOptionalImportNumber(value);
return normalized === null || normalized === undefined ? '' : normalized; return normalized === null || normalized === undefined ? '' : normalized;
}; };
// 明细接口返回运单实体字段 startDate/endDate/estimatedStartTime/estimatedEndTime,
// 而明细表与编辑回填按导入列字段名 actualStartDate 等读取,重载后在此对齐,否则实际/预计时间列为空。
const detailDateAliases = {
actualStartDate: 'startDate',
actualEndDate: 'endDate',
planStartDate: 'estimatedStartTime',
planEndDate: 'estimatedEndTime',
};
const normalizeDetailRow = row => { const normalizeDetailRow = row => {
const next = { ...(row || {}) }; const next = { ...(row || {}) };
OPTIONAL_NUMBER_PROPS.forEach(prop => { OPTIONAL_NUMBER_PROPS.forEach(prop => {
next[prop] = formatOptionalNumber(next[prop]); next[prop] = formatOptionalNumber(next[prop]);
}); });
Object.entries(detailDateAliases).forEach(([prop, source]) => {
if (String(next[prop] ?? '').trim()) return;
if (String(next[source] ?? '').trim()) next[prop] = next[source];
});
return next; return next;
}; };
const normalizeImportRow = row => ({ const normalizeImportRow = row => ({
@@ -1200,7 +1200,11 @@
:loading="taskDriverLoading" :loading="taskDriverLoading"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@select="handleTaskDriverSelect" @select="handleTaskDriverSelect"
/> >
<template #default="{ item }">
<span>{{ driverOptionText(item) }}</span>
</template>
</el-autocomplete>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="isRoadTransport" v-if="isRoadTransport"
@@ -1279,7 +1283,11 @@
:loading="taskEscortLoading" :loading="taskEscortLoading"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@select="handleTaskEscortSelect" @select="handleTaskEscortSelect"
/> >
<template #default="{ item }">
<span>{{ driverOptionText(item) }}</span>
</template>
</el-autocomplete>
</el-form-item> </el-form-item>
<el-form-item v-if="showRoadEscortFields" label="押运人手机号"> <el-form-item v-if="showRoadEscortFields" label="押运人手机号">
<el-input <el-input
@@ -1594,6 +1602,13 @@
<el-link type="primary" v-if="canComplete(row)" @click="handleAction('complete', row)"> <el-link type="primary" v-if="canComplete(row)" @click="handleAction('complete', row)">
{{ completeActionLabel }} {{ completeActionLabel }}
</el-link> </el-link>
<el-link
type="primary"
v-if="canConfirmReturn(row)"
@click="handleAction('confirmReturn', row)"
>
{{ completeActionLabel }}
</el-link>
<el-link type="primary" v-if="canMaintainMileage(row)" @click="openMileageDialog(row)"> <el-link type="primary" v-if="canMaintainMileage(row)" @click="openMileageDialog(row)">
维护里程 维护里程
</el-link> </el-link>
@@ -1653,7 +1668,11 @@
style="width: 100%" style="width: 100%"
@select="handleReassignDriverSelect" @select="handleReassignDriverSelect"
@change="handleReassignDriverChange" @change="handleReassignDriverChange"
/> >
<template #default="{ item }">
<span>{{ driverOptionText(item) }}</span>
</template>
</el-autocomplete>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="手机号" label="手机号"
@@ -2252,6 +2271,13 @@
<span v-if="record.volume">体积 {{ record.volume }}方</span> <span v-if="record.volume">体积 {{ record.volume }}方</span>
<span v-if="record.quantity">数量 {{ record.quantity }}</span> <span v-if="record.quantity">数量 {{ record.quantity }}</span>
</div> </div>
<!-- 里程只挂在末位节点上(needMileage 由后端下发),显示的是运单里程 -->
<div
v-if="record.needMileage && Number(record.mileage) > 0"
class="waybill-manage-page__punch-record-line"
>
里程 {{ record.mileage }} 公里
</div>
<div <div
v-if="record.remark" v-if="record.remark"
class="waybill-manage-page__punch-record-line" class="waybill-manage-page__punch-record-line"
@@ -2848,6 +2874,20 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 里程只在末位打卡节点出现:后端按「末位 + 计费规则含里程」下发 needMileage,前端不自己推 -->
<el-col v-if="waybillPunchEditMileageVisible" :span="8">
<el-form-item label="里程">
<el-input
v-model="waybillPunchEditForm.mileage"
maxlength="10"
:disabled="waybillPunchEditMileageLocked"
placeholder="请输入"
@input="handleWaybillPunchMileageInput"
>
<template #append>公里</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="备注"> <el-form-item label="备注">
<el-input <el-input
@@ -3683,6 +3723,7 @@ import {
getVoucherImages as getProcessConfigVoucherImages, getVoucherImages as getProcessConfigVoucherImages,
} from '@/api/business/process-config'; } from '@/api/business/process-config';
import { import {
getList as getWaybillList,
getPunchRecords as getWaybillPunchRecords, getPunchRecords as getWaybillPunchRecords,
locateVehicle, locateVehicle,
savePunchRecord, savePunchRecord,
@@ -3692,9 +3733,7 @@ import VoucherExecutionDetail from './voucher-execution-detail.vue';
import PunchRecordPhotos from './punch-record-photos.vue'; import PunchRecordPhotos from './punch-record-photos.vue';
import AddressMapPicker from '@/components/address-map-picker/main.vue'; import AddressMapPicker from '@/components/address-map-picker/main.vue';
import { getList as getDriverList } from '@/api/transportCapacity/driver'; import { getList as getDriverList } from '@/api/transportCapacity/driver';
import { import { fetchDriverCandidates, driverOptionText } from '@/utils/carrier-org-resource';
fetchDriversByCarrierOrganizations,
} from '@/utils/carrier-org-resource';
import { getDictionary } from '@/api/system/dictbiz'; import { getDictionary } from '@/api/system/dictbiz';
import { getDictionary as getSystemDictionary } from '@/api/system/dict'; import { getDictionary as getSystemDictionary } from '@/api/system/dict';
import { addressTypeOptions } from '@/option/base/common-address'; import { addressTypeOptions } from '@/option/base/common-address';
@@ -4011,6 +4050,7 @@ export default {
weight: '', weight: '',
volume: '', volume: '',
quantity: '', quantity: '',
mileage: '',
remark: '', remark: '',
photos: [], photos: [],
}, },
@@ -4370,9 +4410,15 @@ export default {
addBtn: this.canCreate, addBtn: this.canCreate,
}; };
}, },
// 打卡记录编辑入口与运单编辑同权限:能改运单的人才能订正它的打卡流水 // 打卡记录编辑入口与运单编辑同权限:能改运单的人才能订正它的打卡流水。
// 时间轴的「编辑打卡 / 补卡」链接是本页唯一写入入口,提交固定 punched=true。
// 取消打卡不在本页;待回单确认的运单不显示该链接。
canEditWaybillPunchRecord() { canEditWaybillPunchRecord() {
return this.hasPermission(`${this.config.permission}_edit`) && !this.readonlyScope; return (
this.hasPermission(`${this.config.permission}_edit`) &&
!this.readonlyScope &&
this.statusValue(this.detailRow) !== 'pending_return_confirm'
);
}, },
waybillPunchEditNode() { waybillPunchEditNode() {
return this.findWaybillPunchEditNodeConfig(this.waybillPunchEditRecord); return this.findWaybillPunchEditNodeConfig(this.waybillPunchEditRecord);
@@ -4407,6 +4453,15 @@ export default {
waybillPunchEditPunchDisabled() { waybillPunchEditPunchDisabled() {
return this.waybillPunchEditNode?.punch === false; return this.waybillPunchEditNode?.punch === false;
}, },
// 里程:后端下发 needMileage 才出现(末位打卡节点 + 计费规则含里程);在途打卡不采集里程
waybillPunchEditMileageVisible() {
const record = this.waybillPunchEditRecord || {};
return record.needMileage === true && record.type !== 'enroute';
},
// 已生成结算单的运单里程冻结:输入框置灰,改动由后端拒绝
waybillPunchEditMileageLocked() {
return this.waybillPunchEditRecord?.mileageLocked === true;
},
waybillPunchEditRules() { waybillPunchEditRules() {
return { punchTime: [{ required: true, message: '请选择打卡时间', trigger: 'change' }] }; return { punchTime: [{ required: true, message: '请选择打卡时间', trigger: 'change' }] };
}, },
@@ -4595,9 +4650,14 @@ export default {
}, },
taskCarrierValue: { taskCarrierValue: {
get() { get() {
return this.form.carrierType === '自运' if (this.form.carrierType === '自运') {
? this.form.carrierName || '' return this.form.carrierName || '';
: this.form.carrierContractId || this.form.carrierName || ''; }
return this.taskCarrierOptionValue({
carrierContractId: this.form.carrierContractId,
carrierPartyId: this.form.carrierPartyId,
carrierName: this.form.carrierName,
});
}, },
set(value) { set(value) {
this.handleTaskCarrierChange(value); this.handleTaskCarrierChange(value);
@@ -4800,6 +4860,7 @@ export default {
}, },
methods: { methods: {
formatContractOptionLabel, formatContractOptionLabel,
driverOptionText,
parseRouteChangeContent, parseRouteChangeContent,
// 页面被 keep-alive 缓存/卸载时调用,关闭本页所有 append-to-body 的弹窗, // 页面被 keep-alive 缓存/卸载时调用,关闭本页所有 append-to-body 的弹窗,
// 避免 Teleport 出去的 DOM 残留在 body 上盖住后续页面(详见 data 里的说明)。 // 避免 Teleport 出去的 DOM 残留在 body 上盖住后续页面(详见 data 里的说明)。
@@ -5060,11 +5121,16 @@ export default {
); );
}, },
canCopy(row) { canCopy(row) {
if (!row) return false;
// 归属本账号可见范围的运单允许复制。跨组织但项目归属本账号的记录会被标成只读,
// 只读仅拦截编辑/删除,不拦截复制。具备查看、新增、编辑或复制权限即可使用。
return ( return (
this.hasAction('copy') && this.hasAction('copy') &&
this.hasPermission(`${this.config.permission}_copy`) &&
!this.readonlyScope && !this.readonlyScope &&
!row.readonly (this.hasPermission(`${this.config.permission}_copy`) ||
this.hasPermission(`${this.config.permission}_add`) ||
this.hasPermission(`${this.config.permission}_edit`) ||
this.hasPermission(`${this.config.permission}_view`))
); );
}, },
canEnable(row) { canEnable(row) {
@@ -5099,7 +5165,8 @@ export default {
return false; return false;
} }
const status = this.statusValue(row); const status = this.statusValue(row);
return status === 'pending'; // 待回单确认可取消,取消不生成应收应付
return status === 'pending' || status === 'pending_return_confirm';
}, },
canComplete(row) { canComplete(row) {
if ( if (
@@ -5111,8 +5178,22 @@ export default {
return false; return false;
} }
const status = this.statusValue(row); const status = this.statusValue(row);
// 待回单确认的「完成」由确认回单入口承担(走打卡节点与未了结异常校验);
// 只有确认回单不可用时才退回这里的普通完成
if (status === 'pending_return_confirm') {
return !this.canConfirmReturn(row);
}
return status === 'processing' || status === 'running'; return status === 'processing' || status === 'running';
}, },
canConfirmReturn(row) {
return (
this.hasAction('confirmReturn') &&
this.hasPermission(`${this.config.permission}_complete`) &&
!this.readonlyScope &&
!row.readonly &&
this.statusValue(row) === 'pending_return_confirm'
);
},
canMaintainMileage(row) { canMaintainMileage(row) {
return ( return (
this.config.permission === 'waybill_manage' && this.config.permission === 'waybill_manage' &&
@@ -5926,6 +6007,8 @@ export default {
weight: record.weight || '', weight: record.weight || '',
volume: record.volume || '', volume: record.volume || '',
quantity: record.quantity || '', quantity: record.quantity || '',
// 里程只有正整数口径,后端若仍下发 "200.00" 会过不了提交时的正整数校验
mileage: Number(record.mileage) > 0 ? String(Number(record.mileage)) : '',
remark: record.remark || '', remark: record.remark || '',
photos: (record.photos || []) photos: (record.photos || [])
.filter(photo => photo?.url) .filter(photo => photo?.url)
@@ -6001,10 +6084,22 @@ export default {
photos.splice(index, 1); photos.splice(index, 1);
this.waybillPunchEditForm.photos = photos; this.waybillPunchEditForm.photos = photos;
}, },
// 里程只收整数:非数字直接丢掉,长度与「维护里程」一致(≤10 位)
handleWaybillPunchMileageInput(value) {
this.waybillPunchEditForm.mileage = String(value || '')
.replace(/\D/g, '')
.slice(0, 10);
},
async submitWaybillPunchRecordEdit() { async submitWaybillPunchRecordEdit() {
await this.$refs.waybillPunchRecordEditFormRef.validate(); await this.$refs.waybillPunchRecordEditFormRef.validate();
const record = this.waybillPunchEditRecord; const record = this.waybillPunchEditRecord;
const form = this.waybillPunchEditForm; const form = this.waybillPunchEditForm;
// 管理端里程选填:留空表示不改动运单里程,所以只校验填了值的情况
const mileageText = String(form.mileage || '').trim();
if (this.waybillPunchEditMileageVisible && mileageText && !/^[1-9]\d{0,9}$/.test(mileageText)) {
this.$message.warning('里程必须为不超过10位的正整数');
return;
}
const submit = const submit =
typeof this.api?.savePunchRecord === 'function' ? this.api.savePunchRecord : savePunchRecord; typeof this.api?.savePunchRecord === 'function' ? this.api.savePunchRecord : savePunchRecord;
this.waybillPunchRecordEditDialog.submitting = true; this.waybillPunchRecordEditDialog.submitting = true;
@@ -6023,6 +6118,8 @@ export default {
weight: form.weight, weight: form.weight,
volume: form.volume, volume: form.volume,
quantity: form.quantity, quantity: form.quantity,
// 不传代表不改;后端按 null 处理
mileage: mileageText ? Number(mileageText) : undefined,
remark: form.remark, remark: form.remark,
photos: form.photos.map(photo => ({ url: photo.url, type: photo.type })), photos: form.photos.map(photo => ({ url: photo.url, type: photo.type })),
}); });
@@ -6543,6 +6640,9 @@ export default {
if (status === 'completed') { if (status === 'completed') {
return 'primary'; return 'primary';
} }
if (status === 'pending_return_confirm') {
return 'warning';
}
return 'warning'; return 'warning';
}, },
normalizeRow(row, saveAsDraft = false) { normalizeRow(row, saveAsDraft = false) {
@@ -7195,6 +7295,8 @@ export default {
this.form.carrierName = ''; this.form.carrierName = '';
this.form.carrierId = ''; this.form.carrierId = '';
this.form.carrierContractId = ''; this.form.carrierContractId = '';
this.form.carrierPartyId = '';
this.form.carrierPartyRole = '';
this.hasProjectProcessConfig = false; this.hasProjectProcessConfig = false;
this.waybillProcessConfigRows = []; this.waybillProcessConfigRows = [];
return; return;
@@ -7209,6 +7311,8 @@ export default {
this.form.carrierName = ''; this.form.carrierName = '';
this.form.carrierId = ''; this.form.carrierId = '';
this.form.carrierContractId = ''; this.form.carrierContractId = '';
this.form.carrierPartyId = '';
this.form.carrierPartyRole = '';
this.loadProjectProcessConfigState(); this.loadProjectProcessConfigState();
this.loadTaskCarrierOptions(project); this.loadTaskCarrierOptions(project);
if (this.contractSelectEnabled) { if (this.contractSelectEnabled) {
@@ -7665,6 +7769,8 @@ export default {
this.form.carrierName = ''; this.form.carrierName = '';
this.form.carrierId = ''; this.form.carrierId = '';
this.form.carrierContractId = ''; this.form.carrierContractId = '';
this.form.carrierPartyId = '';
this.form.carrierPartyRole = '';
this.form.driverId = ''; this.form.driverId = '';
this.form.driverName = ''; this.form.driverName = '';
this.form.driverPhone = ''; this.form.driverPhone = '';
@@ -7719,7 +7825,9 @@ export default {
}, },
taskCarrierOptionValue(item = {}) { taskCarrierOptionValue(item = {}) {
if (this.form.carrierType !== '自运') { if (this.form.carrierType !== '自运') {
return item.carrierContractId || item.value || item.id || ''; if (item.value) return item.value;
return [item.carrierContractId || item.id || '', item.carrierPartyId || '', item.carrierName || '']
.join('__');
} }
return ( return (
item.carrierName || item.carrierName ||
@@ -7757,55 +7865,76 @@ export default {
names.forEach(append); names.forEach(append);
return options; return options;
}, },
parseContractPartyRows(partyJson, fallbackName = '', partyRole = 'B') {
const rows = this.parseJsonArray(partyJson)
.map(item => ({
id: item.id || '',
customerId: item.customerId || '',
customerName: String(item.customerName || '').trim(),
creditCode: item.creditCode || '',
partyRole,
}))
.filter(item => item.customerName);
if (rows.length) return rows;
return String(fallbackName || '')
.split(/[、,,]/)
.map(name => name.trim())
.filter(Boolean)
.map(name => ({
id: '',
customerId: '',
customerName: name,
creditCode: '',
partyRole,
}));
},
getWaybillCarrierContractOptions(contracts = []) { getWaybillCarrierContractOptions(contracts = []) {
const carrierNameCounts = contracts.reduce((counts, contract) => { const options = [];
const carrierName = String( contracts.forEach(contract => {
contract.partyB || if (!contract?.id) return;
contract.partyBName || const parties = [
contract.contractPartyB || ...this.parseContractPartyRows(contract.partyBJson, contract.partyB, 'B'),
contract.customerContractPartyB || ...this.parseContractPartyRows(contract.partyCJson, contract.partyC, 'C'),
contract.secondParty || ];
contract.secondPartyName || if (!parties.length) {
'' const fallbackName = String(
).trim();
if (carrierName) counts[carrierName] = (counts[carrierName] || 0) + 1;
return counts;
}, {});
return contracts
.map(contract => {
const carrierName = String(
contract.partyB || contract.partyB ||
contract.partyBName || contract.partyBName ||
contract.contractPartyB || contract.contractPartyB ||
contract.customerContractPartyB || contract.customerContractPartyB ||
contract.secondParty || contract.secondParty ||
contract.secondPartyName || contract.secondPartyName ||
'' ''
).trim(); ).trim();
if (!carrierName || !contract.id) return null; if (fallbackName) {
const contractIdentifier = contract.contractName || contract.contractNo || contract.id; parties.push({
return { id: '',
customerId: '',
customerName: fallbackName,
creditCode: '',
partyRole: 'B',
});
}
}
const contractIdentifier = contract.contractName || contract.contractNo || contract.id;
parties.forEach(party => {
const roleLabel = party.partyRole === 'C' ? '丙方' : '乙方';
const optionValue = [contract.id, party.id || '', party.customerName].join('__');
options.push({
id: contract.id, id: contract.id,
carrierId: carrierId: party.customerId || '',
contract.partyBId || value: optionValue,
contract.partyBUserId || label: `${party.customerName}(${roleLabel}) - ${contractIdentifier}`,
contract.contractPartyBId || carrierName: party.customerName,
contract.customerContractPartyBId || fullName: party.customerName,
contract.secondPartyId ||
contract.secondPartyUserId ||
contract.carrierId ||
'',
value: contract.id,
label:
carrierNameCounts[carrierName] > 1
? `${carrierName}(${contractIdentifier})`
: carrierName,
carrierName,
fullName: carrierName,
carrierContractId: contract.id, carrierContractId: contract.id,
}; carrierPartyId: party.id || '',
}) carrierPartyRole: party.partyRole,
.filter(Boolean); creditCode: party.creditCode || '',
});
});
});
return options;
}, },
resetWaybillCarrierContractState() { resetWaybillCarrierContractState() {
this.waybillCarrierContractRequestId += 1; this.waybillCarrierContractRequestId += 1;
@@ -7835,6 +7964,8 @@ export default {
this.form.carrierName = ''; this.form.carrierName = '';
this.form.carrierId = ''; this.form.carrierId = '';
this.form.carrierContractId = ''; this.form.carrierContractId = '';
this.form.carrierPartyId = '';
this.form.carrierPartyRole = '';
} }
return options; return options;
}) })
@@ -7925,10 +8056,14 @@ export default {
); );
if (this.form.carrierType !== '自运') { if (this.form.carrierType !== '自运') {
this.form.carrierContractId = carrier?.carrierContractId || ''; this.form.carrierContractId = carrier?.carrierContractId || '';
this.form.carrierPartyId = carrier?.carrierPartyId || '';
this.form.carrierPartyRole = carrier?.carrierPartyRole || '';
this.form.carrierId = carrier?.carrierId || ''; this.form.carrierId = carrier?.carrierId || '';
this.form.carrierName = carrier?.carrierName || ''; this.form.carrierName = carrier?.carrierName || '';
} else { } else {
this.form.carrierId = carrier?.carrierContractId ? '' : carrier?.id || ''; this.form.carrierId = carrier?.carrierContractId ? '' : carrier?.id || '';
this.form.carrierPartyId = '';
this.form.carrierPartyRole = '';
this.form.carrierName = value || ''; this.form.carrierName = value || '';
} }
// 承运商变更后清空司机/车辆,避免跨组织脏数据 // 承运商变更后清空司机/车辆,避免跨组织脏数据
@@ -7942,19 +8077,10 @@ export default {
this.taskDriverOptions = []; this.taskDriverOptions = [];
this.taskEscortOptions = []; this.taskEscortOptions = [];
}, },
getTaskCarrierFilter() {
return {
carrierId: this.form.carrierId || '',
carrierName: this.form.carrierName || '',
};
},
loadTaskDriverOptions() { loadTaskDriverOptions() {
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]); if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
this.taskDriverLoading = true; this.taskDriverLoading = true;
return fetchDriversByCarrierOrganizations( return fetchDriverCandidates({ size: 9999, posts: '司机' })
{ size: 9999, posts: '司机' },
this.getTaskCarrierFilter()
)
.then(records => { .then(records => {
this.taskDriverOptions = records; this.taskDriverOptions = records;
return this.taskDriverOptions; return this.taskDriverOptions;
@@ -7965,16 +8091,8 @@ export default {
}, },
fetchTaskDriverSuggestions(queryString, callback) { fetchTaskDriverSuggestions(queryString, callback) {
const keyword = String(queryString || '').trim(); const keyword = String(queryString || '').trim();
const carrier = this.getTaskCarrierFilter();
if (!carrier.carrierId && !carrier.carrierName) {
callback([]);
return;
}
this.taskDriverLoading = true; this.taskDriverLoading = true;
fetchDriversByCarrierOrganizations( fetchDriverCandidates({ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
carrier
)
.then(records => { .then(records => {
this.taskDriverOptions = records; this.taskDriverOptions = records;
callback( callback(
@@ -7994,16 +8112,12 @@ export default {
}, },
fetchTaskEscortSuggestions(queryString, callback) { fetchTaskEscortSuggestions(queryString, callback) {
const keyword = String(queryString || '').trim(); const keyword = String(queryString || '').trim();
const carrier = this.getTaskCarrierFilter();
if (!carrier.carrierId && !carrier.carrierName) {
callback([]);
return;
}
this.taskEscortLoading = true; this.taskEscortLoading = true;
fetchDriversByCarrierOrganizations( fetchDriverCandidates({
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' }, size: 20,
carrier ...(keyword ? { driverName: keyword } : {}),
) posts: '押运员',
})
.then(records => { .then(records => {
this.taskEscortOptions = records; this.taskEscortOptions = records;
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' }))); callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
@@ -10150,8 +10264,14 @@ export default {
cancel: '取消', cancel: '取消',
complete: this.completeActionLabel, complete: this.completeActionLabel,
reassign: '重新派单', reassign: '重新派单',
confirmReturn: '完成',
}[action]; }[action];
this.$confirm(`确定${actionName}该${this.config.title}?`, '提示', { // 回单确认是终态动作(本轮不做撤销),把后果写在提示里,别让人以为还能退回
const confirmText =
action === 'confirmReturn'
? '回单确认后该运单将置为已完成,且不可撤销。是否继续?'
: `确定${actionName}该${this.config.title}?`;
this.$confirm(confirmText, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
@@ -10204,20 +10324,8 @@ export default {
}, },
fetchReassignDriverSuggestions(queryString, callback) { fetchReassignDriverSuggestions(queryString, callback) {
const keyword = String(queryString || '').trim(); const keyword = String(queryString || '').trim();
const row = this.reassignDrawer.row || {};
const carrier = {
carrierId: row.carrierId || '',
carrierName: row.carrierName || '',
};
if (!carrier.carrierId && !carrier.carrierName) {
callback([]);
return;
}
this.reassignDrawer.driverLoading = true; this.reassignDrawer.driverLoading = true;
fetchDriversByCarrierOrganizations( fetchDriverCandidates({ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
carrier
)
.then(records => { .then(records => {
this.reassignDriverOptions = records; this.reassignDriverOptions = records;
callback( callback(
@@ -10356,7 +10464,14 @@ export default {
this.$message.warning('请选择至少一条数据'); this.$message.warning('请选择至少一条数据');
return; return;
} }
this.$confirm('确定完成选中的运单?', '提示', { const pendingReturnCount = this.selectionList.filter(
row => this.statusValue(row) === 'pending_return_confirm'
).length;
const tip =
pendingReturnCount > 0
? `确定完成选中的运单?其中 ${pendingReturnCount} 条为待回单确认,确认后将转为已完成。`
: '确定完成选中的运单?';
this.$confirm(tip, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
@@ -10370,25 +10485,38 @@ export default {
this.onLoad(this.page, this.query); this.onLoad(this.page, this.query);
}); });
}, },
handleRoadLoading() { async handleRoadLoading() {
if (!this.selectionList.length) { if (!this.selectionList.length) {
this.$message.warning('请选择至少一条数据'); this.$message.warning('请选择至少一条数据');
return; return;
} }
const nonRoadWaybills = this.selectionList.filter(row => !this.waybillIsRoadTransport(row)); const selectedIds = this.selectionList.map(row => row.id).filter(Boolean);
if (nonRoadWaybills.length) { if (!selectedIds.length) {
this.$message.warning('公路配载仅支持运输方式为“公路运输”的运单'); this.$message.warning('请选择至少一条数据');
return; return;
} }
const unavailableWaybills = this.selectionList.filter( // 可配载与否只看后端查询结果
row => let loadableIds;
!['processing', 'running'].includes(this.statusValue(row)) || try {
row.loadingId || const res = await getWaybillList(1, selectedIds.length, {
row.loadingManageId || loadableOnly: 1,
row.loadingNo ids: selectedIds.join(','),
); });
if (unavailableWaybills.length) { loadableIds = new Set(extractRecords(res).map(row => String(row.id)));
this.$message.warning('公路配载仅支持进行中且未配载的运单'); } catch (error) {
this.$message.error('校验运单可配载状态失败,请重试');
return;
}
const rejectedRows = this.selectionList.filter(row => !loadableIds.has(String(row.id)));
if (rejectedRows.length) {
const rejectedText = rejectedRows
.map(row => {
const reason = this.roadLoadingRejectReasonText(row);
const no = row.waybillNo || row.id || '';
return `${no}(${reason})`;
})
.join('、');
this.$message.warning(`${rejectedText};公路配载仅支持公路、待执行且未配载的运单`);
return; return;
} }
try { try {
@@ -10411,6 +10539,13 @@ export default {
}, },
}); });
}, },
// 只给后端判定为不可配载的运单生成可读原因,本身不参与准入判定
roadLoadingRejectReasonText(row = {}) {
if (!this.waybillIsRoadTransport(row)) return '非公路运输';
if (String(row.businessStatus || '') !== 'pending') return '状态不是待执行';
if (row.loadingNo) return '已关联配载单';
return '运单数据可能已变化,请刷新后重试';
},
}, },
}; };
</script> </script>
@@ -0,0 +1,21 @@
<template>
<mk-public-shell biz-type="contract-change" :get-form="getForm">
<contract-manage-change ref="page" />
</mk-public-shell>
</template>
<script>
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
import ContractManageChange from '@/views/business/contract-manage-change.vue';
export default {
name: 'ContractChangePublicView',
components: { MkPublicShell, ContractManageChange },
methods: {
getForm() {
const row = this.$refs.page?.form || {};
return { ...row, subject: row.contractName || row.contractNo || '' };
},
},
};
</script>
+407
View File
@@ -0,0 +1,407 @@
<template>
<basic-container class="contract-change-page">
<avue-crud
:option="tableOption"
:table-loading="loading"
:data="data"
v-model:page="page"
v-model="tableForm"
ref="crud"
:permission="permissionList"
@search-change="searchChange"
@search-reset="searchReset"
@selection-change="selectionChange"
@current-change="currentChange"
@size-change="sizeChange"
@refresh-change="refreshChange"
@on-load="onLoad"
>
<template #menu-left>
<el-button type="primary" plain :loading="hcsRepushingId === 'batch'" @click="handleHcsBatchRepush">
批量同步
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
plain
v-if="hasPermission(`${config.permission}_delete`)"
@click="handleDelete"
>
批量删除
</el-button>
</template>
<template #approvalStatus="{ row }">
{{ row.approvalStatusName || statusLabel(row.approvalStatus) }}
</template>
<template #syncStatus="{ row }">
<el-tag v-if="row.syncStatus" :type="syncTagType(row.syncStatus)" class="status-text">
{{ syncLabel(row.syncStatus) }}
</el-tag>
<span v-else>-</span>
</template>
<template #menu="{ row }">
<el-link type="primary" v-if="canView(row)" @click="openForm('view', row)"> 查看 </el-link>
<el-link type="primary" v-if="canEdit(row)" @click="openForm('edit', row)"> 编辑 </el-link>
<el-link type="danger" v-if="canDelete(row)" @click="rowDel(row)"> 删除 </el-link>
<el-link
v-for="operation in customOperations(row)"
:key="operation.action"
:type="
operation.type === 'warning'
? 'warning'
: operation.type === 'success'
? 'success'
: 'primary'
"
@click="handleCustomOperation(operation, row)"
>
{{ operation.label }}
</el-link>
</template>
</avue-crud>
</basic-container>
</template>
<script>
import * as api from '@/api/business/contract-change';
import { repushBatch as hcsRepushBatch } from '@/api/business/hcs';
import { getMKApprovalUrl } from '@/api/system/business-process';
import { config, option } from '@/option/business/contract-change';
import { approvalStatusOptions } from '@/option/business/common';
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
import { isAdministrator } from '@/utils/auth';
import { mapGetters } from 'vuex';
const contractChangeFormRoute = '/business/contract-change/form';
const contractChangeListRefreshKey = 'contract-change-list-refresh';
export default {
name: 'ContractChange',
data() {
return {
api,
config,
tableForm: {},
tableOption: this.buildTableOption(),
loading: true,
data: [],
page: {
pageSize: 10,
currentPage: 1,
total: 0,
},
query: {},
selectionList: [],
mkEnabled: false,
hcsRepushingId: null,
};
},
computed: {
...mapGetters(['permission', 'userInfo', 'website']),
permissionList() {
return {
addBtn: false,
viewBtn: false,
delBtn: false,
editBtn: false,
};
},
isAdmin() {
return isAdministrator(this.userInfo);
},
ids() {
return this.selectionList.map(item => item.id).join(',');
},
},
created() {
loadMkEnabled()
.then(enabled => {
this.mkEnabled = enabled;
})
.catch(() => {
this.mkEnabled = false;
});
},
activated() {
if (sessionStorage.getItem(contractChangeListRefreshKey)) {
sessionStorage.removeItem(contractChangeListRefreshKey);
this.onLoad(this.page, this.query);
}
},
methods: {
buildTableOption() {
return {
...option,
addBtn: false,
viewBtn: false,
editBtn: false,
delBtn: false,
menuWidth: 320,
column: (option.column || []).map(column => ({
...column,
display: false,
align: 'center',
headerAlign: 'center',
})),
};
},
hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
},
statusLabel(status) {
return approvalStatusOptions.find(item => item.value === status)?.label || status || '-';
},
syncLabel(value) {
return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value;
},
syncTagType(value) {
return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info';
},
canHcsRepush(row) {
return row.approvalStatus === 'approved' && row.contractId && row.syncStatus !== 'synced';
},
handleHcsBatchRepush() {
if (this.hcsRepushingId) return;
if (!this.selectionList.length) {
this.$message.warning('请选择至少一条数据');
return;
}
const rows = this.selectionList.filter(item => this.canHcsRepush(item));
if (!rows.length) {
this.$message.warning('请选择审批通过且未同步成功的变更单');
return;
}
this.$confirm(`确定同步选中的${rows.length}条合同变更到合规系统?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
const loading = this.$loading({
lock: true,
text: '正在推送合规,请稍候…',
background: 'rgba(255, 255, 255, 0.6)',
});
this.hcsRepushingId = 'batch';
const contractIds = [...new Set(rows.map(item => item.contractId))];
const changeIds = rows.map(item => item.id).join(',');
hcsRepushBatch('contract', contractIds)
.then(res => {
if (res.data?.success === false) {
this.$message.error(res.data?.msg || '批量同步失败');
return api.markSyncStatus(changeIds, 'failed');
}
return api.markSyncStatus(changeIds, 'synced').then(() => {
this.$message.success(res.data?.data || '批量同步成功');
this.onLoad(this.page, this.query);
});
})
.catch(() => api.markSyncStatus(changeIds, 'failed'))
.finally(() => {
this.hcsRepushingId = null;
loading.close();
});
});
},
inStatus(row, statusList) {
if (!statusList || !statusList.length) return true;
return statusList.includes(row.approvalStatus);
},
canEdit(row) {
return (
this.hasPermission(`${this.config.permission}_edit`) &&
!row.readonly &&
this.inStatus(row, this.config.editStatus)
);
},
canDelete(row) {
return (
this.hasPermission(`${this.config.permission}_delete`) &&
!row.readonly &&
this.inStatus(row, this.config.deleteStatus)
);
},
canView(row) {
return (
this.hasPermission(`${this.config.permission}_view`) &&
this.inStatus(row, this.config.viewStatus)
);
},
canOperation(operation, row) {
if (operation.permission && !this.hasPermission(operation.permission)) return false;
if (
row.readonly &&
['submitApproval', 'withdraw', 'approve', 'reject'].includes(operation.action)
) {
return false;
}
return this.inStatus(row, operation.status);
},
customOperations(row) {
return (this.config.operations || []).filter(operation => {
if (operation.mkOnly && !this.mkEnabled) return false;
if (operation.localOnly && this.mkEnabled) return false;
return this.canOperation(operation, row);
});
},
openForm(mode, row = {}) {
const query = { mode, id: row.id };
if (mode === 'edit') query.name = '编辑合同变更';
else if (mode === 'view') query.name = '查看合同变更';
this.$router.push({ path: contractChangeFormRoute, query });
},
handleCustomOperation(operation, row) {
if (operation.action === 'mkAudit') {
this.handleMkAudit(row);
return;
}
const run = () => {
const request = () => this.api[operation.action](row.id);
const afterMk =
operation.action === 'submitApproval'
? submitMkApprovalFlow({
bizType: 'contract-change',
bizId: row.id,
subjectName: row.contractName || '',
approvalStatus: row.approvalStatus || '',
mkEnabled: this.mkEnabled,
}).then(() => request())
: request();
const loading =
operation.action === 'approve'
? this.$loading({
lock: true,
text: '正在推送合规,请稍候…',
background: 'rgba(255, 255, 255, 0.6)',
})
: null;
afterMk
.then(res => {
if (operation.action === 'approve' && res.data?.data === false) {
this.$message.warning(res.data?.msg);
this.onLoad(this.page, this.query);
return;
}
if (res.data?.success === false) {
this.$message.error(res.data?.msg || `${operation.label}失败,请联系管理员`);
return;
}
this.$message.success(`${operation.label}成功`);
this.onLoad(this.page, this.query);
})
.finally(() => loading && loading.close());
};
this.$confirm(operation.confirm || `确定${operation.label}该${this.config.title}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => run());
},
handleMkAudit(row) {
if (!row?.id) {
this.$message.warning('变更单ID为空,无法打开审核页');
return;
}
getMKApprovalUrl({ bizId: String(row.id) })
.then(res => {
const url = res?.data?.data;
if (!url) {
this.$message.error('获取审核跳转地址失败');
return;
}
window.open(url, '_blank');
})
.catch(() => {
this.$message.error('获取审核跳转地址失败');
});
},
searchReset() {
this.query = {};
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
selectionChange(list) {
this.selectionList = list;
},
selectionClear() {
this.selectionList = [];
this.$refs.crud?.toggleSelection?.();
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
refreshChange() {
this.onLoad(this.page, this.query);
},
rowDel(row) {
this.$confirm('确定将选择数据删除?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
this.api.remove(row.id).then(() => {
this.$message.success('删除成功!');
this.onLoad(this.page, this.query);
});
});
},
handleDelete() {
if (!this.selectionList.length) {
this.$message.warning('请选择至少一条数据');
return;
}
this.$confirm('确定将选择数据删除?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
this.api.remove(this.ids).then(() => {
this.$message.success('删除成功!');
this.selectionClear();
this.onLoad(this.page, this.query);
});
});
},
onLoad(page, params = {}) {
this.loading = true;
const values = {
...this.query,
...params,
};
this.api
.getList(page.currentPage, page.pageSize, values)
.then(res => {
const data = res.data.data || {};
this.page.total = data.total || 0;
this.data = data.records || [];
this.loading = false;
this.selectionClear();
})
.catch(() => {
this.loading = false;
});
},
},
};
</script>
<style scoped lang="scss">
.contract-change-page {
:deep(.avue-crud__menu) {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
}
</style>
+540 -51
View File
@@ -1,18 +1,32 @@
<template> <template>
<basic-container class="contract-change-page"> <basic-container class="contract-change-page">
<div class="archive-page-form__title">{{ pageTitle }}</div> <div class="archive-page-form__title">{{ pageTitle }}</div>
<el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="auto" class="contract-change-form"> <el-form
ref="formRef"
:model="form"
:rules="rules"
:disabled="pageReadonly"
label-position="right"
label-width="auto"
class="contract-change-form"
>
<section class="change-section contract-basic-section"> <section class="change-section contract-basic-section">
<div class="dialog-section-title">基本信息</div> <div class="dialog-section-title">基本信息</div>
<div class="contract-basic-section__grid"> <div class="contract-basic-section__grid">
<el-form-item label="变更类型" class="contract-basic-section__change-type"> <el-form-item label="变更类型" class="contract-basic-section__change-type">
<el-radio-group v-model="form.changeType"> <el-radio-group v-model="form.changeType" :disabled="pageReadonly">
<el-radio label="合同信息变更" /> <el-radio label="合同信息变更" />
<el-radio label="结算规则变更" />
<el-radio label="终止合同" /> <el-radio label="终止合同" />
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="签约类型" prop="signType"> <el-form-item label="签约类型" prop="signType">
<el-select v-model="form.signType" placeholder="请选择签约类型" clearable> <el-select
v-model="form.signType"
placeholder="请选择签约类型"
clearable
:disabled="isSettlementRuleChange"
>
<el-option <el-option
v-for="item in signTypeOptions" v-for="item in signTypeOptions"
:key="item.value" :key="item.value"
@@ -21,8 +35,14 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="合同编号"> <el-form-item label="合同编号" prop="contractNo">
<el-input v-model="form.contractNo" disabled /> <el-input
v-model="form.contractNo"
maxlength="100"
clearable
placeholder="请输入合同编号"
:disabled="isSettlementRuleChange"
/>
</el-form-item> </el-form-item>
<el-form-item label="合同名称" prop="contractName"> <el-form-item label="合同名称" prop="contractName">
<el-input <el-input
@@ -30,6 +50,7 @@
maxlength="100" maxlength="100"
show-word-limit show-word-limit
placeholder="请输入合同名称" placeholder="请输入合同名称"
:disabled="isSettlementRuleChange"
/> />
</el-form-item> </el-form-item>
<el-form-item label="合同类型"> <el-form-item label="合同类型">
@@ -80,6 +101,7 @@
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
placeholder="请输入" placeholder="请输入"
:disabled="isSettlementRuleChange"
/> />
<span>至</span> <span>至</span>
<el-date-picker <el-date-picker
@@ -88,6 +110,7 @@
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
placeholder="请输入" placeholder="请输入"
:disabled="isSettlementRuleChange"
/> />
</div> </div>
</el-form-item> </el-form-item>
@@ -95,6 +118,7 @@
<el-input <el-input
v-model="form.paymentDays" v-model="form.paymentDays"
placeholder="请输入" placeholder="请输入"
:disabled="isSettlementRuleChange"
@input="value => positiveIntegerInput('paymentDays', value)" @input="value => positiveIntegerInput('paymentDays', value)"
> >
<template #suffix>天</template> <template #suffix>天</template>
@@ -105,22 +129,36 @@
:model-value="contractAmountDisplay" :model-value="contractAmountDisplay"
placeholder="请输入" placeholder="请输入"
clearable clearable
:disabled="isSettlementRuleChange"
@input="handleContractAmountInput" @input="handleContractAmountInput"
@blur="formatContractAmountField" @blur="formatContractAmountField"
@clear="form.contractAmount = null" @clear="form.contractAmount = null"
/> />
</el-form-item> </el-form-item>
<el-form-item label="是否范本"> <el-form-item label="是否范本">
<el-select v-model="form.templateFlag" placeholder="请选择"> <el-select
v-model="form.templateFlag"
placeholder="请选择"
:disabled="isSettlementRuleChange"
>
<el-option label="否" :value="0" /> <el-option label="否" :value="0" />
<el-option label="是" :value="1" /> <el-option label="是" :value="1" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="原件合同编号"> <el-form-item label="原件合同编号">
<el-input v-model="form.originalContractNo" maxlength="100" placeholder="请输入" /> <el-input
v-model="form.originalContractNo"
maxlength="100"
placeholder="请输入"
:disabled="isSettlementRuleChange"
/>
</el-form-item> </el-form-item>
<el-form-item label="是否电子章"> <el-form-item label="是否电子章">
<el-select v-model="form.electronicSealFlag" placeholder="请选择"> <el-select
v-model="form.electronicSealFlag"
placeholder="请选择"
:disabled="isSettlementRuleChange"
>
<el-option label="否" :value="0" /> <el-option label="否" :value="0" />
<el-option label="是" :value="1" /> <el-option label="是" :value="1" />
</el-select> </el-select>
@@ -129,13 +167,19 @@
<el-input <el-input
v-model="form.invoiceCycle" v-model="form.invoiceCycle"
placeholder="请输入" placeholder="请输入"
:disabled="isSettlementRuleChange"
@input="value => positiveIntegerInput('invoiceCycle', value)" @input="value => positiveIntegerInput('invoiceCycle', value)"
> >
<template #suffix>天</template> <template #suffix>天</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="结算币种" prop="settlementCurrency"> <el-form-item label="结算币种" prop="settlementCurrency">
<el-select v-model="form.settlementCurrency" placeholder="请选择结算币种" clearable> <el-select
v-model="form.settlementCurrency"
placeholder="请选择结算币种"
clearable
:disabled="isSettlementRuleChange"
>
<el-option <el-option
v-for="item in settlementCurrencyOptions" v-for="item in settlementCurrencyOptions"
:key="item.value" :key="item.value"
@@ -145,7 +189,12 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="合同格式"> <el-form-item label="合同格式">
<el-select v-model="form.contractFormat" placeholder="请选择合同格式" clearable> <el-select
v-model="form.contractFormat"
placeholder="请选择合同格式"
clearable
:disabled="isSettlementRuleChange"
>
<el-option <el-option
v-for="item in contractFormatOptions" v-for="item in contractFormatOptions"
:key="item.value" :key="item.value"
@@ -158,19 +207,30 @@
<el-input <el-input
v-model="form.copyCount" v-model="form.copyCount"
placeholder="请输入" placeholder="请输入"
:disabled="isSettlementRuleChange"
@input="value => positiveIntegerInput('copyCount', value)" @input="value => positiveIntegerInput('copyCount', value)"
> >
<template #suffix>份</template> <template #suffix>份</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="是否需要加盖法人章"> <el-form-item label="是否需要加盖法人章">
<el-select v-model="form.legalSealFlag" placeholder="请选择" clearable> <el-select
v-model="form.legalSealFlag"
placeholder="请选择"
clearable
:disabled="isSettlementRuleChange"
>
<el-option label="否" :value="0" /> <el-option label="否" :value="0" />
<el-option label="是" :value="1" /> <el-option label="是" :value="1" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="结算方式"> <el-form-item label="结算方式">
<el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable> <el-select
v-model="form.settlementMode"
placeholder="请选择结算方式"
clearable
:disabled="isSettlementRuleChange"
>
<el-option <el-option
v-for="item in settlementModeOptions" v-for="item in settlementModeOptions"
:key="item.value" :key="item.value"
@@ -191,6 +251,7 @@
maxlength="1000" maxlength="1000"
show-word-limit show-word-limit
placeholder="请输入合同主要内容" placeholder="请输入合同主要内容"
:disabled="isSettlementRuleChange"
/> />
</el-form-item> </el-form-item>
<el-form-item label="其他事项说明" class="contract-basic-section__remark"> <el-form-item label="其他事项说明" class="contract-basic-section__remark">
@@ -201,6 +262,7 @@
maxlength="200" maxlength="200"
show-word-limit show-word-limit
placeholder="请输入其他事项说明" placeholder="请输入其他事项说明"
:disabled="isSettlementRuleChange"
/> />
</el-form-item> </el-form-item>
<!-- 风险客商提示:合同已推送合规并留痕高风险/黑名单时展示,变更提交同样要求特批附件 --> <!-- 风险客商提示:合同已推送合规并留痕高风险/黑名单时展示,变更提交同样要求特批附件 -->
@@ -214,6 +276,7 @@
title="用印材料" title="用印材料"
description description
attachment-type attachment-type
:readonly="isSettlementRuleChange"
:attachment-location-options="changeAttachmentLocationOptions" :attachment-location-options="changeAttachmentLocationOptions"
:rows="contractFileRows" :rows="contractFileRows"
:preview="previewAttachment" :preview="previewAttachment"
@@ -222,9 +285,41 @@
/> />
<section class="change-section"> <section class="change-section">
<div class="section-head"><div class="dialog-section-title">计费信息</div><el-button type="primary" plain @click="addPlan">添加</el-button></div> <div class="section-head">
<el-radio-group v-model="feeGenerationMode"><el-radio label="system">系统生成</el-radio><el-radio label="manual">账单导入生成</el-radio></el-radio-group> <div class="dialog-section-title">计费信息</div>
<el-table :data="plans" border class="change-table"><el-table-column type="index" label="序号" width="70" /><el-table-column prop="planName" label="方案名称" min-width="220" /><el-table-column label="默认方案" min-width="120"><template #default="{ row }">{{ row.defaultPlan ? '是' : '否' }}</template></el-table-column><el-table-column prop="remark" label="备注" min-width="260" /><el-table-column label="操作" width="180"><template #default="{ row, $index }"><el-link type="primary" @click="editPlan(row, $index)">编辑</el-link><el-link type="danger" @click="plans.splice($index, 1)">删除</el-link></template></el-table-column></el-table> <el-button
v-if="!pageReadonly && feeGenerationMode !== 'manual'"
type="primary"
plain
@click="addPlan"
>添加</el-button
>
</div>
<el-radio-group v-model="feeGenerationMode"
><el-radio label="system">系统生成</el-radio
><el-radio label="manual">账单导入生成</el-radio></el-radio-group
>
<el-table :data="plans" border class="change-table">
<el-table-column type="index" label="序号" width="70" />
<el-table-column prop="planName" label="方案名称" min-width="180" />
<el-table-column label="适用客商" min-width="180">
<template #default="{ row }">
{{ row.customerName || billingPlanCustomerLabel(row) || '-' }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="200" />
<el-table-column v-if="!pageReadonly" label="操作" width="180">
<template #default="{ row, $index }">
<el-link type="primary" @click="editPlan(row, $index)">编辑</el-link>
<el-link type="danger" @click="plans.splice($index, 1)">删除</el-link>
</template>
</el-table-column>
<el-table-column v-else label="操作" width="100">
<template #default="{ row, $index }">
<el-link type="primary" @click="editPlan(row, $index)">查看</el-link>
</template>
</el-table-column>
</el-table>
</section> </section>
<section class="change-section"> <section class="change-section">
@@ -243,19 +338,106 @@
<el-radio :label="0">关闭</el-radio> <el-radio :label="0">关闭</el-radio>
</el-radio-group> </el-radio-group>
</div> </div>
<el-form v-if="settlementRule.autoGenerate === 1" :model="settlementRule" label-position="right" label-width="auto" class="settlement-form"> <el-form
<el-form-item label="账单起始日" required><el-date-picker v-model="settlementRule.billStartDate" type="date" placeholder="请选择账单起始日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item> v-if="settlementRule.autoGenerate === 1"
<el-form-item label="结算类型" required><el-select v-model="settlementRule.settlementType" placeholder="请选择结算类型" @change="handleSettlementTypeChange"><el-option v-for="item in settlementTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item> :model="settlementRule"
<el-form-item v-if="showSettlementBillCycleType" label="结算周期" required><el-select v-model="settlementRule.billCycleType" placeholder="请选择结算周期" @change="handleCycleTypeChange"><el-option v-for="item in billCycleTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item> label-position="right"
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日" required><el-select v-model="settlementRule.billCutoffDay" placeholder="请选择账单截单日"><el-option v-for="item in billCutoffDayOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item> label-width="auto"
<el-form-item v-if="showSettlementCycleDays" label="周期天数" required><el-select v-model="settlementRule.cycleDays" placeholder="请选择周期天数"><el-option v-for="item in cycleDayOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item> class="settlement-form"
>
<el-form-item label="账单起始日" required
><el-date-picker
v-model="settlementRule.billStartDate"
type="date"
placeholder="请选择账单起始日"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/></el-form-item>
<el-form-item label="结算类型" required
><el-select
v-model="settlementRule.settlementType"
placeholder="请选择结算类型"
@change="handleSettlementTypeChange"
><el-option
v-for="item in settlementTypeOptions"
:key="item"
:label="item"
:value="item"
/></el-select></el-form-item>
<el-form-item v-if="showSettlementBillCycleType" label="结算周期" required
><el-select
v-model="settlementRule.billCycleType"
placeholder="请选择结算周期"
@change="handleCycleTypeChange"
><el-option
v-for="item in billCycleTypeOptions"
:key="item"
:label="item"
:value="item"
/></el-select></el-form-item>
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日" required
><el-select v-model="settlementRule.billCutoffDay" placeholder="请选择账单截单日"
><el-option
v-for="item in billCutoffDayOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/></el-select></el-form-item>
<el-form-item v-if="showSettlementCycleDays" label="周期天数" required
><el-select v-model="settlementRule.cycleDays" placeholder="请选择周期天数"
><el-option
v-for="item in cycleDayOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/></el-select></el-form-item>
</el-form> </el-form>
<el-table v-if="settlementRule.autoGenerate === 1 && showSettlementCustomPeriods" :data="settlementRule.customPeriods || []" border class="change-table custom-periods-table"> <el-table
v-if="settlementRule.autoGenerate === 1 && showSettlementCustomPeriods"
:data="settlementRule.customPeriods || []"
border
class="change-table custom-periods-table"
>
<el-table-column type="index" label="序号" width="70" /> <el-table-column type="index" label="序号" width="70" />
<el-table-column label="运单区间-开始日" min-width="180"><template #default="{ row, $index }"><el-select :model-value="row.startDay" placeholder="请选择" clearable @update:model-value="value => handleCustomPeriodStartDayChange($index, value)"><el-option v-for="item in billCutoffDayOptions" :key="`start-${item.value}`" :label="item.label" :value="item.value" /></el-select></template></el-table-column> <el-table-column label="运单区间-开始日" min-width="180"
<el-table-column label="运单区间-结束日" min-width="200"><template #default="{ row, $index }"><el-select :model-value="row.endDay" placeholder="请选择" clearable @update:model-value="value => handleCustomPeriodEndDayChange($index, value)"><el-option v-for="item in customPeriodEndDayOptions(row)" :key="item.value" :label="item.label" :value="item.value" /></el-select></template></el-table-column> ><template #default="{ row, $index }"
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link v-if="$index === 0" type="primary" @click="addCustomPeriodRow">添加</el-link><el-link v-else type="danger" @click="removeCustomPeriodRow($index)">删除</el-link></template></el-table-column> ><el-select
<template #empty><el-link type="primary" @click="addCustomPeriodRow">添加</el-link></template> :model-value="row.startDay"
placeholder="请选择"
clearable
@update:model-value="value => handleCustomPeriodStartDayChange($index, value)"
><el-option
v-for="item in billCutoffDayOptions"
:key="`start-${item.value}`"
:label="item.label"
:value="item.value"
/></el-select></template
></el-table-column>
<el-table-column label="运单区间-结束日" min-width="200"
><template #default="{ row, $index }"
><el-select
:model-value="row.endDay"
placeholder="请选择"
clearable
@update:model-value="value => handleCustomPeriodEndDayChange($index, value)"
><el-option
v-for="item in customPeriodEndDayOptions(row)"
:key="item.value"
:label="item.label"
:value="item.value"
/></el-select></template
></el-table-column>
<el-table-column v-if="!pageReadonly" label="操作" width="100"
><template #default="{ $index }"
><el-link v-if="$index === 0" type="primary" @click="addCustomPeriodRow">添加</el-link
><el-link v-else type="danger" @click="removeCustomPeriodRow($index)"
>删除</el-link
></template
></el-table-column
>
<template #empty
><el-link v-if="!pageReadonly" type="primary" @click="addCustomPeriodRow">添加</el-link></template
>
</el-table> </el-table>
</section> </section>
@@ -263,24 +445,57 @@
<div class="dialog-section-title"> <div class="dialog-section-title">
付款比例设置 付款比例设置
<el-tooltip content="非必填;配置付款比例时,合计必须等于100%。" placement="top"> <el-tooltip content="非必填;配置付款比例时,合计必须等于100%。" placement="top">
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon> <el-icon style="margin-left: 4px; color: #909399; cursor: pointer"
><el-icon-question-filled
/></el-icon>
</el-tooltip> </el-tooltip>
</div> </div>
<el-table :data="paymentRatioRows" border class="change-table"> <el-table :data="paymentRatioRows" border class="change-table">
<el-table-column type="index" label="序号" width="70" /> <el-table-column type="index" label="序号" width="70" />
<el-table-column prop="paymentTerm" label="付款笔数" min-width="180" /> <el-table-column prop="paymentTerm" label="付款笔数" min-width="180" />
<el-table-column label="付款比例上限(%)" min-width="220"><template #default="{ row }"><el-input-number v-model="row.ratioLimit" :min="0" :max="100" :precision="2" controls-position="right" /></template></el-table-column> <el-table-column label="付款比例上限(%)" min-width="220"
<el-table-column label="备注" min-width="220"><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" /></template></el-table-column> ><template #default="{ row }"
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="paymentRatioRows.splice($index, 1)">删除</el-link></template></el-table-column> ><el-input-number
v-model="row.ratioLimit"
:min="0"
:max="100"
:precision="2"
controls-position="right"
:disabled="isSettlementRuleChange"
/></template
></el-table-column>
<el-table-column label="备注" min-width="220"
><template #default="{ row }"
><el-input
v-model="row.remark"
maxlength="200"
:disabled="isSettlementRuleChange"
/></template
></el-table-column>
<el-table-column v-if="!pageReadonly && !isSettlementRuleChange" label="操作" width="100"
><template #default="{ $index }"
><el-link type="danger" @click="paymentRatioRows.splice($index, 1)"
>删除</el-link
></template
></el-table-column
>
</el-table> </el-table>
<el-button type="primary" plain @click="addPaymentRatioRow">添加</el-button> <el-button
v-if="!pageReadonly && !isSettlementRuleChange"
type="primary"
plain
@click="addPaymentRatioRow"
>添加</el-button
>
</section> </section>
<billing-plan-editor <billing-plan-editor
v-model="planDialogVisible" v-model="planDialogVisible"
:value="planEditor" :value="planEditor"
:index="planEditorIndex" :index="planEditorIndex"
:readonly="pageReadonly"
:settlement-currency="form.settlementCurrency" :settlement-currency="form.settlementCurrency"
:party-options="billingPartyOptions"
@save="savePlan" @save="savePlan"
/> />
@@ -290,6 +505,7 @@
attachment-type attachment-type
attachment-location="附件(佐证材料等)" attachment-location="附件(佐证材料等)"
attachment-type-dict-code="contract_supporting_attachment_type" attachment-type-dict-code="contract_supporting_attachment_type"
:readonly="isSettlementRuleChange"
:attachment-location-options="changeAttachmentLocationOptions" :attachment-location-options="changeAttachmentLocationOptions"
:rows="attachments" :rows="attachments"
:preview="previewAttachment" :preview="previewAttachment"
@@ -322,8 +538,9 @@
@update:rows="changeMaterials = $event" @update:rows="changeMaterials = $event"
@upload-to-location="handleAttachmentUploadToLocation" @upload-to-location="handleAttachmentUploadToLocation"
/> />
<div class="page-footer"> <div v-if="!pageReadonly" class="page-footer">
<el-button @click="$router.back()">取消</el-button> <el-button @click="goBack">取消</el-button>
<el-button v-if="isContractChangeModule" @click="saveDraft">保存草稿</el-button>
<el-button type="primary" @click="submit">提交</el-button> <el-button type="primary" @click="submit">提交</el-button>
</div> </div>
</el-form> </el-form>
@@ -336,8 +553,10 @@
</template> </template>
<script> <script>
import * as api from '@/api/business/contract-manage'; import * as contractApi from '@/api/business/contract-manage';
import { submitMkApprovalFlow } from '@/utils/mk-approval'; import * as changeApi from '@/api/business/contract-change';
import { getMkPublicDetail } from '@/api/mk-process';
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
import BillingPlanEditor from './components/billing-plan-editor.vue'; import BillingPlanEditor from './components/billing-plan-editor.vue';
import ContractAttachmentSection, { import ContractAttachmentSection, {
isContractSealMaterialLocation, isContractSealMaterialLocation,
@@ -374,6 +593,8 @@ const defaultContractFormatOptions = [
{ label: '电子合同', value: '电子合同' }, { label: '电子合同', value: '电子合同' },
{ label: '纸质合同', value: '纸质合同' }, { label: '纸质合同', value: '纸质合同' },
]; ];
const contractChangeListRoute = '/business/contract-change';
const contractChangeListRefreshKey = 'contract-change-list-refresh';
const normalizeOptionalPositiveInteger = value => { const normalizeOptionalPositiveInteger = value => {
if (value === undefined || value === null || value === '') return null; if (value === undefined || value === null || value === '') return null;
const number = Number(value); const number = Number(value);
@@ -413,6 +634,45 @@ const normalizeCustomPeriods = (periods = []) =>
endDay: Number(item?.endDay) > 0 ? Number(item.endDay) : '', endDay: Number(item?.endDay) > 0 ? Number(item.endDay) : '',
})); }));
const createPartyRow = (row = {}) => ({
id: row.id || `${Date.now()}${Math.floor(Math.random() * 1000)}`,
customerId: row.customerId || '',
customerName: row.customerName || '',
creditCode: row.creditCode || row.unifiedCreditCode || '',
contactId: row.contactId || '',
contactName: row.contactName || '',
contactPhone: row.contactPhone || '',
});
const partyRoleLabel = role => {
if (role === 'A') return '甲方';
if (role === 'B') return '乙方';
if (role === 'C') return '丙方';
return role || '';
};
const normalizePartyRows = (partyJson, fallbackName, requireOne) => {
const rows = (() => {
if (Array.isArray(partyJson)) return partyJson;
if (!partyJson) return [];
try {
const data = JSON.parse(partyJson);
return Array.isArray(data) ? data : [];
} catch (error) {
return [];
}
})().map(item => createPartyRow(item));
if (rows.length) return rows;
if (fallbackName) {
return String(fallbackName)
.split(/[、,]/)
.map(name => name.trim())
.filter(Boolean)
.map(name => createPartyRow({ customerName: name }));
}
return requireOne ? [createPartyRow()] : [];
};
export default { export default {
components: { BillingPlanEditor, ContractAttachmentSection, ElImageViewer, OpenFileViewer }, components: { BillingPlanEditor, ContractAttachmentSection, ElImageViewer, OpenFileViewer },
data() { data() {
@@ -420,6 +680,9 @@ export default {
form: {}, form: {},
period: [], period: [],
plans: [], plans: [],
partyARows: [],
partyBRows: [],
partyCRows: [],
attachments: [], attachments: [],
contractFiles: [], contractFiles: [],
contractFileRows: [], contractFileRows: [],
@@ -445,7 +708,7 @@ export default {
}, },
preSettlementConfig: {}, preSettlementConfig: {},
formalSettlementConfig: {}, formalSettlementConfig: {},
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'], settlementTypeOptions: ['月结', '日结', '周结', '固定天数周期结算'],
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'], billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
billCutoffDayOptions: Array.from({ length: 31 }, (_, index) => ({ billCutoffDayOptions: Array.from({ length: 31 }, (_, index) => ({
label: `${index + 1}日`, label: `${index + 1}日`,
@@ -458,7 +721,7 @@ export default {
settlementModeDictOptions: [], settlementModeDictOptions: [],
contractFormatDictOptions: [], contractFormatDictOptions: [],
planDialogVisible: false, planDialogVisible: false,
planEditor: { planName: '', defaultPlan: false, remark: '', rules: [] }, planEditor: { planName: '', remark: '', rules: [] },
planEditorIndex: -1, planEditorIndex: -1,
billingElements: [ billingElements: [
'按重量', '按重量',
@@ -470,6 +733,24 @@ export default {
'按数量', '按数量',
], ],
rules: { rules: {
contractNo: [
{
validator: (rule, value, callback) => {
if (this.isSettlementRuleChange) {
return callback();
}
const contractNo = String(value || '').trim();
if (!contractNo) {
return callback(new Error('请输入合同编号'));
}
if (contractNo.length > 100) {
return callback(new Error('合同编号不能超过100个字符'));
}
callback();
},
trigger: 'blur',
},
],
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }], contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
contractAmount: [ contractAmount: [
{ required: true, message: '请输入合同金额', trigger: 'blur' }, { required: true, message: '请输入合同金额', trigger: 'blur' },
@@ -492,12 +773,25 @@ export default {
}, },
computed: { computed: {
pageTitle() { pageTitle() {
if (this.pageReadonly) return this.$route.query.name || '查看合同变更';
return this.$route.query.name || '合同变更'; return this.$route.query.name || '合同变更';
}, },
isContractChangeModule() {
return (
this.$route.path === '/business/contract-change/form' ||
this.$route.path === '/business/contract-change/public-view'
);
},
isPublicViewPage() {
return this.$route.path === '/business/contract-change/public-view';
},
pageReadonly() {
return this.isPublicViewPage || this.$route.query.mode === 'view';
},
// 高风险/黑名单客商提示:按合同推送合规留痕 hcs_risk_level/hcs_black_status 判定(与合同详情口径一致) // 高风险/黑名单客商提示:按合同推送合规留痕 hcs_risk_level/hcs_black_status 判定(与合同详情口径一致)
riskTipText() { riskTipText() {
const riskLevel = String(this.form.hcsRiskLevel || '').trim(); const riskLevel = String(this.form.hcsRiskLevel || '').trim();
const blacklist = String(this.form.hcsBlackStatus || '').toLowerCase() === 'yes'; const blacklist = String(this.form.kingdeeBlackStatus || '').toLowerCase() === 'yes';
if (riskLevel !== '2' && !blacklist) return ''; if (riskLevel !== '2' && !blacklist) return '';
const parties = [this.form.partyA, this.form.partyB] const parties = [this.form.partyA, this.form.partyB]
.map(name => String(name || '').trim()) .map(name => String(name || '').trim())
@@ -505,7 +799,36 @@ export default {
.join('、'); .join('、');
return `【${parties || '相对方'}】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷。提交变更前须上传「特批附件」类型附件`; return `【${parties || '相对方'}】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷。提交变更前须上传「特批附件」类型附件`;
}, },
isSettlementRuleChange() {
return this.form.changeType === '结算规则变更';
},
/**
* 计费方案适用客商:客户合同仅甲方;承运商合同仅乙方、丙方
*/
billingPartyOptions() {
const category = String(this.form.contractCategory || '').trim();
const allowedRoles =
category === '承运商合同' ? ['B', 'C'] : category === '客户合同' ? ['A'] : ['A', 'B', 'C'];
return [
...this.partyARows.map(row => ({ ...row, partyRole: 'A' })),
...this.partyBRows.map(row => ({ ...row, partyRole: 'B' })),
...this.partyCRows.map(row => ({ ...row, partyRole: 'C' })),
]
.filter(row => allowedRoles.includes(row.partyRole))
.filter(row => row.customerName || row.customerId)
.map(row => ({
value: row.id,
label: `${partyRoleLabel(row.partyRole)}-${row.customerName || row.customerId}`,
partyRole: row.partyRole,
partyRowId: row.id,
customerId: row.customerId,
customerName: row.customerName,
}));
},
changeAttachmentLocationOptions() { changeAttachmentLocationOptions() {
if (this.isSettlementRuleChange) {
return ['变更材料'];
}
return ['用印材料', '附件(佐证材料等)', '变更材料']; return ['用印材料', '附件(佐证材料等)', '变更材料'];
}, },
contractAmountDisplay() { contractAmountDisplay() {
@@ -606,13 +929,25 @@ export default {
async load() { async load() {
const id = this.$route.query.id; const id = this.$route.query.id;
if (!id) return; if (!id) return;
const res = await api.getDetail(id); let data = {};
const data = res.data?.data || res.data || {}; if (this.isPublicViewPage) {
const res = await getMkPublicDetail('contract-change', id);
data = res.data?.data || res.data || {};
} else if (this.isContractChangeModule) {
const res = await changeApi.getDetail(id);
data = res.data?.data || res.data || {};
} else {
const res = await contractApi.getDetail(id);
data = res.data?.data || res.data || {};
}
const isChangeRecord = this.isContractChangeModule || this.isPublicViewPage;
this.form = { this.form = {
...data, ...data,
changeContent: '', // 独立变更单保留 contractId;旧路由以合同 id 作为主键
changeReason: '', contractId: data.contractId || (isChangeRecord ? data.contractId : data.id),
changeAttachmentsJson: '', changeContent: isChangeRecord ? data.changeContent || '' : '',
changeReason: isChangeRecord ? data.changeReason || '' : '',
changeAttachmentsJson: data.changeAttachmentsJson || '',
copyCount: normalizeOptionalPositiveInteger(data.copyCount), copyCount: normalizeOptionalPositiveInteger(data.copyCount),
invoiceCycle: normalizeOptionalPositiveInteger(data.invoiceCycle), invoiceCycle: normalizeOptionalPositiveInteger(data.invoiceCycle),
paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays),
@@ -620,14 +955,17 @@ export default {
templateFlag: data.templateFlag ?? 0, templateFlag: data.templateFlag ?? 0,
electronicSealFlag: data.electronicSealFlag ?? 0, electronicSealFlag: data.electronicSealFlag ?? 0,
archiveStatus: data.archiveStatus || '未归档', archiveStatus: data.archiveStatus || '未归档',
changeType: '合同信息变更', changeType: data.changeType || '合同信息变更',
}; };
this.changeMaterials = []; this.changeMaterials = this.parse(data.changeAttachmentsJson);
this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : [];
this.partyARows = normalizePartyRows(data.partyAJson, data.partyA, true);
this.partyBRows = normalizePartyRows(data.partyBJson, data.partyB, true);
this.partyCRows = normalizePartyRows(data.partyCJson, data.partyC, false);
this.plans = this.parse(data.billingPlanJson); this.plans = this.parse(data.billingPlanJson);
this.attachments = this.mergeChangeAttachments( this.attachments = this.mergeChangeAttachments(
this.parse(data.attachmentsJson), this.parse(data.attachmentsJson),
this.parse(data.changeAttachmentsJson) isChangeRecord ? [] : this.parse(data.changeAttachmentsJson)
); );
this.contractFileRows = normalizeContractFileRows(this.parse(data.contractFileJson)); this.contractFileRows = normalizeContractFileRows(this.parse(data.contractFileJson));
const rules = this.parseObject(data.settlementRuleJson); const rules = this.parseObject(data.settlementRuleJson);
@@ -669,6 +1007,70 @@ export default {
data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system');
this.paymentRatioRows = this.parse(data.paymentRatioJson); this.paymentRatioRows = this.parse(data.paymentRatioJson);
}, },
goBack() {
if (this.isContractChangeModule) {
this.$router.push(contractChangeListRoute);
return;
}
this.$router.back();
},
buildChangePayload() {
if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule };
else this.formalSettlementConfig = { ...this.settlementRule };
const settlementRule = {
preSettlementConfig: this.preSettlementConfig,
formalSettlementConfig: this.formalSettlementConfig,
};
if (this.isSettlementRuleChange) {
return {
id: this.form.id,
contractId: this.form.contractId,
changeType: this.form.changeType,
changeContent: this.form.changeContent,
changeReason: this.form.changeReason,
feeGenerationMode: this.feeGenerationMode,
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
billingPlanJson: JSON.stringify(this.plans),
settlementRuleJson: JSON.stringify(settlementRule),
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
};
}
return {
...this.form,
contractId: this.form.contractId,
changeType: this.form.changeType || '合同信息变更',
settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB',
copyCount: normalizeOptionalPositiveInteger(this.form.copyCount),
invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle),
paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays),
contractAmount: normalizeOptionalAmount(this.form.contractAmount),
startDate: this.period[0],
endDate: this.period[1],
feeGenerationMode: this.feeGenerationMode,
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
billingPlanJson: JSON.stringify(this.plans),
settlementRuleJson: JSON.stringify(settlementRule),
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
paymentRatioJson: JSON.stringify(this.paymentRatioRows),
contractFileJson: JSON.stringify(this.contractFileRows),
attachmentsJson: JSON.stringify(this.attachments),
changeContent: this.form.changeContent,
changeReason: this.form.changeReason,
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
};
},
async saveDraft() {
if (this.form.changeType === '终止合同') {
this.$message.warning('终止合同无需保存草稿,请直接提交');
return;
}
const payload = this.buildChangePayload();
await changeApi.saveDraft(payload);
this.$message.success('草稿已保存');
},
parse(value) { parse(value) {
try { try {
const result = JSON.parse(value || '[]'); const result = JSON.parse(value || '[]');
@@ -718,10 +1120,32 @@ export default {
.replace(/\D/g, '') .replace(/\D/g, '')
.replace(/^0+/, ''); .replace(/^0+/, '');
}, },
addPlan() { this.planEditorIndex = -1; this.planEditor = { planName: `计费方案${this.plans.length + 1}`, defaultPlan: !this.plans.length, remark: '', rules: [{}] }; this.planDialogVisible = true; }, addPlan() {
editPlan(row, index) { this.planEditorIndex = index; this.planEditor = JSON.parse(JSON.stringify(row)); this.planDialogVisible = true; }, this.planEditorIndex = -1;
toggleDefaultPlan(value) { if (value) this.plans.forEach(item => { item.defaultPlan = false; }); }, this.planEditor = {
savePlan(value, index) { if (index < 0) this.plans.push(value); else this.plans.splice(index, 1, value); if (value.defaultPlan) this.plans.forEach((item, current) => { if (current !== (index < 0 ? this.plans.length - 1 : index)) item.defaultPlan = false; }); }, planName: `计费方案${this.plans.length + 1}`,
remark: '',
rules: [{}],
};
this.planDialogVisible = true;
},
editPlan(row, index) {
this.planEditorIndex = index;
this.planEditor = JSON.parse(JSON.stringify(row));
this.planDialogVisible = true;
},
billingPlanCustomerLabel(row) {
const matched = this.billingPartyOptions.find(
item => String(item.partyRowId) === String(row.partyRowId)
);
return matched?.label || row.customerName || '';
},
savePlan(value, index) {
const nextPlan = { ...value };
delete nextPlan.defaultPlan;
if (index < 0) this.plans.push(nextPlan);
else this.plans.splice(index, 1, nextPlan);
},
handleSettlementTypeChange(value) { if (value !== '月结') { this.settlementRule.billCycleType = ''; this.settlementRule.billCutoffDay = ''; this.settlementRule.customPeriods = []; } else if (this.settlementRule.billCycleType === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; if (value !== '固定天数周期结算') this.settlementRule.cycleDays = ''; }, handleSettlementTypeChange(value) { if (value !== '月结') { this.settlementRule.billCycleType = ''; this.settlementRule.billCutoffDay = ''; this.settlementRule.customPeriods = []; } else if (this.settlementRule.billCycleType === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; if (value !== '固定天数周期结算') this.settlementRule.cycleDays = ''; },
handleCycleTypeChange(value) { if (value !== '固定截单日') this.settlementRule.billCutoffDay = ''; if (value === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; }, handleCycleTypeChange(value) { if (value !== '固定截单日') this.settlementRule.billCutoffDay = ''; if (value === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; },
attachmentUrl(row = {}) { return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || ''; }, attachmentUrl(row = {}) { return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || ''; },
@@ -731,6 +1155,12 @@ export default {
previewAttachment(row, rows = this.attachments) { const url = this.attachmentUrl(row); if (!url) { this.$message.warning('附件地址为空,无法预览'); return; } if (this.isAttachmentImage(row)) { this.imagePreviewUrls = (rows || []).filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item)).map(item => this.attachmentUrl(item)); this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0); this.imagePreviewVisible = true; return; } this.previewFile = { name: this.attachmentName(row), url, mimeType: row.mimeType || row.contentType || '' }; this.documentPreviewVisible = true; }, previewAttachment(row, rows = this.attachments) { const url = this.attachmentUrl(row); if (!url) { this.$message.warning('附件地址为空,无法预览'); return; } if (this.isAttachmentImage(row)) { this.imagePreviewUrls = (rows || []).filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item)).map(item => this.attachmentUrl(item)); this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0); this.imagePreviewVisible = true; return; } this.previewFile = { name: this.attachmentName(row), url, mimeType: row.mimeType || row.contentType || '' }; this.documentPreviewVisible = true; },
handleAttachmentUploadToLocation({ location, rows = [] }) { handleAttachmentUploadToLocation({ location, rows = [] }) {
if (!rows.length) return; if (!rows.length) return;
if (this.isSettlementRuleChange) {
if (location === '变更材料') {
this.changeMaterials = [...(this.changeMaterials || []), ...rows];
}
return;
}
if (isContractSealMaterialLocation(location)) { if (isContractSealMaterialLocation(location)) {
this.contractFileRows = [...(this.contractFileRows || []), ...rows]; this.contractFileRows = [...(this.contractFileRows || []), ...rows];
return; return;
@@ -754,7 +1184,66 @@ export default {
removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); }, removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; }, validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; },
validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; }, validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), contractAmount: normalizeOptionalAmount(this.form.contractAmount), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); await submitMkApprovalFlow({ bizType: 'contract-manage', bizId: this.form.id, subjectName: this.form.contractName || '', approvalStatus: this.form.approvalStatus || 'change_rejected' }); this.$message.success('变更已提交'); this.$router.back(); }, async submit() {
if (this.form.changeType === '终止合同') {
const contractId = this.form.contractId || this.form.id;
await contractApi.terminate(contractId, this.form.changeReason);
this.$message.success('合同已终止');
this.goBack();
return;
}
await this.$refs.formRef.validate();
if (!this.isSettlementRuleChange) {
const contractNo = String(this.form.contractNo || '').trim();
if (!contractNo) {
this.$message.warning('请输入合同编号');
return;
}
if (contractNo.length > 100) {
this.$message.warning('合同编号不能超过100个字符');
return;
}
this.form.contractNo = contractNo;
const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0);
if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) {
this.$message.warning('付款比例上限合计必须等于100%');
return;
}
}
if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule };
else this.formalSettlementConfig = { ...this.settlementRule };
if (
!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') ||
!this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')
) {
return;
}
const payload = this.buildChangePayload();
if (this.isContractChangeModule) {
await changeApi.submit(payload);
await submitMkApprovalFlow({
bizType: 'contract-change',
bizId: this.form.id,
subjectName: this.form.contractName || '',
approvalStatus: this.form.approvalStatus || '',
mkEnabled: await loadMkEnabled().catch(() => false),
});
sessionStorage.setItem(contractChangeListRefreshKey, '1');
this.$message.success('变更已提交');
this.$router.push(contractChangeListRoute);
return;
}
await contractApi.submitChange(payload);
await submitMkApprovalFlow({
bizType: 'contract-manage',
bizId: this.form.id,
subjectName: this.form.contractName || '',
approvalStatus: this.form.approvalStatus || 'change_rejected',
mkEnabled: await loadMkEnabled().catch(() => false),
});
this.$message.success('变更已提交');
this.$router.back();
},
}, },
}; };
</script> </script>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More