Compare commits

...

34 Commits

Author SHA1 Message Date
52d4b6b486 Merge remote-tracking branch 'websoft/master'
# Conflicts:
#	src/option/transportCapacity/transport-ship.js
#	src/option/transportCapacity/transport-vehicle.js
2026-08-07 08:28:42 +08:00
6a02e9126b 1、车船模块fix bug
2、运力模块fix bug
3、新增设备台账
4、其他bug 修复
5、调整组织、人员模块
2026-08-07 08:28:21 +08:00
bb580b2bfc style(business): 优化派遣摘要及路线项布局样式
- 调整派遣摘要网格列宽,改善整体布局比例
- 增加派遣路线容器间距,提升视觉分隔效果
- 修改派遣路线项最大宽度和内边距,提升内容展示空间
- 添加派遣路线项最小高度及虚线边框,增强区块感和可视性
- 调整派遣路线项边角圆角,优化界面圆润度
2026-08-07 01:41:35 +08:00
6c91889e33 style(business): 优化调度路线组件样式及结构调整
- 用车辆图标替换调度路线横线,提升视觉表现
- 调整调度路线布局,使用 grid 增强对齐和间距控制
- 修改调度路线节点样式,统一圆形背景和阴影效果
- 优化标题、地址、联系人文本样式,增加省略号和颜色区分
- 调整调度路线图标容器样式,增加边框和柔和阴影
- 使用伪元素实现两侧虚线提醒,增强组件连接感
2026-08-07 01:29:32 +08:00
4bef3430e0 refactor(business): 优化派车信息展示和样式布局
- 将基本信息部分整体包裹在section-card内并调整布局顺序
- 调整收发货路线结构,合并地址和联系人信息至路线节点内
- 使用CSS grid替代部分flex布局,优化网格间距和对齐
- 更新文字换行和溢出处理,增强可读性
- 删除冗余的收发货路线详细信息区域,精简代码结构
- 统一section-card的样式和内部内容伸缩布局
- 调整附件显示方式,保证链接样式一致性
2026-08-07 01:21:46 +08:00
17d12d279d refactor(business): 优化派车信息展示和样式布局
- 将基本信息部分整体包裹在section-card内并调整布局顺序
- 调整收发货路线结构,合并地址和联系人信息至路线节点内
- 使用CSS grid替代部分flex布局,优化网格间距和对齐
- 更新文字换行和溢出处理,增强可读性
- 删除冗余的收发货路线详细信息区域,精简代码结构
- 统一section-card的样式和内部内容伸缩布局
- 调整附件显示方式,保证链接样式一致性
2026-08-07 01:03:29 +08:00
9cf005cece refactor(business): 优化派车信息展示和样式布局
- 将基本信息部分整体包裹在section-card内并调整布局顺序
- 调整收发货路线结构,合并地址和联系人信息至路线节点内
- 使用CSS grid替代部分flex布局,优化网格间距和对齐
- 更新文字换行和溢出处理,增强可读性
- 删除冗余的收发货路线详细信息区域,精简代码结构
- 统一section-card的样式和内部内容伸缩布局
- 调整附件显示方式,保证链接样式一致性
2026-08-07 00:54:19 +08:00
d8db9b1edd feat(table): 调整表格操作列宽度并统一文字链接间距
- /business/project-apply 主表操作列宽度固定为 220,移除 applyTableMenuWidth 调用
- /business/temporary-credit-limit 通过传入 menuWidth prop 固定操作列宽度为 180
- /base/common-address 操作列宽度由 220 降至 180
- /business/process-config 操作列宽度由 320 调整为 180,修复末尾语法错误
- /vehicle/insurance-record 操作列宽度先降至 140 后改回 160,后全部车/船务管理模块记录页统一为 180
- 多个车/船务管理模块相关 option 文件及 vue 文件操作列宽统调为 180
- 新增 business-crud-page 组件 menuWidth prop,支持外部传值控制操作列宽度
- 全站操作列文字链接去除 el-link 默认 margin,避免和 gap 叠加导致文字间距过宽
- 优化操作列按钮过多时自动换行展示逻辑,确保按钮不被裁切
2026-08-07 00:44:26 +08:00
cf04d48010 feat(table): 调整表格操作列宽度并统一文字链接间距
- /business/project-apply 主表操作列宽度固定为 220,移除 applyTableMenuWidth 调用
- /business/temporary-credit-limit 通过传入 menuWidth prop 固定操作列宽度为 180
- /base/common-address 操作列宽度由 220 降至 180
- /business/process-config 操作列宽度由 320 调整为 180,修复末尾语法错误
- /vehicle/insurance-record 操作列宽度先降至 140 后改回 160,后全部车/船务管理模块记录页统一为 180
- 多个车/船务管理模块相关 option 文件及 vue 文件操作列宽统调为 180
- 新增 business-crud-page 组件 menuWidth prop,支持外部传值控制操作列宽度
- 全站操作列文字链接去除 el-link 默认 margin,避免和 gap 叠加导致文字间距过宽
- 优化操作列按钮过多时自动换行展示逻辑,确保按钮不被裁切
2026-08-06 23:55:30 +08:00
454608d7be feat(dispatch): 优化运输计划调度弹窗视觉布局并提升紧凑度
- 弹窗标题和单号合并一行,移除分隔符,提升信息整体感
- 基本信息和收发货路线由左右双列改为上下堆叠,改善视觉层次
- 基本信息网格调整为4列布局,货物信息扩大占2列,附件独占一行
- 计划执行时间范围由‘-’改为‘ ~ ’,更清晰表达时间区间
- 路线节点展示改为横向徽章加下方双列详细信息,避免空白和居中留白
- 调整字体大小、颜色及间距,整体风格更简洁且信息更紧凑
- 移除无用边距和min-height约束,使弹窗整体更加紧凑精简
- 更新代理配置,开发期/api请求代理指向指定内网地址,统一路径无需重写,
  以解决跨域问题并保持与后端网关路径一致
- 进行了完整构建验证,确保修改不会影响现有功能稳定性
2026-08-06 23:10:06 +08:00
d82d06dc78 feat(dispatch): 优化运输计划调度弹窗视觉布局并提升紧凑度
- 弹窗标题和单号合并一行,移除分隔符,提升信息整体感
- 基本信息和收发货路线由左右双列改为上下堆叠,改善视觉层次
- 基本信息网格调整为4列布局,货物信息扩大占2列,附件独占一行
- 计划执行时间范围由‘-’改为‘ ~ ’,更清晰表达时间区间
- 路线节点展示改为横向徽章加下方双列详细信息,避免空白和居中留白
- 调整字体大小、颜色及间距,整体风格更简洁且信息更紧凑
- 移除无用边距和min-height约束,使弹窗整体更加紧凑精简
- 更新代理配置,开发期/api请求代理指向指定内网地址,统一路径无需重写,
  以解决跨域问题并保持与后端网关路径一致
- 进行了完整构建验证,确保修改不会影响现有功能稳定性
2026-08-06 23:06:45 +08:00
f03f764472 feat(dispatch): 优化运输计划调度弹窗视觉布局并提升紧凑度
- 弹窗标题和单号合并一行,移除分隔符,提升信息整体感
- 基本信息和收发货路线由左右双列改为上下堆叠,改善视觉层次
- 基本信息网格调整为4列布局,货物信息扩大占2列,附件独占一行
- 计划执行时间范围由‘-’改为‘ ~ ’,更清晰表达时间区间
- 路线节点展示改为横向徽章加下方双列详细信息,避免空白和居中留白
- 调整字体大小、颜色及间距,整体风格更简洁且信息更紧凑
- 移除无用边距和min-height约束,使弹窗整体更加紧凑精简
- 更新代理配置,开发期/api请求代理指向指定内网地址,统一路径无需重写,
  以解决跨域问题并保持与后端网关路径一致
- 进行了完整构建验证,确保修改不会影响现有功能稳定性
2026-08-06 19:53:34 +08:00
1e4de76978 Merge remote-tracking branch 'websoft/master'
# Conflicts:
#	src/option/business/process-config.js
#	src/views/business/process-config.vue
#	src/views/vehicle/customer-archive.vue
2026-08-06 17:41:03 +08:00
e6d4edf362 项目、合同fix 2026-08-06 17:40:03 +08:00
1862115e1d feat(business-crud-page): 优化运输计划调度弹窗UI与交互体验
- 使用全局<section-card>组件重构调度弹窗三块分区,提升视觉一致性
- 将弹窗灰底设置为#f5f6fa,调整padding及溢出滚动,增强弹窗内容可读性
- 收发货路线样式由黑实线改为主色号虚线并添加箭头,增强方向感
- 待调度列表纳入白卡容器,新增按钮及汇总文字移入列表卡extra slot
- 操作列宽度由180调整至220,预留扩展空间
- 增加分隔线和间距分割调度表底部区域,优化布局层次
2026-08-06 17:39:04 +08:00
49236cad56 style(ui): 调整对话框宽度以优化界面布局
- 将driver.vue中driver-dialog宽度从92%调整为90%
- 将process-config.vue中process-config-dialog宽度从1440px调整为70%
- 保持对话框顶部间距和关闭事件不变
2026-08-06 16:59:34 +08:00
02b2e3b673 refactor(transportCapacity): 使用 section-card 重构司机管理弹窗布局
- 将司机管理页面新增/编辑弹窗中四个分组改为使用标准 section-card 组件包裹
- 删除手写的扁平样式和红色竖条,统一使用全局 element-ui 样式维护弹窗视觉风格
- 调整司机证件上传区尺寸为 240x151 px,保持身份证比例并左对齐显示
- 优化弹窗中各表单项布局,提高视觉层次与一致性
- 更新 scoped 样式,移除无用的 section-title 样式,保持代码清洁
2026-08-06 16:53:23 +08:00
53676a1150 refactor(vehicle): 优化customer-archive弹窗结构与样式
- 使用<section-card>替换旧版灰底分组块与内层白卡,统一表单分组样式
- 删除冗余的基本信息大标题,减少嵌套层级
- 将客商材料和变更记录区域改用<section-card>,操作按钮通过插槽放置卡头右侧
- 清理无效的scoped样式,简化样式管理
- 统一弹窗内部灰底背景并增加16px内边距,与project-apply保持一致
- 确认vue文件与sass编译无误,保证改动稳定性
2026-08-06 15:09:44 +08:00
5e92124710 Merge remote-tracking branch 'websoft/master' 2026-08-06 11:52:14 +08:00
8a262d9d41 style(ui): 统一弹窗分组白卡样式并改造相关弹窗布局
- 新增全局弹窗灰底背景色,提升视觉一致性
- 新增通用分组白卡组件 section-card,标题融入卡头,支持额外插槽
- 统一 Avue 弹窗表单及分组为白卡样式
- 调整 common-route.vue 弹窗,使用 section-card 分组包裹查询条件与常用地址
- 改造 loading-manage.vue 弹窗所有分区,采用 section-card 统一风格
- 全局样式调整弹窗内容区表单项间距及两列表单布局工具类
- 在 main.js 全局注册 section-card 组件,便于项目统一复用
2026-08-06 09:27:49 +08:00
badfed7ff1 Merge remote-tracking branch 'websoft/master' 2026-08-06 02:57:58 +08:00
c339852e04 1、基础数据修复bug
2、客商模块修复bug
2026-08-06 02:57:55 +08:00
819a59e6fc style(project-apply): 调整项目申请样式优化布局
- 将项目申请表单内边距改为0取消左右填充
- 修改弹窗主体溢出控制,新增横向溢出隐藏设置
- 调整弹窗主体内边距为16px统一四周间距
- 设置弹窗背景色为浅灰色提升视觉一致性
2026-08-05 21:13:38 +08:00
f663098b4a style(project-apply): 调整项目申请样式优化布局
- 将项目申请表单内边距改为0取消左右填充
- 修改弹窗主体溢出控制,新增横向溢出隐藏设置
- 调整弹窗主体内边距为16px统一四周间距
- 设置弹窗背景色为浅灰色提升视觉一致性
2026-08-05 21:11:35 +08:00
ef417a0290 style(ui): 优化分页箭头及project-apply弹窗样式
- 撤销分页中“上一页/下一页”按钮隐藏,恢复原生箭头图标显示
- 重新删除element-ui.scss中多余的分页箭头文字注入和display:none规则
- 全局Avue CRUD弹窗内容区背景改为灰色 #f5f6fa,提升视觉层次感
- project-apply弹窗背景改为灰色底,分组区块改为白色卡片风格,统一界面风格
- 分区标题与内容区域合并为单张白色卡片,调整圆角和阴影实现视觉统一
- 相应区域内边距、圆角和阴影细节优化,保持结构和功能不变
2026-08-05 20:53:48 +08:00
e5a7aca09e style(vehicle): 移除红色背景样式以优化界面显示
- 删除了 .archive-dialog 的红色背景色定义
- 保留了弹窗主体的最大高度和滚动样式
- 改善了归档对话框的视觉效果和用户体验
2026-08-05 18:25:24 +08:00
4d0cc2d595 feat(customer-archive): 优化客商档案弹窗基本信息分组UI
- 将单一el-row拆分为工商信息、联系信息、财务/信用信息、其他信息四个分组
- 每个分组外层使用灰色背景块,内层字段区为白底卡片,提升视觉层次感
- 字段顺序重新调整,保留全部字段并优化布局,联系信息三项均分一行
- 新增.archive-form__group样式,定义背景色、圆角、内外边距及阴影效果
- 复用全局.dialog-section-title样式作为分组标题样式,保持风格统一
- 确保表单校验正常,vue/compiler-sfc解析无错误,标签闭合平衡
- 在项目长期记忆新增设计约定,明确弹窗字段分组的UI方案及理由
2026-08-05 18:05:07 +08:00
f6451643e8 Merge remote-tracking branch 'origin/master' 2026-08-05 15:55:19 +08:00
8dddb72328 fix(cargo-type): 优化导入失败原因展示格式
- 移除导入失败原因中的“第N行”前缀,简化信息展示
- 失败原因编号从字母改为数字序号,提升可读性
- 备注超长提示编号改为数字编号,保持格式统一
- 保留导入失败原因列标红及列宽自动换行等已有样式调整
2026-08-05 15:55:11 +08:00
fa99cc3eff Merge remote-tracking branch 'websoft/master' 2026-08-05 15:52:31 +08:00
433c611195 1、完善发货模板
2、完善运输计划
3、完善运单管理
2026-08-05 15:52:24 +08:00
6cf535cc79 feat(option): 允许货物类型列支持排序
- 在货物类型的相关列中添加 sortable 属性
- 设置 sortable 为 true 以启用列排序功能
- 适配数据表的展示以支持排序交互
- 保持原有列的宽度和显示配置不变
2026-08-05 15:52:12 +08:00
e683c2cc00 style(pagination): 优化分页组件上一页/下一页样式及环境配置说明
- 取消分页组件中上一页、下一页按钮的额外宽度设置,使其与页码按钮等宽
- 恢复上一页、下一页的原生箭头图标显示,取消自定义文字和隐藏图标的样式规则
- 更新分页相关的注释,明确首页/尾页按钮依旧由脚本控制不注入,上一页/下一页显示原生箭头
- 修改 .env.development 配置,增加 API 代理和跨域说明,避免直接使用绝对地址引发的 CORS 问题
- 优化 VITE_APP_API 配置为 /api,配合 Vite 代理减少跨域错误,保障开发环境稳定运行
2026-08-05 14:35:36 +08:00
3268f12501 style(pagination): 优化分页组件上一页/下一页样式及环境配置说明
- 取消分页组件中上一页、下一页按钮的额外宽度设置,使其与页码按钮等宽
- 恢复上一页、下一页的原生箭头图标显示,取消自定义文字和隐藏图标的样式规则
- 更新分页相关的注释,明确首页/尾页按钮依旧由脚本控制不注入,上一页/下一页显示原生箭头
- 修改 .env.development 配置,增加 API 代理和跨域说明,避免直接使用绝对地址引发的 CORS 问题
- 优化 VITE_APP_API 配置为 /api,配合 Vite 代理减少跨域错误,保障开发环境稳定运行
2026-08-05 14:35:30 +08:00
132 changed files with 14891 additions and 3748 deletions

View File

@@ -3,6 +3,8 @@
VITE_APP_ENV = 'development' VITE_APP_ENV = 'development'
#接口地址 #接口地址
# 开发环境建议填 [/api]:由下方 vite.config.mjs 的 proxy 同源转发到后端(172.16.203.228:8000),避免跨域(CORS)被浏览器拦截
# 如需直连线上绝对地址(如 http://172.16.203.228:8000/api),必须让后端 CORS 把 Allow-Origin 改为具体前端域名,不能用 `*`(配合 credentials 会被浏览器拒绝)
VITE_APP_API=/api VITE_APP_API=/api
#调试参数 #调试参数

View File

@@ -0,0 +1,18 @@
# 2026-08-04 工作记录
## 撰写《AI 辅助编程规范与实战示例》文档
- 应业主想了解"如何用 AI 编程"的需求,以 `src/views/base/cargo-type.vue`(货物类型模块)为实例,撰写对外说明文档。
- 产出文件:`/Users/gxwebsoft/VUE/tms-erp-web-ws/AI辅助编程规范与实战示例.md`
- 文档结构:协作流程总览 → AGENTS.md 规范条款 → cargo-type 模块实例拆解(三级文件分离、校验三处一致、审计字段用姓名、导入失败明细加工、样式规范)→ 人机协作人工兜底项 → 5 条经验 → 给业主的价值建议。
- 文档引用了真实代码片段cargo-type.vue / option / api / utils/import-excel.js强调需求卡 + AGENTS.md + 复用 utils 三件套作为"单一事实来源"。
## 生成 Word(.docx) 版本
- 用 docx-jsmanaged node workspace将《AI 辅助编程规范与实战示例.md》转成 Word 文档:`/Users/gxwebsoft/VUE/tms-erp-web-ws/AI辅助编程规范与实战示例.docx`
- 踩坑docx-js 会在段落间注入非法空节点 `<0/>``<N/>`),导致 XML 不合法;后处理用正则剔除 `<\d+/>` 后重新校验通过。
- 生成脚本:`/Users/gxwebsoft/.workbuddy/binaries/node/workspace/gen_doc.js`(含标题/表格/代码块/项目符号样式)。
## 分页组件:上一页/下一页改为原生箭头图标
- 需求:全站不隐藏上一页/下一页,改显示原生箭头图标;首页/尾页保持不注入。
- 改动文件:`src/styles/element-ui.scss`3 处历史遗留规则)、`src/utils/pagination.js`(注释同步)。
- 删除的遗留规则:①隐藏原生 `.el-icon` 箭头;②用 `::before` 注入「上一页/下一页」文字;③整体 `display:none` 隐藏 `.btn-prev/.btn-next`。首页/尾页仍由 pagination.js 增强器不注入,且各页面 layout 无 first/last。
- 效果:上一页/下一页显示 Element Plus 原生箭头(‹ 带边框与页码按钮视觉统一hover 变主色,禁用态灰显。

View File

@@ -0,0 +1,63 @@
# 2026-08-05 工作记录
## 排查:改线上接口后前端"运行不了"CORS 根因)
- 现象:用户将 `.env.development``VITE_APP_API` 改为 `http://172.16.203.228:8000/api`(直连线上/测试后端)后仍运行不了。
- 根因Saber3 的 axios 配了 `withCredentials = true``src/axios.js:79`。该后端openresty + blade-gateway对跨域请求返回 `Access-Control-Allow-Origin: *``Access-Control-Allow-Credentials: true`,浏览器规定 credentials 模式下 Allow-Origin 不能是 `*`,非法组合会被直接拒绝 → 所有接口失败。
- 之前用 `/api` 能跑是因为走 Vite 代理,浏览器请求同源 localhost:2888不触发跨域。
- 已改动:
1. `.env.development``VITE_APP_API` 改回 `/api`,并加注释说明两种模式。
2. `vite.config.mjs``server.proxy['/api']` target 改为 `http://172.16.203.228:8000`,并去掉 `rewrite` 去掉 `/api` 的逻辑(保留 `/api` 前缀,对齐 openresty 期望路径)。
- 注意:改 `.env` / `vite.config.mjs` 后必须重启 `npm run dev`Vite 启动时加载环境变量,运行中改动不会热更新)。
- 若确需直连绝对地址(如生产/部署):必须让后端 CORS 把 Allow-Origin 改为具体前端域名blade-gateway 的 CorsConfig 用 `setAllowedOriginPatterns` 或显式域名),不可用 `*` 配合 credentials。
## 调整:货物类型导入失败原因展示格式
- 文件:`src/views/base/cargo-type.vue`
- 修改 `decorateCargoTypeFailDetail`
- 移除「第N行」前缀
- 失败原因编号从 `A. B. C.` 改为 `1. 2. 3.`
- 备注超长兜底提示也同步使用数字编号。
## UI 优化客商档案弹窗「基本信息」分组vehicle/customer-archive.vue
- 背景:原弹窗「基本信息」是一整块 `el-row` 平铺 20 个字段,信息密度过高。
- 方案(与用户确认):采用「分区标题平铺 + 浅色背景块」而非嵌套卡片(契合 AGENTS.md 去卡片化哲学;全局 `.dialog-section-title` 已自带 4px 主色竖条)。
- 改动:
- 模板:将单一 `el-row` 拆分为 4 个 `.archive-form__group`:工商信息 / 联系信息(3项 span=8) / 财务·信用信息 / 其他信息(备注 span=24);字段顺序重组但全部保留,地址、备注保持 span=24。
- 样式:新增 `.archive-form__group`#fafafa 背景、8px 圆角、16px/20px 内边距、组间距 16px子标题复用 `.dialog-section-title`14px/500
- 校验:@vue/compiler-sfc 解析 0 错误;标签闭合平衡。
-此前讨论中提及的「Tab 内容加内边距」「材料区标题竖条」未纳入本次(材料区已用 dialog-section-title如需可后续追加。
## 排查:分页「上一页/下一页原生箭头」样式被还原
- 现象昨日08-04已对 `src/styles/element-ui.scss` 三处历史遗留规则做删除(隐藏 `.el-icon` 箭头、用 `::before` 注入「上一页/下一页」文字、整体 `display:none` 隐藏 `.btn-prev/.btn-next`),今早用户反馈效果被覆盖。
- 排查:重新 Grep 确认 `element-ui.scss` 三处原隐藏规则全部恢复成原始状态;而 `src/utils/pagination.js` 的注释("统一展示为原生箭头图标")未被还原,说明只有该样式文件被外部覆盖(疑似 git 还原 / 重新拉取 / 某生成流程),并非逻辑层问题。
- 已重新逐条应用三处删除(单条提交,避开并行编辑的缓存冲突),并验证无遗留 `display:none`/`content:'上一页'``pagination.js` 无需再改。
- 提醒:`element-ui.scss` 若处于版本控制或生成流程中,可能被再次还原;如需长期生效,建议把该改动提交入 git或排查是否有自动覆盖该文件的脚本。
## UI 调整Avue CRUD 弹窗内容区背景改为灰色 #f5f6fa
- 文件:`src/styles/element-ui.scss` 第 225 行附近 `.avue-crud__dialog .el-dialog__body`
- 改动:在原 `max-height`/`overflow-y` 规则中新增 `background: #f5f6fa;`
- 范围:**全局生效**(所有 Avue CRUD 弹窗的内容区都会变灰。起因是用户想在货物类型cargo-type新增弹窗用灰底衬托。
- 注意:弹窗默认 `append-to-body`scoped 样式选不到;如需仅货物类型单页变灰,需给 option 加 `dialogCustomClass` 再单独写选择器。用户未要求收窄,暂保持全局。
## UI 改造project-apply 弹窗灰底+白卡片布局
- 文件:`src/views/business/project-apply.vue`
- 需求:弹窗背景色 `#f5f6fa`,每个分组区域用白色卡片
- 改动:
- 弹窗 bodyglobal 选择器):加 `background: #f5f6fa`
- `__grid``__table``__textarea-list``__material-head``__upload``__change-textarea` 统一加白底 + 6px 圆角 + 极淡阴影 + 内边距
- 效果:灰蓝底弹窗上浮白色分组卡片,与客商档案弹窗的「灰色分组块+白色内层卡片」方案一致
## 二次优化project-apply 分区标题也包进白色卡片
- 文件:`src/views/business/project-apply.vue`
- 需求:把「项目基本信息」这类分区标题也装进白色卡片内(标题+内容合一张白卡)
- 改动:
- `.dialog-section-title`(排除 material 标题)加白底 + 上圆角 `6px 6px 0 0` + `z-index:1`,紧接内容块
- `__grid`/`__table`/`__textarea-list`/`__change-textarea` 圆角改为下圆角 `0 0 6px 6px`,与标题拼接成完整白卡
- 材料区(`__material-head`/`__upload`)标题与按钮本就在白卡内,不受影响
- 纯 CSS 改动,未动模板结构;用 `:not()` 排除材料标题避免破坏既有布局

View File

@@ -0,0 +1,116 @@
# 2026-08-06 工作日志
## 全站表格操作列文字链接统一为蓝色
- 需求:客户档案页(/vehicle/customer-archive操作列颜色统一蓝色且全站统一风格。
- 实现:在 `src/styles/element-ui.scss` 新增全局规则,覆盖 Avue `#menu` 槽位(`.avue-crud__menu .el-link`)与手写表格操作列(`.el-table .el-table__body .cell:has(.el-link) .el-link`)中所有 `el-link``--el-link-text-color` / hover / active / disabled 变量为主色 `#409eff` 及其浅/深色,从而把 warning/success/danger 等类型强制转蓝。
- 单点生效,无需逐页改 `type`。dev server(2888) 已运行会热更新。
- 注意:选择器以「表格体内含 el-link 的单元格」为目标,本仓库表格内 el-link 仅用于操作列,无副作用;若未来某数据单元格用 el-link 作强调色需另行排除。
## 运输计划调度弹窗 UX 分析
- 完成 `/business/transport-plan` 调度弹窗的 UX 审查,对比 project-apply 页面风格
- 输出 6 项改进方案P0-P5核心问题为缺少灰底(#f5f6fa)+白卡(section-card)分组设计模式
- 所有改动集中在 `business-crud-page.vue` 一个文件
- 待用户确认后实施第一阶段P0灰底 + P1白卡分组
## 已实施的改进(全部在 business-crud-page.vue
- P0: `__dispatch-dialog``.el-dialog__body` 改为 `padding:16px; background:#f5f6fa; overflow-y:auto`(原 padding:0
- P1: 调度弹窗三块分区用全局 `<section-card>` 包裹:基本信息 / 收发货路线 / 待调度列表;"新增"按钮+汇总文字移入列表卡 `#extra` slot
- P2: `__dispatch-route-line` 由黑色实线改为 `#409EFF` 虚线 + 箭头(主色方向感)
- P3: 待调度列表纳入白卡容器(随 P1 完成)
- P4: 调度表操作列 `width` 180→220 预留扩展
- P5: `__dispatch-footer` 增加 `border-top` + padding/margin-top 分隔
-`__dispatch-list` / `__dispatch-top`(border-bottom/32px padding) 样式已清理section-card 已全局注册,无需 import
- 验证SFC 模板单独编译通过6 处 section-card 引用配对平衡);完整 vite build 仍因预存 `xlsx` 幽灵依赖pnpm 未装)失败,与本次改动无关
- 注意:本项目完整 build 依赖 `xlsx@0.18.5`package.json 有声明但 node_modules 缺失),属环境预存问题,非本次引入
## 运输计划调度弹窗二次视觉紧凑化
- 客户反馈:弹窗“空、不紧凑、内容多但不好看”
- 改动仍集中在 `business-crud-page.vue`
- 模板:
- 标题与单号/状态标签合并到同一行,移除 `|` 分隔符
- 基本信息与收发货路线由左右双列改为上下堆叠的白卡,各自独占一行
- 基本信息网格改为 4 列:客户 / 项目 / 合同编号 / 计划执行时间;货物信息占 2 列;附件独占一行
- 日期展示由 `-` 改为 ` ~ `,更明确是区间
- 路线改为“横向节点 + 下方双列明细”:起点/终点徽章+名称居中,发货地址/收货地址左对齐分两列,避免居中留白
- 样式:
- 顶部标题字号 22px→18px顶部 padding 22px→0
- 信息标签字号 14px→12px、颜色 #666#909399,值行高 1.45→1.4
- 基本信息网格 3 列/48px 列间距/18px 行间距 → 4 列/24px 列间距/12px 行间距
- 路线徽章 46px→32px、字号 16px→13px、圆角 6px→4px
- 路线标题字号 22px→16px取消居中最长名称省略
- 路线区域取消 `min-height:200px` 和居中,改为紧凑堆叠
- 卡片间统一 `margin-bottom: 12px`
- 验证:`npm run build` 成功
## /business/project-apply 主表操作列收窄
- 需求project-apply 主表avue-crud #menu)操作列太宽,缩小一点。
- 原:`buildTableOption()` 调用 `applyTableMenuWidth(option, 4)` → 强制 `menuWidth: 320`four 档因按钮数≥4。
- 改动:`src/views/business/project-apply.vue``buildTableOption()` 改为直接返回对象并显式设 `menuWidth: 220`,移除 `applyTableMenuWidth` 调用及对应 import仅本文件使用
- 说明220 低于 AGENTS.md「>3 按钮≥320px」约定但属用户明确要求操作列链接已全局支持 flex-wrap按钮多时会自动换行不会裁切。
- 影响范围:仅 project-apply 主表;子表(附件/变更/用户权限)操作列 width 维持 110/100/100 不变;全局 `TABLE_MENU_WIDTH` 未改。
## /business/temporary-credit-limit 操作列宽度固定为 180
- 需求:该页操作列宽度也给 180。
- 背景:该页用共享组件 `business-crud-page.vue`,其 `option` 在 data() 中由 `cloneOption(crudOption, estimateMenuButtonCount(config))` 生成菜单宽度按按钮数估算≥4 按钮→320option 文件本身无 menuWidth。
- 改动:
1. `business-crud-page.vue` 新增可选 prop `menuWidth`Number默认 nulldata() 中生成 `option` 时若 `menuWidth != null` 则强制覆盖 `opt.menuWidth`。默认 null 不影响其他页面。
2. `temporary-credit-limit.vue` 调用处加 `:menu-width="180"`
- 复用提示:后续任何使用 `business-crud-page` 的页面想固定操作列宽度,直接传 `:menu-width="N"` 即可,无需再改 option 或组件逻辑。
- 注意180 同样低于 AGENTS「>3 按钮≥320」约定按用户明确要求链接支持换行兜底。
## /base/common-address 操作列宽度 220 → 180
- 需求:「这个也调一下宽度」。上一条刚把 temporary-credit-limit 设为 180用户用「也」字故对齐为 180。
- 机制:该页是独立 `avue-crud``option` 来自 `@/option/base/common-address.js``createOption()`,其中已显式设 `menuWidth: 220`
- 改动:将 `src/option/base/common-address.js``menuWidth: 220``180`。操作列仅 3 个链接(查看/编辑/删除)。
- 说明:低于 AGENTS「>3 按钮≥320」约定按用户明确要求链接支持换行兜底。
## 操作列宽度的三种设置入口(备忘)
- 独立 avue-crud + 自管 option直接改 option 文件的 `menuWidth`(如 common-address
- 独立 avue-crud + buildTableOption 调 `applyTableMenuWidth(option, N)`helper 会按按钮数强制下限≥4→320要更窄需绕过 helper 直接设 `menuWidth`(如 project-apply 改 220
- 共用 `business-crud-page` 组件:新增可选 prop `menuWidth`(默认 null 不动既有逻辑),调用处传 `:menu-width="N"`(如 temporary-credit-limit 传 180
## /business/process-config 操作列宽度 320 → 180
- 需求:「这个也调一下宽度」。延续上几条把操作列设 180 的系列要求。
- 机制:该页是独立 `avue-crud``option` 来自 `@/option/business/process-config.js`,原先包在 `applyTableMenuWidth({...}, 5)`five 档→强制 `menuWidth: 320`,因菜单有 6 个链接:查看/编辑/复制/启用/停用/删除)。
- 改动:移除 `applyTableMenuWidth` 包裹与对应 importoption 改为普通对象并显式设 `menuWidth: 180`
- 说明180 明显低于 AGENTS「>3 按钮≥320」约定按用户明确要求链接已全局支持 flex-wrap 换行兜底6 个按钮会多行展示、不裁切。
## 修复 process-config.js 末尾语法错误(红色警)
- 现象:用户反馈 `src/option/business/process-config.js` 最后一行有红色警。
- 根因:上一轮把 `applyTableMenuWidth({...}, 5)` 去掉外层函数包裹时,结尾 `}, 5);` 被改成 `});`,多留了一个 `)`(因 `export const option = {` 本身没有左括号),导致括号不匹配。
- 改动:末尾 `});``};`。移除 `applyTableMenuWidth` import 时无误。
- 验证node 动态 import 不再报 SyntaxError仅剩 node 无法解析 extensionless `./common` 的模块解析提示,与语法无关),说明语法已恢复。
## /vehicle/insurance-record 操作列宽度 160 → 140紧凑
- 需求:「这个也调一下宽度,紧凑一点」。本页与上几条不同,用户明确要求更紧凑(其他页给的 180 比这页还宽)。
- 机制:独立 `avue-crud``option` 来自 `@/option/vehicle/insurance-record.js`;无自定义 `#menu` 槽,行菜单用 Avue 内置(仅显式 `viewBtn: true`edit/del 默认开启→3 个链接:查看/编辑/删除)。原已显式 `menuWidth: 160`
- 改动:将 `menuWidth: 160``140`
- 说明:低于 AGENTS「>3 按钮≥320」约定按用户明确要求链接已全局支持 flex-wrap 换行兜底。
## 车/船务管理模块下所有记录页操作列宽度统一为 160
- 需求:用户截图列出「车/船务管理」下 11 个子菜单,要求操作栏宽度都设为 160。
- 对应页面/option 文件:
- 保险记录 insurance-recordoption → `src/option/vehicle/insurance-record.js`(由 140 改回 160
- 违章记录 violation-recordoption → `src/option/vehicle/violation-record.js`(原 160无需改动
- 维修记录 maintenance-recordinline option → `src/views/vehicle/maintenance-record.vue`(原 160无需改动
- 保养记录 maintenance-planinline option → `src/views/vehicle/maintenance-plan.vue`(原 160无需改动
- 换胎记录 tire-replacement-recordoption → `src/option/vehicle/tire-replacement-record.js`(原 160无需改动
- 事故记录 accident-recordoption → `src/option/vehicle/accident-record.js`(原 160无需改动
- 年检记录 annual-inspection-recordoption → `src/option/vehicle/annual-inspection-record.js`(原 160无需改动
- 里程记录 mileage-recordoption → `src/option/vehicle/mileage-record.js`(原 160无需改动
- 变更记录 transport-change-recordoption → `src/option/vehicle/transport-change-record.js`(原 160无需改动
- 油电记录 oil-electric-recordoption → `src/option/vehicle/oil-electric-record.js`(原 160无需改动
- ETC记录 etc-recordoption → `src/option/vehicle/etc-record.js`(原 160无需改动
- 实际改动:仅 `insurance-record.js` 从 140 恢复到 160其余 10 个已经是 160。
- 注意:`src/option/vehicle/other-expense-record.js` 不在截图菜单内,保持不动。
## 车/船务管理模块操作列宽度 160 → 180用户反馈 160 偏小)
- 需求:上一轮统一 160 后,用户觉得还是小,全部改成 180。
- 改动11 个文件 `menuWidth: 160``180`9 个 option 文件 + maintenance-record.vue / maintenance-plan.vue 两处 inline option
- 现状:车/船务管理模块全部 11 个记录页操作列均为 180。
## 操作列文字链接间距过宽问题
- 现象:用户截图反馈操作列文字链接之间间距太宽,要求统一风格。
- 根因:全局已设 `.avue-crud .avue-crud__menu { gap: 8px; }` 与手写表格操作列 `gap: 8px`,但 Avue 默认 `.el-link``.cell` 上可能存在 `margin-right`,与 gap 叠加后显得过宽。
- 改动:`src/styles/element-ui.scss` 新增规则,对 `.avue-crud .avue-crud__menu .el-link` 和手写表格操作列 `.el-link` 强制 `margin: 0`,使间距完全由 8px gap 控制,不再叠加。
- 影响范围全站表格操作列Avue #menu 与手写操作列),仅作用于操作列链接,不修改普通数据单元格。

View File

@@ -0,0 +1,66 @@
# 项目长期记忆tms-erp-web-ws / Saber3 客商模块)
## 设计约定(经用户确认的决策)
### 弹窗内多字段表单分组:使用全局 `<section-card>` 组件(项目统一标准)
**整体结构**
- 整个弹窗 body 由 `src/styles/element-ui.scss` 全局 `.el-dialog__body { background: #f5f6fa; padding: 16px }` 统一灰底(无需页面再覆盖)。
- 每个业务分组 = 一张白底卡片。用全局组件 `<section-card title="分组名">...</section-card>`,自动渲染 4px 主色竖条 + 8px 间距的卡头与白底 body圆角 6px + 极淡阴影),与 `loading-manage.vue` / `customer-archive.vue` 等已落地页面一致。
- 严禁再手写 `.xxx-form__block` + `.dialog-section-title` override 拼凑卡片(项目已有 `<section-card>` 标准组件)。
**HTML 结构**(每组三件套):
```vue
<section-card title="分组标题">
<el-row :gutter="18">
<el-col :span="8">...</el-col>
</el-row>
<!-- 同一组的所有 row 都放同一张白卡内 -->
</section-card>
```
**适用**:新增/编辑弹窗中字段超过 ~10 个、需按业务语义分组时。
### 简单分组(仅 3 项)
- 联系信息等仅 3 项的分组,可用 `el-col :span="8"` 让三项均分一行,避免 4 列网格留白。
### 上传证件区域尺寸规范transportCapacity 模块强制)
- **比例**:中国二代身份证标准尺寸 85.6 × 53.98 mm → 宽:高 = **1.586 : 1**(横放)
- **尺寸**:取真实身份证的 ~75% → 固定 **240 × 151 px**1.586:1 精确151 × 1.586 ≈ 240
- **位置**:在 el-col 列内 `margin: 0` 左对齐显示(不再 `width: 100%` 占满整栏,也不再居中)
- **适用范围**:所有 `<image-upload-field large>` 上传区,包括身份证正反面、大头照、机动车行驶证主页正/反面/副页正/反面、道路运输证、机动车登记本、船舶所有权/安全环保/国籍/最低安全配员/光船租赁/营业运输 等所有 cert 图片
- **实现方式**:在每个页面 scoped 样式里覆盖 class-prefix 对应的 `--large` 选择器(`.driver-uploader--large` / `.vehicle-uploader--large` / `.ship-uploader--large`
```scss
:deep(.xxx-uploader) { display: block; } // 不再 width:100%
:deep(.xxx-uploader--large) {
display: block;
width: 240px;
margin: 0;
}
:deep(.xxx-uploader--large .el-upload),
:deep(.xxx-uploader--large.el-upload) {
width: 100%;
height: 151px;
}
```
- **CSS 顺序要点**`.xxx-uploader`(特定性 0,0,1,0必须写在 `.xxx-uploader--large`(同为 0,0,1,0之前源码位置决定覆盖生效。
### 上传图片点击放大预览(全局组件 `image-upload-field` 已实现)
- **组件**`src/components/image-upload-field/main.vue`。原裸 `<img>` 改为 `<el-image :preview-src-list="[value]" fit="contain" preview-teleported :z-index="3000" @click.stop>`。点击图片即全屏预览(滚轮缩放 / 旋转 / 下载),零额外依赖。
- **预览与上传分离**:图片 `@click.stop` 阻止冒泡到 `el-upload`(否则会触发重传);"更换"入口form 模式图片下方 `el-link`、card 模式 head 的 `actionText`)通过 `el-upload` 内部隐藏 `input.el-upload__input` 的 `.click()` 触发文件框。
- **为什么用 DOM click**Element Plus `el-upload` 仅 expose `abort/submit/clearFiles/handleStart/handleRemove`**没有** `openFileDialog`,故用 `this.$refs.uploadRef.$el.querySelector('.el-upload__input').click()`。
- **readonly 模式**:仅渲染 `el-image` 预览,不显示上传入口(查看档案时也能放大看证件)。
- **z-index 注意**:务必 `preview-teleported`(预览层挂 body+ `:z-index="3000"`,否则会被 `el-dialog` 遮罩2000 区间)盖住。
- **适用范围**全仓通用组件driver / vehicle / ship 三个页面自动获得该能力(改动一次组件即可)。
## 关键文件
- 通用分组白卡组件:`src/components/section-card/main.vue`(已全局注册为 `<section-card>`,支持 `title` / `#title` / `#extra` slot
- 弹窗灰底 body + section-card 全局样式:`src/styles/element-ui.scss``.el-dialog__body`、`.section-card { &__header / &__bar / &__title / &__extra / &__body }`)。
- 司机档案弹窗:`src/views/transportCapacity/driver.vue`(手写 `el-form` + `<section-card>` 4 大分组:基础身份信息 / 驾驶证信息 / 从业资格证信息 / 司机联系信息)。
- 车辆档案弹窗:`src/views/transportCapacity/vehicle.vue`(手写 `el-form` + `<section-card>` 2 大分组:基础车辆信息 / 车辆资质图片6 张大图按身份证比例 240×151 左对齐)。
- 船舶档案弹窗:`src/views/transportCapacity/ship.vue`(手写 `el-form` + `<section-card>` 2 大分组:基础船舶信息 / 船舶证书信息;内部 6 张 cert 图片按身份证比例 240×151 左对齐;子证书分段用 `.cert-block` + border-top
- 客商档案弹窗:`src/views/vehicle/customer-archive.vue`(工商信息 / 联系信息 / 财务/信用信息 / 其他信息 等分组也用 `<section-card>`)。
- 项目补录弹窗:`src/views/business/project-apply.vue`(手写 `__grid` / `__table` / `__textarea-list` 同款白卡结构,先于 `<section-card>` 组件化之前落地,可视为视觉参照)。
## 环境注意
- Vite 代理跨域Saber3 axios 开 `withCredentials=true`,直连绝对地址后端须让 CORS `Allow-Origin` 为具体域名(不能用 `*`),否则浏览器拒绝。改 `.env`/`vite.config.mjs` 必须重启 dev。

View File

@@ -169,12 +169,15 @@ Avue 表格/表单配置独立存放于 `src/option/` 目录,与 `views` 和 `
- 表格偶数行需统一使用 `#FAFAFA` 背景色,固定列单元格必须与对应行背景保持一致。 - 表格偶数行需统一使用 `#FAFAFA` 背景色,固定列单元格必须与对应行背景保持一致。
- 表格操作列同时展示“查看、编辑、删除”等三个按钮时,操作列宽度统一不小于 `220px`;操作列最大按钮数量超过 3 个时,操作列宽度统一加宽到不小于 `320px`;最大按钮数量超过 4 个时,仍按 `320px` 保持列宽,并从第 5 个按钮开始换行展示,禁止出现按钮裁切或显示不全。 - 表格操作列同时展示“查看、编辑、删除”等三个按钮时,操作列宽度统一不小于 `220px`;操作列最大按钮数量超过 3 个时,操作列宽度统一加宽到不小于 `320px`;最大按钮数量超过 4 个时,仍按 `320px` 保持列宽,并从第 5 个按钮开始换行展示,禁止出现按钮裁切或显示不全。
- 表格操作列按钮统一仅展示文字,禁止配置 `icon` / `:icon` 或在操作按钮、操作下拉项中嵌入图标;顶部工具栏按钮不受此条限制。 - 表格操作列按钮统一仅展示文字,禁止配置 `icon` / `:icon` 或在操作按钮、操作下拉项中嵌入图标;顶部工具栏按钮不受此条限制。
- 纯文字操作入口统一使用 `<el-link>`,禁止使用带 `text` 属性的 `<el-button>`;提交、确认、上传等具有明确命令语义或需要加载状态的按钮可继续使用 `<el-button>`
- 所有表格操作列中的文字链接统一保持 `8px` 间距操作按钮超过操作列可用宽度时必须自动换行禁止链接连续粘连或被裁切。Avue 自定义 `#menu` 插槽和手写 `el-table-column` 操作列均适用。
- `.avue-crud__header` 顶部间距统一为 `12px` - `.avue-crud__header` 顶部间距统一为 `12px`
- 分页组件整体靠右展示,必须展示接口返回的数据总条数,`X条/页` 的页容量选择器必须放在总条数右侧。 - 分页组件整体靠右展示,必须展示接口返回的数据总条数,`X条/页` 的页容量选择器必须放在总条数右侧。
- `.basic-container__card` 的直接子级 `.el-card__body` 内边距统一去除,保持 `padding: 0` - `.basic-container__card` 的直接子级 `.el-card__body` 内边距统一去除,保持 `padding: 0`
- 所有 CRUD 页面搜索栏需与下方表格拆分为独立区域,两者垂直间距统一为 `8px` - 所有 CRUD 页面搜索栏需与下方表格拆分为独立区域,两者垂直间距统一为 `8px`
- 所有 CRUD 页面搜索栏操作按钮(搜索、清空等)需在搜索表单下方独占一行并放置在最右侧;搜索条件超过一行时必须显示“展开/折叠”,默认折叠且仅展示一行搜索条件;一行布局默认展示 4 个筛选项Avue 配置统一使用 `searchIndex: 4` - 所有 CRUD 页面搜索栏操作按钮(搜索、清空等)需在搜索表单下方独占一行并放置在最右侧;搜索条件超过一行时必须显示“展开/折叠”,默认折叠且仅展示一行搜索条件;一行布局默认展示 4 个筛选项Avue 配置统一使用 `searchIndex: 4`
- 所有 CRUD 页面中新增、批量导入等顶部按钮组行不需要背景色,应保持透明背景。 - 所有 CRUD 页面中新增、批量导入等顶部按钮组行不需要背景色,应保持透明背景。
- 所有 CRUD 页面顶部工具栏的“新增/新建”主操作按钮统一使用蓝底白字的实心主按钮(`type="primary"` 且不使用 `plain`),视觉标准与“新建运单”一致;批量导入、导出等辅助工具按钮仍使用描边样式。
- 所有 `.basic-container__card` 内的 Avue 内层 `el-card` 需去卡片化,仅保留外层容器卡片样式,禁止出现二级卡片边框、阴影或额外内边距。 - 所有 `.basic-container__card` 内的 Avue 内层 `el-card` 需去卡片化,仅保留外层容器卡片样式,禁止出现二级卡片边框、阴影或额外内边距。
### 4.5 全局注册组件 ### 4.5 全局注册组件

Binary file not shown.

View File

@@ -0,0 +1,225 @@
# AI 辅助编程规范与实战示例——以「货物类型模块」为例
> 文档目的:向业主说明我们团队如何用 AI 协作完成前端开发,包含协作流程、工程规范、以及一个可直接对照代码的真实模块示例,便于业主了解我们的工作方式、质量保障手段与可复用的经验。
---
## 一、背景:我们为什么用 AI 编程
本项目Saber3 企业级后台)是一个模块多、表单多、校验规则多、样式规范严苛的大型 Vue 3 工程。传统人工开发存在两个痛点:
1. **重复劳动多**:每个 CRUD 模块的列表、搜索、新增、编辑、导入、导出结构高度相似,但样式细节(表格线色、偶数行底色、操作列宽度、弹窗竖条等)又极易被写错。
2. **规范易漂移**:数十个模块由不同人开发,若没有统一约束,代码风格、校验口径、审计字段展示会逐渐不一致。
我们引入 AI 编程的核心思路不是"让 AI 自由发挥",而是**把人类沉淀的工程规范喂给 AI让 AI 在规范的边界内批量、稳定地产出代码,再由人工做业务与视觉复核**。这样既提效,又保住质量底线。
---
## 二、我们的 AI 协作流程(总览)
```
需求澄清 ──> 需求卡(单一事实来源)──> AGENTS.md工程规范──> AI 生成代码
│ │
└──────────────── 人工复核 / 验收 ──────────────────────────┘
```
四个关键阶段:
| 阶段 | 产物 | 负责人 | 说明 |
| ------- | --------------------------- | ------- | -------------------------------- |
| 1. 需求澄清 | 模块需求卡(.md | 产品/前端 | 用 `模块需求卡生成规范.md` 统一格式,拆成 REQ 编号项 |
| 2. 规范约束 | AGENTS.md | 架构/前端 | 把技术栈、风格、样式、校验、审计等写成"对 AI 的强制条款" |
| 3. 代码生成 | view / option / api / utils | AI + 前端 | AI 按 AGENTS.md 生成,前端实时纠偏 |
| 4. 复核验收 | 可运行模块 | 前端/测试 | 校验业务规则、视觉规范、边界异常 |
**核心原则:需求卡和 AGENTS.md 是 AI 与人对齐的"单一事实来源",不靠口头交代。**
---
## 三、工程规范如何约束 AIAGENTS.md 核心条款节选)
我们维护了一份 `AGENTS.md`,作为 AI 每次动手前的"必读规范"。与本示例强相关的条款有:
### 3.1 文件同构结构
```
src/
├── views/base/cargo-type.vue # 页面(模板 + 交互逻辑)
├── option/base/cargo-type.js # Avue 表格/表单配置(与 views 同构路径)
└── api/base/cargo-type.js # 接口层(按业务模块组织)
```
> 规范要点Avue 配置独立存放于 `src/option/`,与 `views` 和 `api` 保持同构的模块路径映射。AI 生成时不会把配置写死在页面里。
### 3.2 样式规范(极易被写错,故用规范强制)
- 表格线条颜色统一 `#EFF1F7`;偶数行背景 `#FAFAFA`,固定列须与行背景一致;
- 操作列同时展示"查看/编辑/删除"时宽度不小于 `220px`
- `.avue-crud__header` 顶部间距 `12px`
- `.basic-container__card` 内层 `el-card` 去卡片化(`padding: 0`
- 主色统一 `#409eff`
### 3.3 表单校验规范
- 金额类非负、经纬度范围、备注类 ≤200 字(前端阻止提交并提示);
- 新增、导入、编辑三处的校验口径必须一致;
- 审计字段(创建人/更新人)**禁止直接展示用户 ID**,须绑定后端返回的姓名字段(`createUserName`)。
### 3.4 批量导入失败明细规范(本项目重点)
- 部分数据导入失败时,前端必须自动下载后端返回的失败 Excel
- 失败 Excel 字段顺序须与原模板一致,最后追加"导入失败原因"列;
- 前端需根据 `content-type` 区分"JSON 成功响应"与"Excel 文件流"
- 失败明细文件命名含业务名、导入失败明细、时间戳。
---
## 四、实例拆解:货物类型模块
### 4.1 模块能力一览
货物类型是一个**两级层级**的主数据模块一级货物类型2 位编码)+ 二级货物类型4 位编码,前 2 位须与上级一致)。功能包括:列表、条件查询、新增、编辑、查看、删除、批量删除、**批量导入 + 模板下载 + 失败明细加工**、批量导出。
### 4.2 文件同构结构(真实存在)
```
src/views/base/cargo-type.vue # 589 行,页面 + 交互
src/option/base/cargo-type.js # 189 行Avue 列配置 + 校验器
src/api/base/cargo-type.js # 74 行7 个接口方法
src/utils/import-excel.js # 109 行,通用导入弹窗(被本模块复用)
```
### 4.3 AI 按规范落地的关键代码点
下面每一条都对应前述规范条款,可直接对照源码。
**① 三级文件分离(对应 3.1**
页面只负责交互,配置与接口独立:
```js
// src/views/base/cargo-type.vue
import { getList, getNextCode, getParentOptions, remove, submit } from '@/api/base/cargo-type';
import { getCargoTypeOption } from '@/option/base/cargo-type';
```
**② 表单校验三处一致(对应 3.3**
前端表单option与 JS 内 `validateRow` 共用同一套规则口径,避免"表单能过、导入却崩"
```js
// src/views/base/cargo-type.vue —— 提交前的统一校验
validateRow(row) {
const pattern = row.typeLevel === 1 ? /^\d{2}$/ : /^\d{4}$/;
if (!pattern.test(row.cargoCode)) { this.$message.warning('货物类型编码格式不正确'); return false; }
if (row.typeLevel === 2 && !row.cargoCode.startsWith(row.parentCargoCode)) {
this.$message.warning('二级编码前2位必须与上级货物类型编码一致'); return false;
}
if (row.remark.length > 200) { this.$message.warning('备注不能超过200字'); return false; }
return true;
}
```
```js
// src/option/base/cargo-type.js —— 弹窗表单的同源校验器
{ validator: (rule, value, callback) => {
const form = ctx.form || {};
const pattern = form.typeLevel === 1 ? /^\d{2}$/ : /^\d{4}$/;
// 同样的规则:格式 + 二级前缀一致性
}, trigger: 'blur' }
```
**③ 审计字段用姓名不用 ID对应 3.3**
```vue
<template #createUserName="{ row }">
{{ row.createUserName || row.createUser || '' }}
</template>
```
列表直接绑定 `createUserName`,避免出现一串无意义的用户 ID。
**④ 导入失败明细加工(对应 3.4,本项目 AI 协作的亮点)**
后端只返回原始失败原因文本AI 被要求生成一个**可独立测试的小函数** `decorateCargoTypeFailDetail`,把原因按 A./B./C. 编号、追加规则说明、仅"导入失败原因"列标红、自动加宽列:
```js
// src/views/base/cargo-type.vue
export const decorateCargoTypeFailDetail = async workbook => {
// 1. 映射表头列号
// 2. 清理非原因列的多余红色字体
// 3. 把每行原因拆条、编号、拼接规则说明
// 4. 仅操作"导入失败原因"列样式(黑字、无填充、自动换行)
// 5. 按 CARGO_TYPE_FAIL_COL_WIDTH 加宽各列
};
```
该函数通过通用导入工具 `import-excel.js``failDetailDecorator` 钩子注入,做到**模块零耦合复用**
```js
// 复用通用导入弹窗,仅传入本模块的加工函数
handleImport() {
openImportDialog(this, '货物类型', undefined, {
failDetailDecorator: decorateCargoTypeFailDetail,
});
}
```
**⑤ 样式规范落地(对应 3.2**
```scss
/* src/views/base/cargo-type.vue <style> */
:deep(.el-table) {
--el-table-border-color: #eff1f7; /* 表格线条统一色 */
}
:deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
background: #fafafa; /* 偶数行底色,固定列同色 */
}
:deep(.basic-container__card) > .el-card__body { padding: 0; } /* 去卡片化 */
```
### 4.4 人机协作中AI 不擅长、需要人工兜底的部分
诚实地说AI 不是万能的。本模块在协作中由人工重点把控的有:
1. **业务规则澄清**:一级/二级层级关系、编码前缀约束这类"业务语义",需要产品/前端先讲清AI 才能写出正确校验。
2. **视觉细节微调**:表格行高、表头高度、间距的最终眼检,依赖人工在浏览器里确认。
3. **前后端字段契约**`CargoTypeVO` 是否返回 `createUserName`、失败 Excel 的表头文案必须与后端对齐AI 无法替你确认。
4. **破坏性操作确认**:删除/批量删除的二次确认文案与风险,由人工定稿。
---
## 五、经验沉淀(可复用的 5 条)
1. **把规范写成 `AGENTS.md`,比口头交代可靠 10 倍。** AI 每次都会读,规范不会因人离职而丢失。
2. **需求卡是 AI 与人对齐的单一事实来源。** 用统一格式REQ 编号 + 功能点/异常边界/反例)避免"我以为你要的是……"。
3. **把重复能力抽成 utils。**`import-excel.js`新模块导入直接复用AI 只需写"本模块特有的加工函数"。
4. **校验三处一致**:前端表单、前端导入、后端校验口径对齐,且一处改、三处同步改。
5. **让 AI 生成"小而纯"的函数**(如失败明细加工函数),比让它改一整个 600 行页面更可控、更易测、更易复核。
---
## 六、给业主的价值与建议
| 维度 | 传统方式 | 我们的 AI 协作方式 |
| ------- | --------- | ---------------- |
| 单模块开发周期 | 长,易返工 | 短,规范内一次成型 |
| 多模块一致性 | 易漂移 | AGENTS.md 强制统一 |
| 规范传承 | 靠老人带 | 沉淀为文件,新人/AI 即取即用 |
| 质量风险 | 校验、审计字段易漏 | 规范条款逐项约束 |
**建议**:业主要求交付的不只是代码,更是"可持续维护的规范资产"。我们交付的每个模块背后都有需求卡 + AGENTS.md + 复用 utils 三件套,后续无论是换人维护还是扩展新模块,成本都显著更低。
---
*附:本示例涉及的全部源码路径*
- `src/views/base/cargo-type.vue`
- `src/option/base/cargo-type.js`
- `src/api/base/cargo-type.js`
- `src/utils/import-excel.js`
- `AGENTS.md`(工程规范总纲)
- `模块需求卡生成规范.md`(需求卡格式规范)

View File

@@ -36,7 +36,8 @@
"vue-i18n": "^11.4.2", "vue-i18n": "^11.4.2",
"vue-router": "^4.3.2", "vue-router": "^4.3.2",
"vue3-clipboard": "^1.0.0", "vue3-clipboard": "^1.0.0",
"vuex": "^4.1.0" "vuex": "^4.1.0",
"xlsx": "0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.4", "@vitejs/plugin-vue": "^5.2.4",

71
pnpm-lock.yaml generated
View File

@@ -92,6 +92,9 @@ importers:
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)
xlsx:
specifier: 0.18.5
version: 0.18.5
devDependencies: devDependencies:
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^5.2.4 specifier: ^5.2.4
@@ -2772,30 +2775,30 @@ snapshots:
dependencies: dependencies:
vue: 3.5.34 vue: 3.5.34
'@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/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)':
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@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/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)
dom7: 4.0.6 dom7: 3.0.0
is-url: 1.2.4 is-url: 1.2.4
lodash.throttle: 4.1.1 lodash.throttle: 4.1.1
nanoid: 5.1.11 nanoid: 3.3.12
slate: 0.123.0 slate: 0.72.8
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@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/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)
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@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/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)':
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: 4.0.6 dom7: 3.0.0
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
@@ -2806,9 +2809,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: 5.1.11 nanoid: 3.3.12
scroll-into-view-if-needed: 3.1.0 scroll-into-view-if-needed: 3.1.0
slate: 0.123.0 slate: 0.72.8
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
@@ -2816,9 +2819,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@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(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@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(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@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/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/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)
@@ -2837,14 +2840,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@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/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)
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@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/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)
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
@@ -2856,8 +2859,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@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(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@3.0.0)(lodash.throttle@4.1.1)(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/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)
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
@@ -2867,36 +2870,36 @@ 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@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/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)
dom7: 4.0.6 dom7: 4.0.6
nanoid: 5.1.11 nanoid: 5.1.11
slate: 0.123.0 slate: 0.123.0
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3)': '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3)':
dependencies: dependencies:
'@wangeditor/core': 1.1.19(@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.isequal@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/core': 1.1.19(@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.isequal@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)
dom7: 4.0.6 dom7: 3.0.0
is-url: 1.2.4 is-url: 1.2.4
lodash.throttle: 4.1.1 lodash.throttle: 4.1.1
nanoid: 5.1.11 nanoid: 3.3.12
slate: 0.123.0 slate: 0.72.8
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': '@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)':
dependencies: dependencies:
'@wangeditor/core': 1.1.19(@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.isequal@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/core': 1.1.19(@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.isequal@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)
dom7: 3.0.0 dom7: 3.0.0
prismjs: 1.30.0 prismjs: 1.30.0
slate: 0.72.8 slate: 0.72.8
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wangeditor/core@1.1.19(@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.isequal@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/core@1.1.19(@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.isequal@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)':
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: 4.0.6 dom7: 3.0.0
event-emitter: 0.3.5 event-emitter: 0.3.5
html-void-elements: 2.0.1 html-void-elements: 2.0.1
i18next: 20.6.1 i18next: 20.6.1
@@ -2908,9 +2911,9 @@ snapshots:
lodash.isequal: 4.5.0 lodash.isequal: 4.5.0
lodash.throttle: 4.1.1 lodash.throttle: 4.1.1
lodash.toarray: 4.4.0 lodash.toarray: 4.4.0
nanoid: 5.1.11 nanoid: 3.3.12
scroll-into-view-if-needed: 2.2.31 scroll-into-view-if-needed: 2.2.31
slate: 0.123.0 slate: 0.72.8
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
@@ -2923,9 +2926,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/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3) '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3)
'@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)
'@wangeditor/core': 1.1.19(@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.isequal@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/core': 1.1.19(@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.isequal@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/list-module': 1.0.5(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)
'@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3)
'@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3) '@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3)
@@ -2945,14 +2948,14 @@ snapshots:
'@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': '@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)':
dependencies: dependencies:
'@wangeditor/core': 1.1.19(@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.isequal@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/core': 1.1.19(@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.isequal@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)
dom7: 3.0.0 dom7: 3.0.0
slate: 0.72.8 slate: 0.72.8
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3)': '@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3)':
dependencies: dependencies:
'@wangeditor/core': 1.1.19(@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.isequal@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/core': 1.1.19(@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.isequal@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)
dom7: 3.0.0 dom7: 3.0.0
lodash.isequal: 4.5.0 lodash.isequal: 4.5.0
lodash.throttle: 4.1.1 lodash.throttle: 4.1.1
@@ -2964,8 +2967,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/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@4.0.6)(lodash.throttle@4.1.1)(nanoid@5.1.11)(slate@0.123.0)(snabbdom@3.6.3) '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@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.isequal@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))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.3)
'@wangeditor/core': 1.1.19(@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.isequal@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/core': 1.1.19(@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.isequal@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)
dom7: 3.0.0 dom7: 3.0.0
lodash.foreach: 4.5.0 lodash.foreach: 4.5.0
slate: 0.72.8 slate: 0.72.8
@@ -2975,7 +2978,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/core': 1.1.19(@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.isequal@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/core': 1.1.19(@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.isequal@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)
dom7: 3.0.0 dom7: 3.0.0
nanoid: 3.3.12 nanoid: 3.3.12
slate: 0.72.8 slate: 0.72.8

View File

@@ -82,6 +82,16 @@ export const createCrudApi = baseUrl => ({
}, },
}); });
}, },
dispatch(payload) {
const data = payload && typeof payload === 'object' ? payload : { id: payload };
const id = data.id || payload;
return request({
url: `${baseUrl}/dispatch`,
method: 'post',
params: id ? { id } : undefined,
data,
});
},
reassign(id) { reassign(id) {
return request({ return request({
url: `${baseUrl}/reassign`, url: `${baseUrl}/reassign`,

View File

@@ -45,6 +45,13 @@ export const reject = id =>
params: { id }, params: { id },
}); });
export const updateAttachments = row =>
request({
url: `${baseUrl}/update-attachments`,
method: 'post',
data: row,
});
export const withdraw = id => export const withdraw = id =>
request({ request({
url: `${baseUrl}/withdraw`, url: `${baseUrl}/withdraw`,

View File

@@ -0,0 +1,91 @@
import request from '@/axios';
const baseUrl = '/blade-transport/loading-manage';
export const exportUrl = `${baseUrl}/export-loading-manage`;
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 saveDraft = row =>
request({
url: `${baseUrl}/save-draft`,
method: 'post',
data: row,
});
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 copy = id =>
request({
url: `${baseUrl}/copy`,
method: 'post',
params: {
id,
},
});
export const reassign = row =>
request({
url: `${baseUrl}/reassign`,
method: 'post',
data: row,
});
export const cancel = id =>
request({
url: `${baseUrl}/cancel`,
method: 'post',
params: {
id,
},
});
export const complete = id =>
request({
url: `${baseUrl}/complete`,
method: 'post',
params: {
id,
},
});
export const batchComplete = ids =>
request({
url: `${baseUrl}/batch-complete`,
method: 'post',
params: {
ids,
},
});

View File

@@ -1,4 +1,5 @@
import { createCrudApi } from './common'; import { createCrudApi } from './common';
import request from '@/axios';
const api = createCrudApi('/blade-transport/shipping-template'); const api = createCrudApi('/blade-transport/shipping-template');
@@ -7,3 +8,4 @@ export const getDetail = api.getDetail;
export const submit = api.submit; export const submit = api.submit;
export const remove = api.remove; export const remove = api.remove;
export const copy = api.copy; export const copy = api.copy;
export const nextCode = () => request({ url: '/blade-transport/shipping-template/next-code', method: 'get' });

View File

@@ -1,11 +1,38 @@
import { createCrudApi } from './common'; import { createCrudApi } from './common';
import request from '@/axios';
const api = createCrudApi('/blade-transport/transport-plan'); const baseUrl = '/blade-transport/transport-plan';
const api = createCrudApi(baseUrl);
export const getList = api.getList; export const getList = api.getList;
export const getDetail = api.getDetail; export const getDetail = api.getDetail;
export const submit = api.submit; export const submit = api.submit;
export const dispatch = api.dispatch;
export const remove = api.remove; export const remove = api.remove;
export const copy = api.copy; export const copy = api.copy;
export const cancel = api.cancel; export const cancel = api.cancel;
export const complete = api.complete; export const complete = api.complete;
export const importTransportPlan = ({
file,
projectId,
projectName,
customerName,
contractId,
contractName,
}) => {
const data = new FormData();
data.append('file', file);
data.append('projectId', projectId || '');
data.append('projectName', projectName || '');
data.append('customerName', customerName || '');
data.append('contractId', contractId || '');
data.append('contractName', contractName || '');
return request({
url: `${baseUrl}/import-transport-plan`,
method: 'post',
data,
responseType: 'blob',
timeout: 60000,
});
};

View File

@@ -21,3 +21,12 @@ export const roadLoading = ids =>
ids, ids,
}, },
}); });
export const getImportBatches = params => request({ url: `${baseUrl}/import-batch/list`, method: 'get', params });
export const getImportDetails = params => request({ url: `${baseUrl}/import-batch/details`, method: 'get', params });
export const removeImportBatches = ids => request({ url: `${baseUrl}/import-batch/remove`, method: 'post', params: { ids } });
export const getImportOptions = () => request({ url: `${baseUrl}/import-batch/options`, method: 'get' });
export const getImportContracts = projectId => request({ url: `${baseUrl}/import-batch/contracts`, method: 'get', params: { projectId } });
export const exportImportTemplate = () => request({ url: `${baseUrl}/import-batch/export-template`, method: 'get', responseType: 'blob' });
export const saveImportDraft = data => request({ url: `${baseUrl}/import-batch/draft`, method: 'post', data });
export const confirmImport = data => request({ url: `${baseUrl}/import-batch/confirm`, method: 'post', data });

View File

@@ -61,6 +61,13 @@ export const getUser = id => {
}); });
}; };
export const getCustomerOptions = () => {
return request({
url: '/blade-system/user/customer-options',
method: 'get',
});
};
export const getUserPlatform = id => { export const getUserPlatform = id => {
return request({ return request({
url: '/blade-system/user/platform-detail', url: '/blade-system/user/platform-detail',

View File

@@ -70,11 +70,11 @@ export const recognitionIDCard = url => {
}); });
}; };
export const recognitionTransportCertificates = objectKeys => { export const recognitionTransportCertificates = certificates => {
return request({ return request({
url: '/blade-file/file/recognitionTransportCertificates', url: '/blade-file/file/recognitionTransportCertificates',
method: 'post', method: 'post',
timeout: 60000, timeout: 60000,
data: objectKeys, data: certificates,
}); });
}; };

View File

@@ -51,6 +51,14 @@ export const changeStatus = (id, status) => {
}); });
}; };
export const auditCertification = (id, certificationStatus, rejectReason) => {
return request({
url: '/blade-transport/transport-vehicle/audit-certification',
method: 'post',
params: { id, certificationStatus, rejectReason },
});
};
export const getExpiryStat = params => { export const getExpiryStat = params => {
return request({ return request({
url: '/blade-transport/transport-vehicle/expiry-stat', url: '/blade-transport/transport-vehicle/expiry-stat',

View File

@@ -40,6 +40,16 @@ export const submitApproval = id => {
}); });
}; };
export const withdrawApproval = id => {
return request({
url: '/blade-transport/customer-archive/withdraw-approval',
method: 'post',
params: {
id,
},
});
};
export const approve = id => { export const approve = id => {
return request({ return request({
url: '/blade-transport/customer-archive/approve', url: '/blade-transport/customer-archive/approve',

View File

@@ -0,0 +1,23 @@
import request from '@/axios';
export const getList = (current, size, params) =>
request({
url: '/blade-transport/equipment-ledger/list',
method: 'get',
params: { ...params, current, size },
});
export const getDetail = id =>
request({ url: '/blade-transport/equipment-ledger/detail', method: 'get', params: { id } });
export const getNextEquipmentCode = () =>
request({ url: '/blade-transport/equipment-ledger/next-equipment-code', method: 'get' });
export const add = row =>
request({ url: '/blade-transport/equipment-ledger/submit', method: 'post', data: row });
export const update = row =>
request({ url: '/blade-transport/equipment-ledger/submit', method: 'post', data: row });
export const remove = ids =>
request({ url: '/blade-transport/equipment-ledger/remove', method: 'post', params: { ids } });

View File

@@ -1,28 +1,57 @@
<template> <template>
<el-form-item v-if="mode === 'form'" :label="label" :prop="prop"> <el-form-item v-if="mode === 'form'" :label="label" :prop="prop">
<el-upload <div :class="[`${classPrefix}-uploader`, { [`${classPrefix}-uploader--large`]: large }]">
:class="[`${classPrefix}-uploader`, { [`${classPrefix}-uploader--large`]: large }]" <el-upload
action="/api/blade-resource/oss/endpoint/put-file" ref="uploadRef"
name="file" class="upload-inner"
:headers="uploadHeaders" action="/api/blade-resource/oss/endpoint/put-file"
:show-file-list="false" name="file"
:disabled="readonly" :headers="uploadHeaders"
:before-upload="beforeUpload" :show-file-list="false"
:on-progress="handleProgress" :disabled="readonly"
:on-success="handleSuccess" :before-upload="beforeUpload"
:on-error="handleError" :on-progress="handleProgress"
> :on-success="handleSuccess"
<img v-if="value" :src="value" :class="`${classPrefix}-uploader__image`" /> :on-error="handleError"
<div v-else :class="`${classPrefix}-uploader__empty`">{{ emptyText }}</div> >
</el-upload> <el-image
v-if="value"
:src="value"
:class="`${classPrefix}-uploader__image`"
:preview-src-list="[value]"
fit="contain"
preview-teleported
:z-index="3000"
@click.stop
/>
<div v-else :class="`${classPrefix}-uploader__empty`">{{ emptyText }}</div>
</el-upload>
<el-link
v-if="value && !readonly"
type="primary"
class="upload-replace-link"
@click.stop="triggerUpload"
>
更换
</el-link>
</div>
</el-form-item> </el-form-item>
<el-form-item v-else :prop="prop" :class="`${classPrefix}-upload-item`"> <el-form-item v-else :prop="prop" :class="`${classPrefix}-upload-item`">
<div :class="[`${classPrefix}-upload-card`, { [`${classPrefix}-upload-card--large`]: large }]"> <div :class="[`${classPrefix}-upload-card`, { [`${classPrefix}-upload-card--large`]: large }]">
<div v-if="label" :class="`${classPrefix}-upload-card__head`"> <div v-if="label" :class="`${classPrefix}-upload-card__head`">
<span :class="`${classPrefix}-upload-card__label`">{{ label }}</span> <span :class="`${classPrefix}-upload-card__label`">{{ label }}</span>
<span :class="`${classPrefix}-upload-card__action`">{{ actionText }}</span> <el-link
v-if="!readonly"
type="primary"
:class="`${classPrefix}-upload-card__action`"
@click.stop="triggerUpload"
>
{{ actionText }}
</el-link>
<span v-else :class="`${classPrefix}-upload-card__action`">{{ actionText }}</span>
</div> </div>
<el-upload <el-upload
ref="uploadRef"
:class="`${classPrefix}-upload-card__uploader`" :class="`${classPrefix}-upload-card__uploader`"
action="/api/blade-resource/oss/endpoint/put-file" action="/api/blade-resource/oss/endpoint/put-file"
name="file" name="file"
@@ -34,7 +63,16 @@
:on-success="handleSuccess" :on-success="handleSuccess"
:on-error="handleError" :on-error="handleError"
> >
<img v-if="value" :src="value" :class="`${classPrefix}-upload-card__image`" /> <el-image
v-if="value"
:src="value"
:class="`${classPrefix}-upload-card__image`"
:preview-src-list="[value]"
fit="contain"
preview-teleported
:z-index="3000"
@click.stop
/>
<div v-else :class="`${classPrefix}-upload-card__empty`">{{ emptyText }}</div> <div v-else :class="`${classPrefix}-upload-card__empty`">{{ emptyText }}</div>
</el-upload> </el-upload>
</div> </div>
@@ -86,6 +124,11 @@ export default {
this.closeUploadLoading(); this.closeUploadLoading();
}, },
methods: { methods: {
triggerUpload() {
const uploadEl = this.$refs.uploadRef && this.$refs.uploadRef.$el;
const input = uploadEl && uploadEl.querySelector('.el-upload__input');
if (input) input.click();
},
showUploadLoading() { showUploadLoading() {
if (this.uploadLoading) { if (this.uploadLoading) {
return; return;

View File

@@ -0,0 +1,28 @@
<template>
<div class="section-card">
<div v-if="title || $slots.title || $slots.extra" class="section-card__header">
<span class="section-card__bar" />
<span class="section-card__title">
<slot name="title">{{ title }}</slot>
</span>
<span class="section-card__extra">
<slot name="extra" />
</span>
</div>
<div class="section-card__body">
<slot />
</div>
</div>
</template>
<script>
export default {
name: 'SectionCard',
props: {
title: {
type: String,
default: ''
}
}
}
</script>

View File

@@ -85,6 +85,9 @@ const MIME_MAP = {
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ppt: 'application/vnd.ms-powerpoint', ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
eml: 'message/rfc822',
msg: 'application/vnd.ms-outlook',
zip: 'application/zip',
txt: 'text/plain', txt: 'text/plain',
}; };

View File

@@ -39,6 +39,8 @@ import codeSetting from './views/tool/codesetting.vue';
import formSetting from './views/tool/formsetting.vue'; import formSetting from './views/tool/formsetting.vue';
import tenantPackage from './views/system/tenantpackage.vue'; import tenantPackage from './views/system/tenantpackage.vue';
import tenantDatasource from './views/system/tenantdatasource.vue'; import tenantDatasource from './views/system/tenantdatasource.vue';
// 通用弹窗分组白卡(灰底弹窗内使用,标题在卡内)
import sectionCard from './components/section-card/main.vue';
window.$crudCommon = crudCommon; window.$crudCommon = crudCommon;
debug(); debug();
@@ -61,6 +63,7 @@ app.component('codeSetting', codeSetting);
app.component('formSetting', formSetting); app.component('formSetting', formSetting);
app.component('tenantPackage', tenantPackage); app.component('tenantPackage', tenantPackage);
app.component('tenantDatasource', tenantDatasource); app.component('tenantDatasource', tenantDatasource);
app.component('sectionCard', sectionCard);
app.config.globalProperties.$app = app; app.config.globalProperties.$app = app;
app.config.globalProperties.$dayjs = dayjs; app.config.globalProperties.$dayjs = dayjs;
app.config.globalProperties.website = website; app.config.globalProperties.website = website;

View File

@@ -56,10 +56,10 @@ export const getCargoTypeOption = ctx => ({
type: 'radio', type: 'radio',
dataType: 'number', dataType: 'number',
dicData: typeLevelDic, dicData: typeLevelDic,
search: true, search: false,
searchSpan: 6,
value: 2, value: 2,
span: 24, span: 24,
order: 100,
hide: true, hide: true,
minWidth: 120, minWidth: 120,
editDisabled: true, editDisabled: true,
@@ -70,20 +70,34 @@ export const getCargoTypeOption = ctx => ({
label: '货物类型', label: '货物类型',
prop: 'cargoName', prop: 'cargoName',
minWidth: 150, minWidth: 150,
search: true,
searchPlaceholder: '请输入',
searchSpan: 6,
searchOrder: 2,
order: 70,
placeholder: '请输入',
maxlength: 50, maxlength: 50,
showWordLimit: true, showWordLimit: true,
showOverflowTooltip: true, showOverflowTooltip: true,
rules: [{ validator: validateCargoName, trigger: 'blur' }], rules: [
{ required: true, message: '请输入货物类型名称', trigger: 'blur' },
{ validator: validateCargoName, trigger: 'blur' },
],
}, },
{ {
label: '货物类型编码', label: '货物类型编码',
prop: 'cargoCode', prop: 'cargoCode',
minWidth: 140, minWidth: 140,
search: true, search: true,
searchPlaceholder: '请输入',
searchSpan: 6, searchSpan: 6,
searchOrder: 1,
maxlength: 4, maxlength: 4,
order: 60,
placeholder: '请输入',
editDisabled: true, editDisabled: true,
rules: [ rules: [
{ required: true, message: '请输入货物类型编码', trigger: 'blur' },
{ {
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
const form = ctx.form || {}; const form = ctx.form || {};
@@ -115,6 +129,8 @@ export const getCargoTypeOption = ctx => ({
hide: true, hide: true,
display: false, display: false,
span: 12, span: 12,
order: 90,
placeholder: '请输入',
rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }], rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }],
}, },
{ {
@@ -128,16 +144,19 @@ export const getCargoTypeOption = ctx => ({
minWidth: 160, minWidth: 160,
}, },
{ {
label: '上级货物编码', label: '上级货物类型编码',
prop: 'parentCargoCode', prop: 'parentCargoCode',
slot: true, slot: true,
readonly: true, readonly: true,
editDisabled: true, editDisabled: true,
addDisabled: true,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
display: false, display: false,
span: 12, span: 12,
order: 80,
minWidth: 160, minWidth: 160,
placeholder: '请输入',
rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }], rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }],
}, },
{ {
@@ -156,6 +175,7 @@ export const getCargoTypeOption = ctx => ({
minRows: 4, minRows: 4,
span: 24, span: 24,
minWidth: 180, minWidth: 180,
placeholder: '请输入',
maxlength: 200, maxlength: 200,
showWordLimit: true, showWordLimit: true,
overHidden: true, overHidden: true,
@@ -171,6 +191,7 @@ export const getCargoTypeOption = ctx => ({
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
display: false, display: false,
sortable: true,
minWidth: 160, minWidth: 160,
}, },
{ {
@@ -182,6 +203,7 @@ export const getCargoTypeOption = ctx => ({
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
display: false, display: false,
sortable: true,
minWidth: 160, minWidth: 160,
}, },
], ],

View File

@@ -27,18 +27,154 @@ export const createOption = () => ({
delBtn: false, delBtn: false,
addBtn: false, addBtn: false,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuFixed: 'right',
menuWidth: 180,
column: [ column: [
{
label: '地址名称',
prop: 'addressName',
search: true,
searchOrder: 8,
span: 24,
minWidth: 150,
showOverflowTooltip: true,
placeholder: '请输入',
maxlength: 100,
showWordLimit: true,
rules: [
{ required: true, message: '请输入地址名称', trigger: 'blur' },
{ max: 100, message: '地址名称不能超过100个字', trigger: 'blur' },
],
},
{
label: '地址编号',
prop: 'addressCode',
minWidth: 120,
addDisplay: false,
editDisplay: false,
},
{ {
label: '类型', label: '类型',
prop: 'addressType', prop: 'addressType',
type: 'select', type: 'select',
formslot: true, formslot: true,
search: true, search: true,
searchOrder: 7,
span: 24, span: 24,
minWidth: 140,
dicData: addressTypeOptions, dicData: addressTypeOptions,
rules: [{ required: true, message: '请选择类型', trigger: 'change' }], rules: [{ required: true, message: '请选择类型', trigger: 'change' }],
}, },
{
label: '站点编码',
prop: 'siteCodeDisplay',
search: false,
minWidth: 120,
addDisplay: false,
editDisplay: false,
},
{
label: '详细地址',
prop: 'detailAddress',
search: true,
searchOrder: 5,
formslot: true,
type: 'textarea',
minRows: 3,
span: 24,
minWidth: 220,
showOverflowTooltip: true,
rules: [
{ required: true, message: '请输入详细地址', trigger: 'blur' },
{ max: 255, message: '详细地址不能超过255个字', trigger: 'blur' },
],
},
{
label: '经度',
prop: 'longitude',
minWidth: 120,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '纬度',
prop: 'latitude',
minWidth: 120,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '行政区划',
prop: 'regionName',
search: true,
searchOrder: 4,
display: false,
minWidth: 150,
rules: [{ required: true, message: '请选择行政区划', trigger: 'change' }],
},
{
label: '联系人',
prop: 'contactName',
search: true,
searchOrder: 2,
minWidth: 110,
placeholder: '请输入',
maxlength: 50,
rules: [{ max: 50, message: '联系人不能超过50个字', trigger: 'blur' }],
},
{
label: '联系方式',
prop: 'contactPhone',
search: true,
searchOrder: 1,
minWidth: 130,
placeholder: '请输入',
maxlength: 20,
rules: [{ max: 20, message: '联系方式不能超过20个字', trigger: 'blur' }],
},
{
label: '组织',
prop: 'deptName',
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 4,
span: 24,
minWidth: 160,
overHidden: true,
showOverflowTooltip: true,
placeholder: '请输入',
maxlength: 200,
showWordLimit: true,
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
},
{
label: '更新时间',
prop: 'updateTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
width: 180,
addDisplay: false,
editDisplay: false,
},
{
label: '创建时间',
prop: 'createTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
width: 180,
addDisplay: false,
editDisplay: false,
},
{ {
label: '港口', label: '港口',
prop: 'portId', prop: 'portId',
@@ -75,133 +211,24 @@ export const createOption = () => ({
span: 24, span: 24,
rules: [{ required: true, message: '请选择机场标准名称', trigger: 'change' }], rules: [{ required: true, message: '请选择机场标准名称', trigger: 'change' }],
}, },
{
label: '地址名称',
prop: 'addressName',
search: true,
span: 24,
minWidth: 150,
maxlength: 100,
showWordLimit: true,
rules: [
{ required: true, message: '请输入地址名称', trigger: 'blur' },
{ max: 100, message: '地址名称不能超过100个字', trigger: 'blur' },
],
},
{
label: '地址编号',
prop: 'addressCode',
minWidth: 120,
addDisplay: false,
editDisplay: false,
},
{
label: '站点编码',
prop: 'siteCodeDisplay',
search: false,
minWidth: 120,
addDisplay: false,
editDisplay: false,
},
{ {
label: '站点编码', label: '站点编码',
prop: 'siteCode', prop: 'siteCode',
search: true, search: true,
searchOrder: 6,
hide: true, hide: true,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{
label: '行政区划',
prop: 'regionName',
search: true,
display: false,
minWidth: 150,
rules: [{ required: true, message: '请选择行政区划', trigger: 'change' }],
},
{
label: '详细地址',
prop: 'detailAddress',
search: true,
formslot: true,
type: 'textarea',
minRows: 3,
span: 24,
minWidth: 220,
rules: [
{ required: true, message: '请输入详细地址', trigger: 'blur' },
{ max: 255, message: '详细地址不能超过255个字', trigger: 'blur' },
],
},
{ {
label: '组织', label: '组织',
prop: 'deptId', prop: 'deptId',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 3,
hide: true, hide: true,
display: false, display: false,
dicData: [], dicData: [],
}, },
{
label: '组织',
prop: 'deptName',
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
{
label: '联系人',
prop: 'contactName',
search: true,
minWidth: 110,
maxlength: 50,
rules: [{ max: 50, message: '联系人不能超过50个字', trigger: 'blur' }],
},
{
label: '联系方式',
prop: 'contactPhone',
search: true,
minWidth: 130,
maxlength: 20,
rules: [{ max: 20, message: '联系方式不能超过20个字', trigger: 'blur' }],
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 4,
span: 24,
hide: true,
maxlength: 200,
showWordLimit: true,
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
},
{
label: '更新人',
prop: 'updateUserName',
minWidth: 120,
addDisplay: false,
editDisplay: false,
},
{
label: '更新时间',
prop: 'updateTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 160,
addDisplay: false,
editDisplay: false,
},
{
label: '创建时间',
prop: 'createTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 160,
addDisplay: false,
editDisplay: false,
},
], ],
}); });

View File

@@ -6,6 +6,15 @@ import {
textRule, textRule,
} from './common'; } 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 [];
};
export const config = { export const config = {
title: '常用货物', title: '常用货物',
permission: 'common_cargo', permission: 'common_cargo',
@@ -17,12 +26,29 @@ export const config = {
}; };
export const option = createCrudOption([ export const option = createCrudOption([
{
label: '货物名称',
prop: 'cargoName',
minWidth: 150,
placeholder: '请输入',
rules: textRule('货物名称', 100, true),
},
{
label: '货物编号',
prop: 'cargoCode',
formslot: true,
minWidth: 120,
maxlength: 20,
showWordLimit: true,
rules: [
{ required: true, message: '请输入货物编号', trigger: 'blur' },
{ max: 20, message: '货物编号不能超过20个字符', trigger: 'blur' },
],
},
{ {
label: '一级货物类型', label: '一级货物类型',
prop: 'firstCargoTypeName', prop: 'firstCargoTypeName',
formslot: true, formslot: true,
search: true,
searchOrder: 3,
minWidth: 150, minWidth: 150,
editDisabled: true, editDisabled: true,
rules: [{ required: true, message: '请选择一级货物类型', trigger: 'change' }], rules: [{ required: true, message: '请选择一级货物类型', trigger: 'change' }],
@@ -39,8 +65,6 @@ export const option = createCrudOption([
label: '二级货物类型', label: '二级货物类型',
prop: 'secondCargoTypeName', prop: 'secondCargoTypeName',
formslot: true, formslot: true,
search: true,
searchOrder: 4,
minWidth: 150, minWidth: 150,
editDisabled: true, editDisabled: true,
rules: [{ required: true, message: '请选择二级货物类型', trigger: 'change' }], rules: [{ required: true, message: '请选择二级货物类型', trigger: 'change' }],
@@ -48,47 +72,118 @@ export const option = createCrudOption([
{ {
label: '二级货物类型编码', label: '二级货物类型编码',
prop: 'secondCargoTypeCode', prop: 'secondCargoTypeCode',
hide: true,
display: false, display: false,
addDisplay: false, addDisplay: false,
editDisabled: true, editDisabled: true,
}, },
// 搜索字段与表格、表单字段独立,避免筛选配置影响数据展示和编辑布局。
{ {
label: '货物名称', label: '货物名称',
prop: 'cargoName', prop: 'searchCargoName',
searchProp: 'cargoName',
search: true, search: true,
searchOrder: 1, searchOrder: 5,
minWidth: 150, searchPlaceholder: '请输入',
rules: textRule('货物名称', 100, true), hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '货物编号', label: '货物编号',
prop: 'cargoCode', prop: 'searchCargoCode',
formslot: true, searchProp: 'cargoCode',
search: true,
searchOrder: 4,
searchPlaceholder: '请输入',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '一级货物类型',
prop: 'searchFirstCargoTypeName',
searchProp: 'firstCargoTypeName',
type: 'select',
searchType: 'select',
search: true,
searchOrder: 3,
searchPlaceholder: '请选择',
dicUrl: '/blade-system/cargo-type/list?current=1&size=9999&typeLevel=1',
dicFormatter: listDicFormatter,
props: {
label: 'cargoName',
value: 'cargoName',
},
filterable: true,
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '二级货物类型',
prop: 'searchSecondCargoTypeName',
searchProp: 'secondCargoTypeName',
type: 'select',
searchType: 'select',
search: true, search: true,
searchOrder: 2, searchOrder: 2,
minWidth: 120, searchPlaceholder: '请选择',
maxlength: 20, dicUrl: '/blade-system/cargo-type/list?current=1&size=9999&typeLevel=2',
showWordLimit: true, dicFormatter: listDicFormatter,
rules: [ props: {
{ required: true, message: '请输入货物编号', trigger: 'blur' }, label: 'cargoName',
{ max: 20, message: '货物编号不能超过20个字符', trigger: 'blur' }, value: 'cargoName',
], },
filterable: true,
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '组织', label: '组织',
prop: 'deptName', prop: 'searchDeptId',
searchProp: 'deptId',
type: 'select',
searchType: 'select',
search: true, search: true,
searchOrder: 5, searchOrder: 1,
searchPlaceholder: '请选择',
dicData: [],
props: {
label: 'label',
value: 'value',
},
filterable: true,
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '包装品牌',
prop: 'packageBrand',
slot: true,
minWidth: 150, minWidth: 150,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '品牌', label: '品牌',
prop: 'brand', prop: 'brand',
search: false, search: false,
hide: true,
minWidth: 120, minWidth: 120,
placeholder: '请输入',
rules: textRule('品牌', 50), rules: textRule('品牌', 50),
}, },
{ {
@@ -96,10 +191,25 @@ export const option = createCrudOption([
prop: 'packageType', prop: 'packageType',
type: 'select', type: 'select',
dicData: packageOptions, dicData: packageOptions,
hide: true,
minWidth: 100, minWidth: 100,
}, },
{ {
label: '货值', label: '规格',
prop: 'specification',
minWidth: 120,
placeholder: '请输入',
rules: textRule('规格', 50),
},
{
label: '型号',
prop: 'model',
minWidth: 120,
placeholder: '请输入',
rules: textRule('型号', 50),
},
{
label: '单价',
prop: 'cargoValue', prop: 'cargoValue',
formslot: true, formslot: true,
minWidth: 120, minWidth: 120,
@@ -129,29 +239,26 @@ export const option = createCrudOption([
minWidth: 120, minWidth: 120,
}, },
{ {
label: '规格', label: '尺寸',
prop: 'specification', prop: 'sizeText',
minWidth: 120,
rules: textRule('规格', 50),
},
{
label: '型号',
prop: 'model',
minWidth: 120,
rules: textRule('型号', 50),
},
{
label: '说明',
prop: 'descriptionOne',
minWidth: 160,
rules: textRule('说明', 100),
},
{
label: '数据来源',
prop: 'dataSource',
minWidth: 120, minWidth: 120,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
rules: textRule('尺寸', 100),
},
{
label: '其他说明1',
prop: 'descriptionOne',
minWidth: 160,
placeholder: '请输入',
rules: textRule('其他说明1', 100),
},
{
label: '其他说明2',
prop: 'descriptionTwo',
minWidth: 160,
placeholder: '请输入',
rules: textRule('其他说明2', 100),
}, },
{ {
label: '备注', label: '备注',
@@ -159,12 +266,25 @@ export const option = createCrudOption([
type: 'textarea', type: 'textarea',
minRows: 4, minRows: 4,
span: 24, span: 24,
hide: true,
maxlength: 200, maxlength: 200,
showWordLimit: true, showWordLimit: true,
placeholder: '请输入',
rules: textRule('备注', 200), rules: textRule('备注', 200),
}, },
...auditColumns, {
label: '组织',
prop: 'deptName',
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
...auditColumns
.filter(column => ['updateTime', 'createTime'].includes(column.prop))
.sort(
(first, second) =>
['updateTime', 'createTime'].indexOf(first.prop) -
['updateTime', 'createTime'].indexOf(second.prop)
),
]); ]);
export const excelOption = { export const excelOption = {

View File

@@ -5,6 +5,8 @@ export const config = {
permission: 'common_route', permission: 'common_route',
exportUrl: '/blade-transport/common-route/export-common-route', exportUrl: '/blade-transport/common-route/export-common-route',
exportName: '常用线路', exportName: '常用线路',
importUrl: '/blade-transport/common-route/import-common-route',
templateUrl: '/blade-transport/common-route/export-template',
enableAllDept: false, enableAllDept: false,
}; };
@@ -12,17 +14,14 @@ export const option = createCrudOption([
{ {
label: '线路名称', label: '线路名称',
prop: 'routeName', prop: 'routeName',
search: true, span: 12,
searchOrder: 1,
span: 24,
minWidth: 150, minWidth: 150,
overHidden: true,
rules: textRule('线路名称', 100, true), rules: textRule('线路名称', 100, true),
}, },
{ {
label: '线路编号', label: '线路编号',
prop: 'routeCode', prop: 'routeCode',
search: true,
searchOrder: 2,
minWidth: 120, minWidth: 120,
addDisplay: false, addDisplay: false,
editDisabled: true, editDisabled: true,
@@ -30,8 +29,6 @@ export const option = createCrudOption([
{ {
label: '发货地', label: '发货地',
prop: 'departureName', prop: 'departureName',
search: true,
searchOrder: 3,
minWidth: 140, minWidth: 140,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
@@ -40,10 +37,9 @@ export const option = createCrudOption([
label: '发货地址', label: '发货地址',
prop: 'departureAddress', prop: 'departureAddress',
formslot: true, formslot: true,
search: true,
searchOrder: 4,
span: 24, span: 24,
minWidth: 220, minWidth: 220,
overHidden: true,
rules: textRule('发货地址', 255, true), rules: textRule('发货地址', 255, true),
}, },
{ {
@@ -53,6 +49,8 @@ export const option = createCrudOption([
precision: 6, precision: 6,
minWidth: 120, minWidth: 120,
hide: true, hide: true,
addDisplay: false,
editDisplay: false,
}, },
{ {
label: '发货纬度', label: '发货纬度',
@@ -61,6 +59,8 @@ export const option = createCrudOption([
precision: 6, precision: 6,
minWidth: 120, minWidth: 120,
hide: true, hide: true,
addDisplay: false,
editDisplay: false,
}, },
{ {
label: '发货联系人', label: '发货联系人',
@@ -77,8 +77,6 @@ export const option = createCrudOption([
{ {
label: '收货地', label: '收货地',
prop: 'arrivalName', prop: 'arrivalName',
search: true,
searchOrder: 5,
minWidth: 140, minWidth: 140,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
@@ -87,10 +85,9 @@ export const option = createCrudOption([
label: '收货地址', label: '收货地址',
prop: 'arrivalAddress', prop: 'arrivalAddress',
formslot: true, formslot: true,
search: true,
searchOrder: 6,
span: 24, span: 24,
minWidth: 220, minWidth: 220,
overHidden: true,
rules: textRule('收货地址', 255, true), rules: textRule('收货地址', 255, true),
}, },
{ {
@@ -100,6 +97,8 @@ export const option = createCrudOption([
precision: 6, precision: 6,
minWidth: 120, minWidth: 120,
hide: true, hide: true,
addDisplay: false,
editDisplay: false,
}, },
{ {
label: '收货纬度', label: '收货纬度',
@@ -108,6 +107,8 @@ export const option = createCrudOption([
precision: 6, precision: 6,
minWidth: 120, minWidth: 120,
hide: true, hide: true,
addDisplay: false,
editDisplay: false,
}, },
{ {
label: '收货联系人', label: '收货联系人',
@@ -121,25 +122,160 @@ export const option = createCrudOption([
minWidth: 140, minWidth: 140,
rules: [phoneRule('收货联系方式'), ...textRule('收货联系方式', 50)], rules: [phoneRule('收货联系方式'), ...textRule('收货联系方式', 50)],
}, },
{
label: '组织',
prop: 'deptName',
search: true,
searchOrder: 7,
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
{ {
label: '备注', label: '备注',
prop: 'remark', prop: 'remark',
type: 'textarea', type: 'textarea',
minRows: 4, minRows: 4,
span: 24, span: 24,
hide: true, minWidth: 180,
maxlength: 500, maxlength: 500,
showWordLimit: true, showWordLimit: true,
rules: textRule('备注', 500), rules: textRule('备注', 500),
}, },
...auditColumns, {
label: '组织',
prop: 'deptName',
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
// 搜索字段独立于表格和表单字段,避免筛选配置影响数据展示和编辑布局。
{
label: '线路名称',
prop: 'searchRouteName',
searchProp: 'routeName',
search: true,
searchOrder: 7,
searchPlaceholder: '请输入',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '线路编号',
prop: 'searchRouteCode',
searchProp: 'routeCode',
search: true,
searchOrder: 6,
searchPlaceholder: '请输入',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '发货地',
prop: 'searchDepartureName',
searchProp: 'departureName',
search: true,
searchOrder: 5,
searchPlaceholder: '请输入',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '发货地址',
prop: 'searchDepartureAddress',
searchProp: 'departureAddress',
search: true,
searchOrder: 4,
searchPlaceholder: '请输入',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '收货地',
prop: 'searchArrivalName',
searchProp: 'arrivalName',
search: true,
searchOrder: 3,
searchPlaceholder: '请输入',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '收货地址',
prop: 'searchArrivalAddress',
searchProp: 'arrivalAddress',
search: true,
searchOrder: 2,
searchPlaceholder: '请输入',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '组织',
prop: 'searchDeptId',
searchProp: 'deptId',
search: true,
searchOrder: 1,
searchType: 'cascader',
searchPlaceholder: '请选择',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
dicData: [],
props: {
label: 'label',
value: 'value',
children: 'children',
},
checkStrictly: true,
emitPath: false,
showAllLevels: false,
filterable: true,
},
...auditColumns
.filter(column => column.prop !== 'createUserName')
.map(column =>
['createTime', 'updateTime'].includes(column.prop) ? { ...column, minWidth: 190 } : column
)
.sort(
(first, second) =>
['updateUserName', 'updateTime', 'createTime'].indexOf(first.prop) -
['updateUserName', 'updateTime', 'createTime'].indexOf(second.prop)
),
]); ]);
export const excelOption = {
submitBtn: false,
emptyBtn: false,
column: [
{
label: '导入模板',
prop: 'excelTemplate',
formslot: true,
span: 24,
},
{
label: 'Excel文件',
prop: 'excelFile',
type: 'upload',
drag: true,
loadText: '模板上传中,请稍等',
span: 24,
propsHttp: {
res: 'data',
},
tip: '请上传 .xls,.xlsx 标准格式文件',
},
],
};

View File

@@ -87,8 +87,10 @@ export const approvalStatusOptions = [
]; ];
export const effectiveTypeOptions = [ export const effectiveTypeOptions = [
{ label: '临时', value: 'temporary' }, { label: '未生效', value: 'not_effective' },
{ label: '正式', value: 'formal' }, { label: '临时生效', value: 'temporary_effective' },
{ label: '正式生效', value: 'formal_effective' },
{ label: '已过期', value: 'expired' },
]; ];
export const businessTypeOptions = [ export const businessTypeOptions = [

View File

@@ -6,11 +6,18 @@ import {
createCrudOption, createCrudOption,
effectiveTypeOptions, effectiveTypeOptions,
selectRule, selectRule,
settlementModeOptions,
signTypeOptions, signTypeOptions,
textRule, textRule,
} from './common'; } from './common';
const contractSettlementModeOptions = [
{ label: '现结', value: '现结' },
{ label: '日结', value: '日结' },
{ label: '周结', value: '周结' },
{ label: '月结', value: '月结' },
{ label: '按固定天数周期', value: '按固定天数周期' },
];
const listDicFormatter = res => { const listDicFormatter = res => {
const data = res?.data || res; const data = res?.data || res;
if (Array.isArray(data)) return data; if (Array.isArray(data)) return data;
@@ -25,7 +32,13 @@ export const config = {
permission: 'contract_manage', permission: 'contract_manage',
exportUrl: '/blade-transport/contract-manage/export-contract-manage', exportUrl: '/blade-transport/contract-manage/export-contract-manage',
exportName: '合同管理', exportName: '合同管理',
enableAllDept: false, defaultForm: {
contractCategory: '客户合同',
},
projectPlaceholder: '请选择',
organizationPlaceholder: '请选择',
enableAllDept: true,
allDeptReadonly: false,
batchDelete: false, batchDelete: false,
enableContractForm: true, enableContractForm: true,
enableProjectSelect: true, enableProjectSelect: true,
@@ -39,6 +52,7 @@ export const config = {
enableChangeRecord: true, enableChangeRecord: true,
enableOrganizationSelect: true, enableOrganizationSelect: true,
enableContractCreateActions: true, enableContractCreateActions: true,
enableContractExpiryTags: true,
actions: ['copy'], actions: ['copy'],
statusProp: 'approvalStatus', statusProp: 'approvalStatus',
statusTextProp: 'approvalStatusName', statusTextProp: 'approvalStatusName',
@@ -63,9 +77,25 @@ export const config = {
label: '撤回', label: '撤回',
statusProp: 'approvalStatus', statusProp: 'approvalStatus',
status: ['reviewing'], status: ['reviewing'],
stage: ['temporary'], stage: ['temporary', 'formal'],
permission: 'contract_manage_withdraw', permission: 'contract_manage_withdraw',
}, },
{
action: 'approve',
label: '通过',
statusProp: 'approvalStatus',
status: ['reviewing'],
stage: ['temporary', 'formal'],
permission: 'contract_manage_approve',
},
{
action: 'reject',
label: '驳回',
statusProp: 'approvalStatus',
status: ['reviewing'],
stage: ['temporary', 'formal'],
permission: 'contract_manage_reject',
},
{ {
action: 'submitFormal', action: 'submitFormal',
label: '重新提交转正', label: '重新提交转正',
@@ -143,16 +173,18 @@ export const option = createCrudOption([
prop: 'contractNo', prop: 'contractNo',
search: true, search: true,
searchOrder: 6, searchOrder: 6,
searchPlaceholder: '请输入',
order: 185, order: 185,
disabled: true, disabled: true,
placeholder: '不填写,则系统自动生成', placeholder: '不填写,则系统自动生成',
minWidth: 160, minWidth: 200,
}, },
{ {
label: '合同名称', label: '合同名称',
prop: 'contractName', prop: 'contractName',
search: true, search: true,
searchOrder: 7, searchOrder: 7,
searchPlaceholder: '请输入',
order: 180, order: 180,
minWidth: 180, minWidth: 180,
rules: textRule('合同名称', 100, true), rules: textRule('合同名称', 100, true),
@@ -163,6 +195,7 @@ export const option = createCrudOption([
search: true, search: true,
searchOrder: 8, searchOrder: 8,
searchType: 'select', searchType: 'select',
searchPlaceholder: '请选择',
formslot: true, formslot: true,
order: 140, order: 140,
dicUrl: '/blade-transport/project-apply/list?current=1&size=9999', dicUrl: '/blade-transport/project-apply/list?current=1&size=9999',
@@ -187,6 +220,7 @@ export const option = createCrudOption([
search: true, search: true,
searchOrder: 3, searchOrder: 3,
searchType: 'cascader', searchType: 'cascader',
searchPlaceholder: '全部',
formslot: true, formslot: true,
order: 120, order: 120,
dicData: [], dicData: [],
@@ -208,6 +242,7 @@ export const option = createCrudOption([
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 9, searchOrder: 9,
searchPlaceholder: '请选择',
order: 170, order: 170,
dicData: contractCategoryOptions, dicData: contractCategoryOptions,
minWidth: 130, minWidth: 130,
@@ -217,9 +252,8 @@ export const option = createCrudOption([
label: '签约类型', label: '签约类型',
prop: 'signType', prop: 'signType',
type: 'select', type: 'select',
search: true, search: false,
searchOrder: 1, span: 6,
span: 24,
order: 190, order: 190,
dicData: signTypeOptions, dicData: signTypeOptions,
minWidth: 120, minWidth: 120,
@@ -231,6 +265,7 @@ export const option = createCrudOption([
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 5, searchOrder: 5,
searchPlaceholder: '请选择',
dicData: effectiveTypeOptions, dicData: effectiveTypeOptions,
hide: true, hide: true,
display: false, display: false,
@@ -240,7 +275,8 @@ export const option = createCrudOption([
prop: 'partyA', prop: 'partyA',
type: 'select', type: 'select',
order: 160, order: 160,
dicUrl: '/blade-transport/customer-archive/list?current=1&size=9999&approvalStatus=approved', dicUrl:
'/blade-transport/customer-archive/list?current=1&size=9999&customerType=客户&approvalStatus=approved&status=1',
dicFormatter: listDicFormatter, dicFormatter: listDicFormatter,
props: { props: {
label: 'fullName', label: 'fullName',
@@ -255,7 +291,8 @@ export const option = createCrudOption([
prop: 'partyB', prop: 'partyB',
type: 'select', type: 'select',
order: 150, order: 150,
dicUrl: '/blade-transport/customer-archive/list?current=1&size=9999&approvalStatus=approved', dicUrl:
'/blade-transport/customer-archive/list?current=1&size=9999&customerType=承运商&approvalStatus=approved&status=1',
dicFormatter: listDicFormatter, dicFormatter: listDicFormatter,
props: { props: {
label: 'fullName', label: 'fullName',
@@ -333,14 +370,14 @@ export const option = createCrudOption([
rules: [{ required: true, message: '请选择签订日期', trigger: 'change' }], rules: [{ required: true, message: '请选择签订日期', trigger: 'change' }],
}, },
{ {
label: '结算方式', label: '结算类型',
prop: 'settlementMode', prop: 'settlementMode',
type: 'select', type: 'select',
dicData: settlementModeOptions, dicData: contractSettlementModeOptions,
hide: true, hide: true,
order: 90, order: 90,
minWidth: 120, minWidth: 120,
rules: selectRule('结算方式'), rules: selectRule('结算类型'),
}, },
{ {
label: '合同格式', label: '合同格式',
@@ -391,7 +428,7 @@ export const option = createCrudOption([
type: 'datetime', type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss', format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 160, minWidth: 190,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
@@ -401,6 +438,7 @@ export const option = createCrudOption([
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 2, searchOrder: 2,
searchPlaceholder: '请选择',
slot: true, slot: true,
dicData: contractStageOptions, dicData: contractStageOptions,
minWidth: 120, minWidth: 120,
@@ -413,6 +451,7 @@ export const option = createCrudOption([
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 4, searchOrder: 4,
searchPlaceholder: '请选择',
slot: true, slot: true,
dicData: contractApprovalStatusOptions, dicData: contractApprovalStatusOptions,
minWidth: 130, minWidth: 130,
@@ -571,3 +610,6 @@ export const option = createCrudOption([
option.span = 8; option.span = 8;
option.dialogWidth = 1480; option.dialogWidth = 1480;
option.dialogTop = '10px';
option.dialogCloseBtn = true;
option.menuWidth = 320;

View File

@@ -0,0 +1,36 @@
import { dataSourceOptions } from './common';
export const transportTypeDict = {
dicUrl: '/blade-system/dict-biz/dictionary?code=transport_type',
props: {
label: 'dictValue',
value: 'dictKey',
},
};
export const carrierTypeOptions = ['承运商', '自运', '网签平台'];
export const loadingStatusOptions = [
{ label: '草稿', value: 'draft' },
{ label: '待执行', value: 'pending' },
{ label: '进行中', value: 'running' },
{ label: '已完成', value: 'completed' },
{ label: '已取消', value: 'cancelled' },
];
export const loadingStatusMap = loadingStatusOptions.reduce((result, item) => {
result[item.value] = item.label;
return result;
}, {});
export const loadingManageConfig = {
title: '配载管理',
permission: 'loading_manage',
exportUrl: '/blade-transport/loading-manage/export-loading-manage',
exportName: '配载管理',
createText: '新建配载',
batchCompleteText: '批量完成',
statusProp: 'businessStatus',
statusTextProp: 'businessStatusName',
dataSourceOptions,
};

View File

@@ -6,21 +6,20 @@ import {
selectRule, selectRule,
textRule, textRule,
} from './common'; } from './common';
import { applyTableMenuWidth } from '@/utils/table-menu';
export const config = { export const config = {
title: '过程配置', title: '过程配置',
permission: 'process_config', permission: 'process_config',
exportUrl: '/blade-transport/process-config/export-process-config', exportUrl: '/blade-transport/process-config/export-process-config',
exportName: '过程配置', exportName: '过程配置',
enableAllDept: false, enableAllDept: true,
actions: ['copy', 'enable', 'disable'], actions: ['copy', 'enable', 'disable'],
statusProp: 'status', statusProp: 'status',
statusTextProp: 'statusName', statusTextProp: 'statusName',
deleteStatus: [0, 2], deleteStatus: [0, 2],
}; };
export const option = applyTableMenuWidth({ export const option = {
...createCrudOption([ ...createCrudOption([
{ {
label: '', label: '',
@@ -29,21 +28,13 @@ export const option = applyTableMenuWidth({
span: 24, span: 24,
hide: true, hide: true,
}, },
{
label: '配置编号',
prop: 'configCode',
search: true,
searchOrder: 4,
minWidth: 130,
addDisplay: false,
editDisplay: false,
editDisabled: true,
},
{ {
label: '配置名称', label: '配置名称',
prop: 'configName', prop: 'configName',
search: true, search: true,
searchOrder: 6, searchOrder: 6,
searchSpan: 6,
searchPlaceholder: '请输入',
span: 12, span: 12,
minWidth: 150, minWidth: 150,
addDisplay: false, addDisplay: false,
@@ -51,13 +42,16 @@ export const option = applyTableMenuWidth({
rules: textRule('配置名称', 100, true), rules: textRule('配置名称', 100, true),
}, },
{ {
label: '项目ID集合', label: '配置编号',
prop: 'projectIds', prop: 'configCode',
hide: true, search: true,
display: false, searchOrder: 4,
searchSpan: 6,
searchPlaceholder: '请输入',
minWidth: 130,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
rules: textRule('项目ID集合', 1000, true), editDisabled: true,
}, },
{ {
label: '项目', label: '项目',
@@ -65,7 +59,9 @@ export const option = applyTableMenuWidth({
formslot: true, formslot: true,
search: true, search: true,
searchOrder: 5, searchOrder: 5,
searchType: 'select', searchType: 'input',
searchSpan: 6,
searchPlaceholder: '请输入',
type: 'textarea', type: 'textarea',
minRows: 2, minRows: 2,
span: 12, span: 12,
@@ -77,48 +73,98 @@ export const option = applyTableMenuWidth({
rules: textRule('项目', 1000, true), rules: textRule('项目', 1000, true),
}, },
{ {
label: '包含过程节点', label: '过程节点',
prop: 'includedNodes', prop: 'includedNodes',
search: true,
searchOrder: 3,
type: 'select', type: 'select',
multiple: true, multiple: true,
dicData: nodeOptions, dicData: nodeOptions,
minWidth: 180, minWidth: 260,
rules: selectRule('包含过程节点'), overHidden: false,
slot: true,
addDisplay: false,
editDisplay: false,
rules: selectRule('过程节点'),
}, },
{ {
label: '默认完成运输天数', label: '默认运输完成天数',
prop: 'defaultFinishDays', prop: 'defaultFinishDays',
formslot: true, formslot: true,
type: 'number', type: 'number',
min: 1, min: 0,
max: 999, max: 999,
span: 12, span: 12,
minWidth: 170,
addDisplay: false,
editDisplay: false,
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 4,
span: 24,
minWidth: 180,
addDisplay: false,
editDisplay: false,
maxlength: 500,
showWordLimit: true,
rules: textRule('备注', 500),
},
{
label: '组织',
prop: 'deptName',
minWidth: 150, minWidth: 150,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
...auditColumns
.filter(column => ['updateTime', 'createTime'].includes(column.prop))
.map(column => ({ ...column, minWidth: 190 }))
.sort(
(first, second) =>
['updateTime', 'createTime'].indexOf(first.prop) -
['updateTime', 'createTime'].indexOf(second.prop)
),
{
label: '项目ID集合',
prop: 'projectIds',
hide: true,
display: false,
addDisplay: false,
editDisplay: false,
rules: textRule('项目ID集合', 1000, true),
},
{ {
label: '状态', label: '状态',
prop: 'status', prop: 'status',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 1, searchOrder: 1,
searchSpan: 6,
searchPlaceholder: '请选择',
dicData: processStatusOptions, dicData: processStatusOptions,
slot: true, slot: true,
minWidth: 100, hide: true,
fixed: false,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{ {
label: '组织', label: '组织',
prop: 'deptName', prop: 'searchDeptId',
searchProp: 'deptId',
type: 'select',
search: true, search: true,
searchOrder: 2, searchOrder: 2,
minWidth: 150, searchSpan: 6,
searchPlaceholder: '请选择',
hide: true,
display: false,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: false,
dicData: [],
filterable: true,
}, },
{ {
label: '', label: '',
@@ -127,22 +173,10 @@ export const option = applyTableMenuWidth({
span: 24, span: 24,
hide: true, hide: true,
}, },
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 4,
span: 24,
hide: true,
addDisplay: false,
editDisplay: false,
maxlength: 500,
showWordLimit: true,
rules: textRule('备注', 500),
},
...auditColumns,
]), ]),
saveBtnText: '保存',
updateBtnText: '保存',
dialogTop: '10px', dialogTop: '10px',
dialogWidth: '96%', dialogWidth: '96%',
searchSpan: 8, menuWidth: 200,
}, 5); };

View File

@@ -29,6 +29,14 @@ const userDicFormatter = res => {
})); }));
}; };
const formatUserRealName = (row, role) =>
row[`${role}RealName`] ||
row[`${role}UserRealName`] ||
row[`${role}User`]?.realName ||
row[role]?.realName ||
row[`${role}UserName`] ||
'';
export const config = { export const config = {
title: '项目管理', title: '项目管理',
permission: 'project_apply', permission: 'project_apply',
@@ -94,8 +102,9 @@ export const option = createCrudOption([
label: '立项申请单号', label: '立项申请单号',
prop: 'applyNo', prop: 'applyNo',
search: true, search: true,
searchPlaceholder: '请输入',
searchOrder: 7, searchOrder: 7,
minWidth: 160, minWidth: 220,
addDisplay: false, addDisplay: false,
editDisabled: true, editDisabled: true,
}, },
@@ -110,6 +119,7 @@ export const option = createCrudOption([
label: '项目名称', label: '项目名称',
prop: 'projectName', prop: 'projectName',
search: true, search: true,
searchPlaceholder: '请输入',
searchOrder: 6, searchOrder: 6,
minWidth: 180, minWidth: 180,
maxlength: 50, maxlength: 50,
@@ -128,6 +138,7 @@ export const option = createCrudOption([
prop: 'projectType', prop: 'projectType',
type: 'select', type: 'select',
search: true, search: true,
searchPlaceholder: '请选择',
searchOrder: 3, searchOrder: 3,
dicData: projectTypeOptions, dicData: projectTypeOptions,
minWidth: 120, minWidth: 120,
@@ -156,7 +167,7 @@ export const option = createCrudOption([
label: '承办部门', label: '承办部门',
prop: 'undertakeDeptName', prop: 'undertakeDeptName',
search: true, search: true,
searchLabel: '承办单位', searchPlaceholder: '请输入',
searchOrder: 5, searchOrder: 5,
minWidth: 150, minWidth: 150,
rules: textRule('承办部门', 50, true), rules: textRule('承办部门', 50, true),
@@ -170,6 +181,7 @@ export const option = createCrudOption([
label: '项目负责人', label: '项目负责人',
prop: 'principalUserName', prop: 'principalUserName',
minWidth: 130, minWidth: 130,
formatter: row => formatUserRealName(row, 'principal'),
rules: textRule('项目负责人', 50, true), rules: textRule('项目负责人', 50, true),
}, },
{ {
@@ -177,6 +189,7 @@ export const option = createCrudOption([
prop: 'principalUserId', prop: 'principalUserId',
type: 'select', type: 'select',
search: true, search: true,
searchPlaceholder: '请选择',
searchOrder: 4, searchOrder: 4,
filterable: true, filterable: true,
dicUrl: '/blade-system/user/user-list', dicUrl: '/blade-system/user/user-list',
@@ -193,6 +206,7 @@ export const option = createCrudOption([
prop: 'handlerUserName', prop: 'handlerUserName',
hide: true, hide: true,
minWidth: 130, minWidth: 130,
formatter: row => formatUserRealName(row, 'handler'),
rules: textRule('项目经办人', 50, true), rules: textRule('项目经办人', 50, true),
}, },
{ {
@@ -336,6 +350,7 @@ export const option = createCrudOption([
prop: 'approvalStatus', prop: 'approvalStatus',
type: 'select', type: 'select',
search: true, search: true,
searchPlaceholder: '请选择',
searchOrder: 2, searchOrder: 2,
slot: true, slot: true,
dicData: projectApprovalStatusOptions, dicData: projectApprovalStatusOptions,
@@ -362,6 +377,7 @@ export const option = createCrudOption([
prop: 'effectiveType', prop: 'effectiveType',
type: 'select', type: 'select',
search: true, search: true,
searchPlaceholder: '请选择',
searchOrder: 1, searchOrder: 1,
dicData: effectiveTypeOptions, dicData: effectiveTypeOptions,
value: 'temporary', value: 'temporary',

View File

@@ -1,11 +1,9 @@
import { import { auditColumns, createCrudOption, selectRule, textRule, transportTypeOptions } from './common';
auditColumns,
createCrudOption, const templateTypeOptions = [
templateTypeOptions, { label: '运输计划', value: '运输计划' },
transportTypeOptions, { label: '运单', value: '运单' },
selectRule, ];
textRule,
} from './common';
export const config = { export const config = {
title: '发货模板', title: '发货模板',
@@ -13,155 +11,203 @@ export const config = {
exportUrl: '/blade-transport/shipping-template/export-shipping-template', exportUrl: '/blade-transport/shipping-template/export-shipping-template',
exportName: '发货模板', exportName: '发货模板',
enableAllDept: false, enableAllDept: false,
enableProjectSelect: true,
enableAttachmentTable: true,
enableTransportPlanForm: true,
enableTemplateCodePreview: true,
attachmentTitle: '附件',
defaultForm: {
templateType: '运输计划',
},
transportFormRequiredFields: [
['projectName', '项目'],
['contractName', '客户合同'],
['transportType', '运输方式'],
['departureAddress', '发货地址'],
['arrivalAddress', '收货地址'],
],
actions: ['copy'], actions: ['copy'],
operations: [
{
action: 'createFromTemplate',
label: '从模板新建',
permission: 'shipping_template_add',
},
],
}; };
export const option = createCrudOption([ export const option = {
{ ...createCrudOption([
label: '模板编号', {
prop: 'templateCode', label: '',
search: true, prop: 'templateInfoTitle',
minWidth: 150, formslot: true,
addDisplay: false, span: 24,
editDisabled: true, order: 400,
}, hide: true,
{ labelWidth: 0,
label: '模板名称', },
prop: 'templateName', {
search: true, label: '模板编号',
minWidth: 150, prop: 'templateCode',
rules: textRule('模板名称', 50, true), search: true,
}, searchOrder: 4,
{ span: 8,
label: '模板类型', order: 390,
prop: 'templateType', minWidth: 170,
type: 'select', disabled: true,
search: true, placeholder: '系统自动生成',
dicData: templateTypeOptions, },
minWidth: 120, {
value: '运输计划', label: '模板名称',
rules: selectRule('模板类型'), prop: 'templateName',
}, search: true,
{ searchOrder: 3,
label: '项目', span: 8,
prop: 'projectName', order: 380,
search: true, minWidth: 150,
minWidth: 150, rules: textRule('模板名称', 50, true),
rules: textRule('项目', 100, true), },
}, {
{ label: '模板类型',
label: '项目ID', prop: 'templateType',
prop: 'projectId', type: 'select',
hide: true, search: true,
}, searchOrder: 2,
{ span: 8,
label: '客户合同', order: 370,
prop: 'contractName', dicData: templateTypeOptions,
minWidth: 160, minWidth: 120,
rules: textRule('客户合同', 100, true), rules: selectRule('模板类型'),
}, },
{ {
label: '客户合同ID', label: '备注',
prop: 'contractId', prop: 'remark',
hide: true, type: 'textarea',
}, minRows: 3,
{ span: 24,
label: '运输类型', order: 360,
prop: 'transportType', minWidth: 180,
type: 'select', maxlength: 200,
dicData: transportTypeOptions, showWordLimit: true,
minWidth: 130, rules: textRule('备注', 200),
rules: selectRule('运输类型'), },
}, {
{ label: '',
label: '发货地', prop: 'basicInfoTitle',
prop: 'departureName', formslot: true,
minWidth: 140, span: 24,
rules: textRule('发货地', 100, true), order: 350,
}, hide: true,
{ labelWidth: 0,
label: '发货地址', },
prop: 'departureAddress', {
minWidth: 220, label: '项目',
rules: textRule('发货地址', 255, true), prop: 'projectName',
}, formslot: true,
{ search: true,
label: '发货联系人', searchOrder: 1,
prop: 'departureContact', span: 8,
minWidth: 120, order: 340,
rules: textRule('发货联系人', 20), minWidth: 150,
}, rules: textRule('项目', 100, true),
{ },
label: '发货联系方式', {
prop: 'departurePhone', label: '客户合同',
minWidth: 140, prop: 'contractName',
rules: textRule('发货联系方式', 20), formslot: true,
}, span: 8,
{ order: 330,
label: '收货地', minWidth: 160,
prop: 'arrivalName', rules: textRule('客户合同', 100, true),
minWidth: 140, },
rules: textRule('收货地', 100, true), {
}, label: '运输方式',
{ prop: 'transportType',
label: '收货地址', type: 'select',
prop: 'arrivalAddress', span: 8,
minWidth: 220, order: 320,
rules: textRule('收货地址', 255, true), dicData: transportTypeOptions,
}, minWidth: 130,
{ rules: selectRule('运输方式'),
label: '收货联系人', },
prop: 'arrivalContact', {
minWidth: 120, label: '',
rules: textRule('收货联系人', 20), prop: 'shippingInfoTitle',
}, formslot: true,
{ span: 24,
label: '收货联系方式', order: 310,
prop: 'arrivalPhone', hide: true,
minWidth: 140, labelWidth: 0,
rules: textRule('收货联系方式', 20), },
}, {
{ label: '发货地址',
label: '货物信息JSON', prop: 'departureAddress',
prop: 'goodsJson', formslot: true,
type: 'textarea', span: 24,
minRows: 5, order: 300,
span: 24, minWidth: 220,
hide: true, rules: textRule('发货地址', 255, true),
}, },
{ {
label: '运费信息JSON', label: '收货地址',
prop: 'freightJson', prop: 'arrivalAddress',
type: 'textarea', formslot: true,
minRows: 4, span: 24,
span: 24, order: 290,
hide: true, minWidth: 220,
}, rules: textRule('收货地址', 255, true),
{ },
label: '附件JSON', {
prop: 'attachmentsJson', label: '',
type: 'textarea', prop: 'goodsInfoTitle',
minRows: 3, formslot: true,
span: 24, span: 24,
hide: true, order: 280,
}, hide: true,
{ labelWidth: 0,
label: '所属组织', },
prop: 'deptName', {
minWidth: 150, label: '',
addDisplay: false, prop: 'goodsJson',
editDisplay: false, type: 'textarea',
}, formslot: true,
{ span: 24,
label: '备注', order: 270,
prop: 'remark', hide: true,
type: 'textarea', labelWidth: '0px',
minRows: 4, className: 'business-crud-page__full-form-item',
span: 24, },
hide: true, {
maxlength: 200, label: '',
showWordLimit: true, prop: 'attachmentTitle',
rules: textRule('备注', 200), formslot: true,
}, span: 24,
...auditColumns, order: 260,
]); hide: true,
labelWidth: 0,
},
{
label: '',
prop: 'attachmentsJson',
type: 'textarea',
formslot: true,
span: 24,
order: 250,
hide: true,
labelWidth: '0px',
className: 'business-crud-page__full-form-item',
},
{ label: '项目ID', prop: 'projectId', hide: true, display: false },
{ label: '客户合同ID', prop: 'contractId', hide: true, display: false },
{ label: '发货地', prop: 'departureName', hide: true, display: false },
{ label: '发货联系人', prop: 'departureContact', hide: true, display: false },
{ label: '发货联系方式', prop: 'departurePhone', hide: true, display: false },
{ label: '收货地', prop: 'arrivalName', hide: true, display: false },
{ label: '收货联系人', prop: 'arrivalContact', hide: true, display: false },
{ label: '收货联系方式', prop: 'arrivalPhone', hide: true, display: false },
{ label: '所属组织', prop: 'deptName', minWidth: 150, addDisplay: false, editDisplay: false },
...auditColumns,
]),
dialogWidth: '96%',
};

View File

@@ -28,6 +28,9 @@ export const config = {
exportName: '临时额度申请', exportName: '临时额度申请',
enableAllDept: false, enableAllDept: false,
enableProjectSelect: true, enableProjectSelect: true,
projectQueryParams: {
temporaryCreditLimitSelectable: true,
},
enableAttachmentTable: true, enableAttachmentTable: true,
enableDraftSave: true, enableDraftSave: true,
saveAsDraft: true, saveAsDraft: true,
@@ -108,6 +111,7 @@ export const option = {
value: 'projectName', value: 'projectName',
}, },
filterable: true, filterable: true,
span: 12,
minWidth: 180, minWidth: 180,
rules: textRule('项目名称', 100, true), rules: textRule('项目名称', 100, true),
}, },
@@ -122,14 +126,16 @@ export const option = {
prop: 'projectCode', prop: 'projectCode',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 150, minWidth: 150,
rules: textRule('项目编号', 50, true), rules: textRule('项目编号', 50),
}, },
{ {
label: '承办部门', label: '承办部门',
prop: 'undertakeDeptName', prop: 'undertakeDeptName',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 150, minWidth: 150,
rules: textRule('承办部门', 50, true), rules: textRule('承办部门', 50, true),
}, },
@@ -138,6 +144,7 @@ export const option = {
prop: 'projectFundLimit', prop: 'projectFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 200, minWidth: 200,
rules: [nonNegativeRule('项目资金使用额度')], rules: [nonNegativeRule('项目资金使用额度')],
}, },
@@ -146,6 +153,7 @@ export const option = {
prop: 'usedFundLimit', prop: 'usedFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 220, minWidth: 220,
value: 0, value: 0,
rules: [nonNegativeRule('已使用项目资金额度')], rules: [nonNegativeRule('已使用项目资金额度')],
@@ -155,6 +163,7 @@ export const option = {
prop: 'remainingFundLimit', prop: 'remainingFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 230, minWidth: 230,
value: 0, value: 0,
rules: [nonNegativeRule('剩余项目资金使用额度')], rules: [nonNegativeRule('剩余项目资金使用额度')],
@@ -163,6 +172,7 @@ export const option = {
label: '申请临时额度(万元)', label: '申请临时额度(万元)',
prop: 'applyLimit', prop: 'applyLimit',
formslot: true, formslot: true,
span: 12,
minWidth: 180, minWidth: 180,
rules: [ rules: [
{ required: true, message: '请输入申请临时额度', trigger: 'blur' }, { required: true, message: '请输入申请临时额度', trigger: 'blur' },
@@ -175,6 +185,7 @@ export const option = {
type: 'date', type: 'date',
format: 'YYYY-MM-DD', format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD',
span: 12,
minWidth: 140, minWidth: 140,
rules: [{ required: true, message: '请选择申请有效期至', trigger: 'change' }], rules: [{ required: true, message: '请选择申请有效期至', trigger: 'change' }],
}, },
@@ -260,6 +271,6 @@ export const option = {
}, },
...auditColumns.map(column => ({ ...column, hide: true })), ...auditColumns.map(column => ({ ...column, hide: true })),
]), ]),
dialogWidth: 1480, dialogWidth: 1010,
index: false, index: false,
}; };

View File

@@ -116,9 +116,13 @@ export const config = {
exportName: '运输计划', exportName: '运输计划',
enableAllDept: false, enableAllDept: false,
enableProjectSelect: true, enableProjectSelect: true,
templateCreateTarget: 'transport-plan',
enableAttachmentTable: true, enableAttachmentTable: true,
enableTransportPlanForm: true, enableTransportPlanForm: true,
enableTransportPlanCreateActions: true, enableTransportPlanCreateActions: true,
enableTransportPlanImport: true,
transportPlanImportTitle: '导入发货计划',
transportPlanTemplateUrl: '/blade-transport/transport-plan/export-template',
attachmentTitle: '附件', attachmentTitle: '附件',
searchRangeMap: { searchRangeMap: {
planStartDateRange: ['planStartDateStart', 'planStartDateEnd'], planStartDateRange: ['planStartDateStart', 'planStartDateEnd'],
@@ -129,6 +133,55 @@ export const config = {
statusTextProp: 'businessStatusName', statusTextProp: 'businessStatusName',
deleteStatus: ['draft'], deleteStatus: ['draft'],
editStatus: ['draft', 'waiting_dispatch'], editStatus: ['draft', 'waiting_dispatch'],
detailButton: true,
detailSections: [
{
title: '基本信息',
fields: [
['planNo', '计划单号'],
['planName', '计划名称'],
['projectName', '项目名称'],
['customerName', '客户名称'],
['transportType', '运输方式'],
['businessStatus', '调度状态'],
['planStartDate', '计划开始日期'],
['planEndDate', '计划结束日期'],
['dataSource', '数据来源'],
],
},
{
title: '收发货信息',
fields: [
['departureName', '发货地'],
['departureAddress', '发货地址', 2],
['departureContact', '发货联系人'],
['departurePhone', '发货联系方式'],
['arrivalName', '收货地'],
['arrivalAddress', '到货地址', 2],
['arrivalContact', '收货联系人'],
['arrivalPhone', '收货联系方式'],
],
},
{
title: '其它信息',
fields: [
['remark', '备注', 2],
['updateUserName', '更新人'],
['updateTime', '更新时间'],
['createTime', '创建时间'],
],
},
],
operations: [
{
action: 'dispatch',
label: '调度',
statusProp: 'businessStatus',
status: ['waiting_dispatch'],
permission: 'transport_plan_edit',
confirm: '确定调度该运输计划?',
},
],
}; };
export const option = { export const option = {

View File

@@ -2,6 +2,7 @@ import {
auditColumns, auditColumns,
createCrudOption, createCrudOption,
dataSourceOptions, dataSourceOptions,
phoneRule,
selectRule, selectRule,
textRule, textRule,
waybillStatusOptions, waybillStatusOptions,
@@ -96,9 +97,17 @@ export const config = {
exportName: '运单管理', exportName: '运单管理',
importUrl: '/blade-transport/waybill-manage/import-waybill-manage', importUrl: '/blade-transport/waybill-manage/import-waybill-manage',
enableAllDept: false, enableAllDept: false,
enableProjectSelect: true,
templateCreateTarget: 'waybill-manage',
enableContractSelect: true,
enableShippingPlanSelect: true,
enableShippingInfoForm: true,
enableTaskInfoForm: true,
batchDelete: false, batchDelete: false,
actionButtonLikeSearch: true,
createText: '新建运单', createText: '新建运单',
importText: '导入运单', importText: '导入运单',
enableWaybillImport: true,
roadLoading: true, roadLoading: true,
roadLoadingText: '公路配载', roadLoadingText: '公路配载',
searchRangeMap: { searchRangeMap: {
@@ -115,6 +124,15 @@ export const config = {
export const option = { export const option = {
...createCrudOption([ ...createCrudOption([
{
label: '',
prop: 'basicInfoTitle',
formslot: true,
span: 24,
order: 900,
hide: true,
labelWidth: 0,
},
{ {
label: '运单号', label: '运单号',
prop: 'waybillNo', prop: 'waybillNo',
@@ -129,6 +147,8 @@ export const option = {
prop: 'loadingNo', prop: 'loadingNo',
search: true, search: true,
searchOrder: 5, searchOrder: 5,
span: 6,
order: 840,
minWidth: 140, minWidth: 140,
}, },
{ {
@@ -137,13 +157,18 @@ export const option = {
search: true, search: true,
searchLabel: '多联总单', searchLabel: '多联总单',
searchOrder: 6, searchOrder: 6,
span: 6,
order: 830,
minWidth: 140, minWidth: 140,
}, },
{ {
label: '项目名称', label: '项目',
prop: 'projectName', prop: 'projectName',
formslot: true,
search: true, search: true,
searchOrder: 22, searchOrder: 22,
span: 6,
order: 890,
minWidth: 150, minWidth: 150,
rules: textRule('项目', 100, true), rules: textRule('项目', 100, true),
}, },
@@ -154,6 +179,10 @@ export const option = {
searchLabel: '客户名称', searchLabel: '客户名称',
searchOrder: 21, searchOrder: 21,
minWidth: 150, minWidth: 150,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
display: false,
}, },
{ {
label: '车牌号/航班号/船号/班列号', label: '车牌号/航班号/船号/班列号',
@@ -162,6 +191,7 @@ export const option = {
searchLabel: '车/船/航班/班列', searchLabel: '车/船/航班/班列',
searchOrder: 13, searchOrder: 13,
minWidth: 210, minWidth: 210,
display: false,
}, },
{ {
label: '司机', label: '司机',
@@ -170,6 +200,7 @@ export const option = {
searchLabel: '司机名称', searchLabel: '司机名称',
searchOrder: 14, searchOrder: 14,
minWidth: 120, minWidth: 120,
display: false,
}, },
{ {
label: '联系方式', label: '联系方式',
@@ -179,6 +210,7 @@ export const option = {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
display: false,
}, },
{ {
label: '运输方式', label: '运输方式',
@@ -199,6 +231,8 @@ export const option = {
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 20, searchOrder: 20,
span: 6,
order: 850,
...transportTypeDict, ...transportTypeDict,
minWidth: 130, minWidth: 130,
rules: selectRule('运输类型'), rules: selectRule('运输类型'),
@@ -207,6 +241,10 @@ export const option = {
label: '过程节点', label: '过程节点',
prop: 'currentProcessNode', prop: 'currentProcessNode',
minWidth: 140, minWidth: 140,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
display: false,
}, },
{ {
label: '承运类型', label: '承运类型',
@@ -216,6 +254,7 @@ export const option = {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
display: false,
}, },
{ {
label: '承运商', label: '承运商',
@@ -224,6 +263,7 @@ export const option = {
searchLabel: '承运商名称', searchLabel: '承运商名称',
searchOrder: 15, searchOrder: 15,
minWidth: 150, minWidth: 150,
display: false,
}, },
{ {
label: '货物信息', label: '货物信息',
@@ -238,17 +278,23 @@ export const option = {
{ {
label: '发货地址', label: '发货地址',
prop: 'departureAddress', prop: 'departureAddress',
formslot: true,
search: true, search: true,
searchOrder: 17, searchOrder: 17,
span: 24,
order: 790,
minWidth: 220, minWidth: 220,
rules: textRule('发货地址', 255, true), rules: textRule('发货地址', 255, true),
}, },
{ {
label: '到货地址', label: '到货地址',
prop: 'arrivalAddress', prop: 'arrivalAddress',
formslot: true,
search: true, search: true,
searchLabel: '收货地址', searchLabel: '收货地址',
searchOrder: 16, searchOrder: 16,
span: 24,
order: 780,
minWidth: 220, minWidth: 220,
rules: textRule('收货地址', 255, true), rules: textRule('收货地址', 255, true),
}, },
@@ -307,14 +353,20 @@ export const option = {
{ {
label: '客户合同', label: '客户合同',
prop: 'contractName', prop: 'contractName',
formslot: true,
span: 6,
order: 880,
minWidth: 160, minWidth: 160,
rules: textRule('客户合同', 100, true), rules: textRule('客户合同', 100, true),
}, },
{ {
label: '计划名称', label: '发货计划',
prop: 'planName', prop: 'planName',
formslot: true,
search: true, search: true,
searchOrder: 7, searchOrder: 7,
span: 6,
order: 870,
minWidth: 150, minWidth: 150,
}, },
{ {
@@ -324,6 +376,7 @@ export const option = {
searchOrder: 18, searchOrder: 18,
formatter: row => formatGoodsField(row, ['cargoType', 'goodsType', 'typeName']), formatter: row => formatGoodsField(row, ['cargoType', 'goodsType', 'typeName']),
minWidth: 130, minWidth: 130,
display: false,
rules: textRule('货物类型', 100, true), rules: textRule('货物类型', 100, true),
}, },
{ {
@@ -333,6 +386,7 @@ export const option = {
searchOrder: 19, searchOrder: 19,
formatter: row => formatGoodsField(row, ['cargoName', 'goodsName', 'name']), formatter: row => formatGoodsField(row, ['cargoName', 'goodsName', 'name']),
minWidth: 150, minWidth: 150,
display: false,
rules: textRule('货物名称', 100, true), rules: textRule('货物名称', 100, true),
}, },
{ {
@@ -356,6 +410,8 @@ export const option = {
prop: 'originalNo', prop: 'originalNo',
search: true, search: true,
searchOrder: 12, searchOrder: 12,
span: 6,
order: 860,
minWidth: 140, minWidth: 140,
}, },
{ {
@@ -365,30 +421,36 @@ export const option = {
searchLabel: '运输批次', searchLabel: '运输批次',
searchOrder: 3, searchOrder: 3,
minWidth: 140, minWidth: 140,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
display: false,
}, },
{ {
label: '预计发货时间', label: '预计发货日期',
prop: 'estimatedStartTime', prop: 'estimatedStartTime',
type: 'datetime', type: 'date',
format: 'YYYY-MM-DD HH:mm:ss', format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD',
formatter: row => getFirstValue(row, ['estimatedStartTime', 'planStartDate']), formatter: row => getFirstValue(row, ['estimatedStartTime', 'planStartDate']),
minWidth: 170, minWidth: 170,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
display: false,
}, },
{ {
label: '预计完成时间', label: '预计完成日期',
prop: 'estimatedEndTime', prop: 'estimatedEndTime',
type: 'datetime', type: 'date',
format: 'YYYY-MM-DD HH:mm:ss', format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD',
formatter: row => getFirstValue(row, ['estimatedEndTime', 'planEndDate']), formatter: row => getFirstValue(row, ['estimatedEndTime', 'planEndDate']),
minWidth: 170, minWidth: 170,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
display: false,
}, },
{ {
label: '备注', label: '备注',
@@ -396,11 +458,12 @@ export const option = {
type: 'textarea', type: 'textarea',
minRows: 4, minRows: 4,
span: 24, span: 24,
order: 810,
minWidth: 180, minWidth: 180,
overHidden: true, overHidden: true,
maxlength: 500, maxlength: 200,
showWordLimit: true, showWordLimit: true,
rules: textRule('备注', 500), rules: textRule('备注', 200),
}, },
{ {
label: '数据来源', label: '数据来源',
@@ -410,6 +473,10 @@ export const option = {
searchOrder: 10, searchOrder: 10,
dicData: dataSourceOptions, dicData: dataSourceOptions,
minWidth: 120, minWidth: 120,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
display: false,
}, },
{ {
...auditColumn('createTime'), ...auditColumn('createTime'),
@@ -431,6 +498,34 @@ export const option = {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{
label: '',
prop: 'shippingInfoTitle',
formslot: true,
span: 24,
order: 800,
hide: true,
labelWidth: 0,
},
{
label: '',
prop: 'taskInfoTitle',
formslot: true,
span: 24,
order: 770,
hide: true,
labelWidth: 0,
},
{
label: '',
prop: 'taskInfoForm',
formslot: true,
span: 24,
order: 760,
hide: true,
labelWidth: '0px',
className: 'business-crud-page__full-form-item',
},
{ {
label: '实际发货时间', label: '实际发货时间',
prop: 'startDateRange', prop: 'startDateRange',
@@ -444,6 +539,7 @@ export const option = {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
display: false,
}, },
{ {
label: '实际完成时间', label: '实际完成时间',
@@ -458,6 +554,7 @@ export const option = {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
display: false,
}, },
{ {
label: '创建时间', label: '创建时间',
@@ -478,51 +575,70 @@ export const option = {
prop: 'relationNo', prop: 'relationNo',
search: true, search: true,
searchOrder: 2, searchOrder: 2,
span: 6,
order: 820,
minWidth: 140, minWidth: 140,
hide: true, hide: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '货物信息JSON', label: '货物信息',
prop: 'goodsJson', prop: 'goodsJson',
type: 'textarea', type: 'textarea',
minRows: 5, minRows: 5,
span: 24, span: 24,
hide: true, hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '承运信息JSON', label: '承运信息',
prop: 'carrierJson', prop: 'carrierJson',
type: 'textarea', type: 'textarea',
minRows: 4, minRows: 4,
span: 24, span: 24,
hide: true, hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '过程配置JSON', label: '过程配置',
prop: 'processJson', prop: 'processJson',
type: 'textarea', type: 'textarea',
minRows: 4, minRows: 4,
span: 24, span: 24,
hide: true, hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '运费信息JSON', label: '运费信息',
prop: 'freightJson', prop: 'freightJson',
type: 'textarea', type: 'textarea',
minRows: 4, minRows: 4,
span: 24, span: 24,
hide: true, hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '附件JSON', label: '附件',
prop: 'attachmentsJson', prop: 'attachmentsJson',
type: 'textarea', type: 'textarea',
minRows: 3, minRows: 3,
span: 24, span: 24,
hide: true, hide: true,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
}, },
{ {
label: '所属组织', label: '所属组织',
@@ -532,6 +648,36 @@ export const option = {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{
label: '发货地',
prop: 'departureName',
hide: true,
display: false,
minWidth: 140,
rules: textRule('发货地', 100, true),
},
{
label: '收货地',
prop: 'arrivalName',
hide: true,
display: false,
minWidth: 140,
rules: textRule('收货地', 100, true),
},
{
label: '发货联系方式',
prop: 'departurePhone',
hide: true,
display: false,
rules: [phoneRule('发货联系方式'), ...textRule('发货联系方式', 50)],
},
{
label: '收货联系方式',
prop: 'arrivalPhone',
hide: true,
display: false,
rules: [phoneRule('收货联系方式'), ...textRule('收货联系方式', 50)],
},
{ {
label: '项目ID', label: '项目ID',
prop: 'projectId', prop: 'projectId',
@@ -544,6 +690,33 @@ export const option = {
hide: true, hide: true,
display: false, display: false,
}, },
{
label: '发货计划ID',
prop: 'planId',
hide: true,
display: false,
},
{
label: '任务录入模式',
prop: 'taskEntryMode',
hide: true,
display: false,
value: 'simple',
},
{
label: '任务备注',
prop: 'taskRemark',
hide: true,
display: false,
},
{
label: '任务信息',
prop: 'taskInfoJson',
hide: true,
display: false,
},
]), ]),
dialogWidth: '96%',
dialogTop: '10px',
menuWidth: 320, menuWidth: 320,
}; };

View File

@@ -1,4 +1,3 @@
import website from '@/config/website';
import { getDeptLazyTree } from '@/api/system/dept'; import { getDeptLazyTree } from '@/api/system/dept';
export const userOption = safe => { export const userOption = safe => {
@@ -25,42 +24,41 @@ export const userOption = safe => {
searchShow: true, searchShow: true,
searchMenuSpan: 24, searchMenuSpan: 24,
searchIcon: true, searchIcon: true,
searchIndex: 8, searchIndex: 4,
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
selection: true, selection: true,
addBtn: false, addBtn: false,
viewBtn: true, viewBtn: true,
dialogType: 'drawer', dialogType: 'dialog',
dialogWidth: 1000, dialogWidth: '90%',
labelPosition: 'right',
labelWidth: 120,
dialogClickModal: false, dialogClickModal: false,
column: [ column: [
{ {
label: '登录账号', label: '账号',
prop: 'account', prop: 'account',
search: true, search: true,
searchOrder: 2,
display: false, display: false,
}, },
{ {
label: '所属租户', label: '姓名',
prop: 'tenantName',
slot: true,
display: false,
},
{
label: '用户姓名',
prop: 'realName', prop: 'realName',
search: true, search: true,
searchOrder: 4,
display: false, display: false,
}, },
{ {
label: '所属角色', label: '手机号',
prop: 'roleName', prop: 'phone',
slot: true, search: true,
overHidden: true, searchOrder: 3,
display: false, display: false,
}, },
{ label: '工号', prop: 'code', display: false },
{ {
label: '所属部门', label: '所属部门',
prop: 'deptName', prop: 'deptName',
@@ -68,33 +66,22 @@ export const userOption = safe => {
overHidden: true, overHidden: true,
display: false, display: false,
}, },
{ label: '岗位', prop: 'postName', overHidden: true, display: false },
{ label: '性别', prop: 'sexName', width: 80, display: false },
{ label: 'OA人员id', prop: 'oaPersonId', minWidth: 120, overHidden: true, display: false },
{ label: '备注', prop: 'remark', minWidth: 160, overHidden: true, display: false },
{ {
label: '用户平台', label: '状态',
prop: 'userTypeName', prop: 'status',
width: 100,
slot: true,
display: false,
},
{
label: '用户平台',
type: 'select', type: 'select',
dicUrl: '/blade-system/dict/dictionary?code=user_type', dicData: [
props: { { label: '启用', value: 1 },
label: 'dictValue', { label: '禁用', value: 0 },
value: 'dictKey', ],
},
dataType: 'number', dataType: 'number',
search: true, search: true,
hide: true, searchOrder: 1,
display: false, display: false,
prop: 'userType',
rules: [
{
required: true,
message: '请选择用户平台',
trigger: 'blur',
},
],
}, },
], ],
group: [ group: [
@@ -104,53 +91,13 @@ export const userOption = safe => {
icon: 'el-icon-user-solid', icon: 'el-icon-user-solid',
column: [ column: [
{ {
label: '所属租户', label: '账号名',
prop: 'tenantId',
type: 'tree',
dicUrl: '/blade-system/tenant/select',
props: {
label: 'tenantName',
value: 'tenantId',
},
hide: !website.tenantMode,
addDisplay: website.tenantMode,
editDisplay: website.tenantMode,
viewDisplay: website.tenantMode,
rules: [
{
required: true,
message: '请输入所属租户',
trigger: 'click',
},
],
span: 24,
},
{
label: '登录账号',
prop: 'account', prop: 'account',
span: 6,
rules: [ rules: [
{ {
required: true, required: true,
message: '请输入登录账号', message: '请输入账号',
trigger: 'blur',
},
],
},
{
label: '用户平台',
type: 'select',
dicUrl: '/blade-system/dict/dictionary?code=user_type',
props: {
label: 'dictValue',
value: 'dictKey',
},
dataType: 'number',
slot: true,
prop: 'userType',
rules: [
{
required: true,
message: '请选择用户平台',
trigger: 'blur', trigger: 'blur',
}, },
], ],
@@ -161,6 +108,7 @@ export const userOption = safe => {
hide: true, hide: true,
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
addDisplay: false,
rules: [{ required: true, validator: validatePass, trigger: 'blur' }], rules: [{ required: true, validator: validatePass, trigger: 'blur' }],
}, },
{ {
@@ -169,6 +117,7 @@ export const userOption = safe => {
hide: true, hide: true,
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
addDisplay: false,
rules: [{ required: true, validator: validatePass2, trigger: 'blur' }], rules: [{ required: true, validator: validatePass2, trigger: 'blur' }],
}, },
], ],
@@ -191,12 +140,13 @@ export const userOption = safe => {
], ],
}, },
{ {
label: '用户姓名', label: '姓名',
prop: 'realName', prop: 'realName',
span: 6,
rules: [ rules: [
{ {
required: true, required: true,
message: '请输入用户姓名', message: '请输入姓名',
trigger: 'blur', trigger: 'blur',
}, },
{ {
@@ -209,12 +159,14 @@ export const userOption = safe => {
{ {
label: '手机号码', label: '手机号码',
prop: 'phone', prop: 'phone',
span: 6,
rules: [{ required: true, message: '请输入手机号码', trigger: 'blur' }],
overHidden: true, overHidden: true,
}, },
{ {
label: '电子邮箱', label: '电子邮箱',
prop: 'email', prop: 'email',
hide: true, span: 6,
overHidden: true, overHidden: true,
}, },
{ {
@@ -235,7 +187,7 @@ export const userOption = safe => {
value: 3, value: 3,
}, },
], ],
hide: true, span: 6,
}, },
{ {
label: '用户生日', label: '用户生日',
@@ -261,6 +213,8 @@ export const userOption = safe => {
{ {
label: '用户编号', label: '用户编号',
prop: 'code', prop: 'code',
label: '工号',
span: 6,
}, },
{ {
label: '所属角色', label: '所属角色',
@@ -273,13 +227,7 @@ export const userOption = safe => {
}, },
checkStrictly: true, checkStrictly: true,
slot: true, slot: true,
rules: [ display: false,
{
required: true,
message: '请选择所属角色',
trigger: 'click',
},
],
}, },
{ {
label: '所属部门', label: '所属部门',
@@ -292,6 +240,7 @@ export const userOption = safe => {
}, },
checkStrictly: true, checkStrictly: true,
slot: true, slot: true,
span: 6,
rules: [ rules: [
{ {
required: true, required: true,
@@ -304,7 +253,7 @@ export const userOption = safe => {
label: '所属岗位', label: '所属岗位',
prop: 'postId', prop: 'postId',
type: 'tree', type: 'tree',
multiple: true, multiple: false,
dicData: [], dicData: [],
props: { props: {
label: 'postName', label: 'postName',
@@ -317,6 +266,7 @@ export const userOption = safe => {
trigger: 'click', trigger: 'click',
}, },
], ],
span: 6,
}, },
{ {
label: '直属主管', label: '直属主管',
@@ -330,6 +280,7 @@ export const userOption = safe => {
value: 'id', value: 'id',
}, },
clearable: true, clearable: true,
display: false,
}, },
{ {
label: '是否主管', label: '是否主管',
@@ -346,6 +297,49 @@ export const userOption = safe => {
}, },
], ],
value: 0, value: 0,
display: false,
},
{
label: '人员类别',
prop: 'personCategory',
type: 'select',
span: 6,
value: 1,
dicData: [
{ label: '内部员工', value: 1 },
{ 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: '备注',
prop: 'remark',
type: 'textarea',
span: 24,
maxlength: 200,
showWordLimit: true,
}, },
], ],
}, },

View File

@@ -9,67 +9,74 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
indexWidth: 70,
selection: true, selection: true,
addBtn: false, addBtn: false,
editBtn: false, editBtn: false,
delBtn: false, delBtn: false,
viewBtn: false, viewBtn: false,
menuWidth: 160, menuWidth: 180,
dialogClickModal: false, dialogClickModal: false,
column: [ column: [
{ {
label: '姓名', label: '姓名身份证号',
prop: 'driverName', prop: 'driverName',
search: true, search: true,
searchOrder: 7, searchOrder: 7,
slot: true, slot: true,
width: 110, minWidth: 220,
}, },
{ {
label: '手机号', label: '手机号',
prop: 'mobile', prop: 'mobile',
search: true, search: true,
searchOrder: 6, searchOrder: 6,
width: 130, minWidth: 130,
}, },
{ {
label: '准驾车型', label: '性别',
prop: 'drivingType', prop: 'gender',
width: 80,
align: 'center',
},
{
label: '司机类型',
prop: 'driverType',
search: true, search: true,
searchOrder: 5, searchOrder: 3,
hide: true,
type: 'select', type: 'select',
dicData: [ dicData: [
{ {
label: 'A1', label: '自有员工',
value: 'A1', value: '自有',
}, },
{ {
label: 'A2', label: '外协',
value: 'A2', value: '外协',
}, },
{ {
label: 'A3', label: '承运管理员',
value: 'A3', value: '承运管理员',
},
{
label: 'B1',
value: 'B1',
},
{
label: 'B2',
value: 'B2',
},
{
label: 'C1',
value: 'C1',
},
{
label: 'C2',
value: 'C2',
}, },
], ],
width: 110, minWidth: 110,
align: 'center',
},
{
label: '岗位',
prop: 'posts',
minWidth: 150,
},
{
label: '所属组织',
prop: 'organizationName',
search: true,
searchOrder: 1,
type: 'select',
filterable: true,
dicData: [],
minWidth: 160,
}, },
{ {
label: '状态', label: '状态',
@@ -92,65 +99,52 @@ export const option = {
align: 'center', align: 'center',
}, },
{ {
label: '司机类型', label: '驾驶认证(日期)',
prop: 'driverType', prop: 'drivingLicenseEndDate',
search: true, slot: true,
searchOrder: 3, minWidth: 140,
type: 'select',
dicData: [
{
label: '自有员工',
value: '自有',
},
{
label: '外协',
value: '外协',
},
],
width: 100,
align: 'center', align: 'center',
}, },
{
label: '从业资格认证(日期)',
prop: 'qualificationEndDate',
slot: true,
minWidth: 160,
},
{ {
label: '身份证号', label: '身份证号',
prop: 'idCardNo', prop: 'idCardNo',
search: true, search: true,
searchOrder: 2, searchOrder: 2,
width: 190, hide: true,
}, },
{ {
label: '所属组织', label: '准驾车型',
prop: 'organizationName', prop: 'drivingType',
search: true, search: true,
searchOrder: 1, searchOrder: 5,
hide: true,
type: 'select', type: 'select',
filterable: true, dicData: [
dicData: [], { label: 'A1', value: 'A1' },
width: 140, { label: 'A2', value: 'A2' },
{ label: 'A3', value: 'A3' },
{ label: 'B1', value: 'B1' },
{ label: 'B2', value: 'B2' },
{ label: 'C1', value: 'C1' },
{ label: 'C2', value: 'C2' },
],
}, },
{ {
label: '性别', label: '',
prop: 'gender', prop: 'expireStatus',
width: 80, search: true,
align: 'center', searchOrder: 0,
}, searchSpan: 6,
{ hide: true,
label: '岗位', addDisplay: false,
prop: 'posts', editDisplay: false,
width: 150, viewDisplay: false,
},
{
label: '驾驶认证',
prop: 'drivingLicenseEndDate',
slot: true,
width: 130,
align: 'center',
},
{
label: '从业资格认证',
prop: 'qualificationEndDate',
slot: true,
width: 140,
align: 'center',
}, },
], ],
}; };

View File

@@ -14,23 +14,40 @@ export const option = {
editBtn: false, editBtn: false,
delBtn: false, delBtn: false,
viewBtn: false, viewBtn: false,
menuWidth: 160, menuWidth: 320,
dialogClickModal: false, dialogClickModal: false,
column: [ column: [
{ label: '船舶名', prop: 'shipName', search: true, searchOrder: 5, slot: true, width: 130 }, {
label: '船舶名',
prop: 'shipName',
search: true,
searchOrder: 5,
slot: true,
minWidth: 130,
},
{ {
label: '船舶识别号', label: '船舶识别号',
prop: 'shipIdentifierNo', prop: 'shipIdentifierNo',
search: true, search: true,
searchOrder: 4, searchOrder: 4,
width: 140, minWidth: 140,
},
{
label: '所属组织',
prop: 'organizationName',
search: true,
searchOrder: 1,
type: 'select',
filterable: true,
dicData: [],
minWidth: 160,
}, },
{ {
label: '船检登记号', label: '船检登记号',
prop: 'shipInspectionNo', prop: 'shipInspectionNo',
search: true, search: true,
searchOrder: 3, searchOrder: 3,
width: 130, minWidth: 130,
}, },
{ {
label: '船舶类型', label: '船舶类型',
@@ -44,52 +61,47 @@ export const option = {
{ label: '杂货船', value: '杂货船' }, { label: '杂货船', value: '杂货船' },
{ label: '油船', value: '油船' }, { label: '油船', value: '油船' },
], ],
width: 110, minWidth: 110,
align: 'center',
},
{
label: '所属组织',
prop: 'organizationName',
search: true,
searchOrder: 1,
type: 'select',
filterable: true,
dicData: [],
width: 160,
},
{
label: '状态',
prop: 'status',
type: 'select',
slot: true,
dicData: [
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
width: 90,
align: 'center', align: 'center',
}, },
{ {
label: '国籍证有效期至', label: '国籍证有效期至',
prop: 'nationalityCertEndDate', prop: 'nationalityCertEndDate',
slot: true, slot: true,
width: 150, minWidth: 150,
align: 'center', align: 'center',
}, },
{ {
label: '最低安全配员证书有效期至', label: '最低安全配员证书有效期至',
prop: 'safeManningCertEndDate', prop: 'safeManningCertEndDate',
slot: true, slot: true,
width: 210, minWidth: 210,
align: 'center', align: 'center',
}, },
{ {
label: '营业运输证有效期至', label: '营业运输证有效期至',
prop: 'businessTransportCertEndDate', prop: 'businessTransportCertEndDate',
slot: true, slot: true,
width: 170, minWidth: 170,
align: 'center', align: 'center',
}, },
{ label: '承租有效期至', prop: 'leaseEndDate', slot: true, width: 140, align: 'center' }, {
label: '承租有效期至',
prop: 'leaseEndDate',
slot: true,
minWidth: 140,
align: 'center',
},
{
label: '',
prop: 'expireStatus',
search: true,
searchOrder: 0,
searchSpan: 6,
hide: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
], ],
}; };

View File

@@ -9,21 +9,37 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
selection: true, selection: false,
addBtn: false, addBtn: false,
editBtn: false, editBtn: false,
delBtn: false, delBtn: false,
viewBtn: false, viewBtn: false,
menuWidth: 160, menuWidth: 320,
dialogClickModal: false, dialogClickModal: false,
column: [ column: [
{ {
label: '车牌号', label: '车牌号',
prop: 'plateNo', prop: 'plateNo',
search: true, search: true,
searchOrder: 5, searchOrder: 6,
slot: true, slot: true,
width: 120, minWidth: 120,
},
{
label: '业务关系',
prop: 'businessRelation',
search: true,
searchOrder: 5,
searchValue: '',
type: 'select',
dicData: [
{ label: '全部', value: '' },
{ label: '自有', value: '自有' },
{ label: '管理挂靠', value: '管理挂靠' },
{ label: '业务合作', value: '业务合作' },
],
minWidth: 120,
align: 'center',
}, },
{ {
label: '所属组织', label: '所属组织',
@@ -33,28 +49,13 @@ export const option = {
type: 'select', type: 'select',
filterable: true, filterable: true,
dicData: [], dicData: [],
width: 150, minWidth: 150,
},
{
label: '业务关系',
prop: 'businessRelation',
search: true,
searchOrder: 4,
type: 'select',
dicData: [
{ label: '全部', value: '' },
{ label: '自有', value: '自有' },
{ label: '管理挂靠', value: '管理挂靠' },
{ label: '业务合作', value: '业务合作' },
],
width: 120,
align: 'center',
}, },
{ {
label: '车辆类型', label: '车辆类型',
prop: 'vehicleType', prop: 'vehicleType',
search: true, search: true,
searchOrder: 3, searchOrder: 4,
type: 'select', type: 'select',
dicData: [ dicData: [
{ label: '全部', value: '' }, { label: '全部', value: '' },
@@ -65,49 +66,95 @@ export const option = {
{ label: '拖挂车', value: '拖挂' }, { label: '拖挂车', value: '拖挂' },
{ label: '半挂车', value: '半挂' }, { label: '半挂车', value: '半挂' },
], ],
width: 110, minWidth: 110,
align: 'center', align: 'center',
}, },
{ {
label: '核定载质量(KG)', label: '核定载质量(KG)',
prop: 'approvedLoadKg', prop: 'approvedLoadKg',
width: 140, minWidth: 140,
align: 'right', align: 'right',
}, },
{
label: '绑定司机',
prop: 'boundDriver',
slot: true,
minWidth: 120,
align: 'center',
},
{
label: '车辆类型',
prop: 'vehicleType',
minWidth: 110,
align: 'center',
},
{
label: '认证状态',
prop: 'certificationStatus',
slot: true,
search: true,
searchOrder: 2,
searchValue: '',
type: 'select',
dicData: [
{ label: '全部', value: '' },
{ label: '认证中', value: 0 },
{ label: '已认证', value: 1 },
{ label: '已驳回', value: 2 },
],
minWidth: 110,
align: 'center',
},
{ {
label: '车辆状态', label: '车辆状态',
prop: 'status', prop: 'status',
search: true, search: true,
searchOrder: 2, searchOrder: 3,
type: 'select', type: 'select',
slot: true, slot: true,
dicData: [ dicData: [
{ label: '启用', value: 1 }, { label: '启用', value: 1 },
{ label: '停用', value: 2 }, { label: '停用', value: 2 },
], ],
width: 90, minWidth: 90,
align: 'center', align: 'center',
}, },
{ {
label: '行驶证有效期', label: '道路运输证号',
prop: 'roadTransportCertNo',
minWidth: 160,
},
{
label: '行驶证认证',
prop: 'drivingLicenseEndDate', prop: 'drivingLicenseEndDate',
slot: true, slot: true,
width: 140, minWidth: 140,
align: 'center', align: 'center',
}, },
{ {
label: '道路运输证有效期', label: '道路运输证认证',
prop: 'roadTransportCertEndDate', prop: 'roadTransportCertEndDate',
slot: true, slot: true,
width: 160, minWidth: 160,
align: 'center', align: 'center',
}, },
{ {
label: '年审有效期', label: '道路运输证年审',
prop: 'annualReviewEndDate', prop: 'annualReviewEndDate',
slot: true, slot: true,
width: 130, minWidth: 130,
align: 'center', align: 'center',
}, },
{
label: '',
prop: 'expireStatus',
search: true,
searchOrder: 0,
searchSpan: 6,
hide: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
], ],
}; };

View File

@@ -49,10 +49,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
column: [ column: [
{ {
label: '车船类型', label: '车船类型',
@@ -124,9 +125,8 @@ export const option = {
{ {
label: '直接经济损失', label: '直接经济损失',
prop: 'directEconomicLoss', prop: 'directEconomicLoss',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元',
minWidth: 140, minWidth: 140,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',
@@ -135,9 +135,8 @@ export const option = {
{ {
label: '保险理赔金额', label: '保险理赔金额',
prop: 'insuranceClaimAmount', prop: 'insuranceClaimAmount',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元',
minWidth: 140, minWidth: 140,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',

View File

@@ -48,10 +48,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
column: [ column: [
{ {
label: '车船类型', label: '车船类型',
@@ -140,9 +141,8 @@ export const option = {
{ {
label: '费用', label: '费用',
prop: 'fee', prop: 'fee',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元',
minWidth: 100, minWidth: 100,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',
@@ -155,6 +155,7 @@ export const option = {
label: '评定(复核)单位', label: '评定(复核)单位',
prop: 'assessmentUnit', prop: 'assessmentUnit',
minWidth: 160, minWidth: 160,
overHidden: true,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }], rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],

View File

@@ -0,0 +1,226 @@
import { formatUpdateUserName } from '@/utils/audit';
export const vehicleTypeDic = [
{ label: '车辆', value: '车辆' },
{ label: '船舶', value: '船舶' },
];
export const onlineStatusDic = [
{ label: '否', value: 0 },
{ label: '是', value: 1 },
];
export const option = {
height: 'auto',
calcHeight: 32,
dialogWidth: 960,
labelPosition: 'right',
labelWidth: 'auto',
searchLabelWidth: 160,
tip: false,
searchBtnText: '查询',
emptyBtnText: '重置',
saveBtnText: '提交',
updateBtnText: '提交',
searchShow: true,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 4,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
viewBtn: true,
selection: true,
dialogClickModal: false,
menuWidth: 160,
menuFixed: 'right',
column: [
{
label: '车船类型',
prop: 'vehicleType',
type: 'radio',
dicData: vehicleTypeDic,
value: '车辆',
search: true,
searchOrder: 3,
span: 24,
order: 1,
minWidth: 110,
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
},
{
label: '车牌号/船号',
prop: 'vehicleNo',
slot: true,
formslot: true,
search: true,
searchOrder: 4,
span: 12,
order: 2,
minWidth: 140,
rules: [{ required: true, message: '请输入车牌号/船号', trigger: 'blur' }],
},
{
label: '设备号',
prop: 'equipmentCode',
formslot: true,
search: true,
searchOrder: 2,
span: 12,
order: 3,
minWidth: 150,
rules: [{ required: true, message: '设备编号由系统生成', trigger: 'blur' }],
},
{
label: '设备名称',
prop: 'equipmentName',
span: 12,
order: 4,
minWidth: 150,
rules: [
{ required: true, message: '请输入设备名称', trigger: 'blur' },
{ max: 100, message: '最多 100 个字符', trigger: 'blur' },
],
},
{
label: '出厂日期',
prop: 'factoryDate',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
span: 12,
order: 8,
minWidth: 130,
},
{
label: '设备品牌',
prop: 'equipmentBrand',
span: 12,
order: 5,
minWidth: 130,
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
},
{
label: '设备类型',
prop: 'equipmentType',
type: 'select',
dicData: [],
span: 12,
order: 6,
minWidth: 130,
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'change' }],
},
{
label: '规格型号',
prop: 'specificationModel',
span: 12,
order: 7,
minWidth: 150,
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 3,
span: 24,
order: 10,
minWidth: 180,
overHidden: true,
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
},
{
label: '更新时间',
prop: 'updateTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
addDisplay: false,
editDisplay: false,
viewDisplay: false,
minWidth: 170,
},
{
label: '创建时间',
prop: 'createTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
addDisplay: false,
editDisplay: false,
viewDisplay: false,
minWidth: 170,
},
{
label: '是否在线',
prop: 'onlineStatus',
type: 'select',
dicData: onlineStatusDic,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
minWidth: 100,
},
{
label: '原厂设备号',
prop: 'originalEquipmentNo',
span: 12,
order: 9,
hide: true,
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
},
{
label: '附件',
prop: 'attachments',
slot: true,
formslot: true,
span: 24,
order: 11,
hide: true,
},
{
label: '组织',
prop: 'createDept',
type: 'tree',
dicData: [],
props: { label: 'title', value: 'id' },
checkStrictly: true,
search: true,
searchOrder: 1,
hide: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '更新人',
prop: 'updateUserName',
formatter: formatUpdateUserName,
display: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
],
};
export const excelOption = {
labelPosition: 'right',
submitBtn: false,
emptyBtn: false,
column: [
{
label: '模板上传',
prop: 'excelFile',
type: 'upload',
drag: true,
loadText: '模板上传中,请稍等',
span: 24,
propsHttp: { res: 'data' },
tip: '请上传 .xls,.xlsx 标准格式文件',
action: '/blade-transport/equipment-ledger/import-equipment-ledger',
},
{ label: '模板下载', prop: 'excelTemplate', formslot: true, span: 24 },
],
};

View File

@@ -38,10 +38,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
menuFixed: 'right', menuFixed: 'right',
column: [ column: [
{ {
@@ -103,9 +104,8 @@ export const option = {
{ {
label: '交易金额', label: '交易金额',
prop: 'transactionAmount', prop: 'transactionAmount',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元',
minWidth: 130, minWidth: 130,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',

View File

@@ -46,10 +46,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
column: [ column: [
{ {
label: '车船类型', label: '车船类型',
@@ -128,8 +129,8 @@ export const option = {
{ {
label: '保额', label: '保额',
prop: 'insuredAmount', prop: 'insuredAmount',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元', append: '元',
minWidth: 120, minWidth: 120,
rules: [{ validator: validateNonNegative, trigger: 'blur' }], rules: [{ validator: validateNonNegative, trigger: 'blur' }],
@@ -137,8 +138,8 @@ export const option = {
{ {
label: '保费', label: '保费',
prop: 'premium', prop: 'premium',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元', append: '元',
minWidth: 120, minWidth: 120,
rules: [ rules: [
@@ -181,19 +182,6 @@ export const option = {
display: false, display: false,
minWidth: 170, minWidth: 170,
}, },
{
label: '创建时间',
prop: 'createTimeRange',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
searchRange: true,
search: true,
hide: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{ {
label: '组织', label: '组织',
prop: 'createDept', prop: 'createDept',
@@ -210,6 +198,19 @@ export const option = {
editDisplay: false, editDisplay: false,
viewDisplay: false, viewDisplay: false,
}, },
{
label: '创建时间',
prop: 'createTimeRange',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
searchRange: true,
search: true,
hide: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{ {
label: '更新人', label: '更新人',
prop: 'updateUserName', prop: 'updateUserName',

View File

@@ -34,10 +34,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
menuFixed: 'right', menuFixed: 'right',
column: [ column: [
{ {
@@ -67,9 +68,8 @@ export const option = {
{ {
label: '上月统计里程数', label: '上月统计里程数',
prop: 'previousMonthMileage', prop: 'previousMonthMileage',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '公里',
minWidth: 210, minWidth: 210,
slot: true, slot: true,
rules: [{ validator: validateNonNegative, trigger: 'blur' }], rules: [{ validator: validateNonNegative, trigger: 'blur' }],
@@ -77,9 +77,8 @@ export const option = {
{ {
label: '本月统计里程数', label: '本月统计里程数',
prop: 'currentMonthMileage', prop: 'currentMonthMileage',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '公里',
minWidth: 210, minWidth: 210,
slot: true, slot: true,
rules: [{ validator: validateNonNegative, trigger: 'blur' }], rules: [{ validator: validateNonNegative, trigger: 'blur' }],
@@ -87,9 +86,8 @@ export const option = {
{ {
label: '本月行驶里程数', label: '本月行驶里程数',
prop: 'monthlyMileage', prop: 'monthlyMileage',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '公里',
minWidth: 170, minWidth: 170,
slot: true, slot: true,
rules: [{ validator: validateNonNegative, trigger: 'blur' }], rules: [{ validator: validateNonNegative, trigger: 'blur' }],
@@ -97,9 +95,8 @@ export const option = {
{ {
label: '累计行驶里程数', label: '累计行驶里程数',
prop: 'totalMileage', prop: 'totalMileage',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '公里',
minWidth: 170, minWidth: 170,
slot: true, slot: true,
rules: [{ validator: validateNonNegative, trigger: 'blur' }], rules: [{ validator: validateNonNegative, trigger: 'blur' }],

View File

@@ -35,10 +35,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
menuFixed: 'right', menuFixed: 'right',
column: [ column: [
{ {
@@ -111,25 +112,24 @@ export const option = {
{ {
label: '数量', label: '数量',
prop: 'quantity', prop: 'quantity',
type: 'number', type: 'input',
precision: 2, formslot: true,
minWidth: 110, minWidth: 110,
rules: [{ validator: validateNonNegative, trigger: 'blur' }], rules: [{ validator: validateNonNegative, trigger: 'blur' }],
}, },
{ {
label: '单价', label: '单价',
prop: 'unitPrice', prop: 'unitPrice',
type: 'number', type: 'input',
precision: 2, formslot: true,
minWidth: 120, minWidth: 120,
append: '元/升',
rules: [{ validator: validateNonNegative, trigger: 'blur' }], rules: [{ validator: validateNonNegative, trigger: 'blur' }],
}, },
{ {
label: '交易金额', label: '交易金额',
prop: 'transactionAmount', prop: 'transactionAmount',
type: 'number', type: 'input',
precision: 2, formslot: true,
minWidth: 130, minWidth: 130,
rules: [ rules: [
{ required: true, message: '请输入交易金额', trigger: 'blur' }, { required: true, message: '请输入交易金额', trigger: 'blur' },
@@ -139,8 +139,8 @@ export const option = {
{ {
label: '余额', label: '余额',
prop: 'balance', prop: 'balance',
type: 'number', type: 'input',
precision: 2, formslot: true,
minWidth: 110, minWidth: 110,
rules: [{ validator: validateNonNegative, trigger: 'blur' }], rules: [{ validator: validateNonNegative, trigger: 'blur' }],
}, },

View File

@@ -46,10 +46,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
menuFixed: 'right', menuFixed: 'right',
column: [ column: [
{ {
@@ -102,9 +103,8 @@ export const option = {
{ {
label: '金额', label: '金额',
prop: 'amount', prop: 'amount',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元',
minWidth: 120, minWidth: 120,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',

View File

@@ -39,10 +39,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
column: [ column: [
{ {
label: '车牌号', label: '车牌号',
@@ -88,8 +89,8 @@ export const option = {
{ {
label: '换胎数量', label: '换胎数量',
prop: 'tireQuantity', prop: 'tireQuantity',
type: 'number', type: 'input',
precision: 0, formslot: true,
minWidth: 110, minWidth: 110,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',
@@ -98,9 +99,8 @@ export const option = {
{ {
label: '换胎费用', label: '换胎费用',
prop: 'replacementCost', prop: 'replacementCost',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元',
minWidth: 120, minWidth: 120,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',

View File

@@ -26,10 +26,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
menuFixed: 'right', menuFixed: 'right',
column: [ column: [
{ {

View File

@@ -25,14 +25,6 @@ export const vehicleTypeDic = [
{ label: '船舶', value: '船舶' }, { label: '船舶', value: '船舶' },
]; ];
export const violationTypeDic = [
{ label: '闯红灯', value: '闯红灯' },
{ label: '超速', value: '超速' },
{ label: '违停', value: '违停' },
{ label: '压线', value: '压线' },
{ label: '其他', value: '其他' },
];
export const processStatusDic = [ export const processStatusDic = [
{ label: '未处理', value: '未处理' }, { label: '未处理', value: '未处理' },
{ label: '已处理', value: '已处理' }, { label: '已处理', value: '已处理' },
@@ -57,10 +49,11 @@ export const option = {
searchMenuPosition: 'right', searchMenuPosition: 'right',
border: true, border: true,
index: true, index: true,
indexLabel: '序号',
viewBtn: true, viewBtn: true,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 180,
column: [ column: [
{ {
label: '车船类型', label: '车船类型',
@@ -104,11 +97,11 @@ export const option = {
label: '类型', label: '类型',
prop: 'violationType', prop: 'violationType',
type: 'select', type: 'select',
dicData: violationTypeDic, dicData: [],
minWidth: 120, minWidth: 120,
span: 12, span: 12,
placeholder: '请选择', placeholder: '请选择',
rules: [{ required: true, message: '请选择类型', trigger: 'change' }], rules: [],
}, },
{ {
label: '事项', label: '事项',
@@ -118,10 +111,7 @@ export const option = {
display: false, display: false,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',
rules: [ rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
{ required: true, message: '请输入事项', trigger: 'blur' },
{ max: 100, message: '最多 100 个字符', trigger: 'blur' },
],
}, },
{ {
label: '日期', label: '日期',
@@ -149,8 +139,8 @@ export const option = {
{ {
label: '罚款', label: '罚款',
prop: 'fineAmount', prop: 'fineAmount',
type: 'number', type: 'input',
precision: 2, formslot: true,
append: '元', append: '元',
minWidth: 100, minWidth: 100,
span: 12, span: 12,
@@ -160,8 +150,8 @@ export const option = {
{ {
label: '被扣分数', label: '被扣分数',
prop: 'deductPoints', prop: 'deductPoints',
type: 'number', type: 'input',
precision: 0, formslot: true,
minWidth: 110, minWidth: 110,
span: 12, span: 12,
placeholder: '请输入', placeholder: '请输入',

View File

@@ -207,6 +207,102 @@
justify-content: center; justify-content: center;
} }
// =====================================================================
// 弹窗标准(与 business/project-apply 弹窗视觉一致)
// 灰底弹窗体 + 分组白卡标题融入卡头4px 主色竖条 + 8px 间距)
// =====================================================================
// 1) 所有弹窗内容区统一灰底 + 16px 内边距(与 project-apply 一致,避免内容贴边)
.el-dialog__body {
background-color: #f5f6fa;
padding: 16px;
}
// 2) Avue 默认弹窗表单自动包成白卡,无需逐页改 option 即统一标准
.el-dialog .avue-form {
background: #fff;
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
padding: 16px;
}
// 3) Avue 弹窗内分组option.group渲染为白卡标题在卡内
.el-dialog .avue-form__group {
background: #fff;
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
margin-bottom: 16px;
overflow: hidden;
.avue-group__header {
border-bottom: none;
padding: 14px 16px 4px;
font-weight: 500;
color: #303133;
}
.avue-group__item {
padding: 14px 16px 4px;
}
}
// 4) 弹窗内表单项默认上下间距 16px覆盖默认 18px / 22px
.el-dialog .el-form-item {
margin-bottom: 16px;
}
// 5) 通用分组白卡组件(<section-card>):标题融入卡头
.section-card {
background: #fff;
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
margin-bottom: 16px;
overflow: hidden;
&:last-child {
margin-bottom: 0;
}
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 14px 16px 4px;
font-weight: 500;
color: #303133;
}
&__bar {
flex: none;
width: 4px;
height: 16px;
border-radius: 2px;
background-color: var(--el-color-primary);
}
&__title {
flex: 1;
min-width: 0;
}
&__extra {
margin-left: auto;
flex: none;
}
&__body {
padding: 14px 16px 4px;
}
}
// 6) 弹窗内两列表单栅格工具类(与 project-apply 一致)
.form-grid-2 {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
column-gap: 72px;
row-gap: 0;
}
.avue-crud__dialog .avue-dialog__footer > .el-button { .avue-crud__dialog .avue-dialog__footer > .el-button {
margin-left: 12px; margin-left: 12px;
} }
@@ -225,6 +321,7 @@
.avue-crud__dialog .el-dialog__body { .avue-crud__dialog .el-dialog__body {
max-height: calc(100vh - 200px); max-height: calc(100vh - 200px);
overflow-y: auto; overflow-y: auto;
//background: #f5f6fa;
} }
// 统一表格线条颜色 // 统一表格线条颜色
@@ -267,6 +364,19 @@
margin-top: 12px; margin-top: 12px;
} }
// 统一 CRUD 顶部新增按钮为蓝底白字,保留其他工具按钮的描边样式
.avue-crud__header .el-button--primary.is-plain:has(.el-icon-plus) {
--el-button-bg-color: #409eff;
--el-button-border-color: #409eff;
--el-button-hover-bg-color: #66b1ff;
--el-button-hover-border-color: #66b1ff;
--el-button-active-bg-color: #337ecc;
--el-button-active-border-color: #337ecc;
--el-button-text-color: #fff;
--el-button-hover-text-color: #fff;
--el-button-active-text-color: #fff;
}
// 统一分页为按钮样式并靠右展示 // 统一分页为按钮样式并靠右展示
.avue-crud__pagination, .avue-crud__pagination,
.empty-pagination { .empty-pagination {
@@ -337,12 +447,7 @@
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
} }
.el-pagination .btn-prev, // 上一页 / 下一页 箭头按钮与页码按钮等宽,不再单独加宽
.el-pagination .btn-next,
.erp-pagination-first,
.erp-pagination-last {
min-width: 54px;
}
.el-pagination .el-pager { .el-pagination .el-pager {
gap: 6px; gap: 6px;
@@ -350,18 +455,7 @@
padding: 0; padding: 0;
} }
.el-pagination .btn-prev .el-icon, // 上一页 / 下一页 改为原生箭头图标:不再隐藏原生图标,也不再注入「上一页 / 下一页」文字
.el-pagination .btn-next .el-icon {
display: none;
}
.el-pagination .btn-prev::before {
content: '上一页';
}
.el-pagination .btn-next::before {
content: '下一页';
}
.el-pagination.is-background .el-pager li.is-active, .el-pagination.is-background .el-pager li.is-active,
.el-pagination .el-pager li.is-active { .el-pagination .el-pager li.is-active {
@@ -447,14 +541,7 @@
color: #fff; color: #fff;
} }
// 全站统一:隐藏分页的「上一页 / 下一页」原生导航按钮 // 全站统一:分页展示「上一页 / 下一页」原生箭头图标(不再隐藏;首页 / 尾页仍不注入)
// (「首页 / 尾页」由 src/utils/pagination.js 的全局增强器统一不再注入)
.el-pagination {
.btn-prev,
.btn-next {
display: none !important;
}
}
// 全站统一:隐藏操作栏按钮前的图标 // 全站统一:隐藏操作栏按钮前的图标
.avue-crud__menu .el-button--text i, .avue-crud__menu .el-button--text i,
@@ -462,16 +549,42 @@
display: none !important; display: none !important;
} }
// 全站统一:操作列最多一行展示 4 个按钮,第 5 个开始自动换行 // 全站统一:Avue 操作列文字链接保持间距并支持自动换行
.avue-crud .el-table td .cell.avue-crud__menu { .avue-crud .avue-crud__menu {
display: inline-grid; display: inline-flex !important;
grid-template-columns: repeat(4, max-content); flex-wrap: wrap;
gap: 4px; gap: 8px;
align-items: center; align-items: center;
justify-content: start; justify-content: start;
text-align: left; text-align: left;
} }
// 全站统一:手写表格操作列中的文字链接保持间距并支持自动换行
.el-table .el-table__body .cell:not(.avue-crud__menu):has(> .el-link) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
// 全站统一:表格操作列文字链接统一为主色(蓝色)
// 覆盖 Avue #menu 槽位与手写表格操作列中 el-link 的 warning / success / danger 等类型,
// 同时统一 hover / active / disabled 态,保证全站操作列风格一致。
// 选择器同时覆盖 Avue 行菜单(.avue-crud__menu与任意含 el-link 的表格体单元格(手写操作列)。
.avue-crud__menu .el-link,
.el-table .el-table__body .cell:has(.el-link) .el-link {
--el-link-text-color: var(--el-color-primary);
--el-link-hover-text-color: var(--el-color-primary-light-3);
--el-link-active-text-color: var(--el-color-primary-dark-2);
--el-link-disabled-text-color: var(--el-color-primary-light-5);
}
// 全站统一:去除操作列 el-link 默认 margin避免与 8px gap 叠加导致文字间距过宽
.avue-crud .avue-crud__menu .el-link,
.el-table .el-table__body .cell:not(.avue-crud__menu):has(> .el-link) .el-link {
margin: 0;
}
.avue-crud__menu > .el-button, .avue-crud__menu > .el-button,
.avue-crud__menu > .el-dropdown { .avue-crud__menu > .el-dropdown {
margin: 0; margin: 0;

View File

@@ -47,7 +47,12 @@ const decorateFailExcel = async (blob, businessName, failDetailDecorator) => {
*/ */
export const openImportDialog = (vm, businessName, onSuccess, options = {}) => { export const openImportDialog = (vm, businessName, onSuccess, options = {}) => {
const { failDetailDecorator } = options; const { failDetailDecorator } = options;
const column = vm.findColumn(vm.excelOption.column, 'excelFile'); const excelOption = vm.excelOption || vm.excelOptionConfig;
const column = vm.findColumn(excelOption?.column || [], 'excelFile');
if (!column) {
vm.$message.error('导入配置异常,请稍后重试');
return;
}
column.httpRequest = (option, uploadColumn) => column.httpRequest = (option, uploadColumn) =>
handleImportExcel(vm, option, uploadColumn, businessName, onSuccess, { failDetailDecorator }); handleImportExcel(vm, option, uploadColumn, businessName, onSuccess, { failDetailDecorator });
vm.excelBox = true; vm.excelBox = true;

View File

@@ -111,7 +111,7 @@ const enhancePagination = pagination => {
normalizeComponentPageConfigs(pagination); normalizeComponentPageConfigs(pagination);
// 全站统一:不再注入「首页 / 尾页」按钮; // 全站统一:不再注入「首页 / 尾页」按钮;
// 原生「上一页 / 下一页」由全局样式src/styles/element-ui.scss统一隐藏 // 「上一页 / 下一页」统一展示为原生箭头图标(由全局样式 src/styles/element-ui.scss 控制)。
if (jumpWrapper && !pagination.querySelector(`.${JUMP_BUTTON_CLASS}`)) { if (jumpWrapper && !pagination.querySelector(`.${JUMP_BUTTON_CLASS}`)) {
const jumpButton = createPaginationButton(JUMP_BUTTON_CLASS, '跳转', () => { const jumpButton = createPaginationButton(JUMP_BUTTON_CLASS, '跳转', () => {
const { jumpInput } = getPaginationState(pagination); const { jumpInput } = getPaginationState(pagination);

View File

@@ -41,27 +41,24 @@
<el-tag>{{ row.expireTimeInfo }}</el-tag> <el-tag>{{ row.expireTimeInfo }}</el-tag>
</template> </template>
<template #menu="{ row }"> <template #menu="{ row }">
<el-button <el-link
type="primary" type="primary"
text
v-if="row.status === -1 && userInfo.authority.includes('admin')" v-if="row.status === -1 && userInfo.authority.includes('admin')"
@click.stop="handleEnable(row)" @click.stop="handleEnable(row)"
>启用 >启用
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="row.status === 1 && userInfo.authority.includes('admin')" v-if="row.status === 1 && userInfo.authority.includes('admin')"
@click.stop="handleDisable(row)" @click.stop="handleDisable(row)"
>禁用 >禁用
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
@click.stop="handleRowLog(row)" @click.stop="handleRowLog(row)"
>日志 >日志
</el-button> </el-link>
</template> </template>
<!-- 访问权限动态表单 --> <!-- 访问权限动态表单 -->
<template #apiPath-form="{ type }"> <template #apiPath-form="{ type }">
@@ -84,8 +81,8 @@
</el-row> </el-row>
<el-row v-if="type !== 'view'" :gutter="8"> <el-row v-if="type !== 'view'" :gutter="8">
<el-col :span="8"> <el-col :span="8">
<el-button type="primary" text icon="el-icon-circle-plus" @click="addApiPath" <el-link type="primary" @click="addApiPath"
>添加路径</el-button >添加路径</el-link
> >
</el-col> </el-col>
</el-row> </el-row>
@@ -117,8 +114,8 @@
</el-row> </el-row>
<el-row v-if="type !== 'view'" :gutter="8"> <el-row v-if="type !== 'view'" :gutter="8">
<el-col :span="8"> <el-col :span="8">
<el-button type="primary" text icon="el-icon-circle-plus" @click="addExtParam" <el-link type="primary" @click="addExtParam"
>添加参数</el-button >添加参数</el-link
> >
</el-col> </el-col>
</el-row> </el-row>

View File

@@ -62,15 +62,13 @@
</el-button> </el-button>
</template> </template>
<template #menu="{ row }"> <template #menu="{ row }">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.api_scope_setting" v-if="permission.api_scope_setting"
plain
style="border: 0; background-color: transparent !important" style="border: 0; background-color: transparent !important"
@click.stop="handleDataScope(row)" @click.stop="handleDataScope(row)"
>权限配置 >权限配置
</el-button> </el-link>
</template> </template>
<template #name="{ row }"> <template #name="{ row }">
<i :class="row.source" style="margin-right: 5px" /> <i :class="row.source" style="margin-right: 5px" />

View File

@@ -62,15 +62,13 @@
</el-button> </el-button>
</template> </template>
<template #menu="{ row }"> <template #menu="{ row }">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.data_scope_setting" v-if="permission.data_scope_setting"
plain
style="border: 0; background-color: transparent !important" style="border: 0; background-color: transparent !important"
@click.stop="handleDataScope(row)" @click.stop="handleDataScope(row)"
>权限配置 >权限配置
</el-button> </el-link>
</template> </template>
<template #name="{ row }"> <template #name="{ row }">
<i :class="row.source" style="margin-right: 5px" /> <i :class="row.source" style="margin-right: 5px" />

View File

@@ -52,27 +52,24 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row }"> <template #menu="{ row }">
<el-button <el-link
type="primary" type="primary"
text
v-if="row.status !== 1 && userInfo.authority.includes('administrator')" v-if="row.status !== 1 && userInfo.authority.includes('administrator')"
@click.stop="handleEnable(row)" @click.stop="handleEnable(row)"
>启用 >启用
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="row.status === 1 && userInfo.authority.includes('administrator')" v-if="row.status === 1 && userInfo.authority.includes('administrator')"
@click.stop="handleDisable(row)" @click.stop="handleDisable(row)"
>禁用 >禁用
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="userInfo.authority.includes('administrator')" v-if="userInfo.authority.includes('administrator')"
@click.stop="handleRowLog(row)" @click.stop="handleRowLog(row)"
>日志 >日志
</el-button> </el-link>
</template> </template>
<!-- 匹配路径动态表单 --> <!-- 匹配路径动态表单 -->
<template #limitPattern-form="{ type }"> <template #limitPattern-form="{ type }">
@@ -100,8 +97,7 @@
</el-row> </el-row>
<el-row v-if="type !== 'view'" :gutter="8"> <el-row v-if="type !== 'view'" :gutter="8">
<el-col :span="8"> <el-col :span="8">
<el-button type="primary" text icon="el-icon-circle-plus" @click="addLimitPattern" <el-link type="primary" @click="addLimitPattern">添加路径</el-link
>添加路径</el-button
> >
</el-col> </el-col>
</el-row> </el-row>

View File

@@ -37,15 +37,13 @@
</el-button> </el-button>
</template> </template>
<template #menu="{ row }"> <template #menu="{ row }">
<el-button <el-link
type="primary" type="primary"
text
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
plain
style="border: 0; background-color: transparent !important" style="border: 0; background-color: transparent !important"
@click.stop="handleRowRole(row)" @click.stop="handleRowRole(row)"
>权限设置 >权限设置
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>
<el-dialog <el-dialog
@@ -252,7 +250,7 @@ export default {
viewBtn: true, viewBtn: true,
labelWidth: 'auto', labelWidth: 'auto',
searchLabelWidth: 160, searchLabelWidth: 160,
menuWidth: 160, menuWidth: 320,
dialogWidth: 600, dialogWidth: 600,
dialogClickModal: false, dialogClickModal: false,
column: [ column: [
@@ -468,7 +466,7 @@ export default {
}); });
}, },
rowSave(row, done, loading) { rowSave(row, done, loading) {
add(row).then( add(this.withTenantId(row)).then(
() => { () => {
this.onLoad(this.page); this.onLoad(this.page);
this.$message({ this.$message({
@@ -484,7 +482,7 @@ export default {
); );
}, },
rowUpdate(row, index, done, loading) { rowUpdate(row, index, done, loading) {
update(row).then( update(this.withTenantId(row)).then(
() => { () => {
this.onLoad(this.page); this.onLoad(this.page);
this.$message({ this.$message({
@@ -499,6 +497,12 @@ export default {
} }
); );
}, },
withTenantId(row) {
return {
...row,
tenantId: row.tenantId || this.userInfo.tenantId || website.tenantId,
};
},
rowDel(row) { rowDel(row) {
this.$confirm('确定将选择数据删除?', { this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定', confirmButtonText: '确定',

View File

@@ -60,22 +60,20 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('airport_master_edit')" v-if="hasPermission('airport_master_edit')"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('airport_master_status')" v-if="hasPermission('airport_master_status')"
@click="handleStatus(row)" @click="handleStatus(row)"
> >
{{ row.status === 1 ? '停用' : '启用' }} {{ row.status === 1 ? '停用' : '启用' }}
</el-button> </el-link>
</template> </template>
<template #districtCode-form> <template #districtCode-form>
<el-select <el-select
@@ -368,15 +366,6 @@ export default {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 160,
overHidden: false,
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{ {
label: '详细地址', label: '详细地址',
prop: 'detailAddress', prop: 'detailAddress',
@@ -411,6 +400,15 @@ export default {
{ validator: validateLatitude, trigger: 'blur' }, { validator: validateLatitude, trigger: 'blur' },
], ],
}, },
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 160,
overHidden: false,
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{ {
label: '数据来源', label: '数据来源',
prop: 'dataSource', prop: 'dataSource',
@@ -427,23 +425,6 @@ export default {
{ label: '手动录入', value: '手动录入' }, { label: '手动录入', value: '手动录入' },
], ],
}, },
{
label: '启停状态',
prop: 'status',
type: 'select',
search: true,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
{ {
label: '备注', label: '备注',
prop: 'remark', prop: 'remark',
@@ -485,6 +466,23 @@ export default {
display: false, display: false,
minWidth: 160, minWidth: 160,
}, },
{
label: '启停状态',
prop: 'status',
type: 'select',
search: true,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
], ],
}, },
excelOption: { excelOption: {
@@ -544,8 +542,10 @@ export default {
hasPermission(code) { hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false); return this.isAdmin || this.validData(this.permission && this.permission[code], false);
}, },
findColumn(prop) { findColumn(columns, prop) {
return this.option.column.find(item => item.prop === prop); const targetColumns = Array.isArray(columns) ? columns : this.option.column;
const targetProp = Array.isArray(columns) ? prop : columns;
return targetColumns.find(item => item.prop === targetProp) || null;
}, },
loadProvinceOptions() { loadProvinceOptions() {
getLazyTree(DEFAULT_COUNTRY_CODE).then(res => { getLazyTree(DEFAULT_COUNTRY_CODE).then(res => {
@@ -869,17 +869,12 @@ export default {
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
NProgress.start(); NProgress.start();
exportBlob( exportBlob('/blade-system/airport-master/export-airport-master', this.buildExportParams(), {
'/blade-system/airport-master/export-airport-master', feedback: true,
this.buildExportParams(), })
{ feedback: true }
)
.then(async res => { .then(async res => {
const blob = await addExcelRequiredHeaderMarks(res.data); const blob = await addExcelRequiredHeaderMarks(res.data);
downloadXls( downloadXls(blob, `空港机场主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
blob,
`空港机场主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
);
}) })
.finally(() => { .finally(() => {
NProgress.done(); NProgress.done();

View File

@@ -5,7 +5,7 @@
:table-loading="loading" :table-loading="loading"
:data="data" :data="data"
v-model:page="page" v-model:page="page"
v-model:form="form" v-model="form"
ref="crud" ref="crud"
:permission="permissionList" :permission="permissionList"
:before-open="beforeOpen" :before-open="beforeOpen"
@@ -22,7 +22,7 @@
> >
<template #menu-left> <template #menu-left>
<el-button type="primary" v-if="permission.cargo_type_add" @click="$refs.crud.rowAdd()"> <el-button type="primary" v-if="permission.cargo_type_add" @click="$refs.crud.rowAdd()">
<el-icon class="el-icon--left"><Plus /></el-icon> <el-icon class="el-icon--left"><Plus /></el-icon>建货物类型
</el-button> </el-button>
<el-button type="primary" plain v-if="permission.cargo_type_import" @click="handleImport" <el-button type="primary" plain v-if="permission.cargo_type_import" @click="handleImport"
><el-icon class="el-icon--left"><Upload /></el-icon>批量导入</el-button ><el-icon class="el-icon--left"><Upload /></el-icon>批量导入</el-button
@@ -67,7 +67,7 @@
:remote-method="loadParentOptions" :remote-method="loadParentOptions"
:loading="parentLoading" :loading="parentLoading"
:disabled="dialogType === 'view' || form.typeLevel !== 2" :disabled="dialogType === 'view' || form.typeLevel !== 2"
placeholder="请输入名称或编码搜索" placeholder="请输入"
style="width: 100%" style="width: 100%"
@change="handleParentChange" @change="handleParentChange"
> >
@@ -81,25 +81,23 @@
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.cargo_type_view" v-if="permission.cargo_type_view"
@click="$refs.crud.rowView(row, index)" @click="$refs.crud.rowView(row, index)"
> >
查看 查看
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.cargo_type_edit" v-if="permission.cargo_type_edit"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button type="primary" text v-if="permission.cargo_type_delete" @click="rowDel(row)"> <el-link type="primary" v-if="permission.cargo_type_delete" @click="rowDel(row)">
删除 删除
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>
@@ -162,7 +160,7 @@ const CARGO_TYPE_FAIL_COL_WIDTH = {
/** /**
* 加工后端返回的导入失败明细 Excel * 加工后端返回的导入失败明细 Excel
* - 「导入失败原因」列按 A. B. C. 编号列出,多条以分号隔开 * - 「导入失败原因」列按 1. 2. 3. 编号列出,多条以分号隔开
* - 每个错误后追加对应规则说明,仅失败原因列标红 * - 每个错误后追加对应规则说明,仅失败原因列标红
* - 适当加宽各列、原因列自动换行,避免内容拥挤 * - 适当加宽各列、原因列自动换行,避免内容拥挤
*/ */
@@ -199,7 +197,7 @@ export const decorateCargoTypeFailDetail = async workbook => {
.filter(Boolean); .filter(Boolean);
if (parts.length === 0) return; if (parts.length === 0) return;
const decorated = parts.map((part, index) => { const decorated = parts.map((part, index) => {
const label = `${String.fromCharCode(65 + index)}.`; // A. B. C. const label = `${index + 1}.`; // 1. 2. 3.
const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match)); const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match));
if (rule) { if (rule) {
// 规则说明若已包含在错误文本中则不再重复拼接 // 规则说明若已包含在错误文本中则不再重复拼接
@@ -214,10 +212,10 @@ export const decorateCargoTypeFailDetail = async workbook => {
row.getCell(remarkCol).value == null ? '' : row.getCell(remarkCol).value row.getCell(remarkCol).value == null ? '' : row.getCell(remarkCol).value
).trim(); ).trim();
if (remarkVal.length > 200 && !decorated.some(d => d.includes('备注不能超过200字'))) { if (remarkVal.length > 200 && !decorated.some(d => d.includes('备注不能超过200字'))) {
decorated.push(`${String.fromCharCode(65 + decorated.length)}.备注不能超过200字`); decorated.push(`${decorated.length + 1}.备注不能超过200字`);
} }
} }
reasonCell.value = `${rowNumber}行:${decorated.join('')}`; reasonCell.value = decorated.join('');
// 仅操作 G 列:黑色字体、无背景填充,其余列保持后端原样 // 仅操作 G 列:黑色字体、无背景填充,其余列保持后端原样
reasonCell.fill = { type: 'pattern', pattern: 'none' }; reasonCell.fill = { type: 'pattern', pattern: 'none' };
reasonCell.font = { color: { argb: 'FF000000' } }; reasonCell.font = { color: { argb: 'FF000000' } };
@@ -350,6 +348,11 @@ export default {
const parent = this.parentOptions.find(item => String(item.id) === String(value)); const parent = this.parentOptions.find(item => String(item.id) === String(value));
this.form.parentCargoName = parent ? parent.cargoName : ''; this.form.parentCargoName = parent ? parent.cargoName : '';
this.form.parentCargoCode = parent ? parent.cargoCode || parent.code || '' : ''; this.form.parentCargoCode = parent ? parent.cargoCode || parent.code || '' : '';
if (parent) {
this.$nextTick(() => {
this.$refs.crud?.clearValidate?.(['parentId', 'parentCargoCode']);
});
}
if (!parent) { if (!parent) {
this.form.cargoCode = ''; this.form.cargoCode = '';
return; return;
@@ -542,7 +545,10 @@ export default {
}); });
}, },
buildExportParams() { buildExportParams() {
return { ...this.query }; return {
...this.query,
...(this.ids ? { ids: this.ids } : {}),
};
}, },
handleExport() { handleExport() {
this.$confirm('是否导出货物类型数据?', '提示', { this.$confirm('是否导出货物类型数据?', '提示', {
@@ -566,16 +572,9 @@ export default {
}, },
handleTemplate() { handleTemplate() {
NProgress.start(); NProgress.start();
// 直接下载工程内置的标准导入模板public 目录静态资源),表头与可正常导入的模板一致, exportBlob('/blade-system/cargo-type/export-template', {}, { feedback: true })
// 含一级19 类)及全部二级示例,便于用户参照填写层级关系与编码规则
const templateUrl = `${import.meta.env.BASE_URL}cargo-type-import-template.xlsx`;
fetch(templateUrl)
.then(res => { .then(res => {
if (!res.ok) throw new Error('模板文件不存在'); downloadXls(res.data, '货物类型导入模板.xlsx');
return res.blob();
})
.then(blob => {
downloadXls(blob, '货物类型导入模板.xlsx');
}) })
.catch(() => { .catch(() => {
this.$message.error('模板下载失败,请稍后重试'); this.$message.error('模板下载失败,请稍后重试');

View File

@@ -24,10 +24,9 @@
<el-button <el-button
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
plain
v-if="hasPermission('common_address_add')" v-if="hasPermission('common_address_add')"
@click="$refs.crud.rowAdd()" @click="$refs.crud.rowAdd()"
> >建常用地址
</el-button> </el-button>
<el-button <el-button
type="primary" type="primary"
@@ -54,30 +53,27 @@
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_address_view')" v-if="hasPermission('common_address_view')"
@click="$refs.crud.rowView(row, index)" @click="$refs.crud.rowView(row, index)"
> >
查看 查看
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_address_edit') && !row.readonly" v-if="hasPermission('common_address_edit') && !row.readonly"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_address_delete') && !row.readonly" v-if="hasPermission('common_address_delete') && !row.readonly"
@click="rowDel(row)" @click="rowDel(row)"
> >
删除 删除
</el-button> </el-link>
</template> </template>
<template #addressType-form> <template #addressType-form>
@@ -191,7 +187,6 @@
/> />
<el-input <el-input
v-model="form.detailAddress" v-model="form.detailAddress"
readonly
maxlength="255" maxlength="255"
placeholder="点击后弹出地图组件" placeholder="点击后弹出地图组件"
suffix-icon="el-icon-location" suffix-icon="el-icon-location"
@@ -392,10 +387,96 @@ export default {
column.rules = rules; column.rules = rules;
} }
}, },
setColumnSpan(prop, span) {
const column = this.findColumn(this.option.column, prop);
if (column) {
column.span = span;
}
},
setColumnRow(prop, row) {
const column = this.findColumn(this.option.column, prop);
if (column) {
column.row = row;
}
},
setColumnOrder(prop, order) {
const column = this.findColumn(this.option.column, prop);
if (column) {
column.order = order;
}
},
syncSourceFormLayout(addressType) {
const isPortTerminal = addressType === '港口/码头';
const isRailwayStation = addressType === '铁路车站';
const isAirport = addressType === '空港机场';
const isRegularAddress = addressType === '常规地址';
this.setColumnSpan(
'addressName',
isRegularAddress || isPortTerminal || isRailwayStation || isAirport ? 12 : 24
);
this.setColumnRow('addressName', true);
this.setColumnSpan('stationId', isRailwayStation ? 12 : 24);
this.setColumnSpan('airportId', isAirport ? 12 : 24);
this.setColumnRow('stationId', isRailwayStation);
this.setColumnRow('airportId', isAirport);
const formOrders = {
addressType: 100,
addressName: 70,
contactName: 60,
contactPhone: 50,
detailAddress: 40,
remark: 30,
};
if (isPortTerminal) {
Object.assign(formOrders, {
addressType: 80,
portId: 70,
terminalId: 60,
addressName: 50,
contactName: 40,
contactPhone: 30,
detailAddress: 20,
remark: 10,
});
} else if (isRailwayStation) {
Object.assign(formOrders, {
addressType: 80,
stationId: 70,
addressName: 60,
contactName: 50,
contactPhone: 40,
detailAddress: 30,
remark: 20,
});
} else if (isAirport) {
Object.assign(formOrders, {
addressType: 80,
airportId: 70,
addressName: 60,
contactName: 50,
contactPhone: 40,
detailAddress: 30,
remark: 20,
});
}
[
'addressType',
'portId',
'terminalId',
'stationId',
'airportId',
'addressName',
'contactName',
'contactPhone',
'detailAddress',
'remark',
].forEach(prop => this.setColumnOrder(prop, formOrders[prop]));
},
syncSourceColumnDisplay(addressType) { syncSourceColumnDisplay(addressType) {
const isPortTerminal = addressType === '港口/码头'; const isPortTerminal = addressType === '港口/码头';
const isRailwayStation = addressType === '铁路车站'; const isRailwayStation = addressType === '铁路车站';
const isAirport = addressType === '空港机场'; const isAirport = addressType === '空港机场';
this.syncSourceFormLayout(addressType);
this.setColumnDisplay('portId', isPortTerminal); this.setColumnDisplay('portId', isPortTerminal);
this.setColumnDisplay('terminalId', isPortTerminal); this.setColumnDisplay('terminalId', isPortTerminal);
this.setColumnDisplay('stationId', isRailwayStation); this.setColumnDisplay('stationId', isRailwayStation);
@@ -462,7 +543,9 @@ export default {
status: 1, status: 1,
}) })
.then(res => { .then(res => {
this.terminalOptions = this.normalizeSourceOptions(this.getRecords(res)); this.terminalOptions = this.normalizeSourceOptions(this.getRecords(res)).filter(item =>
this.sameId(item.parentId, this.form.portId)
);
}) })
.finally(() => { .finally(() => {
this.terminalLoading = false; this.terminalLoading = false;
@@ -1184,6 +1267,17 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.common-address-page :deep(.avue-crud__dialog .avue-form__row),
.common-address-page :deep(.avue-crud__dialog .el-row) {
margin-right: -16px;
margin-left: -16px;
}
.common-address-page :deep(.avue-crud__dialog .el-col) {
padding-right: 16px;
padding-left: 16px;
}
.common-address-page { .common-address-page {
&__map-address { &__map-address {
display: flex; display: flex;

View File

@@ -68,22 +68,20 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.currency_edit" v-if="permission.currency_edit"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.currency_status" v-if="permission.currency_status"
@click="handleStatus(row)" @click="handleStatus(row)"
> >
{{ row.status === 1 ? '停用' : '启用' }} {{ row.status === 1 ? '停用' : '启用' }}
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>

View File

@@ -36,22 +36,20 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.customer_type_edit" v-if="permission.customer_type_edit"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.customer_type_status" v-if="permission.customer_type_status"
@click="handleStatus(row)" @click="handleStatus(row)"
> >
{{ row.status === 1 ? '停用' : '启用' }} {{ row.status === 1 ? '停用' : '启用' }}
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>
<empty-pagination <empty-pagination

View File

@@ -65,17 +65,16 @@
</el-input> </el-input>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.fee_item_edit" v-if="permission.fee_item_edit"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button type="primary" text v-if="permission.fee_item_status" @click="handleStatus(row)"> <el-link type="primary" v-if="permission.fee_item_status" @click="handleStatus(row)">
{{ row.status === 1 ? '停用' : '启用' }} {{ row.status === 1 ? '停用' : '启用' }}
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>

View File

@@ -113,22 +113,20 @@
</div> </div>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('port_terminal_edit')" v-if="hasPermission('port_terminal_edit')"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('port_terminal_status')" v-if="hasPermission('port_terminal_status')"
@click="handleStatus(row)" @click="handleStatus(row)"
> >
{{ row.status === 1 ? '停用' : '启用' }} {{ row.status === 1 ? '停用' : '启用' }}
</el-button> </el-link>
</template> </template>
<template #cityCode-form> <template #cityCode-form>
<el-select <el-select
@@ -231,7 +229,15 @@ import 'nprogress/nprogress.css';
const DEFAULT_COUNTRY_CODE = '+86'; const DEFAULT_COUNTRY_CODE = '+86';
const EXCLUDED_EXCEL_HEADERS = ['数据来源', '启停状态']; const EXCLUDED_EXCEL_HEADERS = ['数据来源', '启停状态'];
const REQUIRED_EXPORT_HEADERS = ['港口码头名称', '港口/码头名称', '类型', '国家', '城市', '经度', '纬度']; const REQUIRED_EXPORT_HEADERS = [
'港口码头名称',
'港口/码头名称',
'类型',
'国家',
'城市',
'经度',
'纬度',
];
const getExcelCellText = value => { const getExcelCellText = value => {
if (value === undefined || value === null) return ''; if (value === undefined || value === null) return '';
@@ -258,9 +264,7 @@ const removeWorkbookColumnsByHeaders = workbook => {
} }
}); });
if (indexes.length) { if (indexes.length) {
indexes indexes.sort((a, b) => b - a).forEach(colNumber => worksheet.spliceColumns(colNumber, 1));
.sort((a, b) => b - a)
.forEach(colNumber => worksheet.spliceColumns(colNumber, 1));
break; break;
} }
} }
@@ -385,13 +389,42 @@ export default {
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 160,
column: [ column: [
{
label: '编码',
prop: 'code',
search: true,
formslot: true,
width: 140,
span: 12,
order: 70,
placeholder: '请输入编码',
rules: [
{ required: true, message: '请输入编码', trigger: 'blur' },
{ validator: validateCode, trigger: 'blur' },
],
change: ({ value }) => this.handleCodeChange(value),
blur: ({ value }) => this.handleCodeChange(value),
},
{
label: '港口/码头名称',
prop: 'name',
minWidth: 140,
search: true,
searchOrder: 7,
span: 12,
order: 80,
placeholder: '请输入',
rules: [{ required: true, message: '请输入港口/码头名称', trigger: 'blur' }],
},
{ {
label: '类型', label: '类型',
prop: 'category', prop: 'category',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 6,
formslot: true, formslot: true,
span: 24, span: 24,
order: 100,
dicData: [ dicData: [
{ label: '全部', value: '' }, { label: '全部', value: '' },
{ label: '港口', value: '港口' }, { label: '港口', value: '港口' },
@@ -411,6 +444,7 @@ export default {
dicData: [], dicData: [],
hide: true, hide: true,
span: 12, span: 12,
order: 90,
placeholder: '请选择', placeholder: '请选择',
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }], rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
change: ({ value }) => this.handleParentChange(value), change: ({ value }) => this.handleParentChange(value),
@@ -418,7 +452,7 @@ export default {
{ {
label: '上级港口', label: '上级港口',
prop: 'parentName', prop: 'parentName',
minWidth: 120, minWidth: 140,
slot: true, slot: true,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
@@ -434,39 +468,17 @@ export default {
placeholder: '自动带出', placeholder: '自动带出',
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }], rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
}, },
{
label: '港口/码头名称',
prop: 'name',
minWidth: 140,
search: true,
span: 12,
placeholder: '请输入',
rules: [{ required: true, message: '请输入港口/码头名称', trigger: 'blur' }],
},
{
label: '编码',
prop: 'code',
search: true,
formslot: true,
width: 140,
span: 12,
placeholder: '请输入编码',
rules: [
{ required: true, message: '请输入编码', trigger: 'blur' },
{ validator: validateCode, trigger: 'blur' },
],
change: ({ value }) => this.handleCodeChange(value),
blur: ({ value }) => this.handleCodeChange(value),
},
{ {
label: '国家', label: '国家',
prop: 'country', prop: 'country',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 5,
formslot: true, formslot: true,
filterable: true, filterable: true,
dicData: [], dicData: [],
span: 12, span: 12,
order: 60,
minWidth: 140, minWidth: 140,
overHidden: false, overHidden: false,
placeholder: '自动带出', placeholder: '自动带出',
@@ -480,6 +492,7 @@ export default {
formslot: true, formslot: true,
hide: true, hide: true,
span: 12, span: 12,
order: 50,
placeholder: '自动带出', placeholder: '自动带出',
rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }], rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }],
}, },
@@ -488,6 +501,7 @@ export default {
prop: 'city', prop: 'city',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 4,
filterable: true, filterable: true,
dicData: [], dicData: [],
addDisplay: false, addDisplay: false,
@@ -515,14 +529,44 @@ export default {
prop: 'districtName', prop: 'districtName',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 3,
filterable: true, filterable: true,
dicData: [], dicData: [],
minWidth: 100, minWidth: 100,
hide: true,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{ {
label: '行政区划编号', label: '经度',
prop: 'longitude',
formslot: true,
minWidth: 130,
span: 12,
order: 20,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入经度', trigger: 'blur' },
{ validator: validateLongitude, trigger: 'blur' },
],
},
{
label: '纬度',
prop: 'latitude',
formslot: true,
minWidth: 130,
span: 12,
order: 10,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入纬度', trigger: 'blur' },
{ validator: validateLatitude, trigger: 'blur' },
],
},
{
label: '行政区划编码',
prop: 'regionCode', prop: 'regionCode',
minWidth: 140, minWidth: 140,
span: 12, span: 12,
@@ -540,42 +584,18 @@ export default {
span: 24, span: 24,
minWidth: 220, minWidth: 220,
overHidden: false, overHidden: false,
order: 30,
placeholder: '请输入', placeholder: '请输入',
maxlength: 255, maxlength: 255,
showWordLimit: true, showWordLimit: true,
rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }], rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }],
}, },
{
label: '经度',
prop: 'longitude',
formslot: true,
minWidth: 130,
span: 12,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入经度', trigger: 'blur' },
{ validator: validateLongitude, trigger: 'blur' },
],
},
{
label: '纬度',
prop: 'latitude',
formslot: true,
minWidth: 130,
span: 12,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入纬度', trigger: 'blur' },
{ validator: validateLatitude, trigger: 'blur' },
],
},
{ {
label: '数据来源', label: '数据来源',
prop: 'dataSource', prop: 'dataSource',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 2,
minWidth: 120, minWidth: 120,
overHidden: false, overHidden: false,
addDisplay: false, addDisplay: false,
@@ -587,23 +607,6 @@ export default {
{ label: '手工导入', value: '手工导入' }, { label: '手工导入', value: '手工导入' },
], ],
}, },
{
label: '启停状态',
prop: 'status',
type: 'select',
search: true,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
{ {
label: '备注', label: '备注',
prop: 'remark', prop: 'remark',
@@ -641,9 +644,26 @@ export default {
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD HH:mm:ss',
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
display: false,
minWidth: 160, minWidth: 160,
}, },
{
label: '状态',
prop: 'status',
type: 'select',
search: true,
searchOrder: 1,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
], ],
}, },
excelOption: { excelOption: {
@@ -1272,19 +1292,14 @@ export default {
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
NProgress.start(); NProgress.start();
exportBlob( exportBlob('/blade-system/port-terminal/export-port-terminal', this.buildExportParams(), {
'/blade-system/port-terminal/export-port-terminal', feedback: true,
this.buildExportParams(), })
{ feedback: true }
)
.then(async res => { .then(async res => {
const blob = await removeExcelColumnsByHeaders(res.data, { const blob = await removeExcelColumnsByHeaders(res.data, {
markRequiredHeaders: true, markRequiredHeaders: true,
}); });
downloadXls( downloadXls(blob, `港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
blob,
`港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
);
}) })
.finally(() => { .finally(() => {
NProgress.done(); NProgress.done();

View File

@@ -60,22 +60,20 @@
</el-tag> </el-tag>
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('railway_station_edit')" v-if="hasPermission('railway_station_edit')"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('railway_station_status')" v-if="hasPermission('railway_station_status')"
@click="handleStatus(row)" @click="handleStatus(row)"
> >
{{ row.status === 1 ? '停用' : '启用' }} {{ row.status === 1 ? '停用' : '启用' }}
</el-button> </el-link>
</template> </template>
<template #districtCode-form> <template #districtCode-form>
<el-select <el-select
@@ -337,15 +335,6 @@ export default {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 160,
overHidden: false,
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{ {
label: '详细地址', label: '详细地址',
prop: 'detailAddress', prop: 'detailAddress',
@@ -375,20 +364,13 @@ export default {
rules: [{ validator: validateLatitude, trigger: 'blur' }], rules: [{ validator: validateLatitude, trigger: 'blur' }],
}, },
{ {
label: '数据来源', label: '行政区划编号',
prop: 'dataSource', prop: 'regionCode',
type: 'select', minWidth: 160,
search: true,
minWidth: 120,
overHidden: false, overHidden: false,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
dicData: [ rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
{ label: '全部', value: '' },
{ label: '初始化导入', value: '初始化导入' },
{ label: '批量', value: '批量' },
{ label: '手动', value: '手动' },
],
}, },
{ {
label: '启停状态', label: '启停状态',
@@ -448,6 +430,22 @@ export default {
display: false, display: false,
minWidth: 160, minWidth: 160,
}, },
{
label: '数据来源',
prop: 'dataSource',
type: 'select',
search: true,
minWidth: 120,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '初始化导入', value: '初始化导入' },
{ label: '批量', value: '批量' },
{ label: '手动', value: '手动' },
],
},
], ],
}, },
excelOption: { excelOption: {
@@ -507,8 +505,10 @@ export default {
hasPermission(code) { hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false); return this.isAdmin || this.validData(this.permission && this.permission[code], false);
}, },
findColumn(prop) { findColumn(columns, prop) {
return this.option.column.find(item => item.prop === prop); const targetColumns = Array.isArray(columns) ? columns : this.option.column;
const targetProp = Array.isArray(columns) ? prop : columns;
return targetColumns.find(item => item.prop === targetProp) || null;
}, },
loadProvinceOptions() { loadProvinceOptions() {
getLazyTree(DEFAULT_COUNTRY_CODE).then(res => { getLazyTree(DEFAULT_COUNTRY_CODE).then(res => {

View File

@@ -11,6 +11,37 @@
</el-col> </el-col>
<el-col class="region-page__form-panel" :span="18"> <el-col class="region-page__form-panel" :span="18">
<basic-container> <basic-container>
<el-form class="region-page__search" :model="searchForm" inline>
<el-form-item label="区域编码">
<el-input v-model="searchForm.code" clearable placeholder="请输入区域编码" />
</el-form-item>
<el-form-item label="区域名称">
<el-input v-model="searchForm.name" clearable placeholder="请输入区域名称" />
</el-form-item>
<el-form-item label="区域层级">
<el-select v-model="searchForm.regionLevel" clearable placeholder="请选择区域层级">
<el-option label="省份" :value="1" />
<el-option label="省市" :value="2" />
<el-option label="区县" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="数据来源">
<el-select v-model="searchForm.dataSource" clearable placeholder="请选择数据来源">
<el-option label="手动录入" value="手动录入" />
<el-option label="初始化导入" value="初始化导入" />
</el-select>
</el-form-item>
<el-form-item label="启停状态">
<el-select v-model="searchForm.status" clearable placeholder="请选择启停状态">
<el-option label="启用" :value="1" />
<el-option label="停用" :value="2" />
</el-select>
</el-form-item>
<el-form-item class="region-page__search-actions">
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleSearchReset">清空</el-button>
</el-form-item>
</el-form>
<el-button-group> <el-button-group>
<el-button <el-button
v-if="permission.region_add" v-if="permission.region_add"
@@ -136,14 +167,21 @@ export default {
treeCode: '', treeCode: '',
treeParentCode: '', treeParentCode: '',
treeData: [], treeData: [],
searchForm: {
code: '',
name: '',
regionLevel: undefined,
dataSource: '',
status: undefined,
},
parentOptions: [], parentOptions: [],
syncLoading: false, syncLoading: false,
treeOption: { treeOption: {
nodeKey: 'id', nodeKey: 'id',
lazy: true, lazy: true,
treeLoad: function (node, resolve) { treeLoad: (node, resolve) => {
const parentCode = node.level === 0 ? ROOT_PARENT_CODE : node.data.id; const parentCode = node.level === 0 ? ROOT_PARENT_CODE : node.data.id;
getLazyTree(parentCode).then(res => { getLazyTree(parentCode, this.searchForm).then(res => {
resolve( resolve(
res.data.data.map(item => { res.data.data.map(item => {
return { return {
@@ -402,7 +440,7 @@ export default {
methods: { methods: {
initTree() { initTree() {
this.treeData = []; this.treeData = [];
getLazyTree(this.topCode).then(res => { getLazyTree(this.topCode, this.searchForm).then(res => {
this.treeData = res.data.data.map(item => { this.treeData = res.data.data.map(item => {
return { return {
...item, ...item,
@@ -411,6 +449,20 @@ export default {
}); });
}); });
}, },
handleSearch() {
this.initTree();
this.regionForm = {};
},
handleSearchReset() {
this.searchForm = {
code: '',
name: '',
regionLevel: undefined,
dataSource: '',
status: undefined,
};
this.handleSearch();
},
nodeClick(data) { nodeClick(data) {
this.setRegionColumnDisplay('code', true); this.setRegionColumnDisplay('code', true);
this.treeCode = data.id; this.treeCode = data.id;
@@ -624,20 +676,18 @@ export default {
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
NProgress.start(); NProgress.start();
exportBlob( exportBlob(`/blade-system/region/export-region?${this.website.tokenHeader}=${getToken()}`, {
`/blade-system/region/export-region?${this.website.tokenHeader}=${getToken()}`, feedback: true,
{ feedback: true } }).then(res => {
).then(res => {
downloadXls(res.data, `行政区划数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); downloadXls(res.data, `行政区划数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
NProgress.done(); NProgress.done();
}); });
}); });
}, },
handleTemplate() { handleTemplate() {
exportBlob( exportBlob(`/blade-system/region/export-template?${this.website.tokenHeader}=${getToken()}`, {
`/blade-system/region/export-template?${this.website.tokenHeader}=${getToken()}`, feedback: true,
{ feedback: true } }).then(res => {
).then(res => {
downloadXls(res.data, '行政区划模板.xlsx'); downloadXls(res.data, '行政区划模板.xlsx');
}); });
}, },

View File

@@ -24,10 +24,9 @@
<el-button <el-button
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
plain
v-if="hasPermission('common_cargo_add')" v-if="hasPermission('common_cargo_add')"
@click="$refs.crud.rowAdd()" @click="$refs.crud.rowAdd()"
> >建常用货物
</el-button> </el-button>
<el-button <el-button
type="primary" type="primary"
@@ -113,7 +112,7 @@
clearable clearable
maxlength="20" maxlength="20"
show-word-limit show-word-limit
placeholder="请输入货物编号" placeholder="请输入"
:disabled="dialogReadonly || dialogType === 'edit'" :disabled="dialogReadonly || dialogType === 'edit'"
@input="handleCargoCodeInput" @input="handleCargoCodeInput"
/> />
@@ -144,31 +143,32 @@
</div> </div>
</template> </template>
<template #packageBrand="{ row }">
{{ [row.packageType, row.brand].filter(Boolean).join(' / ') || '-' }}
</template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_cargo_view')" v-if="hasPermission('common_cargo_view')"
@click="$refs.crud.rowView(row, index)" @click="$refs.crud.rowView(row, index)"
> >
查看 查看
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_cargo_edit') && !row.readonly" v-if="hasPermission('common_cargo_edit') && !row.readonly"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_cargo_delete') && !row.readonly" v-if="hasPermission('common_cargo_delete') && !row.readonly"
@click="rowDel(row)" @click="rowDel(row)"
> >
删除 删除
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>
@@ -195,6 +195,7 @@
import * as api from '@/api/business/common-cargo'; import * as api from '@/api/business/common-cargo';
import { getList as getCargoTypeList } from '@/api/base/cargo-type'; import { getList as getCargoTypeList } from '@/api/base/cargo-type';
import { exportBlob } from '@/api/common'; import { exportBlob } from '@/api/common';
import { getDeptTree } from '@/api/system/dept';
import { config, excelOption, option } from '@/option/business/common-cargo'; import { config, excelOption, option } from '@/option/business/common-cargo';
import { priceUnitOptions } from '@/option/business/common'; import { priceUnitOptions } from '@/option/business/common';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
@@ -224,6 +225,7 @@ export default {
secondCargoTypeLoading: false, secondCargoTypeLoading: false,
firstCargoTypeOptions: [], firstCargoTypeOptions: [],
secondCargoTypeOptions: [], secondCargoTypeOptions: [],
deptOptions: [],
page: { page: {
pageSize: 10, pageSize: 10,
pageSizes: [10, 20, 50, 100], pageSizes: [10, 20, 50, 100],
@@ -250,6 +252,9 @@ export default {
return this.selectionList.map(item => item.id).join(','); return this.selectionList.map(item => item.id).join(',');
}, },
}, },
created() {
this.initDeptTree();
},
methods: { methods: {
hasPermission(code) { hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false); return this.isAdmin || this.validData(this.permission && this.permission[code], false);
@@ -259,6 +264,28 @@ export default {
if (Array.isArray(data)) return data; if (Array.isArray(data)) return data;
return (data && data.records) || []; return (data && data.records) || [];
}, },
initDeptTree() {
getDeptTree(this.userInfo.tenantId).then(res => {
this.deptOptions = this.flattenDept(res.data.data || []);
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
if (deptColumn) {
deptColumn.dicData = this.deptOptions;
}
});
},
flattenDept(tree, level = 0) {
const result = [];
tree.forEach(item => {
result.push({
label: `${' '.repeat(level)}${item.title || item.deptName || item.name}`,
value: item.id,
});
if (item.children && item.children.length) {
result.push(...this.flattenDept(item.children, level + 1));
}
});
return result;
},
normalizeCargoTypeOptions(list = []) { normalizeCargoTypeOptions(list = []) {
return list return list
.map(item => ({ .map(item => ({
@@ -322,11 +349,12 @@ export default {
const second = this.secondCargoTypeOptions.find(item => item.cargoCode === value); const second = this.secondCargoTypeOptions.find(item => item.cargoCode === value);
this.form.secondCargoTypeName = second ? second.cargoName : ''; this.form.secondCargoTypeName = second ? second.cargoName : '';
this.form.secondCargoTypeCode = second ? second.cargoCode : ''; this.form.secondCargoTypeCode = second ? second.cargoCode : '';
this.form.cargoCode = second ? String(second.cargoCode).slice(0, 4) : '';
this.syncCargoCodePrefix(); this.syncCargoCodePrefix();
}, },
getCargoCodePrefix() { getCargoCodePrefix() {
return this.form.secondCargoTypeCode && this.form.firstCargoTypeCode return this.form.secondCargoTypeCode
? this.form.firstCargoTypeCode ? String(this.form.secondCargoTypeCode).slice(0, 4)
: ''; : '';
}, },
syncCargoCodePrefix() { syncCargoCodePrefix() {
@@ -386,13 +414,13 @@ export default {
return false; return false;
} }
const cargoCode = String(row.cargoCode || ''); const cargoCode = String(row.cargoCode || '');
const prefix = row.firstCargoTypeCode; const prefix = String(row.secondCargoTypeCode || '').slice(0, 4);
if (!cargoCode || cargoCode === prefix) { if (!cargoCode || cargoCode === prefix) {
this.$message.warning('请输入货物编号'); this.$message.warning('请输入货物编号');
return false; return false;
} }
if (!cargoCode.startsWith(prefix)) { if (!cargoCode.startsWith(prefix)) {
this.$message.warning('货物编号需拼接级货物类型编号在前面'); this.$message.warning('货物编号需拼接级货物类型编号在前面');
return false; return false;
} }
if (cargoCode.length > 20) { if (cargoCode.length > 20) {

View File

@@ -24,10 +24,25 @@
<el-button <el-button
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
plain
v-if="hasPermission('common_route_add')" v-if="hasPermission('common_route_add')"
@click="$refs.crud.rowAdd()" @click="$refs.crud.rowAdd()"
> >建路线
</el-button>
<el-button
type="primary"
icon="el-icon-upload"
plain
v-if="config.importUrl && hasPermission('common_route_import')"
@click="handleImport"
>批量导入
</el-button>
<el-button
type="primary"
icon="el-icon-download"
plain
v-if="config.templateUrl && hasPermission('common_route_template')"
@click="handleTemplate"
>下载模板
</el-button> </el-button>
<el-button <el-button
type="primary" type="primary"
@@ -57,10 +72,10 @@
/> />
<el-input <el-input
v-model="form.departureAddress" v-model="form.departureAddress"
readonly
placeholder="请输入并选择地址" placeholder="请输入并选择地址"
suffix-icon="el-icon-location" suffix-icon="el-icon-location"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@click="openRouteMapDialog('departure')"
/> />
<el-tooltip content="选择常用地址" placement="top"> <el-tooltip content="选择常用地址" placement="top">
<el-button <el-button
@@ -84,10 +99,10 @@
/> />
<el-input <el-input
v-model="form.arrivalAddress" v-model="form.arrivalAddress"
readonly
placeholder="请输入并选择地址" placeholder="请输入并选择地址"
suffix-icon="el-icon-location" suffix-icon="el-icon-location"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@click="openRouteMapDialog('arrival')"
/> />
<el-tooltip content="选择常用地址" placement="top"> <el-tooltip content="选择常用地址" placement="top">
<el-button <el-button
@@ -102,30 +117,27 @@
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_route_view')" v-if="hasPermission('common_route_view')"
@click="$refs.crud.rowView(row, index)" @click="$refs.crud.rowView(row, index)"
> >
查看 查看
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_route_edit') && !row.readonly" v-if="hasPermission('common_route_edit') && !row.readonly"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('common_route_delete') && !row.readonly" v-if="hasPermission('common_route_delete') && !row.readonly"
@click="rowDel(row)" @click="rowDel(row)"
> >
删除 删除
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>
@@ -136,6 +148,16 @@
@load="onLoad(page, query)" @load="onLoad(page, query)"
/> />
<el-dialog :title="`${config.title}导入`" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOptionConfig" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
</el-button>
</template>
</avue-form>
</el-dialog>
<el-dialog <el-dialog
title="选择常用地址" title="选择常用地址"
append-to-body append-to-body
@@ -144,102 +166,147 @@
class="common-route-page__address-dialog" class="common-route-page__address-dialog"
@opened="loadAddressList" @opened="loadAddressList"
> >
<div class="common-route-page__address-search"> <section-card title="查询条件">
<el-form :model="addressQuery" label-position="right" label-width="86px"> <div class="common-route-page__address-search">
<el-row :gutter="16"> <el-form :model="addressQuery" label-position="right" label-width="86px">
<el-col :span="6"> <el-row :gutter="16">
<el-form-item label="地址名称"> <el-col :span="6">
<el-input v-model="addressQuery.addressName" clearable placeholder="请输入" /> <el-form-item label="地址名称">
</el-form-item> <el-input v-model="addressQuery.addressName" clearable placeholder="请输入" />
</el-col> </el-form-item>
<el-col :span="6"> </el-col>
<el-form-item label="地址类型"> <el-col :span="6">
<el-select <el-form-item label="地址类型">
v-model="addressQuery.addressType" <el-select
clearable v-model="addressQuery.addressType"
placeholder="请选择" clearable
style="width: 100%" placeholder="请选择"
> style="width: 100%"
<el-option >
v-for="item in addressTypeOptions" <el-option
:key="item.value" v-for="item in addressTypeOptions"
:label="item.label" :key="item.value"
:value="item.value" :label="item.label"
/> :value="item.value"
</el-select> />
</el-form-item> </el-select>
</el-col> </el-form-item>
<el-col :span="6"> </el-col>
<el-form-item label="详细地址"> <el-col :span="6">
<el-input v-model="addressQuery.detailAddress" clearable placeholder="请输入" /> <el-form-item label="详细地址">
</el-form-item> <el-input v-model="addressQuery.detailAddress" clearable placeholder="请输入" />
</el-col> </el-form-item>
<el-col :span="6" class="common-route-page__address-search-actions"> </el-col>
<el-button type="primary" @click="handleAddressSearch">查询</el-button> <el-col :span="6" class="common-route-page__address-search-actions">
<el-button @click="handleAddressReset">重置</el-button> <el-button type="primary" @click="handleAddressSearch">查询</el-button>
</el-col> <el-button @click="handleAddressReset">重置</el-button>
<el-col :span="6"> </el-col>
<el-form-item label="组织"> <el-col :span="6">
<el-select <el-form-item label="组织">
v-model="addressQuery.deptId" <el-select
clearable v-model="addressQuery.deptId"
filterable clearable
placeholder="请选择" filterable
style="width: 100%" placeholder="请选择"
> style="width: 100%"
<el-option >
v-for="item in deptOptions" <el-option
:key="item.value" v-for="item in deptOptions"
:label="item.label" :key="item.value"
:value="item.value" :label="item.label"
/> :value="item.value"
</el-select> />
</el-form-item> </el-select>
</el-col> </el-form-item>
</el-row> </el-col>
</el-form> </el-row>
</div> </el-form>
</div>
</section-card>
<el-table <section-card title="常用地址">
border <el-table
:data="addressData" border
v-loading="addressLoading" :data="addressData"
height="310" v-loading="addressLoading"
highlight-current-row height="310"
@row-click="selectAddressRow" highlight-current-row
> @row-click="selectAddressRow"
<el-table-column type="index" label="序号" width="80" align="center" /> >
<el-table-column prop="addressName" label="地址名称" min-width="150" /> <el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="addressCode" label="地址编号" min-width="150" /> <el-table-column prop="addressName" label="地址名称" min-width="150" />
<el-table-column prop="addressType" label="类型" min-width="150" /> <el-table-column prop="addressCode" label="地址编号" min-width="150" />
<el-table-column label="站点编码" min-width="170"> <el-table-column prop="addressType" label="类型" min-width="150" />
<template #default="{ row }"> <el-table-column label="站点编码" min-width="170">
{{ row.addressType === '常规地址' ? '/' : row.siteCodeDisplay || row.siteCode || '' }} <template #default="{ row }">
</template> {{ row.addressType === '常规地址' ? '/' : row.siteCodeDisplay || row.siteCode || '' }}
</el-table-column> </template>
<el-table-column prop="detailAddress" label="详细地址" min-width="220" /> </el-table-column>
<el-table-column label="操作" width="120" fixed="right"> <el-table-column prop="detailAddress" label="详细地址" min-width="220" />
<template #default="{ row }"> <el-table-column label="操作" width="120" fixed="right">
<el-button type="primary" text @click.stop="selectAddressRow(row)">选择</el-button> <template #default="{ row }">
</template> <el-link type="primary" @click.stop="selectAddressRow(row)">选择</el-link>
</el-table-column> </template>
</el-table> </el-table-column>
</el-table>
<div class="common-route-page__address-pagination"> <div class="common-route-page__address-pagination">
<el-pagination <el-pagination
v-model:current-page="addressPage.currentPage" v-model:current-page="addressPage.currentPage"
v-model:page-size="addressPage.pageSize" v-model:page-size="addressPage.pageSize"
:page-sizes="[10, 20, 50, 100]" :page-sizes="[10, 20, 50, 100]"
layout="total, prev, pager, next, sizes" layout="total, prev, pager, next, sizes"
:total="addressPage.total" :total="addressPage.total"
@size-change="handleAddressSizeChange" @size-change="handleAddressSizeChange"
@current-change="handleAddressCurrentChange" @current-change="handleAddressCurrentChange"
/> />
</div> </div>
</section-card>
<template #footer> <template #footer>
<el-button @click="addressDialogVisible = false">关闭</el-button> <el-button @click="addressDialogVisible = false">关闭</el-button>
<el-button type="primary" @click="confirmAddressPick">确认</el-button> </template>
</el-dialog>
<el-dialog
title="地图选择地址"
append-to-body
v-model="routeMapVisible"
width="920px"
@opened="initRouteAmap"
>
<div class="common-route-page__map-toolbar">
<el-input
v-model="routeMapKeyword"
clearable
placeholder="输入地址关键词"
@keyup.enter="searchRouteMapKeyword"
/>
<el-button
type="primary"
:icon="Search"
:loading="routeMapLoading"
@click="searchRouteMapKeyword"
>
搜索
</el-button>
</div>
<div ref="routeAmap" class="common-route-page__map"></div>
<div class="common-route-page__map-info">
<span>{{ routeMapStatus }}</span>
<span v-if="routeMapSelected.regionName"
>发货/收货地{{ routeMapSelected.regionName }}</span
>
</div>
<template #footer>
<el-button @click="routeMapVisible = false">取消</el-button>
<el-button
type="primary"
:disabled="!routeMapSelected.longitude"
@click="confirmRouteMapPick"
>
确定
</el-button>
</template> </template>
</el-dialog> </el-dialog>
</basic-container> </basic-container>
@@ -251,18 +318,24 @@ import { getList as getCommonAddressList } from '@/api/base/common-address';
import { exportBlob } from '@/api/common'; import { exportBlob } from '@/api/common';
import { getDeptTree } from '@/api/system/dept'; import { getDeptTree } from '@/api/system/dept';
import { addressTypeOptions } from '@/option/base/common-address'; import { addressTypeOptions } from '@/option/base/common-address';
import { config, option } from '@/option/business/common-route'; import { config, excelOption, option } from '@/option/business/common-route';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { openImportDialog } from '@/utils/import-excel';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { List } from '@element-plus/icons-vue'; import { List, Search } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
let amapLoader;
export default { export default {
data() { data() {
return { return {
List, List,
Search,
config, config,
option, option,
form: {}, form: {},
@@ -270,13 +343,24 @@ export default {
loading: true, loading: true,
data: [], data: [],
dialogReadonly: false, dialogReadonly: false,
excelBox: false,
excelForm: {},
excelOptionConfig: excelOption,
addressTypeOptions, addressTypeOptions,
addressDialogVisible: false, addressDialogVisible: false,
addressTarget: '', addressTarget: '',
addressQuery: {}, addressQuery: {},
addressData: [], addressData: [],
addressLoading: false, addressLoading: false,
addressSelected: null, routeMapVisible: false,
routeMapTarget: '',
routeMapLoading: false,
routeMapKeyword: '',
routeMapStatus: '可搜索地址或点击地图选点',
routeMapSelected: {},
routeAmap: null,
routeAmapMarker: null,
routeAmapGeocoder: null,
deptOptions: [], deptOptions: [],
page: { page: {
pageSize: 10, pageSize: 10,
@@ -318,9 +402,22 @@ export default {
}, },
initDeptTree() { initDeptTree() {
getDeptTree(this.userInfo.tenantId).then(res => { getDeptTree(this.userInfo.tenantId).then(res => {
this.deptOptions = this.flattenDept(res.data.data || []); const deptTree = res.data.data || [];
this.deptOptions = this.flattenDept(deptTree);
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
if (deptColumn) {
deptColumn.dicData = this.buildDeptTree(deptTree);
}
}); });
}, },
buildDeptTree(tree) {
return tree.map(item => ({
label: item.title || item.deptName || item.name,
value: item.id,
children:
item.children && item.children.length ? this.buildDeptTree(item.children) : undefined,
}));
},
flattenDept(tree, level = 0) { flattenDept(tree, level = 0) {
const result = []; const result = [];
tree.forEach(item => { tree.forEach(item => {
@@ -348,7 +445,6 @@ export default {
openAddressDialog(target) { openAddressDialog(target) {
if (this.dialogReadonly) return; if (this.dialogReadonly) return;
this.addressTarget = target; this.addressTarget = target;
this.addressSelected = null;
this.addressPage.currentPage = 1; this.addressPage.currentPage = 1;
this.resetAddressQuery(); this.resetAddressQuery();
this.addressDialogVisible = true; this.addressDialogVisible = true;
@@ -387,14 +483,8 @@ export default {
}); });
}, },
selectAddressRow(row) { selectAddressRow(row) {
this.addressSelected = row; if (!row) return;
}, this.applyAddress(this.addressTarget, row);
confirmAddressPick() {
if (!this.addressSelected) {
this.$message.warning('请选择常用地址');
return;
}
this.applyAddress(this.addressTarget, this.addressSelected);
this.addressDialogVisible = false; this.addressDialogVisible = false;
}, },
applyAddress(target, address) { applyAddress(target, address) {
@@ -412,6 +502,246 @@ export default {
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || ''; this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || ''; this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
}, },
openRouteMapDialog(target) {
if (this.dialogReadonly) return;
const prefix = target === 'departure' ? 'departure' : 'arrival';
this.routeMapTarget = prefix;
this.routeMapKeyword = this.form[`${prefix}Address`] || this.form[`${prefix}Name`] || '';
this.routeMapSelected = this.buildRouteMapSelection({
lng: this.form[`${prefix}Longitude`],
lat: this.form[`${prefix}Latitude`],
address: this.form[`${prefix}Address`],
regionName: this.form[`${prefix}Name`],
});
this.routeMapStatus = this.routeMapSelected.longitude
? '已加载当前选点,可重新选点'
: '可搜索地址或点击地图选点';
this.routeMapVisible = true;
},
loadAmap() {
if (window.AMap && window.AMap.Map) {
return Promise.resolve();
}
if (!amapLoader) {
amapLoader = new Promise((resolve, reject) => {
window._AMapSecurityConfig = {
securityJsCode: AMAP_SECURITY_CODE,
};
const script = document.createElement('script');
script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
script.async = true;
script.onload = resolve;
script.onerror = () => reject(new Error('高德地图组件加载失败'));
document.body.appendChild(script);
});
}
return amapLoader;
},
initRouteAmap() {
this.loadAmap()
.then(() => {
this.$nextTick(() => {
if (!this.routeAmap) {
const center = this.toLngLat(
this.routeMapSelected.longitude || 116.40769,
this.routeMapSelected.latitude || 39.89945
);
this.routeAmap = new window.AMap.Map(this.$refs.routeAmap, {
center,
zoom: this.routeMapSelected.longitude ? 14 : 11,
});
window.AMap.plugin(['AMap.ToolBar', 'AMap.Geocoder'], () => {
this.routeAmap.addControl(new window.AMap.ToolBar());
if (!this.routeAmapGeocoder) {
this.routeAmapGeocoder = new window.AMap.Geocoder();
}
});
this.routeAmap.on('click', event => this.pickRouteMapPoint(event.lnglat));
} else if (typeof this.routeAmap.resize === 'function') {
this.routeAmap.resize();
}
if (this.routeMapSelected.longitude) {
this.renderRouteMapMarker(
this.toLngLat(this.routeMapSelected.longitude, this.routeMapSelected.latitude)
);
}
});
})
.catch(() => {
this.$message.error('高德地图组件加载失败,请稍后重试或重新打开弹窗');
this.routeMapVisible = false;
});
},
searchRouteMapKeyword() {
const keyword = String(this.routeMapKeyword || '').trim();
if (!keyword) {
this.$message.warning('请输入地址关键词');
return;
}
this.loadAmap()
.then(() => {
this.routeMapLoading = true;
return this.ensureRouteAmapGeocoder();
})
.then(() => this.runAmapGeocode('location', keyword))
.then(result => {
const point = this.resolveMapPoint(result);
if (!point) {
this.routeMapStatus = '未找到匹配地址';
this.$message.warning('地图搜索无匹配地址');
return;
}
this.pickRouteMapPoint(point, keyword);
})
.catch(() => {
this.routeMapStatus = '地图搜索失败';
this.$message.error('地图搜索失败,请稍后重试');
})
.finally(() => {
this.routeMapLoading = false;
});
},
pickRouteMapPoint(lnglat, keyword) {
const longitude = this.getPointLng(lnglat);
const latitude = this.getPointLat(lnglat);
if (longitude === undefined || latitude === undefined) {
this.$message.warning('选点坐标无效');
return;
}
const point = this.toLngLat(longitude, latitude);
this.renderRouteMapMarker(point);
this.routeMapSelected = this.buildRouteMapSelection({
lng: longitude,
lat: latitude,
address: keyword,
});
this.routeMapStatus = '正在反查地址...';
this.ensureRouteAmapGeocoder()
.then(() => this.runAmapGeocode('address', point))
.then(result => {
const address = this.resolveMapAddress(result);
this.routeMapSelected = {
...this.routeMapSelected,
detailAddress: address.detailAddress || this.routeMapSelected.detailAddress,
regionName: address.regionName || this.routeMapSelected.regionName,
};
this.routeMapKeyword = this.routeMapSelected.detailAddress || this.routeMapKeyword;
this.routeMapStatus = this.routeMapSelected.detailAddress || '已选点,可确认回填';
})
.catch(() => {
this.routeMapStatus = '反查地址失败';
this.$message.error('反查地址失败,请重新选点');
});
},
renderRouteMapMarker(point) {
if (!this.routeAmap || !window.AMap) return;
if (this.routeAmapMarker) {
this.routeAmapMarker.setMap(null);
}
this.routeAmapMarker = new window.AMap.Marker({ position: point });
this.routeAmapMarker.setMap(this.routeAmap);
this.routeAmap.setCenter(point);
},
confirmRouteMapPick() {
if (!this.routeMapSelected.longitude) {
this.$message.warning('请先搜索或点击地图完成选点');
return;
}
const prefix = this.routeMapTarget;
this.form[`${prefix}Address`] =
this.routeMapSelected.detailAddress || this.form[`${prefix}Address`];
this.form[`${prefix}Name`] = this.routeMapSelected.regionName || this.form[`${prefix}Name`];
this.form[`${prefix}Longitude`] = this.routeMapSelected.longitude;
this.form[`${prefix}Latitude`] = this.routeMapSelected.latitude;
this.routeMapVisible = false;
this.$refs.crud?.validateField?.(`${prefix}Address`);
},
buildRouteMapSelection({ lng, lat, address, regionName }) {
return {
longitude: this.formatCoordinate(lng),
latitude: this.formatCoordinate(lat),
detailAddress: address || '',
regionName: regionName || '',
};
},
formatCoordinate(value) {
if (value === undefined || value === null || value === '') return '';
const numberValue = Number(value);
return Number.isFinite(numberValue) ? numberValue.toFixed(6) : '';
},
toLngLat(lng, lat) {
return new window.AMap.LngLat(Number(lng), Number(lat));
},
getPointLng(point) {
if (!point) return undefined;
if (typeof point.getLng === 'function') return point.getLng();
return point.lng ?? point.lon;
},
getPointLat(point) {
if (!point) return undefined;
if (typeof point.getLat === 'function') return point.getLat();
return point.lat;
},
ensureRouteAmapGeocoder() {
if (this.routeAmapGeocoder) return Promise.resolve(this.routeAmapGeocoder);
return new Promise((resolve, reject) => {
if (!window.AMap || !window.AMap.plugin) {
reject(new Error('高德地图组件未就绪'));
return;
}
window.AMap.plugin(['AMap.Geocoder'], () => {
try {
this.routeAmapGeocoder = new window.AMap.Geocoder();
resolve(this.routeAmapGeocoder);
} catch (error) {
reject(error);
}
});
});
},
runAmapGeocode(action, input) {
return new Promise((resolve, reject) => {
const timer = window.setTimeout(() => {
reject(new Error('高德地图请求超时'));
}, 10000);
const done = (status, result) => {
window.clearTimeout(timer);
if (status === 'complete' && result) {
resolve(result);
return;
}
reject(new Error('高德地图请求失败'));
};
try {
if (action === 'location') {
this.routeAmapGeocoder.getLocation(input, done);
} else {
this.routeAmapGeocoder.getAddress(input, done);
}
} catch (error) {
window.clearTimeout(timer);
reject(error);
}
});
},
resolveMapPoint(result) {
if (!result) return null;
const geocode = Array.isArray(result.geocodes) ? result.geocodes[0] : null;
if (geocode && geocode.location) return geocode.location;
if (result.location) return result.location;
if (result.lnglat) return result.lnglat;
if (result.getLng || result.lng || result.lon) return result;
return null;
},
resolveMapAddress(result = {}) {
const regeocode = result.regeocode || {};
const component = regeocode.addressComponent || {};
const city = Array.isArray(component.city) ? '' : component.city;
return {
detailAddress: regeocode.formattedAddress || '',
regionName: [component.province, city, component.district].filter(Boolean).join(''),
};
},
normalizeRow(row) { normalizeRow(row) {
const submitRow = { ...row }; const submitRow = { ...row };
Object.keys(submitRow).forEach(key => { Object.keys(submitRow).forEach(key => {
@@ -621,6 +951,27 @@ export default {
}); });
}); });
}, },
handleTemplate() {
NProgress.start();
exportBlob(
config.templateUrl,
{
[this.website.tokenHeader]: getToken(),
},
{ feedback: true }
)
.then(res => {
downloadXls(res.data, `${config.title}模板.xlsx`);
})
.finally(() => {
NProgress.done();
});
},
handleImport() {
const column = this.excelOptionConfig.column.find(item => item.prop === 'excelFile');
column.action = config.importUrl;
openImportDialog(this, config.title, () => this.onLoad(this.page, this.query));
},
}, },
}; };
</script> </script>
@@ -662,5 +1013,31 @@ export default {
justify-content: flex-end; justify-content: flex-end;
margin-top: 14px; margin-top: 14px;
} }
&__map-toolbar {
display: flex;
gap: 8px;
margin-bottom: 12px;
.el-input {
flex: 1;
min-width: 0;
}
}
&__map {
width: 100%;
height: 420px;
border: 1px solid #eff1f7;
}
&__map-info {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 10px;
color: #606266;
line-height: 1.6;
}
} }
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
<template>
<el-dialog v-model="visible" title="导入运单" width="90%" append-to-body destroy-on-close>
<div class="waybill-import-toolbar">
<div class="waybill-import-toolbar__search">
<el-form :inline="true" :model="query" label-width="auto">
<el-form-item label="运单批次号"><el-input v-model="query.batchNo" clearable /></el-form-item>
<el-form-item label="承运商"><el-select v-model="query.carrierId" clearable filterable><el-option v-for="item in carriers" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
<el-form-item label="创建时间"><el-date-picker v-model="query.createTimeRange" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" /></el-form-item>
<el-form-item label="创建人"><el-select v-model="query.createUser" clearable filterable><el-option v-for="item in creators" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
</el-form>
<div class="waybill-import-toolbar__search-actions"><el-button type="primary" @click="loadBatches">查询</el-button><el-button @click="resetQuery">重置</el-button></div>
</div>
<div class="waybill-import-toolbar__actions"><el-button type="primary" @click="openCreate">新建导入</el-button><el-button type="danger" plain :disabled="!selected.length" @click="removeBatches">批量删除</el-button></div>
</div>
<el-table :data="batches" border @selection-change="selected = $event">
<el-table-column type="selection" width="50" /><el-table-column type="index" label="序号" width="70" />
<el-table-column prop="batchNo" label="运单批次号" min-width="150" /><el-table-column prop="projectName" label="项目名称" min-width="150" />
<el-table-column prop="carrierName" label="承运商" min-width="140" /><el-table-column prop="carrierType" label="承运类型" min-width="120" />
<el-table-column prop="waybillCount" label="运单数" width="90" /><el-table-column prop="importTypeName" label="导入方式" width="100" />
<el-table-column prop="createTime" label="创建时间" min-width="170" /><el-table-column prop="updateTime" label="更新时间" min-width="170" />
<el-table-column prop="statusName" label="状态" width="100" /><el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }"><el-link type="primary" @click="openDetail(row)">查看</el-link><el-link type="primary" @click="editBatch(row)">编辑</el-link><el-link type="danger" @click="removeBatch(row)">删除</el-link></template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="page.current" v-model:page-size="page.size" layout="total, prev, pager, next, sizes" :page-sizes="[10, 20, 50]" :total="page.total" @current-change="loadBatches" @size-change="loadBatches" />
</el-dialog>
<el-dialog v-model="detailVisible" title="导入运单详情" width="90%" append-to-body>
<el-form :inline="true" :model="detailQuery"><el-form-item label="车牌号/船号"><el-input v-model="detailQuery.vehicleNo" clearable /></el-form-item><el-form-item label="货物名称"><el-input v-model="detailQuery.cargoName" clearable /></el-form-item><el-button type="primary" @click="loadDetails">查询</el-button></el-form>
<el-table :data="details" border><el-table-column type="index" label="序号" width="65" /><el-table-column prop="batchNo" label="运单批次号" min-width="140" /><el-table-column prop="originalNo" label="原始单号" min-width="130" /><el-table-column prop="vehicleNo" label="车牌号/航班号/船号/班列号" min-width="190" /><el-table-column prop="driverName" label="司机/船长" min-width="120" /><el-table-column prop="transportType" label="运输类型" width="110" /><el-table-column prop="cargoName" label="货物名称" width="130" /><el-table-column prop="cargoType" label="货物类型" width="120" /><el-table-column prop="quantity" label="重量" width="90" /><el-table-column prop="departureAddress" label="发货地址" min-width="220" show-overflow-tooltip /><el-table-column prop="departureContact" label="发货联系人" width="110" /><el-table-column prop="departurePhone" label="发货联系人电话" width="140" /><el-table-column prop="arrivalAddress" label="到货地址" min-width="220" show-overflow-tooltip /><el-table-column prop="arrivalContact" label="收货联系人" width="110" /><el-table-column prop="arrivalPhone" label="收货联系人电话" width="140" /><el-table-column prop="startDate" label="开始时间" width="160" /><el-table-column prop="endDate" label="结束时间" width="160" /><el-table-column prop="unitPrice" label="单价" width="90" /><el-table-column prop="freight" label="运费" width="100" /><el-table-column prop="otherFeeTotal" label="其他费用合计" width="120" /><el-table-column prop="freightTotal" label="运费合计" width="100" /><el-table-column prop="remark" label="备注" min-width="160" /><el-table-column label="操作" width="80">-</el-table-column></el-table>
<el-pagination v-model:current-page="detailPage.current" v-model:page-size="detailPage.size" layout="total, prev, pager, next, sizes" :page-sizes="[10, 20, 50]" :total="detailPage.total" @current-change="loadDetails" @size-change="loadDetails" />
</el-dialog>
<el-dialog v-model="createVisible" title="新建导入" width="85%" append-to-body destroy-on-close class="waybill-import-create">
<section class="waybill-import-create__form-panel">
<el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="auto">
<el-row :gutter="20"><el-col :span="6"><el-form-item label="项目" prop="projectId"><el-select v-model="form.projectId" filterable clearable placeholder="模糊查询后选择" @change="projectChange"><el-option v-for="item in projects" :key="item.id" :label="item.projectName" :value="item.id" /></el-select></el-form-item></el-col><el-col :span="6"><el-form-item label="客户" prop="customerName"><el-input v-model="form.customerName" disabled placeholder="自动带出" /></el-form-item></el-col><el-col :span="6"><el-form-item label="客户合同" prop="contractId"><el-select v-model="form.contractId" filterable clearable placeholder="自动带出"><el-option v-for="item in contracts" :key="item.id" :label="item.contractName" :value="item.id" /></el-select></el-form-item></el-col><el-col :span="6"><el-form-item label="承运类型" prop="carrierType"><el-select v-model="form.carrierType"><el-option label="承运商" value="承运商" /><el-option label="自运" value="自运" /><el-option label="网货平台" value="网货平台" /></el-select></el-form-item></el-col><el-col :span="6"><el-form-item label="承运商"><el-select v-model="form.carrierIds" multiple filterable placeholder="自动带出,多个需选择"><el-option v-for="item in carriers" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col><el-col :span="6"><el-form-item label="导入状态" prop="status"><el-select v-model="form.status"><el-option label="完成" value="completed" /><el-option label="进行中" value="processing" /></el-select></el-form-item></el-col><el-col :span="6"><el-form-item label="导入类型" prop="importType"><el-select v-model="form.importType"><el-option label="运单" value="waybill" /><el-option label="结算单" value="settlement" /></el-select></el-form-item></el-col><el-col :span="6"><el-form-item label="发货计划"><el-select v-model="form.planId" clearable filterable placeholder="请选择"><el-option v-for="item in plans" :key="item.id" :label="item.planName" :value="item.id" /></el-select></el-form-item></el-col></el-row>
<el-form-item label="运单明细表" prop="file"><el-upload action="#" accept=".xls,.xlsx" :auto-upload="false" :limit="1" :file-list="files" :on-change="fileChange" :on-remove="fileRemove"><el-button type="primary">添加附件</el-button></el-upload><span class="waybill-import-create__file-tip">请上传运单明细表仅支持 excel 格式</span><el-link type="primary" @click="downloadTemplate">下载模板</el-link></el-form-item>
</el-form>
<div class="waybill-import-create__form-actions"><el-button @click="createVisible = false">关闭</el-button><el-button @click="saveDraft">保存草稿</el-button><el-button type="primary" @click="confirmImport">确认导入</el-button></div>
</section>
<section class="waybill-import-create__detail-panel" v-if="rows.length"><div class="waybill-import-create__detail-head"><div class="dialog-section-title">导入数据明细</div><el-button type="danger" plain :disabled="!rowSelection.length" @click="removeSelectedRows">批量删除</el-button></div><el-tabs v-model="previewTab"><el-tab-pane :label="`运单数(${rows.length}`" name="all" /><el-tab-pane :label="`疑似重复(${duplicateRows.length}`" name="duplicate" /></el-tabs><el-table :data="previewRows" border max-height="440" @selection-change="rowSelection = $event"><el-table-column type="selection" width="55" /><el-table-column type="index" label="序号" width="65" /><el-table-column v-for="column in detailColumns" :key="column.prop" :prop="column.prop" :label="column.label" min-width="140" show-overflow-tooltip /><el-table-column label="操作" width="180" fixed="right"><template #default="{ row }"><el-link type="primary" @click="cancelRow(row)">取消</el-link><el-link type="primary" @click="saveRow(row)">保存</el-link><el-link type="primary" @click="editRow(row)">编辑</el-link><el-link type="danger" @click="deleteRow(row)">删除</el-link></template></el-table-column></el-table></section>
</el-dialog>
</template>
<script setup>
import { computed, reactive, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getList as getProjectList } from '@/api/business/project-apply';
import * as api from '@/api/business/waybill-manage';
import { downloadXls } from '@/utils/util';
const props = defineProps({ modelValue: Boolean });
const emit = defineEmits(['update:modelValue']);
const visible = computed({ get: () => props.modelValue, set: value => emit('update:modelValue', value) });
const createVisible = ref(false), detailVisible = ref(false), formRef = ref();
const query = reactive({ batchNo: '', carrierId: '', createUser: '', createTimeRange: [] });
const detailQuery = reactive({ vehicleNo: '', cargoName: '' });
const form = reactive({ projectId: '', customerName: '', contractId: '', carrierType: '承运商', carrierIds: [], status: 'completed', importType: 'waybill', planId: '', file: null });
const rules = { projectId: [{ required: true, message: '请选择项目' }], customerName: [{ required: true, message: '客户不能为空' }], contractId: [{ required: true, message: '请选择客户合同' }], carrierType: [{ required: true, message: '请选择承运类型' }], status: [{ required: true, message: '请选择导入状态' }], importType: [{ required: true, message: '请选择导入类型' }], file: [{ required: true, message: '请上传明细表' }] };
const batches = ref([]), details = ref([]), rows = ref([]), selected = ref([]), files = ref([]), projects = ref([]), contracts = ref([]), carriers = ref([]), creators = ref([]), plans = ref([]);
const rowSelection = ref([]), previewTab = ref('all');
const duplicateRows = computed(() => rows.value.filter(row => row._duplicate));
const previewRows = computed(() => previewTab.value === 'duplicate' ? duplicateRows.value : rows.value);
const page = reactive({ current: 1, size: 10, total: 0 }), detailPage = reactive({ current: 1, size: 10, total: 0 });
const detailColumns = [{ prop: 'batchNo', label: '运单批次号' }, { prop: 'originalNo', label: '原始单号' }, { prop: 'vehicleNo', label: '车牌号/航班号/船号/班列号' }, { prop: 'driverName', label: '司机/船长' }, { prop: 'transportType', label: '运输类型' }, { prop: 'cargoName', label: '货物名称' }, { prop: 'cargoType', label: '货物类型' }, { prop: 'quantity', label: '重量' }, { prop: 'departureAddress', label: '发货地址' }, { prop: 'departureContact', label: '发货联系人' }, { prop: 'departurePhone', label: '发货联系人电话' }, { prop: 'arrivalAddress', label: '到货地址' }, { prop: 'arrivalContact', label: '收货联系人' }, { prop: 'arrivalPhone', label: '收货联系人电话' }, { prop: 'startDate', label: '开始时间' }, { prop: 'endDate', label: '结束时间' }, { prop: 'unitPrice', label: '单价' }, { prop: 'freight', label: '运费' }, { prop: 'otherFeeTotal', label: '其他费用合计' }, { prop: 'freightTotal', label: '运费合计' }, { prop: 'remark', label: '备注' }];
const requiredDetailFields = [{ prop: 'vehicleNo', label: '车牌号/航班号/船号/班列号' }, { prop: 'driverName', label: '司机/船长' }, { prop: 'transportType', label: '运输类型' }, { prop: 'cargoName', label: '货物名称' }, { prop: 'cargoType', label: '货物类型' }, { prop: 'departureAddress', label: '发货地址' }, { prop: 'departureContact', label: '发货联系人' }, { prop: 'departurePhone', label: '发货联系人电话' }, { prop: 'arrivalAddress', label: '到货地址' }, { prop: 'arrivalContact', label: '到货联系人' }, { prop: 'arrivalPhone', label: '收货联系人电话' }, { prop: 'startDate', label: '开始时间' }, { prop: 'endDate', label: '结束时间' }, { prop: 'unitPrice', label: '单价' }, { prop: 'freight', label: '运费' }];
const loadBatches = async () => { const res = await api.getImportBatches({ ...query, current: page.current, size: page.size }); batches.value = res.data?.data?.records || []; page.total = res.data?.data?.total || 0; };
const resetQuery = () => { Object.assign(query, { batchNo: '', carrierId: '', createUser: '', createTimeRange: [] }); page.current = 1; loadBatches(); };
const loadDetails = async () => { const res = await api.getImportDetails({ batchId: detailQuery.batchId, ...detailQuery, current: detailPage.current, size: detailPage.size }); details.value = res.data?.data?.records || []; detailPage.total = res.data?.data?.total || 0; };
const openCreate = async () => {
createVisible.value = true;
const projectRes = await getProjectList(1, 9999, {});
projects.value = projectRes.data?.data?.records || [];
api.getImportOptions?.().then(res => {
const data = res.data?.data || {};
contracts.value = data.contracts || [];
carriers.value = data.carriers || [];
creators.value = data.creators || [];
plans.value = data.plans || [];
});
};
const openDetail = row => { detailQuery.batchId = row.id; detailVisible.value = true; loadDetails(); };
const editBatch = row => { openCreate(); Object.assign(form, row); };
const removeBatches = () => api.removeImportBatches(selected.value.map(item => item.id).join(',')).then(loadBatches);
const removeBatch = row => api.removeImportBatches(row.id).then(loadBatches);
const projectChange = id => { const item = projects.value.find(row => row.id === id); form.customerName = item?.customerName || ''; api.getImportContracts?.(id).then(res => { contracts.value = res.data?.data || []; }); };
const fileChange = async (file, list) => {
files.value = list.slice(-1); form.file = file.raw;
const XLSX = await import('xlsx');
const workbook = XLSX.read(await file.raw.arrayBuffer(), { type: 'array', cellDates: true });
const source = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], { defval: '' }).map(item => Object.fromEntries(Object.entries(item).map(([key, value]) => [key.replace(/^\*/, ''), value])));
const keyMap = { '原始单号': 'originalNo', '车牌号/航班号/船号/班列号': 'vehicleNo', '司机/船长': 'driverName', '运输类型': 'transportType', '货物名称': 'cargoName', '货物类型': 'cargoType', '重量': 'quantity', '发货地址': 'departureAddress', '发货联系人': 'departureContact', '发货联系人电话': 'departurePhone', '到货地址': 'arrivalAddress', '收货联系人': 'arrivalContact', '收货联系人电话': 'arrivalPhone', '开始时间': 'startDate', '结束时间': 'endDate', '单价': 'unitPrice', '运费': 'freight', '其他费用合计': 'otherFeeTotal', '运费合计': 'freightTotal', '备注': 'remark' };
const count = new Map();
rows.value = source.map((item, index) => { const row = { _key: `${Date.now()}-${index}`, batchNo: '', ...item }; Object.entries(keyMap).forEach(([label, key]) => { row[key] = item[label] ?? item[key] ?? ''; }); const duplicateKey = JSON.stringify(Object.values(keyMap).map(key => row[key])); count.set(duplicateKey, (count.get(duplicateKey) || 0) + 1); row._duplicateKey = duplicateKey; return row; });
rows.value.forEach(row => { row._duplicate = count.get(row._duplicateKey) > 1; });
};
const fileRemove = () => { files.value = []; form.file = null; };
const downloadTemplate = async () => {
try {
const res = await api.exportImportTemplate();
downloadXls(res.data, '运单批量导入模板.xlsx');
ElMessage.success('模板下载成功');
} catch (error) {
ElMessage.error('模板下载失败');
}
};
const validateImportRows = () => {
if (!rows.value.length) {
ElMessage.warning('请上传至少一条运单明细');
return false;
}
for (const [index, row] of rows.value.entries()) {
const missingField = requiredDetailFields.find(field => !String(row[field.prop] ?? '').trim());
if (missingField) {
ElMessage.warning(`${index + 1}${missingField.label}不能为空`);
return false;
}
}
return true;
};
const editRow = row => { row._editing = true; };
const saveRow = row => { row._editing = false; };
const cancelRow = row => { row._cancelled = true; };
const deleteRow = row => { rows.value = rows.value.filter(item => item !== row); };
const removeSelectedRows = () => { const keys = new Set(rowSelection.value.map(row => row._key)); rows.value = rows.value.filter(row => !keys.has(row._key)); rowSelection.value = []; };
const saveDraft = () => api.saveImportDraft({ ...form, rows: rows.value });
const confirmImport = async () => { await formRef.value?.validate(); if (!validateImportRows()) return; const count = rows.value.filter(row => row._duplicate).length; if (count) await ElMessageBox.confirm(`当前导入有${count}条重复数据,是否确认导入?`, '提示'); if (form.importType === 'settlement') await ElMessageBox.confirm('确认后将同时生成运单、应收应付明细、计结算单,是否继续?', '提示'); await api.confirmImport({ ...form, rows: rows.value }); ElMessage.success('导入成功'); createVisible.value = false; loadBatches(); };
</script>
<style scoped lang="scss">
.waybill-import-toolbar {
margin-bottom: 12px;
&__search {
display: flex;
align-items: flex-start;
gap: 12px;
.el-form {
flex: 1;
margin-bottom: 0;
}
}
&__search-actions {
display: flex;
flex: none;
gap: 8px;
}
&__actions { margin-top: 8px; }
}
.waybill-import-create {
&__form-panel,
&__detail-panel {
border: 1px solid #eff1f7;
padding: 20px 24px;
}
&__detail-panel { margin-top: 12px; }
&__form-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 8px; }
&__detail-head { display: flex; align-items: center; gap: 24px; margin-bottom: 8px; }
&__file-tip { margin: 0 24px; color: #606266; }
}
</style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<business-crud-page :api="api" :config="config" :crud-option="option" /> <business-crud-page :api="api" :config="config" :crud-option="option" :menu-width="245" />
</template> </template>
<script> <script>

File diff suppressed because it is too large Load Diff

View File

@@ -24,10 +24,9 @@
<el-button <el-button
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
plain
v-if="hasPermission('process_config_add')" v-if="hasPermission('process_config_add')"
@click="$refs.crud.rowAdd()" @click="$refs.crud.rowAdd()"
> >建过程配置
</el-button> </el-button>
<el-button <el-button
type="primary" type="primary"
@@ -53,13 +52,23 @@
</el-tag> </el-tag>
</template> </template>
<template #includedNodes="{ row }">
<span class="process-config-page__node-value">
{{ formatProcessNodes(row.includedNodes) }}
</span>
</template>
<template #basicInfo-form> <template #basicInfo-form>
<div class="process-config-page__section"> <div class="process-config-page__section">
<div class="dialog-section-title">基本信息</div> <div class="dialog-section-title">基本信息</div>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="12"> <el-col :span="8">
<div class="process-config-page__field"> <div class="process-config-page__field">
<span class="process-config-page__field-label">配置名称</span> <span
class="process-config-page__field-label process-config-page__field-label--required"
>
配置名称
</span>
<el-input <el-input
v-model="form.configName" v-model="form.configName"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@@ -68,9 +77,13 @@
/> />
</div> </div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="8">
<div class="process-config-page__field"> <div class="process-config-page__field">
<span class="process-config-page__field-label">项目</span> <span
class="process-config-page__field-label process-config-page__field-label--required"
>
项目
</span>
<el-select <el-select
v-model="selectedProjectId" v-model="selectedProjectId"
clearable clearable
@@ -92,33 +105,33 @@
</el-select> </el-select>
</div> </div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="8">
<div class="process-config-page__field"> <div class="process-config-page__field">
<span class="process-config-page__field-label">默认后台完成运输天数</span> <span class="process-config-page__field-label">运输完成时限</span>
<div class="process-config-page__finish-days"> <div class="process-config-page__finish-days">
<span>默认</span> <span>默认</span>
<el-input <el-input
v-model="form.defaultFinishDays" v-model="form.defaultFinishDays"
:disabled="dialogReadonly" :disabled="dialogReadonly"
maxlength="3" maxlength="3"
placeholder="X" placeholder="请输入"
@input="handleFinishDaysInput" @input="handleFinishDaysInput"
/> />
<span>天后完成运输</span> <span>天后完成运输</span>
</div> </div>
</div> </div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="24">
<div class="process-config-page__field"> <div class="process-config-page__field">
<span class="process-config-page__field-label">备注</span> <span class="process-config-page__field-label">备注</span>
<el-input <el-input
v-model="form.remark" v-model="form.remark"
:disabled="dialogReadonly"
:rows="3"
maxlength="500"
placeholder="请输入"
show-word-limit
type="textarea" type="textarea"
:rows="4"
:disabled="dialogReadonly"
maxlength="500"
show-word-limit
placeholder="请输入"
/> />
</div> </div>
</el-col> </el-col>
@@ -196,7 +209,7 @@
<el-radio-group <el-radio-group
v-model="row.confirmMode" v-model="row.confirmMode"
:disabled="dialogReadonly || !row.enabled" :disabled="dialogReadonly || !row.enabled"
@change="syncNodeFields" @change="handleReturnConfirmChange(row)"
> >
<el-radio label="yes"></el-radio> <el-radio label="yes"></el-radio>
<el-radio label="no_confirm_complete">无需确认完成</el-radio> <el-radio label="no_confirm_complete">无需确认完成</el-radio>
@@ -333,54 +346,48 @@
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('process_config_view')" v-if="hasPermission('process_config_view')"
@click="$refs.crud.rowView(row, index)" @click="$refs.crud.rowView(row, index)"
> >
查看 查看
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="canEdit(row)" v-if="canEdit(row)"
@click="$refs.crud.rowEdit(row, index)" @click="$refs.crud.rowEdit(row, index)"
> >
编辑 编辑
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission('process_config_copy')" v-if="hasPermission('process_config_copy')"
@click="handleCopy(row)" @click="handleCopy(row)"
> >
复制 复制
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="canEnable(row)" v-if="canEnable(row)"
@click="handleAction('enable', row)" @click="handleAction('enable', row)"
> >
启用 启用
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="canDisable(row)" v-if="canDisable(row)"
@click="handleAction('disable', row)" @click="handleAction('disable', row)"
> >
停用 停用
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="canDelete(row)" v-if="canDelete(row)"
@click="rowDel(row)" @click="rowDel(row)"
> >
删除 删除
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>
@@ -396,6 +403,7 @@
<script> <script>
import * as api from '@/api/business/process-config'; import * as api from '@/api/business/process-config';
import { getList as getProjectList } from '@/api/business/project-apply'; import { getList as getProjectList } from '@/api/business/project-apply';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common'; import { exportBlob } from '@/api/common';
import { config, option } from '@/option/business/process-config'; import { config, option } from '@/option/business/process-config';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
@@ -519,11 +527,35 @@ export default {
created() { created() {
this.resetNodeRows(); this.resetNodeRows();
this.loadProjectOptions(''); this.loadProjectOptions('');
this.initDeptOptions();
}, },
methods: { methods: {
hasPermission(code) { hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false); return this.isAdmin || this.validData(this.permission && this.permission[code], false);
}, },
handleReturnConfirmChange(row) {
row.confirmInternal = row.confirmMode === 'yes';
this.syncNodeFields();
},
initDeptOptions() {
getDeptTree(this.userInfo.tenantId).then(res => {
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
if (deptColumn) {
deptColumn.dicData = this.flattenDept(res.data.data || []);
}
});
},
flattenDept(tree, level = 0) {
return tree.flatMap(item => [
{
label: `${' '.repeat(level)}${item.title || item.deptName || item.name}`,
value: item.id,
},
...(item.children && item.children.length
? this.flattenDept(item.children, level + 1)
: []),
]);
},
displayStatus(status) { displayStatus(status) {
const statusMap = { const statusMap = {
0: '草稿', 0: '草稿',
@@ -532,6 +564,13 @@ export default {
}; };
return statusMap[status] || status || '未知'; return statusMap[status] || status || '未知';
}, },
formatProcessNodes(nodes) {
return String(nodes || '')
.split(/[,]/)
.map(item => item.trim())
.filter(Boolean)
.join('-');
},
statusTagType(status) { statusTagType(status) {
if (status === 1) return 'success'; if (status === 1) return 'success';
if (status === 2) return 'info'; if (status === 2) return 'info';
@@ -564,7 +603,7 @@ export default {
voucherTypes: [], voucherTypes: [],
frequencyDays: '1', frequencyDays: '1',
timeStart: '00:00', timeStart: '00:00',
timeEnd: '24:00', timeEnd: '23:59',
supportCargo: false, supportCargo: false,
supportVoucher: false, supportVoucher: false,
voucherOptions: [], voucherOptions: [],
@@ -711,7 +750,7 @@ export default {
result.push(`打卡定位:${node.location ? '是' : '否'}`); result.push(`打卡定位:${node.location ? '是' : '否'}`);
if (node.punch) { if (node.punch) {
result.push(`打卡频次:每${node.frequencyDays || 1}天打卡1次`); result.push(`打卡频次:每${node.frequencyDays || 1}天打卡1次`);
result.push(`打卡时段:${node.timeStart || '00:00'}-${node.timeEnd || '24:00'}`); result.push(`打卡时段:${node.timeStart || '00:00'}-${node.timeEnd || '23:59'}`);
} }
} else { } else {
result.push(`打卡操作:${node.punch ? '是' : '否'}`); result.push(`打卡操作:${node.punch ? '是' : '否'}`);
@@ -750,9 +789,15 @@ export default {
this.$message.warning('项目不能为空'); this.$message.warning('项目不能为空');
return false; return false;
} }
if (row.defaultFinishDays && !/^[1-9]\d{0,2}$/.test(String(row.defaultFinishDays))) { if (row.defaultFinishDays !== undefined && row.defaultFinishDays !== '') {
this.$message.warning('请输入正整数'); if (!Number.isInteger(Number(row.defaultFinishDays)) || Number(row.defaultFinishDays) < 0) {
return false; this.$message.warning('完成天数不能小于0');
return false;
}
if (Number(row.defaultFinishDays) > 999) {
this.$message.warning('完成天数不能超过999');
return false;
}
} }
if (row.remark && row.remark.length > 500) { if (row.remark && row.remark.length > 500) {
this.$message.warning('备注不能超过500个字符'); this.$message.warning('备注不能超过500个字符');
@@ -780,43 +825,61 @@ export default {
loading(); loading();
} }
}, },
rowSave(row, done, loading) { needsDuplicateVoucherConfirm() {
this.syncNodeFields(); const signNode = this.nodeRows.find(item => item.key === 'sign');
const submitRow = this.normalizeRow(row); const returnNode = this.nodeRows.find(item => item.key === 'return');
if (!this.validateRow(submitRow)) { const hasSignVoucher =
this.stopSubmitLoading(loading); signNode &&
return; signNode.enabled &&
signNode.uploadVoucher &&
signNode.voucherTypes.includes('签收单');
const hasReturnVoucher =
returnNode &&
returnNode.enabled &&
returnNode.uploadVoucher &&
returnNode.voucherTypes.includes('签收单');
return hasSignVoucher && hasReturnVoucher;
},
confirmDuplicateVoucher() {
if (!this.needsDuplicateVoucherConfirm()) {
return Promise.resolve();
} }
api.submit(submitRow).then( return this.$confirm(
() => { '签收与回单同时勾选了上传凭证,司机将需重复上传凭证,是否确认?',
this.onLoad(this.page); '提示',
this.$message({ type: 'success', message: '操作成功!' }); {
done(); confirmButtonText: '确认',
}, cancelButtonText: '取消',
error => { type: 'warning',
window.console.log(error);
this.stopSubmitLoading(loading);
} }
); );
}, },
rowUpdate(row, index, done, loading) { submitRow(row, done, loading) {
this.syncNodeFields(); this.syncNodeFields();
const submitRow = this.normalizeRow(row); const submitRow = this.normalizeRow(row);
if (!this.validateRow(submitRow)) { if (!this.validateRow(submitRow)) {
this.stopSubmitLoading(loading); this.stopSubmitLoading(loading);
return; return;
} }
api.submit(submitRow).then( this.confirmDuplicateVoucher()
() => { .then(() => api.submit(submitRow))
.then(() => {
this.onLoad(this.page); this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' }); this.$message({ type: 'success', message: '操作成功!' });
done(); done();
}, })
error => { .catch(error => {
window.console.log(error); if (error !== 'cancel' && error !== 'close') {
window.console.log(error);
}
this.stopSubmitLoading(loading); this.stopSubmitLoading(loading);
} });
); },
rowSave(row, done, loading) {
this.submitRow(row, done, loading);
},
rowUpdate(row, index, done, loading) {
this.submitRow(row, done, loading);
}, },
rowDel(row) { rowDel(row) {
this.$confirm('确定将选择数据删除?', { this.$confirm('确定将选择数据删除?', {
@@ -920,7 +983,11 @@ export default {
onLoad(page, params = {}) { onLoad(page, params = {}) {
this.loading = true; this.loading = true;
api api
.getList(page.currentPage, page.pageSize, { ...params, ...this.query, allDept: 0 }) .getList(page.currentPage, page.pageSize, {
...params,
...this.query,
allDept: this.isAdmin ? 1 : 0,
})
.then(res => { .then(res => {
const result = res.data.data || {}; const result = res.data.data || {};
this.page.total = result.total || 0; this.page.total = result.total || 0;
@@ -934,7 +1001,7 @@ export default {
buildExportParams() { buildExportParams() {
return { return {
...this.query, ...this.query,
allDept: 0, allDept: this.isAdmin ? 1 : 0,
ids: this.ids, ids: this.ids,
[this.website.tokenHeader]: getToken(), [this.website.tokenHeader]: getToken(),
}; };
@@ -1010,6 +1077,17 @@ export default {
font-weight: 500; font-weight: 500;
text-align: right; text-align: right;
white-space: nowrap; white-space: nowrap;
&--required::before {
margin-right: 4px;
color: #f56c6c;
content: '*';
}
}
&__node-value {
white-space: normal;
word-break: break-all;
} }
&__finish-days { &__finish-days {

View File

@@ -20,7 +20,6 @@
<el-button <el-button
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
plain
v-if="canMajorProjectSupplement" v-if="canMajorProjectSupplement"
@click="openProjectDialog('majorSupplement')" @click="openProjectDialog('majorSupplement')"
> >
@@ -29,7 +28,6 @@
<el-button <el-button
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
plain
v-if="canCreate" v-if="canCreate"
@click="openProjectDialog('add')" @click="openProjectDialog('add')"
> >
@@ -62,27 +60,25 @@
</template> </template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-button <el-link
type="primary" type="primary"
text
v-if="hasPermission(`${config.permission}_view`)" v-if="hasPermission(`${config.permission}_view`)"
@click="openProjectDialog('view', row)" @click="openProjectDialog('view', row)"
> >
查看 查看
</el-button> </el-link>
<el-button type="primary" text v-if="canEdit(row)" @click="openProjectDialog('edit', row)"> <el-link type="primary" v-if="canEdit(row)" @click="openProjectDialog('edit', row)">
编辑 编辑
</el-button> </el-link>
<el-button <el-link
v-for="operation in customOperations(row)" v-for="operation in customOperations(row)"
:key="operation.action" :key="operation.action"
:type="operation.type || 'primary'" :type="operation.type || 'primary'"
text
@click="handleCustomOperation(operation, row)" @click="handleCustomOperation(operation, row)"
> >
{{ operation.label }} {{ operation.label }}
</el-button> </el-link>
<el-button type="primary" text v-if="canDelete(row)" @click="rowDel(row)"> 删除 </el-button> <el-link type="primary" v-if="canDelete(row)" @click="rowDel(row)"> 删除 </el-link>
</template> </template>
</avue-crud> </avue-crud>
@@ -178,21 +174,16 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="承办部门" prop="undertakeDeptId"> <el-form-item label="承办部门" prop="undertakeDeptId">
<el-select <el-cascader
v-model="form.undertakeDeptId" v-model="form.undertakeDeptId"
placeholder="请选择承办部门" placeholder="请选择承办部门"
filterable
clearable clearable
filterable
:options="deptTreeOptions"
:props="deptCascaderProps"
:disabled="isBasicInfoReadonly" :disabled="isBasicInfoReadonly"
@change="handleUndertakeDeptChange" @change="handleUndertakeDeptChange"
> />
<el-option
v-for="item in deptOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="项目由来" prop="projectSource"> <el-form-item label="项目由来" prop="projectSource">
<el-select <el-select
@@ -219,9 +210,11 @@
<el-form-item label="客户名称" prop="customerNames"> <el-form-item label="客户名称" prop="customerNames">
<el-select <el-select
v-model="selectedCustomerId" v-model="selectedCustomerId"
placeholder="搜索选择" placeholder="支持多选,选择后下方列表展示"
multiple
filterable filterable
clearable clearable
collapse-tags
:loading="customerLoading" :loading="customerLoading"
@visible-change="visible => visible && loadCustomerOptions('客户')" @visible-change="visible => visible && loadCustomerOptions('客户')"
@change="handleCustomerChange" @change="handleCustomerChange"
@@ -259,6 +252,7 @@
v-model="form.fundLimit" v-model="form.fundLimit"
placeholder="实际业务回款周期内付款额度" placeholder="实际业务回款周期内付款额度"
@input="value => handleNumberInput('fundLimit', value)" @input="value => handleNumberInput('fundLimit', value)"
@blur="formatAmountField('fundLimit')"
/> />
</el-form-item> </el-form-item>
<el-form-item label="项目应收账款额度万元" prop="receivableLimit"> <el-form-item label="项目应收账款额度万元" prop="receivableLimit">
@@ -266,6 +260,7 @@
v-model="form.receivableLimit" v-model="form.receivableLimit"
placeholder="回款期内最大应收账款额度" placeholder="回款期内最大应收账款额度"
@input="value => handleNumberInput('receivableLimit', value)" @input="value => handleNumberInput('receivableLimit', value)"
@blur="formatAmountField('receivableLimit')"
/> />
</el-form-item> </el-form-item>
<el-form-item label="应收账款回款期限" prop="receivableDays"> <el-form-item label="应收账款回款期限" prop="receivableDays">
@@ -468,66 +463,75 @@
</el-form-item> </el-form-item>
</div> </div>
<div class="project-apply-form__material-head"> <div class="project-apply-form__material-card">
<div class="dialog-section-title project-apply-form__material-title">项目材料</div> <div class="project-apply-form__material-head">
<el-button type="primary" :disabled="!attachmentRows.length" @click="handleBatchDownload" <div class="dialog-section-title project-apply-form__material-title">项目材料</div>
>批量下载</el-button <el-button
> type="primary"
</div> :disabled="!attachmentRows.length"
<el-table :data="attachmentRows" border class="project-apply-form__table"> @click="handleBatchDownload"
<el-table-column type="index" label="序号" width="80" align="center" /> >批量下载</el-button
<el-table-column label="附件类型" min-width="160" align="center"> >
<template #default="{ row }"> </div>
<el-select v-model="row.fileType" placeholder="请选择" :disabled="dialogReadonly"> <el-alert
<el-option v-if="dialogReadonly && missingRequiredAttachmentTypes.length"
v-for="item in projectAttachmentTypeOptions" class="project-apply-form__material-warning"
:key="item.value" type="warning"
:label="item.label" :closable="false"
:value="item.value" show-icon
:title="`缺少必需材料:${missingRequiredAttachmentTypes.join('、')}`"
/>
<el-table :data="attachmentRows" border class="project-apply-form__material-table">
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column label="附件类型" min-width="160" align="center">
<template #default="{ row }">
<el-select v-model="row.fileType" placeholder="请选择" :disabled="dialogReadonly">
<el-option
v-for="item in projectAttachmentTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="文件名" min-width="220" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.originalName || row.name }}</template>
</el-table-column>
<el-table-column label="附件描述" min-width="240" align="center">
<template #default="{ row }">
<el-input
v-model="row.description"
placeholder="请输入附件描述"
:disabled="dialogReadonly"
/> />
</el-select> </template>
</template> </el-table-column>
</el-table-column> <el-table-column label="文件大小" min-width="120" align="center">
<el-table-column label="文件名" min-width="220" align="center" show-overflow-tooltip> <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
<template #default="{ row }">{{ row.originalName || row.name }}</template> </el-table-column>
</el-table-column> <el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
<el-table-column label="附件描述" min-width="240" align="center"> <el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" />
<template #default="{ row }"> <el-table-column label="操作" width="110" align="center">
<el-input <template #default="{ row, $index }">
v-model="row.description" <el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
placeholder="请输入附件描述" <el-link v-if="!dialogReadonly" type="primary" @click="removeAttachment($index)"
:disabled="dialogReadonly" >删除</el-link
/> >
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="文件大小" min-width="120" align="center"> </el-table>
<template #default="{ row }">{{ formatFileSize(row.size) }}</template> <vehicle-attachment-upload
</el-table-column> v-model="attachmentRows"
<el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" /> class="project-apply-form__upload"
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" /> :readonly="dialogReadonly"
<el-table-column label="操作" width="110" align="center"> :file-types="attachmentFileTypes"
<template #default="{ row, $index }"> :max-size="50"
<el-button type="primary" text @click="downloadAttachment(row)">下载</el-button> button-text="上传附件"
<el-button tip="支持pdfbmpjpegpngjpgdocdocxpptpptxxlsxxlsemlmsgzip的文件格式单个文件大小限制50M"
v-if="!dialogReadonly" @change="handleAttachmentChange"
type="primary" />
text </div>
@click="removeAttachment($index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<vehicle-attachment-upload
v-model="attachmentRows"
class="project-apply-form__upload"
:readonly="dialogReadonly"
:file-types="attachmentFileTypes"
:max-size="500"
button-text="上传附件"
tip="支持pdfbmpjpegpngjpgdocdocxpptpptxxlsxxlsemlmsgzip的文件格式单个文件大小限制500M"
@change="handleAttachmentChange"
/>
<div v-if="!isChangeDialog" class="dialog-section-title project-apply-form__table-title"> <div v-if="!isChangeDialog" class="dialog-section-title project-apply-form__table-title">
变更记录 变更记录
@@ -559,9 +563,9 @@
<el-table-column prop="status" label="状态" min-width="120" align="center" /> <el-table-column prop="status" label="状态" min-width="120" align="center" />
<el-table-column label="操作" width="100" align="center"> <el-table-column label="操作" width="100" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" text @click="handleChangeRecordAction(row)"> <el-link type="primary" @click="handleChangeRecordAction(row)">
{{ row.action || '查看' }} {{ row.action || '查看' }}
</el-button> </el-link>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -598,16 +602,24 @@
:class="{ 'project-apply-dialog__footer--change': isChangeDialog }" :class="{ 'project-apply-dialog__footer--change': isChangeDialog }"
> >
<template v-if="isChangeDialog"> <template v-if="isChangeDialog">
<el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
提交
</el-button>
<el-button type="primary" :loading="submitLoading" @click="saveChangeProject"> <el-button type="primary" :loading="submitLoading" @click="saveChangeProject">
保存 保存
</el-button> </el-button>
<el-button @click="projectBox = false">关闭</el-button> <el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
提交
</el-button>
<el-button @click="handleCancelProject">取消</el-button>
</template> </template>
<template v-else> <template v-else>
<el-button @click="projectBox = false">取消</el-button> <el-button @click="handleCancelProject">取消</el-button>
<el-button
v-if="!dialogReadonly"
type="primary"
:loading="submitLoading"
@click="saveProject"
>
保存
</el-button>
<el-button <el-button
v-if="!dialogReadonly" v-if="!dialogReadonly"
type="primary" type="primary"
@@ -652,7 +664,7 @@
<el-table-column prop="deptName" label="所属部门" min-width="180" align="center" /> <el-table-column prop="deptName" label="所属部门" min-width="180" align="center" />
<el-table-column label="操作" width="100" align="center"> <el-table-column label="操作" width="100" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" text @click="selectUser(row)">选择</el-button> <el-link type="primary" @click="selectUser(row)">选择</el-link>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -683,7 +695,6 @@ import { getDictionary as getSystemDictionary } from '@/api/system/dict';
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz'; import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
import { getList as getUserList } from '@/api/system/user'; import { getList as getUserList } from '@/api/system/user';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util'; import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
@@ -748,6 +759,7 @@ const changeTypeOptions = [
]; ];
const normalProjectAttachmentTypeOptions = [ const normalProjectAttachmentTypeOptions = [
'项目立项报告',
'利润测算表', '利润测算表',
'单一来源供应商申请表/三方比价表', '单一来源供应商申请表/三方比价表',
'合同模板', '合同模板',
@@ -757,6 +769,7 @@ const normalProjectAttachmentTypeOptions = [
].map(item => ({ label: item, value: item })); ].map(item => ({ label: item, value: item }));
const majorProjectAttachmentTypeOptions = [ const majorProjectAttachmentTypeOptions = [
'项目立项报告',
'会议纪要', '会议纪要',
'评审表决票', '评审表决票',
'利润测算表', '利润测算表',
@@ -878,6 +891,8 @@ export default {
fundDemand: [{ label: '资金需求', validator: validateAmount, trigger: 'blur' }], fundDemand: [{ label: '资金需求', validator: validateAmount, trigger: 'blur' }],
handlerUserId: [ handlerUserId: [
{ {
required: true,
message: '请选择项目经办人',
label: '项目经办人', label: '项目经办人',
nameProp: 'handlerUserName', nameProp: 'handlerUserName',
validator: validateUser, validator: validateUser,
@@ -886,6 +901,8 @@ export default {
], ],
principalUserId: [ principalUserId: [
{ {
required: true,
message: '请选择项目负责人',
label: '项目负责人', label: '项目负责人',
nameProp: 'principalUserName', nameProp: 'principalUserName',
validator: validateUser, validator: validateUser,
@@ -902,8 +919,16 @@ export default {
], ],
}, },
deptOptions: [], deptOptions: [],
deptTreeOptions: [],
deptCascaderProps: {
label: 'label',
value: 'value',
children: 'children',
emitPath: false,
checkStrictly: true,
},
cargoTypeOptions: [], cargoTypeOptions: [],
selectedCustomerId: '', selectedCustomerId: [],
selectedCarrierIds: [], selectedCarrierIds: [],
customerOptions: [], customerOptions: [],
carrierOptions: [], carrierOptions: [],
@@ -996,6 +1021,12 @@ export default {
? majorProjectAttachmentTypeOptions ? majorProjectAttachmentTypeOptions
: normalProjectAttachmentTypeOptions; : normalProjectAttachmentTypeOptions;
}, },
missingRequiredAttachmentTypes() {
const uploadedTypes = new Set(this.attachmentRows.map(item => item.fileType).filter(Boolean));
return this.projectAttachmentTypeOptions
.map(item => item.value)
.filter(fileType => !uploadedTypes.has(fileType));
},
}, },
created() { created() {
this.loadDeptOptions(); this.loadDeptOptions();
@@ -1004,18 +1035,17 @@ export default {
}, },
methods: { methods: {
buildTableOption() { buildTableOption() {
return applyTableMenuWidth( return {
{ ...option,
...option, dialogType: 'dialog',
dialogType: 'dialog', addBtn: false,
addBtn: false, viewBtn: false,
viewBtn: false, editBtn: false,
editBtn: false, delBtn: false,
delBtn: false, // 操作列收窄本项目操作按钮较少320px 过宽,调整为 220px链接会自动换行兜底
column: (option.column || []).map(column => ({ ...column, display: false })), menuWidth: 180,
}, column: (option.column || []).map(column => ({ ...column, display: false })),
4 };
);
}, },
hasPermission(code) { hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false); return this.isAdmin || this.validData(this.permission && this.permission[code], false);
@@ -1236,7 +1266,7 @@ export default {
if (type === 'add' || type === 'majorSupplement') { if (type === 'add' || type === 'majorSupplement') {
this.applyProjectDetail({ this.applyProjectDetail({
...emptyForm(), ...emptyForm(),
projectType: type === 'majorSupplement' ? '重大项目' : '', projectType: type === 'majorSupplement' ? '重大项目' : '普通项目',
}); });
this.fillDefaultUsers(); this.fillDefaultUsers();
return; return;
@@ -1256,21 +1286,35 @@ export default {
this.carrierRows = []; this.carrierRows = [];
this.attachmentRows = []; this.attachmentRows = [];
this.changeRows = []; this.changeRows = [];
this.selectedCustomerId = ''; this.selectedCustomerId = [];
this.selectedCarrierIds = []; this.selectedCarrierIds = [];
}, },
applyProjectDetail(row) { applyProjectDetail(row) {
const displayRow = {
...row,
fundLimit: this.formatAmount(row.fundLimit),
receivableLimit: this.formatAmount(row.receivableLimit),
...(this.dialogReadonly
? {
projectScale: this.normalizeReadonlyAmount(row.projectScale),
estimatedProfit: this.normalizeReadonlyAmount(row.estimatedProfit),
fundDemand: this.normalizeReadonlyAmount(row.fundDemand),
}
: {}),
};
const form = { const form = {
...emptyForm(), ...emptyForm(),
...row, ...displayRow,
handlerUserName: this.resolveUserRealName(displayRow, 'handler'),
principalUserName: this.resolveUserRealName(displayRow, 'principal'),
businessDateRange: businessDateRange:
row.businessStartDate && row.businessEndDate displayRow.businessStartDate && displayRow.businessEndDate
? [row.businessStartDate, row.businessEndDate] ? [displayRow.businessStartDate, displayRow.businessEndDate]
: [], : [],
}; };
const situation = this.parseSituation(row.situationRemark); const situation = this.parseSituation(displayRow.situationRemark);
const customerRows = this.parseJsonArray(row.customerJson); const customerRows = this.parseJsonArray(displayRow.customerJson);
const carrierRows = this.parseJsonArray(row.carrierJson); const carrierRows = this.parseJsonArray(displayRow.carrierJson);
this.form = { this.form = {
...form, ...form,
...situation, ...situation,
@@ -1279,9 +1323,11 @@ export default {
}; };
this.customerRows = customerRows; this.customerRows = customerRows;
this.carrierRows = carrierRows; this.carrierRows = carrierRows;
this.attachmentRows = this.parseJsonArray(row.attachmentsJson); this.attachmentRows = this.parseJsonArray(displayRow.attachmentsJson);
this.changeRows = this.buildChangeRows(row); this.changeRows = this.buildChangeRows(displayRow);
this.selectedCustomerId = this.getCustomerRowId(this.customerRows[0]); this.selectedCustomerId = this.customerRows
.map(item => this.getCustomerRowId(item))
.filter(Boolean);
this.selectedCarrierIds = this.carrierRows this.selectedCarrierIds = this.carrierRows
.map(item => this.getCustomerRowId(item)) .map(item => this.getCustomerRowId(item))
.filter(Boolean); .filter(Boolean);
@@ -1290,7 +1336,13 @@ export default {
fillDefaultUsers() { fillDefaultUsers() {
const userId = this.userInfo?.userId || this.userInfo?.id || ''; const userId = this.userInfo?.userId || this.userInfo?.id || '';
const userName = const userName =
this.userInfo?.realName || this.userInfo?.userName || this.userInfo?.account || ''; this.userInfo?.realName ||
this.userInfo?.real_name ||
this.userInfo?.name ||
this.userInfo?.userName ||
this.userInfo?.user_name ||
this.userInfo?.account ||
'';
if (userId && userName) { if (userId && userName) {
this.form.handlerUserId = userId; this.form.handlerUserId = userId;
this.form.handlerUserName = userName; this.form.handlerUserName = userName;
@@ -1298,6 +1350,16 @@ export default {
this.form.principalUserName = userName; this.form.principalUserName = userName;
} }
}, },
resolveUserRealName(row = {}, role) {
return (
row[`${role}RealName`] ||
row[`${role}UserRealName`] ||
row[`${role}User`]?.realName ||
row[role]?.realName ||
row[`${role}UserName`] ||
''
);
},
resolveChangeType(row = {}) { resolveChangeType(row = {}) {
if (row.changeType) return row.changeType; if (row.changeType) return row.changeType;
const node = row.currentNode || ''; const node = row.currentNode || '';
@@ -1333,6 +1395,36 @@ export default {
}); });
}); });
}, },
saveProject() {
this.submitLoading = true;
this.api
.saveDraft(this.normalizeSubmitForm())
.then(res => {
if (res.data?.success === false || res.data?.data === false) {
this.$message.error(res.data?.msg || '保存失败,请联系管理员');
return;
}
this.$message.success('保存成功!');
this.projectBox = false;
this.onLoad(this.page, this.query);
})
.finally(() => {
this.submitLoading = false;
});
},
handleCancelProject() {
if (!['add', 'majorSupplement', 'change'].includes(this.dialogType)) {
this.projectBox = false;
return;
}
this.$confirm('确认后直接关闭弹窗,列表无数据变更,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
this.projectBox = false;
});
},
saveChangeProject() { saveChangeProject() {
this.submitChangeForm(false); this.submitChangeForm(false);
}, },
@@ -1369,6 +1461,8 @@ export default {
const [businessStartDate, businessEndDate] = this.form.businessDateRange || []; const [businessStartDate, businessEndDate] = this.form.businessDateRange || [];
const submitRow = { const submitRow = {
...this.form, ...this.form,
fundLimit: this.formatAmount(this.form.fundLimit),
receivableLimit: this.formatAmount(this.form.receivableLimit),
businessStartDate, businessStartDate,
businessEndDate, businessEndDate,
customerJson: JSON.stringify(this.customerRows), customerJson: JSON.stringify(this.customerRows),
@@ -1482,6 +1576,11 @@ export default {
}) })
.filter(Boolean); .filter(Boolean);
}, },
normalizeReadonlyAmount(value) {
return value === null || value === undefined || value === '' || Number(value) === -1
? ''
: value;
},
mergeCustomerOptions(options = [], selectedOptions = []) { mergeCustomerOptions(options = [], selectedOptions = []) {
const result = [...selectedOptions]; const result = [...selectedOptions];
options.forEach(option => { options.forEach(option => {
@@ -1510,7 +1609,7 @@ export default {
changeType: changeType:
row.changeType || row.changeType ||
(String(row.currentNode || '').includes('备案') ? '项目备案调整' : '项目变更'), (String(row.currentNode || '').includes('备案') ? '项目备案调整' : '项目变更'),
handler: row.handlerUserName || '', handler: this.resolveUserRealName(row, 'handler'),
content: row.changeContent || '', content: row.changeContent || '',
reason: row.changeReason || '', reason: row.changeReason || '',
status: row.approvalStatusName || row.approvalStatus || '', status: row.approvalStatusName || row.approvalStatus || '',
@@ -1534,9 +1633,18 @@ export default {
}, },
loadDeptOptions() { loadDeptOptions() {
getDeptTree().then(res => { getDeptTree().then(res => {
this.deptOptions = this.flattenDept(res.data.data || []); const deptTree = res.data.data || [];
this.deptOptions = this.flattenDept(deptTree);
this.deptTreeOptions = this.toDeptCascaderOptions(deptTree);
}); });
}, },
toDeptCascaderOptions(list = []) {
return list.map(item => ({
label: item.title || item.deptName || item.name,
value: item.id,
children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined,
}));
},
flattenDept(list = [], level = 0) { flattenDept(list = [], level = 0) {
return list.reduce((result, item) => { return list.reduce((result, item) => {
const label = item.title || item.deptName || item.name; const label = item.title || item.deptName || item.name;
@@ -1571,7 +1679,11 @@ export default {
const loadingKey = type === '客户' ? 'customerLoading' : 'carrierLoading'; const loadingKey = type === '客户' ? 'customerLoading' : 'carrierLoading';
const optionsKey = type === '客户' ? 'customerOptions' : 'carrierOptions'; const optionsKey = type === '客户' ? 'customerOptions' : 'carrierOptions';
this[loadingKey] = true; this[loadingKey] = true;
getCustomerArchiveList(1, 100, { customerType: type, status: 1 }) getCustomerArchiveList(1, 100, {
customerType: type,
status: 1,
...(type === '客户' ? { approvalStatus: 'approved' } : {}),
})
.then(res => { .then(res => {
const records = (res.data.data && res.data.data.records) || []; const records = (res.data.data && res.data.data.records) || [];
const selectedOptions = this.buildCustomerOptionsFromRows( const selectedOptions = this.buildCustomerOptionsFromRows(
@@ -1595,16 +1707,16 @@ export default {
raw: item, raw: item,
}; };
}, },
handleCustomerChange(id) { handleCustomerChange(ids) {
if (!id) { if (!ids || !ids.length) {
this.customerRows = []; this.customerRows = [];
this.form.customerNames = ''; this.form.customerNames = '';
this.form.customerJson = ''; this.form.customerJson = '';
return; return;
} }
this.loadCustomerDetailRow(id, 'customer').then(row => { Promise.all(ids.map(id => this.loadCustomerDetailRow(id, 'customer'))).then(rows => {
this.customerRows = [row]; this.customerRows = rows;
this.form.customerNames = row.customer; this.form.customerNames = rows.map(item => item.customer).join('、');
this.form.customerJson = JSON.stringify(this.customerRows); this.form.customerJson = JSON.stringify(this.customerRows);
this.syncSelectedCustomerOptions(); this.syncSelectedCustomerOptions();
this.$refs.projectForm?.validateField('customerNames'); this.$refs.projectForm?.validateField('customerNames');
@@ -1657,6 +1769,14 @@ export default {
this.form[prop] = this.form[prop] =
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0]; parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
}, },
formatAmount(value) {
if (value === null || value === undefined || value === '' || Number(value) === -1) return '';
const amount = Number(value);
return Number.isFinite(amount) ? amount.toFixed(2) : value;
},
formatAmountField(prop) {
this.form[prop] = this.formatAmount(this.form[prop]);
},
handleIntegerInput(prop, value) { handleIntegerInput(prop, value) {
this.form[prop] = String(value || '').replace(/[^\d]/g, ''); this.form[prop] = String(value || '').replace(/[^\d]/g, '');
}, },
@@ -1737,7 +1857,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.project-apply-form { .project-apply-form {
padding: 0 8px; padding: 0;
:deep(.el-input), :deep(.el-input),
:deep(.el-select), :deep(.el-select),
@@ -1755,12 +1875,30 @@ export default {
line-height: 20px; line-height: 20px;
} }
// 分区标题融入白色卡片顶部(与下方内容块拼成一张完整卡片)
:deep(.dialog-section-title:not(.project-apply-form__material-title)) {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 8px;
background: #fff;
border-radius: 6px 6px 0 0;
padding: 14px 16px 4px;
margin-bottom: 0;
font-weight: 500;
}
&__grid { &__grid {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
column-gap: 72px; column-gap: 72px;
row-gap: 0; row-gap: 0;
margin-bottom: 8px; margin-bottom: 16px;
background: #fff;
border-radius: 0 0 6px 6px;
padding: 14px 16px 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
} }
&__picker-icon { &__picker-icon {
@@ -1782,7 +1920,11 @@ export default {
&__table { &__table {
width: 100%; width: 100%;
margin-bottom: 24px; margin-bottom: 16px;
background: #fff;
border-radius: 0 0 6px 6px;
padding: 12px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
:deep(.el-table__header th) { :deep(.el-table__header th) {
background: #f5f7fa; background: #f5f7fa;
@@ -1796,6 +1938,12 @@ export default {
} }
&__textarea-list { &__textarea-list {
margin-bottom: 16px;
background: #fff;
border-radius: 0 0 6px 6px;
padding: 14px 16px 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
:deep(.el-form-item) { :deep(.el-form-item) {
align-items: flex-start; align-items: flex-start;
} }
@@ -1809,12 +1957,38 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-top: 8px; margin-bottom: 12px;
}
&__material-card {
margin-bottom: 16px;
background: #fff;
border-radius: 6px;
padding: 14px 16px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
&__material-table {
width: 100%;
margin-bottom: 12px;
:deep(.el-table__header th) {
background: #f5f7fa;
color: #303133;
font-weight: 600;
}
:deep(.el-table__cell) {
border-color: #eff1f7;
}
}
&__material-warning {
margin-bottom: 12px;
} }
&__material-title { &__material-title {
flex: 1; flex: 1;
margin-bottom: 14px;
} }
&__material-warn { &__material-warn {
@@ -1825,11 +1999,19 @@ export default {
} }
&__upload { &__upload {
margin: -12px 0 24px; margin: 0 0 16px;
background: #fff;
border-radius: 6px;
padding: 12px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
} }
&__change-textarea { &__change-textarea {
margin-bottom: 24px; margin-bottom: 16px;
background: #fff;
border-radius: 0 0 6px 6px;
padding: 14px 16px 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
:deep(.el-form-item__content) { :deep(.el-form-item__content) {
width: 100%; width: 100%;
@@ -1841,7 +2023,9 @@ export default {
:global(.project-apply-dialog .el-dialog__body) { :global(.project-apply-dialog .el-dialog__body) {
max-height: 76vh; max-height: 76vh;
overflow-y: auto; overflow-y: auto;
padding: 16px 24px 8px; overflow-x: hidden;
padding: 16px;
background: #f5f6fa;
} }
.project-apply-dialog__footer { .project-apply-dialog__footer {

View File

@@ -1,5 +1,5 @@
<template> <template>
<business-crud-page :api="api" :config="config" :crud-option="option" /> <business-crud-page :api="api" :config="config" :crud-option="option" :menu-width="270" />
</template> </template>
<script> <script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<business-crud-page :api="api" :config="config" :crud-option="option" /> <business-crud-page :api="api" :config="config" :crud-option="option" :menu-width="270" />
</template> </template>
<script> <script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<business-crud-page :api="api" :config="config" :crud-option="option" /> <business-crud-page :api="api" :config="config" :crud-option="option" :menu-width="220" />
</template> </template>
<script> <script>

View File

@@ -23,27 +23,24 @@
</el-radio-group> </el-radio-group>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.flow_manager_state" v-if="permission.flow_manager_state"
@click.stop="handleState(scope.row, scope.index)" @click.stop="handleState(scope.row, scope.index)"
>变更状态 >变更状态
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.flow_manager_image" v-if="permission.flow_manager_image"
@click.stop="handleImage(scope.row, scope.index)" @click.stop="handleImage(scope.row, scope.index)"
>流程图 >流程图
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.flow_manager_remove" v-if="permission.flow_manager_remove"
@click.stop="handleSlotDelete(scope.row, scope.index)" @click.stop="handleSlotDelete(scope.row, scope.index)"
>删除 >删除
</el-button> </el-link>
</template> </template>
<template #tenantId="{ row }"> <template #tenantId="{ row }">
<el-tag>{{ row.tenantId === '' ? '通用' : row.tenantId }}</el-tag> <el-tag>{{ row.tenantId === '' ? '通用' : row.tenantId }}</el-tag>

View File

@@ -36,30 +36,27 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
size="default" size="default"
v-if="permission.flow_model_update" v-if="permission.flow_model_update"
@click.stop="handleUpdate(scope.row, scope.index)" @click.stop="handleUpdate(scope.row, scope.index)"
>配置 >配置
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
size="default" size="default"
v-if="permission.flow_model_deploy" v-if="permission.flow_model_deploy"
@click.stop="handleDeploy(scope.row, scope.index)" @click.stop="handleDeploy(scope.row, scope.index)"
>部署 >部署
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
size="default" size="default"
v-if="permission.flow_model_delete" v-if="permission.flow_model_delete"
@click.stop="handleSlotDelete(scope.row, scope.index)" @click.stop="handleSlotDelete(scope.row, scope.index)"
>删除 >删除
</el-button> </el-link>
</template> </template>
<template #version="{ row }"> <template #version="{ row }">
<el-tag>v{{ row.version }}</el-tag> <el-tag>v{{ row.version }}</el-tag>

View File

@@ -35,9 +35,9 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button type="primary" text @click="handleRun(scope.row)" <el-link type="primary" @click="handleRun(scope.row)"
> >
</el-button> </el-link>
</template> </template>
<template #enable="{ row }"> <template #enable="{ row }">
<el-switch <el-switch

View File

@@ -35,9 +35,9 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button type="primary" text @click="handleLink(scope.row)" <el-link type="primary" @click="handleLink(scope.row)"
>服务跳转 >服务跳转
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>
</basic-container> </basic-container>

View File

@@ -23,20 +23,18 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
@click.stop="handleDesign(scope.row.name)" @click.stop="handleDesign(scope.row.name)"
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
>设计 >设计
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
@click.stop="handlePreview(scope.row.name)" @click.stop="handlePreview(scope.row.name)"
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
>预览 >预览
</el-button> </el-link>
</template> </template>
<template #name="{ row }"> <template #name="{ row }">
<el-tag style="cursor: pointer" @click="handlePreview(row.name)">{{ row.name }}</el-tag> <el-tag style="cursor: pointer" @click="handlePreview(row.name)">{{ row.name }}</el-tag>

View File

@@ -37,13 +37,12 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.attach_download" v-if="permission.attach_download"
@click="handleDownload(scope.row)" @click="handleDownload(scope.row)"
>下载 >下载
</el-button> </el-link>
</template> </template>
<template #attachSize="{ row }"> <template #attachSize="{ row }">
<el-tag>{{ formatFileSize(row.attachSize) }}</el-tag> <el-tag>{{ formatFileSize(row.attachSize) }}</el-tag>

View File

@@ -31,20 +31,18 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
@click="handleDebug(scope.row)" @click="handleDebug(scope.row)"
>调试 >调试
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.oss_enable" v-if="permission.oss_enable"
@click.stop="handleEnable(scope.row)" @click.stop="handleEnable(scope.row)"
>启用 >启用
</el-button> </el-link>
</template> </template>
<template #status="{ row }"> <template #status="{ row }">
<el-tag>{{ row.statusName }}</el-tag> <el-tag>{{ row.statusName }}</el-tag>

View File

@@ -31,20 +31,18 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
@click="handleDebug(scope.row)" @click="handleDebug(scope.row)"
>调试 >调试
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.sms_enable" v-if="permission.sms_enable"
@click.stop="handleEnable(scope.row)" @click.stop="handleEnable(scope.row)"
>启用 >启用
</el-button> </el-link>
</template> </template>
<template #status="{ row }"> <template #status="{ row }">
<el-tag>{{ row.statusName }}</el-tag> <el-tag>{{ row.statusName }}</el-tag>

View File

@@ -46,19 +46,16 @@
{{ row.lockEndTime || '永久' }} {{ row.lockEndTime || '永久' }}
</template> </template>
<template #menu="{ row }"> <template #menu="{ row }">
<el-button <el-link
text
type="primary" type="primary"
@click.stop="$refs.authLockCrud.rowView(row)" @click.stop="$refs.authLockCrud.rowView(row)"
>查看</el-button >查看</el-link
> >
<el-button <el-link
v-if="(row.lockStatus === 'LOCKED' || row.lockStatus === 'PRE_LOCKED') && !row.expired" v-if="(row.lockStatus === 'LOCKED' || row.lockStatus === 'PRE_LOCKED') && !row.expired"
text
type="primary" type="primary"
@click.stop="handleAuthLockUnlock(row)" @click.stop="handleAuthLockUnlock(row)"
>解锁</el-button >解锁</el-link>
>
</template> </template>
</avue-crud> </avue-crud>
</el-drawer> </el-drawer>

View File

@@ -18,12 +18,10 @@
@on-load="authLogOnLoad" @on-load="authLogOnLoad"
> >
<template #menu="{ row }"> <template #menu="{ row }">
<el-button <el-link
text
type="primary" type="primary"
@click.stop="$refs.authLogCrud.rowView(row)" @click.stop="$refs.authLogCrud.rowView(row)"
>查看</el-button >查看</el-link>
>
</template> </template>
<template #account="{ row }"> <template #account="{ row }">
<el-tag size="small" effect="light">{{ row.account }}</el-tag> <el-tag size="small" effect="light">{{ row.account }}</el-tag>

View File

@@ -32,32 +32,129 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
@click.stop="handleAdd(scope.row, scope.index)" @click.stop="handleAdd(scope.row, scope.index)"
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
>新增子项 >新增子项
</el-button> </el-link>
</template> </template>
<template #deptCategory="{ row }"> <template #deptCategory="{ row }">
<el-tag>{{ row.deptCategoryName }}</el-tag> <el-tag>{{ getDeptCategoryLabel(row.deptCategory) }}</el-tag>
</template>
<template #leaderId="{ row }">
{{ getLeaderValue(row.leaderId, 'realName') }}
</template>
<template #leaderPhone="{ row }">
{{ getLeaderValue(row.leaderId, 'phone') }}
</template>
<template #status="{ row }">
<el-tag :type="Number(row.status) === 1 ? 'success' : 'info'">
{{ Number(row.status) === 1 ? '启用' : '停用' }}
</el-tag>
</template> </template>
</avue-crud> </avue-crud>
<el-dialog v-model="carrierDialogVisible" title="选择承运商" width="1100px" append-to-body>
<el-form :inline="true" :model="carrierQuery" class="carrier-search-form">
<el-form-item label="名称">
<el-input v-model="carrierQuery.fullName" placeholder="请输入承运商名称" clearable />
</el-form-item>
<el-form-item label="准入标准">
<el-select v-model="carrierQuery.accessType" placeholder="全部" clearable>
<el-option label="临时" value="temporary" />
<el-option label="正式" value="formal" />
</el-select>
</el-form-item>
<el-form-item label="审批状态">
<el-select v-model="carrierQuery.approvalStatus" placeholder="全部" clearable>
<el-option label="草稿" value="draft" />
<el-option label="审核中" value="reviewing" />
<el-option label="审核通过" value="approved" />
<el-option label="审核不通过" value="rejected" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="carrierQuery.status" placeholder="全部" clearable>
<el-option label="启用" :value="1" />
<el-option label="停用" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchCarrier">搜索</el-button>
<el-button @click="resetCarrierQuery">清空</el-button>
</el-form-item>
</el-form>
<el-table :data="carrierData" border stripe height="420" @row-dblclick="selectCarrier">
<el-table-column prop="customerCode" label="客商编号" min-width="130" />
<el-table-column prop="shortName" label="客商简称" min-width="140" />
<el-table-column prop="fullName" label="客商名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="customerType" label="客户类型" min-width="120" />
<el-table-column prop="customerNature" label="客户性质" min-width="120" />
<el-table-column prop="unifiedCreditCode" label="统一信用代码" min-width="180" />
<el-table-column prop="deptName" label="所属组织" min-width="150" />
<el-table-column prop="accessType" label="准入标准" min-width="110">
<template #default="{ row }">{{ formatAccessType(row.accessType) }}</template>
</el-table-column>
<el-table-column prop="approvalStatus" label="审批状态" min-width="120">
<template #default="{ row }">{{ formatApprovalStatus(row.approvalStatus) }}</template>
</el-table-column>
<el-table-column prop="currentNode" label="当前节点" min-width="130" />
<el-table-column prop="currentProcessor" label="当前处理人" min-width="130" />
<el-table-column prop="approvedTime" label="审核通过时间" min-width="170" />
<el-table-column prop="status" label="状态" width="90">
<template #default="{ row }">{{ Number(row.status) === 1 ? '启用' : '停用' }}</template>
</el-table-column>
<el-table-column label="操作" width="90" fixed="right">
<template #default="{ row }">
<el-link type="primary" @click="selectCarrier(row)">选择</el-link>
</template>
</el-table-column>
</el-table>
<div class="carrier-pagination">
<el-pagination
v-model:current-page="carrierPage.current"
v-model:page-size="carrierPage.size"
:total="carrierPage.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@current-change="loadCarrierList"
@size-change="handleCarrierSizeChange"
/>
</div>
</el-dialog>
</basic-container> </basic-container>
</template> </template>
<script> <script>
import { getLazyList, remove, update, add, getDept, getDeptTree } from '@/api/system/dept'; import { getLazyList, remove, update, add, getDept, getDeptTree } from '@/api/system/dept';
import { getLeaderList } from '@/api/system/user'; import { getLeaderList } from '@/api/system/user';
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import website from '@/config/website'; import website from '@/config/website';
import func from '@/utils/func'; import func from '@/utils/func';
const escapeRegExp = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export default { export default {
data() { data() {
return { return {
form: {}, form: {},
leaderOptions: [],
parentDeptCode: '',
parentDept: null,
carrierDialogVisible: false,
carrierData: [],
carrierQuery: {
fullName: '',
accessType: '',
approvalStatus: '',
status: undefined,
},
carrierPage: {
current: 1,
size: 10,
total: 0,
},
selectionList: [], selectionList: [],
query: {}, query: {},
loading: true, loading: true,
@@ -74,29 +171,170 @@ export default {
searchShow: true, searchShow: true,
searchMenuSpan: 24, searchMenuSpan: 24,
searchIcon: true, searchIcon: true,
searchIndex: 8, searchIndex: 4,
searchMenuPosition: 'right', searchMenuPosition: 'right',
tree: true, tree: true,
border: true, border: true,
index: true, index: true,
selection: true, selection: true,
viewBtn: true, viewBtn: true,
menuWidth: 160, menuWidth: 320,
dialogWidth: 800, dialogWidth: 800,
dialogClickModal: false, dialogClickModal: false,
labelPosition: 'right',
labelWidth: 'auto',
column: [ column: [
{ {
label: '机构名称', label: '组织名称',
prop: 'deptName', prop: 'deptName',
minWidth: 160,
order: 80,
search: true, search: true,
rules: [ rules: [
{ {
required: true, required: true,
message: '请输入机构名称', message: '请输入组织名称',
trigger: 'blur', trigger: 'blur',
}, },
], ],
}, },
{
label: '组织编码',
prop: 'deptCode',
minWidth: 140,
order: 70,
maxlength: 30,
showWordLimit: true,
rules: [
{
required: true,
message: '请输入组织编码',
trigger: 'blur',
},
{
validator: (rule, value, callback) => {
const deptCode = String(value || '').trim();
if (deptCode.length > 30) {
callback(new Error('组织编码不能超过30个字符'));
return;
}
if (!this.parentDeptCode) {
callback(new Error('请先选择上级组织'));
return;
}
const pattern = new RegExp(`^${escapeRegExp(this.parentDeptCode)}-\\d+$`);
if (!pattern.test(deptCode)) {
callback(new Error(`组织编码格式应为:${this.parentDeptCode}-分段数字`));
return;
}
callback();
},
trigger: 'blur',
},
],
},
{
label: '组织简称',
prop: 'shortName',
minWidth: 140,
order: 60,
},
{
label: '组织类型',
type: 'select',
dicData: [
{ label: '一级集团', value: 1 },
{ label: '二级事业部', value: 2 },
{ label: '部门', value: 3 },
{ label: '三级分公司', value: 4 },
{ label: '物流园区', value: 5 },
{ label: '承运商', value: 6 },
],
dataType: 'number',
minWidth: 120,
order: 90,
prop: 'deptCategory',
slot: true,
change: ({ value }) => this.handleDeptCategoryChange(value),
rules: [
{
required: true,
message: '请选择组织类型',
trigger: 'change',
},
],
},
{
label: '负责人',
prop: 'leaderId',
minWidth: 120,
slot: true,
addDisplay: false,
editDisplay: false,
},
{
label: '联系电话',
prop: 'leaderPhone',
minWidth: 140,
slot: true,
addDisplay: false,
editDisplay: false,
},
{
label: '助记码',
prop: 'mnemonicCode',
minWidth: 120,
order: 40,
},
{
label: '拼音助记码',
prop: 'pinyinMnemonic',
minWidth: 140,
order: 50,
},
{
label: '排序',
prop: 'sort',
type: 'number',
align: 'right',
width: 80,
order: 30,
rules: [
{
required: true,
message: '请输入排序',
trigger: 'blur',
},
],
},
{
label: '状态',
prop: 'status',
width: 100,
slot: true,
addDisplay: false,
editDisplay: false,
},
{
label: '上级组织',
prop: 'parentId',
dicData: [],
type: 'tree',
hide: true,
addDisabled: false,
order: 100,
change: ({ value }) => this.handleParentChange(value),
props: {
label: 'title',
},
rules: [
{
required: true,
message: '请选择上级组织',
trigger: 'click',
},
],
},
{ {
label: '所属租户', label: '所属租户',
prop: 'tenantId', prop: 'tenantId',
@@ -105,112 +343,37 @@ export default {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
viewDisplay: website.tenantMode, viewDisplay: website.tenantMode,
span: 24,
props: { props: {
label: 'tenantName', label: 'tenantName',
value: 'tenantId', value: 'tenantId',
}, },
hide: !website.tenantMode, hide: true,
search: website.tenantMode, search: website.tenantMode,
rules: [
{
required: true,
message: '请输入所属租户',
trigger: 'click',
},
],
}, },
{ {
label: '机构全称', label: '机构全称',
prop: 'fullName', prop: 'fullName',
search: true, search: true,
rules: [
{
required: true,
message: '请输入机构全称',
trigger: 'blur',
},
],
},
{
label: '上级机构',
prop: 'parentId',
dicData: [],
type: 'tree',
hide: true, hide: true,
addDisabled: false, addDisplay: false,
props: { editDisplay: false,
label: 'title',
},
rules: [
{
required: false,
message: '请选择上级机构',
trigger: 'click',
},
],
},
{
label: '直属主管',
prop: 'leaderId',
type: 'tree',
multiple: true,
filterable: true,
dicData: [],
props: {
label: 'realName',
value: 'id',
},
clearable: true,
hide: true,
},
{
label: '机构类型',
type: 'select',
dicUrl: '/blade-system/dict/dictionary?code=org_category',
props: {
label: 'dictValue',
value: 'dictKey',
},
dataType: 'number',
width: 120,
prop: 'deptCategory',
slot: true,
rules: [
{
required: true,
message: '请输入机构类型',
trigger: 'blur',
},
],
},
{
label: '排序',
prop: 'sort',
type: 'number',
align: 'right',
width: 80,
rules: [
{
required: true,
message: '请输入排序',
trigger: 'blur',
},
],
}, },
{ {
label: '备注', label: '备注',
prop: 'remark', prop: 'remark',
type: 'textarea', type: 'textarea',
span: 24,
minRows: 2,
hide: true, hide: true,
addDisplay: false,
editDisplay: false,
}, },
], ],
}, },
data: [], data: [],
}; };
}, },
created() {
this.loadLeaderOptions();
},
computed: { computed: {
...mapGetters(['userInfo', 'permission']), ...mapGetters(['userInfo', 'permission']),
permissionList() { permissionList() {
@@ -235,13 +398,130 @@ export default {
const column = this.findColumn(this.option.column, 'parentId'); const column = this.findColumn(this.option.column, 'parentId');
column.dicData = res.data.data; column.dicData = res.data.data;
}); });
getLeaderList(tenantId).then(res => { },
const column = this.findColumn(this.option.column, 'leaderId'); handleParentChange(parentId) {
column.dicData = res.data.data; this.loadParentDeptCode(parentId);
},
loadParentDeptCode(parentId) {
this.parentDeptCode = '';
this.parentDept = null;
if (!parentId || String(parentId) === '0') return;
getDept(parentId).then(res => {
this.parentDept = res.data.data || null;
this.parentDeptCode = this.parentDept?.deptCode || '';
this.updateDeptCategoryOptions();
}); });
}, },
updateDeptCategoryOptions() {
const categoryColumn = this.findColumn(this.option.column, 'deptCategory');
const parent = this.parentDept;
if (!parent) return;
let dicData = [];
if (this.isRootDept(parent)) {
dicData =
parent.deptName === '外部组织'
? [{ label: '承运商', value: 6 }]
: [{ label: '集团', value: 1 }];
} else if (Number(parent.deptCategory) === 1) {
dicData = [
{ label: '事业部', value: 2 },
{ label: '部门', value: 3 },
];
} else if ([2, 3].includes(Number(parent.deptCategory))) {
dicData = [
{ label: '分公司', value: 4 },
{ label: '物流园区', value: 5 },
{ label: '承运商', value: 6 },
];
}
categoryColumn.dicData = dicData;
if (!dicData.some(item => item.value === Number(this.form.deptCategory))) {
this.form.deptCategory = dicData[0]?.value;
}
if (Number(this.form.deptCategory) === 6) {
this.openCarrierDialog();
}
},
handleDeptCategoryChange(value) {
if (Number(value) === 6) {
this.openCarrierDialog();
}
},
openCarrierDialog() {
this.carrierDialogVisible = true;
this.loadCarrierList();
},
loadCarrierList() {
getCustomerArchiveList(this.carrierPage.current, this.carrierPage.size, {
...this.carrierQuery,
customerType: '承运商',
}).then(res => {
const page = res.data.data || {};
this.carrierData = page.records || [];
this.carrierPage.total = page.total || 0;
});
},
searchCarrier() {
this.carrierPage.current = 1;
this.loadCarrierList();
},
resetCarrierQuery() {
this.carrierQuery = { fullName: '', accessType: '', approvalStatus: '', status: undefined };
this.searchCarrier();
},
handleCarrierSizeChange(size) {
this.carrierPage.size = size;
this.carrierPage.current = 1;
this.loadCarrierList();
},
selectCarrier(row) {
this.form.carrierCustomerId = row.id;
this.form.deptName = row.fullName;
this.form.shortName = row.shortName;
this.form.mnemonicCode = row.mnemonicCode;
this.carrierDialogVisible = false;
},
formatAccessType(value) {
return value === 'formal' ? '正式' : value === 'temporary' ? '临时' : '-';
},
formatApprovalStatus(value) {
const statusMap = {
draft: '草稿',
reviewing: '审核中',
approved: '审核通过',
rejected: '审核不通过',
};
return statusMap[value] || '-';
},
loadLeaderOptions(tenantId) {
getLeaderList(tenantId).then(res => {
this.leaderOptions = res.data.data || [];
});
},
getDeptCategoryLabel(deptCategory) {
const categoryMap = {
1: '集团',
2: '事业部',
3: '部门',
4: '分公司',
5: '物流园区',
6: '承运商',
};
return categoryMap[Number(deptCategory)] || '-';
},
getLeaderValue(leaderId, field) {
const values = func.split(leaderId);
const result = values
.map(id => this.leaderOptions.find(item => String(item.id) === String(id))?.[field])
.filter(Boolean);
return result.length ? result.join('、') : '-';
},
isRootDept(row) {
return row && (row.parentId === 0 || String(row.parentId) === '0');
},
handleAdd(row) { handleAdd(row) {
this.parentId = row.id; this.parentId = row.id;
this.loadParentDeptCode(row.id);
const column = this.findColumn(this.option.column, 'parentId'); const column = this.findColumn(this.option.column, 'parentId');
column.value = row.id; column.value = row.id;
column.addDisabled = true; column.addDisabled = true;
@@ -249,7 +529,13 @@ export default {
}, },
rowSave(row, done, loading) { rowSave(row, done, loading) {
row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId; row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId;
row.deptCode = String(row.deptCode || '').trim();
row.leaderId = func.join(row.leaderId); row.leaderId = func.join(row.leaderId);
if (Number(row.deptCategory) === 6 && !row.carrierCustomerId) {
this.$message.warning('请选择承运商');
loading();
return;
}
add(row).then( add(row).then(
res => { res => {
// 获取新增数据的相关字段 // 获取新增数据的相关字段
@@ -272,7 +558,13 @@ export default {
}, },
rowUpdate(row, index, done, loading) { rowUpdate(row, index, done, loading) {
row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId; row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId;
row.deptCode = String(row.deptCode || '').trim();
row.leaderId = func.join(row.leaderId); row.leaderId = func.join(row.leaderId);
if (Number(row.deptCategory) === 6 && !row.carrierCustomerId) {
this.$message.warning('请选择承运商');
loading();
return;
}
update(row).then( update(row).then(
() => { () => {
this.$message({ this.$message({
@@ -289,6 +581,10 @@ export default {
); );
}, },
rowDel(row, index, done) { rowDel(row, index, done) {
if (this.isRootDept(row)) {
this.$message.warning('根节点不能删除');
return;
}
this.$confirm('确定将选择数据删除?', { this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -311,13 +607,22 @@ export default {
this.$message.warning('请选择至少一条数据'); this.$message.warning('请选择至少一条数据');
return; return;
} }
const deletableRows = this.selectionList.filter(row => !this.isRootDept(row));
if (deletableRows.length === 0) {
this.$message.warning('根节点不能删除');
return;
}
if (deletableRows.length !== this.selectionList.length) {
this.$message.warning('根节点不能删除,已忽略根节点');
}
const deletableIds = deletableRows.map(row => row.id).join(',');
this.$confirm('确定将选择数据删除?', { this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}) })
.then(() => { .then(() => {
return remove(this.ids); return remove(deletableIds);
}) })
.then(() => { .then(() => {
// 刷新表格数据并重载 // 刷新表格数据并重载
@@ -356,6 +661,7 @@ export default {
if (type === 'add') { if (type === 'add') {
this.form.tenantId = this.form.tenantId || this.userInfo.tenantId || website.tenantId; this.form.tenantId = this.form.tenantId || this.userInfo.tenantId || website.tenantId;
this.initData(this.form.tenantId); this.initData(this.form.tenantId);
this.loadParentDeptCode(this.form.parentId);
} }
if (type === 'edit') { if (type === 'edit') {
this.initData(this.form.tenantId || this.userInfo.tenantId || website.tenantId); this.initData(this.form.tenantId || this.userInfo.tenantId || website.tenantId);
@@ -368,6 +674,7 @@ export default {
if (this.form.parentId === '0') { if (this.form.parentId === '0') {
this.form.parentId = ''; this.form.parentId = '';
} }
this.loadParentDeptCode(this.form.parentId);
if (this.form.hasOwnProperty('leaderId')) { if (this.form.hasOwnProperty('leaderId')) {
this.form.leaderId = func.split(this.form.leaderId); this.form.leaderId = func.split(this.form.leaderId);
} }
@@ -377,6 +684,8 @@ export default {
}, },
beforeClose(done) { beforeClose(done) {
this.parentId = ''; this.parentId = '';
this.parentDeptCode = '';
this.parentDept = null;
const column = this.findColumn(this.option.column, 'parentId'); const column = this.findColumn(this.option.column, 'parentId');
column.value = ''; column.value = '';
column.addDisabled = false; column.addDisabled = false;
@@ -409,4 +718,15 @@ export default {
}; };
</script> </script>
<style></style> <style scoped>
.carrier-search-form {
padding: 12px 12px 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.carrier-pagination {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
</style>

View File

@@ -32,13 +32,12 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
@click.stop="handleRowClick(scope.row)" @click.stop="handleRowClick(scope.row)"
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
>字典配置 >字典配置
</el-button> </el-link>
</template> </template>
<template #code="{ row }"> <template #code="{ row }">
<el-tag @click="handleRowClick(row)" style="cursor: pointer">{{ row.code }}</el-tag> <el-tag @click="handleRowClick(row)" style="cursor: pointer">{{ row.code }}</el-tag>
@@ -79,13 +78,12 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
@click.stop="handleAdd(scope.row, scope.index)" @click.stop="handleAdd(scope.row, scope.index)"
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
>新增子项 >新增子项
</el-button> </el-link>
</template> </template>
<template #isSealed="{ row }"> <template #isSealed="{ row }">
<el-tag>{{ row.isSealed === 0 ? '否' : '是' }}</el-tag> <el-tag>{{ row.isSealed === 0 ? '否' : '是' }}</el-tag>

View File

@@ -32,13 +32,12 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
@click.stop="handleRowClick(scope.row)" @click.stop="handleRowClick(scope.row)"
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
>字典配置 >字典配置
</el-button> </el-link>
</template> </template>
<template #code="{ row }"> <template #code="{ row }">
<el-tag @click="handleRowClick(row)" style="cursor: pointer">{{ row.code }}</el-tag> <el-tag @click="handleRowClick(row)" style="cursor: pointer">{{ row.code }}</el-tag>
@@ -79,13 +78,12 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
@click.stop="handleAdd(scope.row, scope.index)" @click.stop="handleAdd(scope.row, scope.index)"
v-if="userInfo.authority.includes('admin')" v-if="userInfo.authority.includes('admin')"
>新增子项 >新增子项
</el-button> </el-link>
</template> </template>
<template #isSealed="{ row }"> <template #isSealed="{ row }">
<el-tag>{{ row.isSealed === 0 ? '否' : '是' }}</el-tag> <el-tag>{{ row.isSealed === 0 ? '否' : '是' }}</el-tag>

View File

@@ -32,13 +32,12 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
@click.stop="handleAdd(scope.row, scope.index)" @click.stop="handleAdd(scope.row, scope.index)"
v-if="userInfo.authority.includes('admin') && scope.row.category === 1" v-if="userInfo.authority.includes('admin') && scope.row.category === 1"
>新增子项 >新增子项
</el-button> </el-link>
</template> </template>
<template #name="{ row }"> <template #name="{ row }">
<i :class="row.source" style="margin-right: 5px" /> <i :class="row.source" style="margin-right: 5px" />
@@ -89,7 +88,7 @@ export default {
index: true, index: true,
selection: true, selection: true,
viewBtn: true, viewBtn: true,
menuWidth: 160, menuWidth: 320,
dialogClickModal: false, dialogClickModal: false,
column: [ column: [
{ {
@@ -312,8 +311,42 @@ export default {
column.addDisabled = true; column.addDisabled = true;
this.$refs.crud.rowAdd(); this.$refs.crud.rowAdd();
}, },
normalizeSubmitRow(row = {}, isUpdate = false) {
const submitProps = [
'id',
'parentId',
'code',
'name',
'alias',
'path',
'source',
'component',
'sort',
'category',
'action',
'isOpen',
'isDisplay',
'remark',
];
const submitRow = submitProps.reduce((result, prop) => {
if (row[prop] !== undefined) {
result[prop] = row[prop];
}
return result;
}, {});
if (isUpdate) {
submitRow.id = row.id || this.currentEditMenuId;
} else {
delete submitRow.id;
}
if (submitRow.parentId === '') {
submitRow.parentId = 0;
}
return submitRow;
},
rowSave(row, done, loading) { rowSave(row, done, loading) {
add(row).then( const submitRow = this.normalizeSubmitRow(row);
add(submitRow).then(
res => { res => {
// 获取新增数据的相关字段 // 获取新增数据的相关字段
const data = res.data.data; const data = res.data.data;
@@ -332,15 +365,15 @@ export default {
); );
}, },
rowUpdate(row, index, done, loading) { rowUpdate(row, index, done, loading) {
row.id = row.id || this.currentEditMenuId; const submitRow = this.normalizeSubmitRow(row, true);
update(row).then( update(submitRow).then(
() => { () => {
this.$message({ this.$message({
type: 'success', type: 'success',
message: '操作成功!', message: '操作成功!',
}); });
// 数据回调进行刷新 // 数据回调进行刷新
done(row); done({ ...row, ...submitRow });
}, },
error => { error => {
window.console.log(error); window.console.log(error);
@@ -417,6 +450,7 @@ export default {
this.initData(); this.initData();
} }
if (type === 'add') { if (type === 'add') {
this.form.id = undefined;
this.form.sort = 1; this.form.sort = 1;
this.form.isDisplay = 1; this.form.isDisplay = 1;
} }

View File

@@ -83,27 +83,24 @@
<!-- 操作栏模块 --> <!-- 操作栏模块 -->
<el-table-column prop="menu" label="操作" :width="220" align="center"> <el-table-column prop="menu" label="操作" :width="220" align="center">
<template #="{ row }"> <template #="{ row }">
<el-button <el-link
v-if="this.permissionList.viewBtn" v-if="this.permissionList.viewBtn"
type="primary" type="primary"
text
@click="handleView(row)" @click="handleView(row)"
>查看 >查看
</el-button> </el-link>
<el-button <el-link
v-if="this.permissionList.editBtn" v-if="this.permissionList.editBtn"
type="primary" type="primary"
text
@click="handleEdit(row)" @click="handleEdit(row)"
>编辑 >编辑
</el-button> </el-link>
<el-button <el-link
v-if="this.permissionList.delBtn" v-if="this.permissionList.delBtn"
type="primary" type="primary"
text
@click="rowDel(row)" @click="rowDel(row)"
>删除 >删除
</el-button> </el-link>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@@ -115,20 +115,18 @@
</el-tooltip> </el-tooltip>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
v-if="userInfo.authority.includes('administrator') && !recycleMode" v-if="userInfo.authority.includes('administrator') && !recycleMode"
type="primary" type="primary"
text
@click.stop="handleRowDatasource(scope.row)" @click.stop="handleRowDatasource(scope.row)"
>数据源 >数据源
</el-button> </el-link>
<el-button <el-link
v-if="userInfo.authority.includes('administrator') && !recycleMode" v-if="userInfo.authority.includes('administrator') && !recycleMode"
type="primary" type="primary"
text
@click.stop="handleRowPackage(scope.row)" @click.stop="handleRowPackage(scope.row)"
>产品包 >产品包
</el-button> </el-link>
</template> </template>
<template #accountNumber="{ row }"> <template #accountNumber="{ row }">
<el-tag>{{ row.accountNumber > 0 ? row.accountNumber : '不限制' }}</el-tag> <el-tag>{{ row.accountNumber > 0 ? row.accountNumber : '不限制' }}</el-tag>

View File

@@ -38,13 +38,12 @@
</el-button> </el-button>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
v-if="permission.topmenu_setting" v-if="permission.topmenu_setting"
type="primary" type="primary"
text
@click.stop="handleRowMenuSetting(scope.row)" @click.stop="handleRowMenuSetting(scope.row)"
>配置 >配置
</el-button> </el-link>
</template> </template>
<template #name="{ row }"> <template #name="{ row }">
<i :class="row.source" style="margin-right: 5px" /> <i :class="row.source" style="margin-right: 5px" />

View File

@@ -37,7 +37,7 @@
v-if="permission.user_add && !auditMode" v-if="permission.user_add && !auditMode"
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
@click="$refs.crud.rowAdd()" @click="openUserDialog('add')"
> >
</el-button> </el-button>
<el-button <el-button
@@ -125,22 +125,20 @@
</el-button> </el-button>
</template> </template>
<template #menu="{ row, index, size }"> <template #menu="{ row, index, size }">
<el-button <el-link
text
type="primary" type="primary"
v-if="this.permission.user_view" v-if="this.permission.user_view"
@click.stop="$refs.crud.rowView(row, index)" @click.stop="$refs.crud.rowView(row, index)"
>查看 >查看
</el-button> </el-link>
<el-button <el-link
text
type="primary" type="primary"
v-if="this.permission.user_edit" v-if="this.permission.user_edit"
@click.stop="$refs.crud.rowEdit(row, index)" @click.stop="openUserDialog('edit', row)"
>编辑 >编辑
</el-button> </el-link>
<el-dropdown @command="command => handleDropdownCommand(command, row, index)"> <el-dropdown @command="command => handleDropdownCommand(command, row, index)">
<el-button text type="primary"> </el-button> <el-link type="primary"> </el-link>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="setLeader" v-if="userInfo.authority.includes('admin')"> <el-dropdown-item command="setLeader" v-if="userInfo.authority.includes('admin')">
@@ -149,12 +147,6 @@
<el-dropdown-item command="grantRole" v-if="permission.user_role"> <el-dropdown-item command="grantRole" v-if="permission.user_role">
角色配置 角色配置
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item
command="platformConfig"
v-if="userInfo.authority.includes('admin')"
>
平台配置
</el-dropdown-item>
<el-dropdown-item command="authLog" v-if="userInfo.authority.includes('admin')"> <el-dropdown-item command="authLog" v-if="userInfo.authority.includes('admin')">
认证日志 认证日志
</el-dropdown-item> </el-dropdown-item>
@@ -174,20 +166,62 @@
</template> </template>
</el-dropdown> </el-dropdown>
</template> </template>
<template #tenantName="{ row }">
<el-tag>{{ row.tenantName }}</el-tag>
</template>
<template #roleName="{ row }"> <template #roleName="{ row }">
<el-tag>{{ row.roleName }}</el-tag> <el-tag>{{ row.roleName }}</el-tag>
</template> </template>
<template #deptName="{ row }"> <template #deptName="{ row }">
<el-tag>{{ row.deptName }}</el-tag> <el-tag>{{ row.deptName }}</el-tag>
</template> </template>
<template #userTypeName="{ row }">
<el-tag>{{ row.userTypeName }}</el-tag>
</template>
</avue-crud> </avue-crud>
<el-dialog
v-model="userDialog"
:title="userDialogMode === 'add' ? '新增用户' : '编辑用户'"
width="90%"
top="5vh"
append-to-body
destroy-on-close
class="user-edit-dialog"
>
<template #header>
<span class="dialog-title">{{ userDialogMode === 'add' ? '新增用户' : '编辑用户' }}</span>
</template>
<el-form ref="userFormRef" :model="form" :rules="userRules" label-position="right" label-width="120px">
<el-row :gutter="20">
<el-col :span="6"><el-form-item label="账号名" prop="account"><el-input v-model="form.account" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="姓名" prop="realName"><el-input v-model="form.realName" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="手机号" prop="phone"><el-input v-model="form.phone" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="邮箱"><el-input v-model="form.email" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="性别"><el-select v-model="form.sex" class="full-width"><el-option label="男" :value="1" /><el-option label="女" :value="2" /><el-option label="未知" :value="3" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="岗位" prop="postId"><el-tree-select v-model="form.postId" :data="postList" :props="{ label: 'postName', value: 'id' }" check-strictly class="full-width" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="所属组织" prop="deptId"><el-tree-select v-model="form.deptId" :data="permissionDeptTree" :props="{ label: 'title', value: 'id' }" multiple check-strictly show-checkbox class="full-width" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="工号"><el-input v-model="form.code" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="人员类别"><el-select v-model="form.personCategory" class="full-width"><el-option label="内部员工" :value="1" /><el-option label="承运商" :value="2" /></el-select></el-form-item></el-col>
<el-col :span="18"><el-form-item label="数据权限范围"><el-radio-group v-model="form.dataScopeRange"><el-radio :value="1">仅所属组织</el-radio><el-radio :value="2">全部组织</el-radio><el-radio :value="3">自定义</el-radio></el-radio-group></el-form-item></el-col>
<el-col :span="18" :offset="6"><el-form-item label="数据层级范围"><el-radio-group v-model="form.dataLevelRange"><el-radio :value="1">包含下级</el-radio><el-radio :value="2">仅本级</el-radio></el-radio-group></el-form-item></el-col>
<el-col :span="24"><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="200" show-word-limit /></el-form-item></el-col>
</el-row>
</el-form>
<el-tabs class="user-permission-tabs">
<el-tab-pane label="已拥有角色">
<el-table :data="formRoleList" border>
<el-table-column type="index" label="序号" width="70" />
<el-table-column prop="id" label="角色编号" />
<el-table-column prop="title" label="角色名称" />
<el-table-column label="操作" width="90"><template #default="{ row }"><el-link type="danger" @click="removeFormRole(row.id)">删除</el-link></template></el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane v-if="form.dataScopeRange === 3" label="数据权限">
<el-tree :data="permissionDeptTree" show-checkbox node-key="id" :default-checked-keys="form.dataScopeDeptIds || []" :props="{ label: 'title', children: 'children' }" @check="handleDataScopeCheck" />
</el-tab-pane>
<el-tab-pane label="客商权限">
<div class="permission-toolbar customer-toolbar"><el-checkbox v-model="form.includeNewCustomer" :true-value="1" :false-value="0">包含新增的客商</el-checkbox></div>
<el-table ref="customerTable" :data="customerList" border @selection-change="handleCustomerSelection"><el-table-column type="selection" width="55" /><el-table-column prop="customerCode" label="客商编码" /><el-table-column prop="fullName" label="客商名称" /></el-table>
</el-tab-pane>
</el-tabs>
<template #footer><span class="dialog-footer"><el-button type="primary" @click="submitUserForm">保存</el-button><el-button @click="userDialog = false">返回</el-button><el-button v-if="userDialogMode === 'edit'" @click="resetDialogPassword">重置密码</el-button><el-button @click="handleAddRole">添加角色</el-button></span></template>
</el-dialog>
<!-- 用户角色配置 --> <!-- 用户角色配置 -->
<el-dialog title="用户角色配置" append-to-body v-model="roleBox" width="345px"> <el-dialog title="用户角色配置" append-to-body v-model="roleBox" width="345px">
<el-row <el-row
@@ -202,8 +236,13 @@
size="small" size="small"
@change="handleLinkedChange('treeRole')" @change="handleLinkedChange('treeRole')"
/> />
<el-tooltip content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点" placement="top"> <el-tooltip
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon> content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点"
placement="top"
>
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"
><el-icon-question-filled
/></el-icon>
</el-tooltip> </el-tooltip>
</span> </span>
<el-button-group> <el-button-group>
@@ -248,17 +287,6 @@
</avue-form> </avue-form>
</el-dialog> </el-dialog>
<!-- 用户平台配置行级别 -->
<el-dialog title="用户平台配置" append-to-body v-model="platformBox" width="600px">
<avue-form ref="platformFormRef" :option="platformFormOption" v-model="platformForm" />
<template #footer>
<span class="dialog-footer">
<el-button @click="platformBox = false"> </el-button>
<el-button type="primary" @click="submitPlatformConfig"> </el-button>
</span>
</template>
</el-dialog>
<!-- 管理员修改密码 --> <!-- 管理员修改密码 -->
<el-dialog title="修改密码" append-to-body v-model="passwordBox" width="460px"> <el-dialog title="修改密码" append-to-body v-model="passwordBox" width="460px">
<el-form <el-form
@@ -267,7 +295,7 @@
:rules="passwordRules" :rules="passwordRules"
label-width="auto" label-width="auto"
> >
<el-form-item label="登录账号"> <el-form-item label="账号">
<el-input v-model="passwordForm.account" disabled /> <el-input v-model="passwordForm.account" disabled />
</el-form-item> </el-form-item>
<el-form-item label="新密码" prop="password"> <el-form-item label="新密码" prop="password">
@@ -298,10 +326,8 @@
import { import {
getList, getList,
getUser, getUser,
getUserPlatform,
remove, remove,
update, update,
updatePlatform,
add, add,
grant, grant,
resetPassword, resetPassword,
@@ -310,6 +336,7 @@ import {
auditRefuse, auditRefuse,
setLeader, setLeader,
getLeaderList, getLeaderList,
getCustomerOptions,
} from '@/api/system/user'; } from '@/api/system/user';
import { exportBlob } from '@/api/common'; import { exportBlob } from '@/api/common';
import { getDeptTree } from '@/api/system/dept'; import { getDeptTree } from '@/api/system/dept';
@@ -317,14 +344,13 @@ import { getRoleTree } from '@/api/system/role';
import { getPostList } from '@/api/system/post'; import { getPostList } from '@/api/system/post';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { h } from 'vue'; import { h } from 'vue';
import website from '@/config/website';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
import func from '@/utils/func'; import func from '@/utils/func';
import { sensitive } from '@/utils/sensitive'; import { sensitive } from '@/utils/sensitive';
import { excelOption, platformFormOption, treeOption, userOption } from '@/option/system/user'; import { excelOption, treeOption, userOption } from '@/option/system/user';
import AuthLog from './authlog.vue'; import AuthLog from './authlog.vue';
import AuthLock from './authlock.vue'; import AuthLock from './authlock.vue';
@@ -333,13 +359,15 @@ export default {
data() { data() {
return { return {
form: {}, form: {},
userDialog: false,
userDialogMode: 'add',
search: {}, search: {},
sensitiveManager: null, sensitiveManager: null,
roleBox: false, roleBox: false,
roleLinked: false, roleLinked: false,
roleFormMode: false,
grantUserId: '', grantUserId: '',
excelBox: false, excelBox: false,
platformBox: false,
passwordBox: false, passwordBox: false,
initFlag: true, initFlag: true,
auditMode: false, auditMode: false,
@@ -359,11 +387,12 @@ export default {
roleTreeObj: [], roleTreeObj: [],
treeDeptId: '', treeDeptId: '',
treeData: [], treeData: [],
permissionDeptTree: [],
postList: [],
customerList: [],
treeOption: treeOption, treeOption: treeOption,
userOption: userOption(this), userOption: userOption(this),
platformFormOption: platformFormOption,
data: [], data: [],
platformForm: {},
passwordForm: {}, passwordForm: {},
passwordRules: { passwordRules: {
password: [ password: [
@@ -375,16 +404,17 @@ export default {
{ validator: this.validatePassword2, trigger: 'blur' }, { validator: this.validatePassword2, trigger: 'blur' },
], ],
}, },
userRules: {
account: [{ required: true, message: '请输入账号名', trigger: 'blur' }],
realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
deptId: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
},
excelForm: {}, excelForm: {},
excelOption: excelOption, excelOption: excelOption,
}; };
}, },
watch: { watch: {
'form.tenantId'() {
if (this.form.tenantId !== '' && this.initFlag) {
this.initData(this.form.tenantId);
}
},
'excelForm.isCovered'() { 'excelForm.isCovered'() {
if (this.excelForm.isCovered !== '') { if (this.excelForm.isCovered !== '') {
const column = this.findColumn(this.excelOption.column, 'excelFile'); const column = this.findColumn(this.excelOption.column, 'excelFile');
@@ -409,14 +439,20 @@ export default {
}); });
return ids.join(','); return ids.join(',');
}, },
formRoleList() {
const selected = new Set(func.split(this.form.roleId || '').map(String));
const roles = [];
const walk = nodes =>
(nodes || []).forEach(node => {
if (selected.has(String(node.id))) roles.push(node);
walk(node.children);
});
walk(this.roleGrantList);
return roles;
},
}, },
mounted() { mounted() {
// 非租户模式默认加载管理组数据 this.initData(this.userInfo.tenantId || this.website.tenantId);
if (!website.tenantMode) {
this.initData(website.tenantId);
} else {
this.initData();
}
}, },
created() { created() {
// 创建脱敏工具实例 // 创建脱敏工具实例
@@ -439,8 +475,6 @@ export default {
this.$refs.authLog.openUserAuthLog(row.id, row.name); this.$refs.authLog.openUserAuthLog(row.id, row.name);
} else if (command === 'lockUser') { } else if (command === 'lockUser') {
this.$refs.authLock.openLockUser(row.id, row.account); this.$refs.authLock.openLockUser(row.id, row.account);
} else if (command === 'platformConfig') {
this.handlePlatformForRow(row);
} else if (command === 'resetPassword') { } else if (command === 'resetPassword') {
this.handleResetForRow(row); this.handleResetForRow(row);
} else if (command === 'setPassword') { } else if (command === 'setPassword') {
@@ -490,14 +524,17 @@ export default {
}, },
initData(tenantId) { initData(tenantId) {
getRoleTree(tenantId).then(res => { getRoleTree(tenantId).then(res => {
this.roleGrantList = res.data.data;
const column = this.findColumn(this.userOption.group, 'roleId'); const column = this.findColumn(this.userOption.group, 'roleId');
column.dicData = res.data.data; column.dicData = res.data.data;
}); });
getDeptTree(tenantId).then(res => { getDeptTree(tenantId).then(res => {
this.permissionDeptTree = res.data.data;
const column = this.findColumn(this.userOption.group, 'deptId'); const column = this.findColumn(this.userOption.group, 'deptId');
column.dicData = res.data.data; column.dicData = res.data.data;
}); });
getPostList(tenantId).then(res => { getPostList(tenantId).then(res => {
this.postList = res.data.data;
const column = this.findColumn(this.userOption.group, 'postId'); const column = this.findColumn(this.userOption.group, 'postId');
column.dicData = res.data.data; column.dicData = res.data.data;
}); });
@@ -505,9 +542,18 @@ export default {
const column = this.findColumn(this.userOption.group, 'leaderId'); const column = this.findColumn(this.userOption.group, 'leaderId');
column.dicData = res.data.data; column.dicData = res.data.data;
}); });
getCustomerOptions().then(res => {
this.customerList = res.data.data || [];
});
}, },
submitRole() { submitRole() {
const roleList = this.$refs.treeRole.getCheckedKeys().join(','); const roleList = this.$refs.treeRole.getCheckedKeys().join(',');
if (this.roleFormMode) {
this.form.roleId = roleList;
this.roleBox = false;
this.roleFormMode = false;
return;
}
grant(this.grantUserId, roleList).then(() => { grant(this.grantUserId, roleList).then(() => {
this.roleBox = false; this.roleBox = false;
this.$message({ this.$message({
@@ -517,6 +563,98 @@ export default {
this.onLoad(this.page); this.onLoad(this.page);
}); });
}, },
handleAddRole() {
this.roleFormMode = true;
this.roleTreeObj = func.split(this.form.roleId || '');
this.roleLinked = false;
getRoleTree(this.userInfo.tenantId).then(res => {
this.roleGrantList = res.data.data;
this.roleBox = true;
this.$nextTick(() => this.applyCheckedKeys('treeRole', this.roleTreeObj));
});
},
removeFormRole(roleId) {
this.form.roleId = func
.split(this.form.roleId || '')
.filter(id => String(id) !== String(roleId));
},
handleDataScopeCheck(_, checked) {
this.form.dataScopeDeptIds = checked.checkedKeys;
},
handleCustomerSelection(rows) {
this.form.customerIds = rows.map(row => row.id);
},
openUserDialog(mode, row = {}) {
this.userDialogMode = mode;
if (mode === 'add') {
this.form = {
account: '',
realName: '',
phone: '',
email: '',
sex: 3,
postId: '',
deptId: [],
code: '',
personCategory: 1,
dataScopeRange: 1,
dataLevelRange: 1,
remark: '',
roleId: '',
dataScopeDeptIds: [],
customerIds: [],
includeNewCustomer: 0,
};
this.userDialog = true;
this.$nextTick(() => this.$refs.userFormRef && this.$refs.userFormRef.clearValidate());
return;
}
getUser(row.id).then(res => {
const user = res.data.data;
this.form = {
...user,
deptId: func.split(user.deptId || ''),
postId: func.split(user.postId || '')[0] || '',
roleId: func.join(user.roleId || ''),
dataScopeDeptIds: user.dataScopeDeptIds || [],
customerIds: user.customerIds || [],
includeNewCustomer: user.includeNewCustomer || 0,
personCategory: user.personCategory || 1,
dataScopeRange: user.dataScopeRange || 1,
dataLevelRange: user.dataLevelRange || 1,
};
this.sensitiveManager.saveInitialData(this.form);
this.userDialog = true;
this.$nextTick(() => {
this.$refs.userFormRef && this.$refs.userFormRef.clearValidate();
(this.form.customerIds || []).forEach(id => {
const customer = this.customerList.find(item => String(item.id) === String(id));
if (customer) this.$refs.customerTable && this.$refs.customerTable.toggleRowSelection(customer, true);
});
});
});
},
submitUserForm() {
this.$refs.userFormRef.validate(valid => {
if (!valid) return;
const row = {
...this.form,
deptId: func.join(this.form.deptId),
postId: func.join(this.form.postId),
roleId: func.join(this.form.roleId),
leaderId: func.join(this.form.leaderId),
};
const request = this.userDialogMode === 'add' ? add(row) : update(this.sensitiveManager.getSubmitData(row));
request.then(() => {
this.userDialog = false;
this.onLoad(this.page);
this.$message.success('操作成功!');
});
});
},
resetDialogPassword() {
this.handleResetForRow(this.form);
},
rowSave(row, done, loading) { rowSave(row, done, loading) {
row.deptId = func.join(row.deptId); row.deptId = func.join(row.deptId);
row.roleId = func.join(row.roleId); row.roleId = func.join(row.roleId);
@@ -781,23 +919,6 @@ export default {
if (!tree) return; if (!tree) return;
tree.setCheckedKeys(keys); tree.setCheckedKeys(keys);
}, },
// ========== 平台配置(行级别) ==========
handlePlatformForRow(row) {
getUserPlatform(row.id).then(res => {
this.platformForm = { ...res.data.data, account: row.account };
this.platformBox = true;
});
},
submitPlatformConfig() {
updatePlatform(
this.platformForm.id,
this.platformForm.userType,
this.platformForm.userExt
).then(() => {
this.platformBox = false;
this.$message({ type: 'success', message: '操作成功!' });
});
},
handlePasswordForRow(row) { handlePasswordForRow(row) {
this.passwordForm = { this.passwordForm = {
userId: row.id, userId: row.id,
@@ -904,12 +1025,11 @@ export default {
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
this.loading = true; this.loading = true;
getList( const queryParams = { ...this.query, ...params };
page.currentPage, if (this.auditMode) {
page.pageSize, queryParams.status = 0;
Object.assign(params, this.query, { status: this.auditMode ? 0 : 1 }), }
this.treeDeptId getList(page.currentPage, page.pageSize, queryParams, this.treeDeptId).then(res => {
).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;
@@ -933,4 +1053,62 @@ export default {
.box .el-scrollbar__wrap { .box .el-scrollbar__wrap {
overflow: scroll; overflow: scroll;
} }
.user-permission-tabs {
width: 100%;
margin-top: 8px;
}
.user-edit-dialog .dialog-title {
display: inline-flex;
align-items: center;
font-size: 16px;
font-weight: 600;
}
.user-edit-dialog .dialog-title::before {
width: 4px;
height: 18px;
margin-right: 8px;
background: #409eff;
content: '';
}
.user-edit-dialog .el-form-item {
margin-bottom: 16px;
}
.user-edit-dialog .el-form-item__label {
white-space: nowrap;
}
.user-edit-dialog .el-radio-group {
white-space: nowrap;
}
.full-width {
width: 100%;
}
.user-permission-tabs .el-tabs__header {
margin-bottom: 12px;
}
.permission-toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: 12px;
}
.customer-toolbar {
justify-content: flex-start;
}
.avue-dialog .el-form-item__label {
white-space: nowrap;
}
.avue-dialog .el-radio-group {
white-space: nowrap;
}
</style> </style>

View File

@@ -82,22 +82,20 @@
</el-tooltip> </el-tooltip>
</template> </template>
<template #menu="scope"> <template #menu="scope">
<el-button <el-link
type="primary" type="primary"
text
v-if="permission.code_edit" v-if="permission.code_edit"
class="none-border" class="none-border"
@click.stop="handleCopy(scope.row)" @click.stop="handleCopy(scope.row)"
>复制 >复制
</el-button> </el-link>
<el-button <el-link
type="primary" type="primary"
text
v-if="userInfo.authority.includes('administrator')" v-if="userInfo.authority.includes('administrator')"
class="none-border" class="none-border"
@click.stop="handleBuildSingle(scope.row)" @click.stop="handleBuildSingle(scope.row)"
>生成 >生成
</el-button> </el-link>
</template> </template>
</avue-crud> </avue-crud>
<el-drawer title="代码快速生成" append-to-body v-model="codeGenBox" direction="rtl" size="50%"> <el-drawer title="代码快速生成" append-to-body v-model="codeGenBox" direction="rtl" size="50%">

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