Compare commits

131 Commits

Author SHA1 Message Date
dd8f3584c7 车辆管理,有责任险审核权限的展示‘保额’且必填 2026-02-12 17:55:55 +08:00
a136eeff91 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-02-03 17:34:35 +08:00
9c2c57120b CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-02-03 17:34:32 +08:00
f7b37f7a0b CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-02-03 17:34:30 +08:00
f5c2d1687f CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-02-03 17:34:28 +08:00
a56824ff73 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-02-03 17:34:26 +08:00
b33ecb5a1e CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-02-03 17:34:24 +08:00
af9d7cdbf2 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-02-03 17:34:23 +08:00
1834a1a838 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-02-03 17:34:22 +08:00
3438a7f9b3 Revert "CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求"
This reverts commit dc94c6d261.
2026-01-29 21:32:39 +08:00
edbc60d3f3 Revert "CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求"
This reverts commit 31d1388890.
2026-01-29 21:32:36 +08:00
914249cc3a Revert "CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求"
This reverts commit 14a14e2c87.
2026-01-29 21:32:33 +08:00
1217b7671c Revert "CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求"
This reverts commit 62f2c166c6.
2026-01-29 21:32:31 +08:00
bd3e358ec7 Revert "CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求"
This reverts commit d18fec1ddc.
2026-01-29 21:32:26 +08:00
9788542b38 Revert "CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求"
This reverts commit 3a6d0878aa.
2026-01-29 21:32:22 +08:00
65a53ab267 Revert "CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求"
This reverts commit 87546848d2.
2026-01-29 21:32:19 +08:00
0f70e55e66 Revert "CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求"
This reverts commit fed4ae6edf.
2026-01-29 21:32:18 +08:00
e800ee1b7e VConsole去除 2026-01-29 11:26:56 +08:00
3fbcc7a125 VConsole调试 2026-01-29 10:24:25 +08:00
c90bf52b4d CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-28 11:15:51 +08:00
c9ac87b104 CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-28 10:02:18 +08:00
47fdf8f528 CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-27 16:28:33 +08:00
9192625165 CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-27 15:31:31 +08:00
5ebbce5c1f CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-27 15:29:56 +08:00
dc5327c8e4 h5链接替换33 2026-01-27 15:16:25 +08:00
044832a8f7 CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-27 15:12:39 +08:00
19c5809af2 CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-27 15:02:42 +08:00
d41684d7ed h5(返回) 2026-01-27 14:38:30 +08:00
60445b1523 h5链接替换-文件替换 2026-01-27 14:33:40 +08:00
534ef90f2e CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-27 14:23:15 +08:00
f8484391b8 CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-27 14:16:08 +08:00
414f2cd320 h5修改 2026-01-27 14:12:49 +08:00
d12ef20726 CRM_26-01-29#story#8070,调度APP-司机信息复制-仅子公司 2026-01-27 14:11:25 +08:00
ed7ce35a91 h5链接替换 2026-01-26 10:23:14 +08:00
6b5bc0494c 服务商人员,电话格式验证规则同步系统后台 2026-01-22 16:57:27 +08:00
fed4ae6edf CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-01-22 13:21:45 +08:00
87546848d2 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-01-22 13:14:30 +08:00
3a6d0878aa CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-01-22 13:12:08 +08:00
d18fec1ddc CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-01-22 11:48:20 +08:00
62f2c166c6 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-01-22 11:33:59 +08:00
14a14e2c87 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-01-22 10:56:15 +08:00
8a457952fa CRM_26-01-29#task#13363,潜在服务商审核-财务信息-h5做必填 2026-01-21 13:56:14 +08:00
c657f3b5ac 省市修改 2026-01-19 17:22:25 +08:00
31d1388890 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-01-15 17:40:24 +08:00
dc94c6d261 CRM_26-01-22#story#7966,关于在打通系统中责任险信息关联的需求 2026-01-15 16:07:45 +08:00
97c86dbee3 二手拖车车源发布成功后关闭弹窗 2026-01-15 15:45:46 +08:00
18ad0e0b2e 潜在服务商信息查询,开户许可证为空时展示优化 2026-01-14 17:31:55 +08:00
f77b0fc53d 潜在服务商信息查询,开户许可证为空时展示优化 2026-01-14 17:26:27 +08:00
8dee7c54df 数据处理 2026-01-12 18:12:57 +08:00
89cd821276 司机管理获取列表前清空列表 2026-01-12 17:56:50 +08:00
276a0b982e 司机管理获取列表前清空列表 2026-01-12 17:45:46 +08:00
b2119b4fd7 司机管理获取列表前清空列表 2026-01-12 17:45:27 +08:00
63b6028690 司机管理获取列表前清空列表 2026-01-12 17:45:17 +08:00
22bf45eda2 VConsole添加 2026-01-12 17:33:40 +08:00
9083f4ff30 CRM_26-01-13#story#7762,潜在服务商增加财务结算信息的需求--曹智龙 2026-01-12 15:19:32 +08:00
783910ba84 CRM_26-01-13#story#7852,为推进人车梳理工作优化调度APP车辆、司机管理功能的需求 2026-01-12 11:17:01 +08:00
ccf61a73ee CRM_26-01-13#story#7852,为推进人车梳理工作优化调度APP车辆、司机管理功能的需求 2026-01-09 14:02:14 +08:00
c6b23f6bb3 CRM_26-01-13#story#7762,潜在服务商增加财务结算信息的需求,许可证回显 2026-01-09 11:42:53 +08:00
d1eb15ed15 CRM_26-01-13#story#7762,潜在服务商增加财务结算信息的需求--曹智龙 2026-01-08 15:21:37 +08:00
c5fca0a4f1 CRM_26-01-13#story#7762,潜在服务商增加财务结算信息的需求--曹智龙 2026-01-07 15:49:51 +08:00
247d087c48 CRM_26-01-13#story#7762,潜在服务商增加财务结算信息的需求--曹智龙 2026-01-07 15:11:23 +08:00
3ba634d602 CRM_26-01-13#story#7762,潜在服务商增加财务结算信息的需求--曹智龙 2026-01-07 14:53:27 +08:00
58f1f5033d VConsole去除 2026-01-04 17:41:09 +08:00
c8400442a5 微信号更改添加判断 2026-01-04 16:37:05 +08:00
6dbb1377be 确认已添加接口返回null时不做任何处理 2026-01-04 16:01:58 +08:00
847fdc48c6 确认已添加接口返回null时不做任何处理 2026-01-04 15:53:21 +08:00
234853b603 vConsole添加 2026-01-04 15:45:56 +08:00
91b3c069ad 下载app接口替换 2025-12-30 10:41:46 +08:00
0732fdcb8e 人员确认添加二次弹框 2025-12-29 19:04:58 +08:00
64b2099c8b 调度h5的连接下载 2025-12-29 17:45:44 +08:00
ac08b52038 确认信息无误提示语更改 2025-12-29 13:17:10 +08:00
ebfb553829 VConsole去除 2025-12-29 13:12:22 +08:00
77335a1850 确认信息无误添加必填项判断 2025-12-29 13:11:15 +08:00
159ce0fbde 跳转app路径更换 2025-12-28 13:01:48 +08:00
1cc79ba5f5 h5链接替换 2025-12-28 12:04:21 +08:00
c3bdcc8251 样式兼容 2025-12-26 14:12:57 +08:00
78bfd97e0c h5立即打开修改(下方文字修改) 2025-12-26 14:05:36 +08:00
ca5bb1520b h5立即打开修改 2025-12-26 13:52:41 +08:00
cf6d94d54f kpi修改 2025-12-26 13:43:19 +08:00
5845ae76da h5分享链接 2025-12-26 13:43:19 +08:00
0d2082ebaf CRM_25-12-23#story#7473,服务商系统、微信号的输入框只读 2025-12-24 10:21:31 +08:00
0208136ac4 车辆审批需求,责任险可查看 2025-12-23 10:53:47 +08:00
a2907a9fd1 车辆审批需求,责任险可查看 2025-12-23 10:37:39 +08:00
c7a1d95346 车辆审批需求,责任险可查看,保存时添加车辆状态字段 2025-12-23 10:23:52 +08:00
911bf1f5e0 车辆审批需求,车辆新增一个车辆状态为‘ 否-服务商停用’ 2025-12-23 09:21:01 +08:00
a3fd0d6f46 CRM_25-12-23#story#7473,服务商系统,添加是否是主账号判断 2025-12-22 19:51:08 +08:00
6fe09d9674 CRM_25-12-23#story#7473,服务商系统,添加是否是主账号判断 2025-12-22 19:47:44 +08:00
44552c5d35 CRM_25-12-23#story#7473,服务商系统,添加是否是主账号判断 2025-12-22 19:44:35 +08:00
75b727d8b2 车辆审批需求,去除比较提交审批接口,使用原本保存接口,新增canSubmitApproval字段,默认true 2025-12-22 17:19:54 +08:00
4eb791c9c6 CRM_25-12-23#story#7473,服务商系统、调度APP新增服务商人员确认的功能 2025-12-22 13:08:25 +08:00
44e1af7262 CRM_25-12-23#story#7473,服务商系统、调度APP新增服务商人员确认的功能 2025-12-22 13:08:10 +08:00
e0f90ca3b2 VConsole日志添加 2025-12-22 10:02:27 +08:00
7e73da59eb CRM_25-12-23#story#7473,服务商系统、调度APP新增服务商人员确认的功能 2025-12-19 14:16:12 +08:00
361f7f16f8 车辆审批提交,去除备注填写,直接走接口 2025-12-18 18:04:58 +08:00
4734f236cf 车辆审批提交,去除备注填写,直接走接口 2025-12-18 17:33:49 +08:00
89cf401882 CRM_25-12-23#story#7473,服务商系统、调度APP新增服务商人员确认 2025-12-18 14:15:04 +08:00
0fc9be28d1 CRM_25-12-23#story#7473,服务商系统、调度APP新增服务商人员确认 2025-12-17 21:53:36 +08:00
1a567dee57 CRM_25-12-09#story#7474,服务商师傅和车辆修改的审批 2025-12-17 16:19:24 +08:00
ecb87e5bc9 CRM_25-12-23#story#7473,服务商系统、调度APP新增服务商人员确认的功能 2025-12-17 16:19:02 +08:00
193e9c8006 kpi修改 2025-12-17 15:55:56 +08:00
94376c91b8 调度h5续保更新条件修改 2025-12-17 15:55:56 +08:00
23984e78ec CRM_25-12-23#story#7473,服务商系统、调度APP新增服务商人员确认的功能 2025-12-17 11:37:59 +08:00
1f2af86b96 去除VConsole 2025-12-16 13:16:06 +08:00
2c344eb2f5 车辆提交审批 2025-12-16 09:23:31 +08:00
552b56d5da CRM_25-12-23#story#7473,服务商系统、调度APP新增服务商人员确认的功能 2025-12-15 13:07:55 +08:00
f9acd76fcf 报备样式修改 2025-12-12 11:40:04 +08:00
b5f30b4c77 CRM_25-12-16#story#7744,调度APP报备调整 2025-12-11 09:54:24 +08:00
b9830e80eb 潜在服务商修改 2025-12-08 13:45:22 +08:00
5bc7c6b1e3 CRM_25-12-09#story#7474,服务商师傅和车辆修改的审批--曹智龙 2025-12-08 10:11:26 +08:00
5a56d4fe1c CRM_25-12-09#story#7474,服务商师傅和车辆修改的审批--曹智龙 2025-12-08 09:57:01 +08:00
f7f9bb7fd0 换UI修改 2025-12-05 11:17:45 +08:00
05ac89814c 遗漏 2025-12-04 13:57:27 +08:00
d178be1acd CRM_25-12-09#story#7686,调度APP中救援责任险参保咨询问题优化 2025-12-04 11:38:34 +08:00
f0576476e9 CRM_25-12-09#story#7697,调度APP的车辆录入功能的漏洞 2025-12-04 11:38:34 +08:00
01e2f9ca8d CRM_25-12-09#story#7484,供应商培训材料问答编辑优化-去除调试 2025-12-04 10:40:32 +08:00
f9d72ee3cc CRM_25-12-09#story#7484,供应商培训材料问答编辑优化-数量统一 2025-12-04 10:26:56 +08:00
0102cc2899 CRM_25-12-09#story#7484,供应商培训材料问答编辑优化-添加调试 2025-12-04 10:18:30 +08:00
7f0109bfcc 调度h5修改 2025-12-04 10:07:00 +08:00
f3716466e9 CRM_25-12-09#story#7484,供应商培训材料问答编辑优化-添加调试 2025-12-04 09:40:31 +08:00
811f232a73 CRM_25-12-09#story#7484,供应商培训材料问答编辑优化--敖煜 2025-12-03 15:12:47 +08:00
72fe6cc266 bugfix 修改2 2025-12-01 13:06:10 +08:00
75cf3db960 bugfix 修改 2025-12-01 11:30:32 +08:00
19ad185126 CRM_25-11-18#story#7366,服务商KPI界面需求2025.9.11(样式调整 resize) 2025-11-27 17:08:58 +08:00
bb7ad8deb1 CRM_25-11-18#story#7366,服务商KPI界面需求2025.9.11(样式调整)(回退) 2025-11-27 16:50:00 +08:00
abd5657366 CRM_25-11-18#story#7366,服务商KPI界面需求2025.9.11(样式调整) 2025-11-27 16:33:14 +08:00
6e7a5a4b42 CRM_25-11-18#story#7366,服务商KPI界面需求2025.9.11 2025-11-27 15:00:34 +08:00
2c61085515 story#7552 潜在服务商,省禁用 2025-11-27 14:42:53 +08:00
55321e5dc5 报备--修改--换插件 2025-11-27 14:25:36 +08:00
327d40318b 报备修改 2025-11-26 10:38:08 +08:00
5eefaa2223 报备修改 2025-11-26 09:33:01 +08:00
ff0e25cfd0 报备修改 2025-11-25 16:27:27 +08:00
54 changed files with 135939 additions and 189 deletions

2
package-lock.json generated
View File

@@ -15,7 +15,7 @@
"dayjs": "^1.8.14",
"decimal.js": "^10.4.3",
"echarts": "^5.2.2",
"element-ui": "^2.15.9",
"element-ui": "^2.15.13",
"less": "^4.1.3",
"less-loader": "^11.1.3",
"qrcode": "^1.5.4",

View File

@@ -16,7 +16,7 @@
"dayjs": "^1.8.14",
"decimal.js": "^10.4.3",
"echarts": "^5.2.2",
"element-ui": "^2.15.9",
"element-ui": "^2.15.13",
"less": "^4.1.3",
"less-loader": "^11.1.3",
"qrcode": "^1.5.4",

View File

@@ -56,7 +56,7 @@
if (isMobile) {// 是移动端不变
// console.log("是移动端不变")
}else{
if(window.location.pathname=='/h5/supplier/dispatch/kpiIndex'){
if(window.location.pathname=='/h5/supplier/dispatch/kpiIndex' || window.location.pathname=='/h5/supplier/dispatch/kpiCaseNew'){
// console.log("执行了执行了")
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);

View File

@@ -0,0 +1,160 @@
# 年终总结看板 Dashboard
道路救援服务年终数据可视化看板项目。
## 项目结构
```
dashboard-demo/
├── index.html # 主看板页面支持URL参数动态加载数据
├── data/
│ ├── 供应商年度KPI.xlsx # 服务商KPI数据主数据源
│ ├── 师傅案件top3.xlsx # 案件量TOP3师傅数据
│ ├── 师傅在线top3.xlsx # 在线时长TOP3师傅数据
│ ├── 拒单率最高地区.xlsx # 拒单率最高地区数据
│ └── 拒单率最高时段.xlsx # 拒单率最高时段数据
└── README.md # 项目说明
```
## 功能特性
- 深色主题数据可视化看板
- **直接读取Excel文件**无需转换为JSON
- 支持通过URL参数 `?providerId=XXX` 切换不同服务商
- 右上角下拉框可实时切换服务商自动从Excel读取服务商列表
- 使用 Chart.js 实现饼状图和雷达图
- 使用 SheetJS (xlsx) 解析Excel文件
- 响应式布局,支持不同屏幕尺寸
- 数据与页面分离,便于维护
## 数据指标
看板展示以下数据指标:
1. **总案件量分布**(饼状图)
- 小修、困境、拖车三类案件占比
2. **年度聚合案件量**
3. **案件量TOP3师傅**
4. **车辆平均总在线时长**
5. **在线时长TOP3师傅**
6. **拒单率最高地区**
7. **拒单率最高时段**
8. **APP使用率**
- 年度APP使用率展示
## Excel数据结构
### 1. 供应商年度KPI.xlsx
| 列名 | 说明 |
|------|------|
| 服务商id | 服务商唯一标识 |
| 服务商 | 服务商名称 |
| 完成案件量 | 总案件数量 |
| 拖车完成量 | 拖车案件数 |
| 小修完成量 | 小修案件数 |
| 困境完成量 | 困境案件数 |
| 聚合案件量 | 聚合案件数量 |
| APP使用率. | APP使用率小数形式如0.998 |
| 年度车辆平均总在线时长(小时) | 车辆平均在线时长 |
### 2. 师傅案件top3.xlsx
| 列名 | 说明 |
|------|------|
| 服务商id | 服务商唯一标识 |
| 服务商 | 服务商名称 |
| 服务人员工号 | 师傅姓名/工号 |
| 完成案件量 | 该师傅完成的案件数 |
### 3. 师傅在线top3.xlsx
| 列名 | 说明 |
|------|------|
| 服务商名称 | 服务商名称 |
| 司机姓名 | 师傅姓名 |
| 年度总在线时长(小时) | 在线时长 |
### 4. 拒单率最高地区.xlsx
| 列名 | 说明 |
|------|------|
| 供应商名称 | 服务商名称 |
| 地区 | 拒单率最高的地区 |
| 拒单率 | 拒单率(小数形式) |
### 5. 拒单率最高时段.xlsx
| 列名 | 说明 |
|------|------|
| 供应商名称 | 服务商名称 |
| 时段 | 拒单率最高的时段 |
| 拒单率 | 拒单率(小数形式) |
## 使用方式
### 1. 启动本地服务器
由于页面使用 `fetch` 加载JSON数据需要通过HTTP服务器访问
```bash
cd C:\Users\Administrator\dashboard-demo
# 使用 Python
python -m http.server 8080
# 或使用 Node.js
npx serve .
# 或使用 PHP
php -S localhost:8080
```
### 2. 访问页面
- 默认服务商:`http://localhost:8080`(自动加载第一个服务商)
- 指定服务商:`http://localhost:8080?providerId=1128`使用服务商id
- 切换服务商:使用页面右上角的下拉框
### 3. 更新数据
直接替换 `data/` 目录下的Excel文件即可页面会自动读取最新数据。
服务商列表从Excel自动生成无需手动配置。
## 技术栈
- HTML5 / CSS3
- JavaScript (ES6+)
- Chart.js - 图表库(饼图、雷达图)
- SheetJS (xlsx) - Excel文件解析
- CSS Grid / Flexbox - 布局
## 后续优化方向
- [ ] 添加更多图表类型(柱状图、折线图等)
- [ ] 支持时间范围筛选
- [ ] 添加数据导出功能
- [ ] 移动端适配优化
- [ ] 添加数据加载骨架屏
- [ ] 支持主题切换(深色/浅色)
## 更新日志
### 2025-01-21
- 改为直接读取Excel文件无需JSON转换
- 服务商列表从Excel自动生成
- 将"AB段里程对比"改为"APP使用率"展示
- 新增服务商选择下拉框
### 2025-01-20
- 初始版本
- 实现基础看板布局
- 支持URL参数切换服务商
- 数据JSON化与页面分离

View File

@@ -0,0 +1,47 @@
{
"serviceProviderId": "SP001",
"serviceProviderName": "上海道路救援服务中心",
"year": 2025,
"summary": {
"totalCases": 100,
"caseBreakdown": {
"minorRepair": 20,
"predicament": 20,
"towing": 60
},
"aggregatedCases": 15
},
"topMastersByCases": [
{ "rank": 1, "name": "小王", "cases": 18 },
{ "rank": 2, "name": "小张", "cases": 16 },
{ "rank": 3, "name": "小李", "cases": 12 }
],
"onlineHours": {
"averageTotal": 2920,
"topMasters": [
{ "rank": 1, "name": "小王", "hours": 3230 },
{ "rank": 2, "name": "小张", "hours": 3028 },
{ "rank": 3, "name": "小李", "hours": 2996 }
]
},
"rejectionRate": {
"highestRegion": {
"name": "崇明",
"rate": 21.2
},
"highestTimeSlot": {
"period": "18:00-22:00",
"description": "晚高峰时段"
}
},
"abMileage": {
"highest": {
"region": "崇明",
"distance": 62
},
"lowest": {
"region": "黄浦",
"distance": 6
}
}
}

View File

@@ -0,0 +1,47 @@
{
"serviceProviderId": "SP002",
"serviceProviderName": "浦东汽车救援有限公司",
"year": 2025,
"summary": {
"totalCases": 156,
"caseBreakdown": {
"minorRepair": 35,
"predicament": 41,
"towing": 80
},
"aggregatedCases": 22
},
"topMastersByCases": [
{ "rank": 1, "name": "老陈", "cases": 28 },
{ "rank": 2, "name": "大刘", "cases": 24 },
{ "rank": 3, "name": "阿强", "cases": 19 }
],
"onlineHours": {
"averageTotal": 3150,
"topMasters": [
{ "rank": 1, "name": "老陈", "hours": 3580 },
{ "rank": 2, "name": "大刘", "hours": 3420 },
{ "rank": 3, "name": "阿强", "hours": 3210 }
]
},
"rejectionRate": {
"highestRegion": {
"name": "奉贤",
"rate": 18.5
},
"highestTimeSlot": {
"period": "07:00-09:00",
"description": "早高峰时段"
}
},
"abMileage": {
"highest": {
"region": "金山",
"distance": 55
},
"lowest": {
"region": "静安",
"distance": 8
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -69,4 +69,52 @@ export function driverRealName( data) {
method:'POST',
contentType: 'application/json'
})
}
//获取需要确认的人员信息列表
export function getConfirmPerson(key){
return request({
url: '/supplierAppV2/dispatchApp/wechat/getNeedConfirmPersonInfo',
method:'GET',
params: key
})
}
//微信相关 获取联系我 二维码
export function getQrCode(key){
return request({
url: '/supplierAppV2/dispatchApp/wechat/getQrCode',
method:'GET',
params: key
})
}
//获取服务商的确认状态
export function getConfirmStatus(key){
return request({
url: '/supplierAppV2/dispatchApp/wechat/getSupplier',
method:'GET',
params: key
})
}
//确认已添加微信按钮
export function confirmAddWechat(key){
return request({
url: '/supplierAppV2/dispatchApp/wechat/confirmAddWechat',
method:'GET',
params: key
})
}
//确认无误
export function confirm(key){
return request({
url: '/supplierAppV2/dispatchApp/wechat/confirm',
method:'GET',
params: key
})
}
//提交审批
export function submitConfirm( data) {
return request('/supplierAppV2/dispatchApp/wechat/submitConfirm', {
data: data,
method:'POST',
contentType: 'application/json'
})
}

View File

@@ -81,4 +81,41 @@ export function getDriverName(key) {
key: key ,
}
});
}
}
export function getVehicleName(key) {
return request({
url: '/supplier/select/vehicle',
method: 'GET',
params: {
key: key ,
}
});
}
// 车辆 总览
export function vehicleTotalInfo(data) {
return request({
url: '/supplier/supplierKPI/querySupplierVehicleStatisticsKpi',
method: 'POST',
data
});
}
// 服务商维度查询车辆
export function vehicleInfoBySupplier(data) {
return request({
url: '/supplier/supplierKPI/querySupplierStatisticsKpiBySupplier',
method: 'POST',
data
});
}
// 车辆维度查询车辆
export function vehicleInfoByVehicle(data) {
return request({
url: '/supplier/supplierKPI/querySupplierVehicleKpi',
method: 'POST',
data
});
}

View File

@@ -18,6 +18,15 @@ export function saveVehicle(data){
data
})
}
//提交审批车辆信息
export function saveSupplierApproval(data){
return request({
url:'/supplier/approval/saveSupplierDriverVehicleApproval',
method:'POST',
contentType:'application/json',
data
})
}
/*车辆更改状态 /supplierAppV2/dispatchApp/user/enableVehicle*/
export function enableVehicle(data){
return request({

View File

@@ -77,6 +77,15 @@ export function updateOrderSettlement(data){
data
})
}
// 获取司机信息
export function getDriverInfo(data){
return request({
url:'/supplierAppV2/dispatchApp/order/getDriverInfo',
method:'POST',
contentType:'application/json',
data
})
}
// 电瓶数量列表
export function batteryCountList(){
return request({
@@ -159,3 +168,12 @@ export function getConfigByCode(data){
data
})
}
//获取最新版本
export function getNewVersion(key){
return request({
url: '/driverApp/app/updateVersion',
method:'GET',
params: key
})
}

BIN
src/assets/greTip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
src/assets/toAppBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

BIN
src/assets/yelTip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

View File

@@ -39,5 +39,13 @@ const authenticationRouter = [
title: '银行卡信息认证',
}
},
{
path: '/personList',
name: 'personList',
component: () => import('@/views/mine/personList.vue'),
meta:{
title: '人员信息',
}
},
]
export default authenticationRouter

View File

@@ -324,6 +324,14 @@ const routes = [
title:'电瓶详情'
}
},
{
path: '/goToApp',
name: 'goToApp',
component:()=>import('@/views/goToApp/goToApp.vue'),
meta: {
title:'电瓶详情'
}
},
...kpiRouter,
...invoiceRouter,
...secondHandRouter,

View File

@@ -7,5 +7,13 @@ const kpiRouter = [
title: 'kpi首页',
}
},
{
path: '/kpiCaseNew',
name: 'kpiCaseNew',
component: () => import('@/views/kpi/kpiCaseNew'),
meta:{
title: 'kpi服务商案件&车辆情况',
}
},
]
export default kpiRouter

View File

@@ -23,6 +23,17 @@
div {
padding-top: 8px;
}
.commonNum{
padding: 2px 5px;
background-color: #9C9C9C;
color: white;
border-radius: 3px;
font-size: 13px;
margin-left: 3px;
}
.notFinish{
background-color: red;
}
.active {
color: #3678FF;
position: relative;

View File

@@ -1,6 +1,6 @@
@import "@/styles/mixin.scss";
::v-deep .van-nav-bar__content{
background-color: #3A3A3A !important;
//background-color: #3A3A3A !important;
}
.wrap{
@include wh(100%,100%);

View File

@@ -19,7 +19,7 @@ if( token ) {
service.interceptors.request.use(
config => {
let reqUrl=config.url
console.log('config',config)
// console.log('config',config)
config.data = config.contentType ? config.data : qs.stringify(config.data)
if (config.testFlag) {
config.data = qs.stringify(config.data, {arrayFormat: 'indices', allowDots: true})
@@ -48,6 +48,7 @@ service.interceptors.request.use(
service.interceptors.response.use(
response => {
console.log('response', response.data)
const res = response.data
if ( res.code === 401 || res.code === 400 || res.code == 500) {
Toast(res.msg || 'Error')

102
src/utils/kpiMixins.js Normal file
View File

@@ -0,0 +1,102 @@
import dayjs from "dayjs";
export const kpiMixins = {
data() {
return {
isMobile: false,
isZd: '',
current:'2024-10',
supplierId:'',
supplierName:'',
startMonthTime: '',
startTime: '',
endTime: '',
}
},
methods: {
applicateHandle() {
if (window.parent) {
const hasListener = window.parent.dispatchEvent(new Event('checkCloseDialog'));
if (hasListener) {
const data = {
action: 'closeDialog',
message: this.supplierId,
// 其他需要传递的参数
};
window.parent.postMessage(data, '*');
} else {
window.history.back();
}
}
},
toOnlineHours(minutes) {
let _hours = parseInt(minutes / 60);
let _minutes = parseInt(minutes % 60);
return _hours + '时' + _minutes + '分'
},
// 初始化获取当月日期
initDate() {
const today = dayjs(); // 获取当前日期
const currentDay = today.date(); // 获取今天是几号1-31
let targetMonth = today; // 默认目标月份是当前月
if (currentDay === 1) {
targetMonth = today.subtract(1, 'month'); // 上个月
}
this.current = targetMonth.format('YYYY-MM');
this.startTime = targetMonth.startOf('month').format('YYYY-MM-DD HH:mm:ss');
let endTime;
if (targetMonth.isSame(today, 'month')) {
endTime = today.subtract(1, 'day').endOf('day');
} else {
endTime = targetMonth.endOf('month');
}
this.endTime = endTime.format('YYYY-MM-DD HH:mm:ss');
this.startMonthTime=this.getStartTimeFromEndTime(this.endTime)
},
//获取近四个月的开始时间
getStartTimeFromEndTime(endTimeStr) {
const startTime = dayjs(endTimeStr).subtract(3, 'month').startOf('month');
return startTime.format('YYYY-MM-DD HH:mm:ss');
},
padZero(num) {
return num < 10 ? `0${num}` : num;
},
checkMobile() {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
this.isMobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
},
// 通用函数,用于处理百分比数据
processPercentage(value) {
value *= 100;
if (value % 1 !== 0) {
value = value.toFixed(2);
}
return value;
},
formatPercentage(value) {
let result = value * 100;
if (Number.isInteger(result)) {
return result.toString() + '%';
} else {
return result.toFixed(2) + '%';
}
},
// 格式化承接案件量数据
formatCurrency(value) {
if (!value) return '';
let num = parseInt(value);
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
},
formatCurrency1(value) {
if (!value) return ''; // 如果值为空,返回空字符串
// 如果值已经包含逗号,直接返回原值
if (value.toString().includes(',')) {
return value;
}
// 否则,添加千分号
let num = parseInt(value);
if (isNaN(num)) return ''; // 如果转换失败,返回空字符串
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
},
}
}

View File

@@ -94,6 +94,7 @@ export const myMixins = {
isWebFunc(){
let res=false
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
console.log('isMobile',isMobile)
if (!isMobile) {// 是移动端不变
res=true
}

View File

@@ -1,6 +1,28 @@
<template>
<div class="wrap">
<div class="navBar">
<van-nav-bar
left-arrow
left-arrow-color="#FFFFFF"
:border="false"
:fixed="true"
:safe-area-inset-top="true"
@click-left="goBack"
>
<template slot="title">
<div v-show="!show">培训文档</div>
<van-field v-model="keyword" placeholder="请输入关键词" v-show="show" @input="getTrainingList"/>
</template>
<template slot="right">
<div class="rightWrap" @click="show = !show">
<img src="@/assets/serach.png" class="img2" v-show="!show"/>
<img src="@/assets/delKey.png" class="img2" v-show="show" @click="initShow"/>
</div>
</template>
</van-nav-bar>
</div>
<!-- <div class="navBar">
<van-nav-bar
:border="false"
:fixed="true"
@@ -24,7 +46,8 @@
</template>
</van-nav-bar>
</div>
<div class="statisticContainer">
-->
<!-- <div class="statisticContainer">
<div class="statisticWrap">
<div class="line1">
<span>培训统计</span>
@@ -43,11 +66,12 @@
</div>
</div>
</div>
</div>
<div class="driver_tab_wrap">
</div>-->
<div class="tab_wrap">
<div v-for="(item, index) in tabArr" :key="index" :class="{'active' : activeIndex == index}"
@click="changeTab(index)">
{{ item.name }}
{{ item.name }}<span v-if="item.count" :class="{'commonNum':true,'notFinish':index==2}">{{item.count}}</span>
<!-- {{ item.name }}-->
</div>
</div>
<div class="contentWrap" v-show="!showEmpty">
@@ -84,7 +108,8 @@ export default {
mixins:[myMixins],
data(){
return{
tabArr: [{name: '车型技术参数', status: 1}, {name: '中道服务规范', status:2},{name: '中道小课堂', status: 3}],
tabArr: [{name: '全部文档', status: 1,count:0}, {name: '已培训', status:2,count:0},{name: '未培训', status: 3,count:0}],
// tabArr: [{name: '车型技术参数', status: 1}, {name: '中道服务规范', status:2},{name: '中道小课堂', status: 3}],
activeIndex: 0,
pageList:[],
totalList:[],
@@ -115,6 +140,10 @@ export default {
});
},
methods:{
initShow(){
this.keyword= ''
this.getTrainingList()
},
onRefresh() {
this.getTrainingList()
setTimeout(() => {
@@ -135,18 +164,38 @@ export default {
})
this.totalList=[]
this.pageList=[]
this.tabArr[0].count=0
this.tabArr[1].count=0
this.tabArr[2].count=0
this.numInfo=res.data
this.totalList=res.data.list
let result=[]
if(res.data.totalNum){
this.tabArr[0].count=res.data.totalNum
}
if(res.data.readNum){
this.tabArr[1].count=res.data.readNum
}
if(res.data.notReadNum){
this.tabArr[2].count=res.data.notReadNum
}
/* this.totalList=res.data.list
if(this.activeIndex === 0){
result=this.totalList?.filter(q => q.title === '车型技术参数');
}else if(this.activeIndex === 1){
result=this.totalList?.filter(q => q.title === '中道服务规范');
}else if(this.activeIndex === 2){
result=this.totalList?.filter(q => q.title === '中道小课堂');
}*/
let result=[]
this.totalList=res?.data?.list ? res?.data?.list[0]?.materials : []
if(this.activeIndex === 0){
result=this.totalList || []
}else if(this.activeIndex === 1){
result=this.totalList?.filter(item => item.alreadyRead==1) || []
}else if(this.activeIndex === 2){
result=this.totalList?.filter(item => item.alreadyRead!=1) || []
}
if(result){
this.pageList=result[0].materials
this.pageList=result
this.time=timeFormat(Date.now())
}
if(this.pageList?.length === 0){
@@ -163,6 +212,19 @@ export default {
@import "@/styles/mixin.scss";
@import "@/styles/docment.scss";
@import "@/styles/driverDocment.scss";
/*::v-deep .van-nav-bar__content{
background-color: #3A3A3A !important;
}*/
.tab_wrap {
.active:after {
width: 37px;
left: 0%;
transform: translateX(0%);
}
.active1:after {
width:48px !important;
}
}
.read{
padding: 2px 8px;
border-radius: 5px;

View File

@@ -22,9 +22,9 @@
</van-nav-bar>
</div>
<div class="tab_wrap">
<div v-for="(item, index) in tabArr" :key="index" :class="{'active' : activeIndex == index}"
<div v-for="(item, index) in tabArr" :key="index" :class="{'active' : activeIndex == index,'active1':activeIndex==0}"
@click="changeTab(index)">
{{ item.name }}
{{ item.name }}<span v-if="item.count" :class="{'commonNum':true,'notFinish':index==2}">{{item.count}}</span>
</div>
</div>
<div class="contentWrap" v-show="!showEmpty">
@@ -61,7 +61,7 @@ export default {
mixins:[myMixins],
data(){
return{
tabArr: [{name: '车型技术参数', status: 1}, {name: '中道服务规范', status:2},{name: '中道小课堂', status: 3}],
tabArr: [{name: '全部文档', status: 1,count:0}, {name: '已培训', status:2,count:0},{name: '未培训', status: 3,count:0}],
activeIndex: 0,
pageNum:1,
pageSize:10,
@@ -73,9 +73,14 @@ export default {
showEmpty:false,
isLoading:false,
supplierId:'',
numInfo:'',
}
},
activated() {
console.log('activated')
},
mounted() {
console.log('mounted')
const urlParams = new URLSearchParams(window.location.search);
this.supplierId = urlParams.get('supplierId');
this.keyword=urlParams.get('keyword') || ''
@@ -111,17 +116,35 @@ export default {
})
this.totalList=[]
this.pageList=[]
this.totalList=res.data.list
this.tabArr[0].count=0
this.tabArr[1].count=0
this.tabArr[2].count=0
this.totalList=res?.data?.list ? res?.data?.list[0]?.materials : []
this.numInfo=res.data
if(res.data.totalNum){
this.tabArr[0].count=res.data.totalNum
}
if(res.data.readNum){
this.tabArr[1].count=res.data.readNum
}
if(res.data.notReadNum){
this.tabArr[2].count=res.data.notReadNum
}
// console.log(' this.totalList',this.totalList)
console.log(' this.tabArr',this.tabArr)
let result=[]
if(this.activeIndex === 0){
result=this.totalList?.filter(q => q.title === '车型技术参数');
result=this.totalList || []
// result=this.totalList?.filter(q => q.title === '车型技术参数');
}else if(this.activeIndex === 1){
result=this.totalList?.filter(q => q.title === '中道服务规范');
// result=this.totalList?.filter(q => q.title === '中道服务规范');
result=this.totalList?.filter(item => item.alreadyRead==1) || []
}else if(this.activeIndex === 2){
result=this.totalList?.filter(q => q.title === '中道小课堂');
result=this.totalList?.filter(item => item.alreadyRead!=1) || []
// result=this.totalList?.filter(q => q.title === '中道小课堂');
}
if(result){
this.pageList=result[0].materials
this.pageList=result
}
if(this.pageList?.length === 0){
this.showEmpty = true
@@ -130,7 +153,7 @@ export default {
}
},
initShow(){
this.keyword= '',
this.keyword= ''
this.getTrainingList()
}
}
@@ -140,6 +163,16 @@ export default {
@import "@/styles/common.scss";
@import "@/styles/mixin.scss";
@import "@/styles/docment.scss";
.tab_wrap {
.active:after {
width: 37px;
left: 0%;
transform: translateX(0%);
}
.active1:after {
width:48px !important;
}
}
.read{
padding: 2px 8px;
border-radius: 5px;

View File

@@ -0,0 +1,73 @@
<template>
<div class="wrap">
<div class="content">
<div class="goToBtn" @click="goAppHandler">立即打开</div>
<div class="content_tip">
<div class="font_cls">如未安装请点击下载</div>
<div class="font_cls">如已安装未自动跳转 <span @click="downLoadHandler">立即下载</span></div>
</div>
</div>
</div>
</template>
<script>
import { getNewVersion } from "@/api/order"
export default {
name: "goToApp",
methods: {
goAppHandler() {
window.location.href = "rvdriver://page/pagesLogin/phoneLogin?source=h5Link";
},
async downLoadHandler() {
getNewVersion({
appType: 5
}).then((res) => {
window.location.href = res?.result?.path
}).catch(() => {
this.$message.error('请求版本失败,请重试');
});
}
}
}
</script>
<style lang="scss" scoped>
.wrap {
width: 100%;
height: 100vh;
background-image: url('@/assets/toAppBg.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
.content {
width: 100%;
position: fixed;
top: 74vh;
}
.goToBtn {
width: 70%;
height: 45px;
margin-left: 15%;
background: linear-gradient( 270deg, #2347C0 0%, #72A3FF 100%);
border-radius: 23px;
//font-weight: bold;
font-size: 17px;
color: #FFFFFF;
letter-spacing: 2px;
line-height: 45px;
text-align: center;
}
.content_tip {
width: 100%;
margin-top: 15px;
.font_cls {
font-size: 14px;
color: #808080;
line-height: 20px;
text-align: center;
span {
color: #007BE9;
}
}
}
</style>

View File

@@ -8,7 +8,7 @@
:border="false"
:fixed="true"
:safe-area-inset-top="true"
@click-left="h5GoBack"
@click-left="back"
/>
</div>
<div class="addContentWrap">
@@ -92,6 +92,57 @@
</template>
</el-input>
</div>
<div class="lineBot" v-if="permissonList?.includes('hasInsuranceAudit')"></div>
<div class="itemContent" v-if="permissonList?.includes('hasInsuranceAudit')" style="align-items: center">
<div class="titleType" style="width: 60px">
<img class="startImg" src="@/assets/start.png" />
<span>保费</span>
</div>
<van-field
type="number"
class="vanIpt"
v-model="liabilityInsuranceAmount"
input-align="right"
>
<template slot="right-icon" >
<span style="white-space: nowrap;"></span>
</template>
</van-field>
</div>
<div class="lineBot" v-if="permissonList?.includes('hasInsuranceAudit')"></div>
<div class="itemContent" v-if="permissonList?.includes('hasInsuranceAudit')">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>保额</span>
</div>
<div style="display:flex;align-items: center;justify-content: flex-end">
<el-select
v-model="liabilityInsuranceQuota"
value-key="name"
class="elSelect"
collapse-tags="collapse-tags"
placeholder="请选择" style="width: 55%"
>
<el-option
v-for="item in liabilityQuotaOptions"
:key="item.value"
:label="item.value"
:value="item.value"
>
</el-option>
</el-select><span style="margin-right: 16px;opacity: .5;">万元</span>
</div>
<!-- <van-field
type="number"
class="vanIpt"
v-model="liabilityInsuranceQuota"
input-align="right"
>
<template slot="right-icon" >
<span style="white-space: nowrap;">万元 </span>
</template>
</van-field>-->
</div>
</template>
<common-btn title="保存" @submitClick="submitBtn"/>
<van-calendar v-model="showDatePicker" :min-date="minDate"
@@ -104,7 +155,7 @@
import {Dialog} from "vant";
import {myMixins} from "@/utils/myMixins"
import {formatDate1} from "@/utils/common"
import { uploadImage, updateInsurance, getInfoById} from "@/api/mine"
import { uploadImage, updateInsurance, getInfoById,userOperationPermissions} from "@/api/mine"
import CommonBtn from "@/components/commonBtn.vue"
export default {
name: "vehicleAdd",
@@ -115,6 +166,7 @@ export default {
children: 'children',
label: 'name'
},
liabilityQuotaOptions:[{value:10},{value:20},{value:30},{value:50},{value:70},{value:80},{value:100},{value:200}],
minDate: new Date(1970, 0, 1), // 设置最小可选日期1970年1月1日
maxDate: new Date(2099, 11, 31), // 设置最大可选日期2099年12月31日
showDatePicker: false,
@@ -130,6 +182,9 @@ export default {
insurancePicturePhoto: '', // 保单照片
isMultiple: false, // 是否支持多选
insuranceCorp: '',
liabilityInsuranceAmount:'',
liabilityInsuranceQuota:'',
permissonList:[],
insuranceOptions: [{
name: '太平洋',
value: 1
@@ -183,11 +238,24 @@ export default {
async mounted() {
this.id=this.$route.params?.id
await this.getPermissions()
if( this.id){
// await this.vehicleInfo()
}
},
methods:{
back() {
this.$router.push({
name:'vehicleAdd',
params:{
id: this.id
}
})
},
async getPermissions(){
let res = await userOperationPermissions();
this.permissonList = res.data
},
async vehicleInfo(){
let res= await getInfoById({
vehicleId:this.id
@@ -259,6 +327,14 @@ export default {
this.$toast('保单有效期不能为空')
return
}
if(this.hasLiabilityInsurance == 1 && this.permissonList?.includes('hasInsuranceAudit') && !this.liabilityInsuranceAmount && this.liabilityInsuranceAmount!=0){
this.$toast('保费不能为空')
return
}
if(this.hasLiabilityInsurance == 1 && !this.liabilityInsuranceQuota && this.permissonList?.includes('hasInsuranceAudit')){
this.$toast('保额不能为空')
return
}
let timeObj;
if(this.dateVal) {
timeObj = this.formatDateTimeRange(this.dateVal)
@@ -271,6 +347,8 @@ export default {
insuranceCorp: this.hasLiabilityInsurance == 1 ? this.insuranceCorp : '',
liabilityInsuranceStartTime: this.hasLiabilityInsurance == 1 ? (timeObj?.startTime || '' ) : '',
liabilityInsuranceEndTime: this.hasLiabilityInsurance == 1 ? (timeObj?.endTime || '') : '',
liabilityInsuranceQuota:this.liabilityInsuranceQuota || '',
liabilityInsuranceAmount:this.liabilityInsuranceAmount || '',
})
}
await updateInsurance(params)
@@ -280,7 +358,8 @@ export default {
this.$toast('添加成功')
}
setTimeout(()=>{
this.$router.back();
// this.$router.back();
this.back()
},2000)
}
},

View File

@@ -67,7 +67,7 @@
accept="image "
/>
</div>
<div class="lineBot"></div>
<!-- <div class="lineBot"></div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
@@ -80,11 +80,12 @@
:preview-size="54"
accept="image "
/>
</div>
</div>-->
<div class="lineBot"></div>
<div class="itemContent">
<div class="titleType">
<span>头像</span>
<img class="startImg" src="@/assets/start.png" />
<span>免冠正面照</span>
</div>
<van-uploader
v-model="iconList"
@@ -307,7 +308,7 @@ export default {
let res = await uploadImage(formData)
this.drivingLicenceContrary = res.data;
},
async iconListHandler(file) { // 上传头像
async iconListHandler(file) { // 上传免冠正面照
const formData = new FormData();
formData.append("file" , file.file);
let res = await uploadImage(formData)
@@ -399,10 +400,14 @@ export default {
this.$toast('驾驶证首页未上传')
return
}
if(!this.drivingLicenceContrary) {
/* if(!this.drivingLicenceContrary) {
this.$toast('驾驶证副页未上传')
return
}
}*/
if(!this.icon) {
this.$toast('免冠正面照未上传')
return
}
let drivingLicenceName=this.drivingLicenceName.replace(/[^\u4e00-\u9fff]+/g, '');
let driverName=this.driverName.replace(/[^\u4e00-\u9fff]+/g, '');

View File

@@ -18,51 +18,109 @@
</template>
</van-nav-bar>
</div>
<van-pull-refresh v-model="isLoading" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div class="itemWrap" v-for="(item,index) in driverList" :key="index">
<div class="name-status">
<div class="namephone">{{ item.driverName }} / {{ item.driverPhone }}</div>
<div class="twoBtn">
<button v-if="permissonList.includes('driverModifyBtn')" class="del" @click="handleStatus(item)">{{ item.states?.label == '启用' ? '停用' : '启用' }}</button>
<button v-if="permissonList.includes('driverModifyBtn')" class="revise" @click="updateDriver(item)">修改</button>
<van-search
v-model="searchVal"
show-action
placeholder="请输入司机姓名、电话、身份证号"
@search="resetHandler"
>
<template #action>
<div @click="resetHandler">搜索</div>
</template>
</van-search>
<div class="filterWrap">
<el-select @change="resetHandler" multiple :collapse-tags="true" v-model="idCardStatusList" placeholder="身份证录入状态" :class="{'customSel':true , 'has-value': idCardStatusList }" clearable>
<el-option
v-for="item in idCardStatusListOptions"
:key="item.value"
:label="item.name"
:value="item.value">
</el-option>
</el-select>
<el-select @change="resetHandler" multiple :collapse-tags="true" v-model="driverLicenseStatusList" placeholder="驾驶证录入状态" :class="{'customSel':true , 'has-value': driverLicenseStatusList }" clearable>
<el-option
v-for="item in driverLicenseStatusListOptions"
:key="item.value"
:label="item.name"
:value="item.value">
</el-option>
</el-select>
<el-select @change="resetHandler" multiple :collapse-tags="true" v-model="authStatusList" placeholder="认证状态" :class="{'customSel':true , 'has-value': authStatusList }" clearable>
<el-option
v-for="item in authStatesOptions"
:key="item.value"
:label="item.name"
:value="item.value">
</el-option>
</el-select>
</div>
<div class="wrap_cls">
<van-pull-refresh v-model="isLoading" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div class="itemWrap" v-for="(item,index) in driverList" :key="index" @click.stop="updateDriver(item)">
<div class="name-status">
<div class="namephone">{{ item.driverName }} / {{ item.driverPhone }}
<span style="margin-left: 10px" :class="item.states?.code == 1 ? 'statusYes' : 'statusNo'">{{ item.states?.label }}</span>
</div>
<div class="twoBtn">
<!-- 只有启用状态下才展示停用按钮-->
<button v-if="permissonList.includes('driverModifyBtn') && item.states?.code==1" class="del" @click.stop="handleStatus(item)">停用</button>
<!-- <button v-if="permissonList.includes('driverModifyBtn')" class="revise" @click="updateDriver(item)">修改</button>-->
</div>
</div>
</div>
<div class="" style="display: flex">
<div class="sex" style="margin-right: 40px">
<span class="halfOpci">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:</span>
<span class="allOpci">{{item.sex?.label}}</span>
<div class="juhe flex-between">
<span class="zdJuhe">录入状态</span>
<span class="flex-right">
<span class="common_cls" v-if="item.idCardStatusStr" :class="getClass(item.idCardStatus)?.className">{{item.idCardStatusStr}}</span>
<span class="common_cls" v-if="item.driverLicenseStatusStr" :class="getClass(item.driverLicenseStatus)?.className">{{item.driverLicenseStatusStr}}</span>
</span>
<!-- <span class="flex-right" v-if="item.idCardStatusStr">
<span class="common_cls" :class="getClass(item.idCardStatus)?.className">{{item.idCardStatusStr}}</span>
</span>-->
</div>
<div class="carType">
<span class="halfOpci">准驾车型:</span>
<span class="allOpci">{{item.drivingModel}}</span>
<div class="juhe flex-between">
<span class="zdJuhe">认证状态</span>
<span class="flex-right" v-if="item.authStatusStr">
<span class="common_cls" :class="getClass(item.authStatusStr)?.className">{{item.authStatusStr}}</span>
</span>
</div>
<div class="" style="display: flex">
<div class="sex" style="margin-right: 40px">
<span class="halfOpci">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:</span>
<span class="allOpci">{{item.sex?.label}}</span>
</div>
<div class="carType">
<span class="halfOpci">准驾车型:</span>
<span class="allOpci">{{item.drivingModel}}</span>
</div>
</div>
<div>
<span class="halfOpci">身份证号:</span>
<span class="allOpci">{{ item.identityCardNumber }}</span>
</div>
<van-icon class="arrow_position" v-if="permissonList.includes('driverModifyBtn')" name="arrow" />
<!-- <div>
<span class="halfOpci">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:</span>
<span :class="item.states?.code == 1 ? 'statusYes' : 'statusNo'">{{ item.states?.label }}</span>
</div>-->
</div>
<div>
<span class="halfOpci">身份证号:</span>
<span class="allOpci">{{ item.identityCardNumber }}</span>
</div>
<div>
<span class="halfOpci">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:</span>
<span :class="item.states?.code == 1 ? 'statusYes' : 'statusNo'">{{ item.states?.label }}</span>
</div>
</div>
</van-list>
</van-pull-refresh>
</van-list>
</van-pull-refresh>
</div>
</div>
</template>
<script>
import {myMixins} from "@/utils/myMixins"
import {driverList,enableAction,userOperationPermissions} from "@/api/mine"
import {driverList, enableAction, userOperationPermissions} from "@/api/mine"
import {Dialog} from "vant";
export default {
name: "driverManage",
mixins:[myMixins],
@@ -79,6 +137,40 @@ export default {
loading: false,
finished: false,
permissonList:[],
searchVal:'',
idCardStatusList:[],
driverLicenseStatusList:[],
authStatusList:[],
idCardStatusListOptions: [{
name: '身份证未录入',
value: 0
},{
name: '身份证核验通过',
value: 1
},{
name: '身份证核验不通过',
value: 2
}],
authStatesOptions: [{
name: '免冠正面照未录入',
value: 1
},{
name: '免冠正面照认证通过',
value: 3
},{
name: '免冠正面照认证不通过',
value: 2
}],
driverLicenseStatusListOptions: [{
name: '驾驶证未录入',
value: 0
},{
name: '驾驶证认证通过',
value: 1
},{
name: '驾驶证认证不通过',
value: 2
}],
}
},
mounted() {
@@ -88,9 +180,41 @@ export default {
// this.getDriverList()
},
methods:{
getClass(id) {
const ids = String(id)
const steps = {
0: {
className: 'default_cls'
},
1: {
className: 'success_cls'
},
2: {
className: 'danger_cls'
},
'免冠正面照未录入': {
className: 'default_cls'
},
'免冠正面照认证通过': {
className: 'success_cls'
},
'免冠正面照认证不通过': {
className: 'danger_cls'
},
}
return steps[ids] || { className: 'default_cls' }
},
async resetHandler() {
this.pageNum=1
this.finished = false;
this.total = 0;
await this.getDriverList();
},
async onLoad(){
await this.getDriverList()
this.pageNum++;
if (this.total>10){
this.pageNum++;
}
// 加载状态结束
this.loading = false;
// 数据全部加载完成
@@ -118,7 +242,11 @@ export default {
async getDriverList(){
let res = await driverList({
pageNum:this.pageNum,
pageSize:this.pageSize
pageSize:this.pageSize,
searchVal: this.searchVal,
idCardStatusList: this.idCardStatusList,
driverLicenseStatusList: this.driverLicenseStatusList,
authStatusList: this.authStatusList,
});
if(res.code == 200){
this.total=res.total
@@ -130,12 +258,11 @@ export default {
this.driverList = preList.concat(arr)
}
}
console.log('this.driverList',this.driverList)
},
async getPermissions(){
let res = await userOperationPermissions();
this.permissonList = res.data
// console.log("司机管理",this.permissonList.includes('driverAddBtn'))
},
async handleStatus(item){
if(item.states.code === 0){
@@ -143,14 +270,23 @@ export default {
}else{
this.states = 0
}
await enableAction({
driverId:item.driverId,
states:this.states
})
this.pageNum = 1;
await this.getDriverList();
Dialog.confirm({
message: '当前状态为启用,是否要改为停用?',
}).then(async () => {
await enableAction({
driverId:item.driverId,
states:this.states
})
this.pageNum = 1;
await this.getDriverList();
}).catch(() => {
});
},
updateDriver(item){
if(!this.permissonList.includes('driverModifyBtn')){//有权限才能修改
return
}
// 在当前组件中进行路由跳转并传递参数对象
this.$router.push({
name: 'driverAdd', // 目标路由的名称
@@ -185,7 +321,7 @@ export default {
.wrap {
background: #F4F5F7;
@include sizingPadding(13px,13px);
//@include sizingPadding(13px,13px);
@include wh(100%, 100%);
overflow-y: auto;
}
@@ -208,7 +344,7 @@ export default {
}
.itemWrap {
@include wh(100%, 104px);
@include wh(100%, 140px);
@include radiusSizing(6px);
@include fontWeightSize(400, 12px);
@include flexBetween;
@@ -216,6 +352,7 @@ export default {
box-shadow: 0px 2px 10px 0px rgba(216, 216, 216, 0.5);
margin-bottom: 10px;
padding: 11px 13px 9px 15px;
position: relative;
.halfOpci {
opacity: .5;
margin-right: 5px;
@@ -224,15 +361,15 @@ export default {
opacity: 1;
}
.statusNo {
color: #FF0000;
color: red;
}
.statusYes {
color: #09B820
color: green
}
}
.name-status {
@include flexColBet;
@include flexBetCen;
.namephone {
@include fontWeightSize(bold, 14px)
}
@@ -256,6 +393,128 @@ export default {
margin-left: 15px;
}
}
}
.filterWrap {
width: 100%;
padding-right: 13px;
padding-left: 13px;
display: flex;
overflow-x: auto; /* 允许横向滚动 */
white-space: nowrap; /* 防止子元素换行 */
padding-bottom: 10px;
-webkit-overflow-scrolling: touch; /* 在iOS上平滑滚动 */
scrollbar-width: none; /* Firefox */
padding-bottom: 10px;
/*padding: 10px;*/
margin-bottom: 10px;
background-color: #fff;
box-sizing: border-box;
&::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
.customSel {
flex: 0 0 auto; /* 防止子元素被压缩 */
width: calc(33% - 5px);
/*width: 100%;*/
height: 25px;
background: #F5F5F5;
border-radius: 4px;
font-size: 10px;
color: #323233;
margin-right: 5px;
::v-deep .el-input__inner{
padding: 0 2px;
height: 25px;
font-size: 10px;
background: #F5F5F5;
border-radius: 4px;
border: none;
}
::v-deep .el-input__icon{
line-height: 25px;
font-size: 10px;
width: 18px;
color: #2A5094;
}
::v-deep .el-input__suffix{
right: 2px;
}
}
.customInput{
/*width: 49%;*/
width: 130px;
}
.has-value ::v-deep .el-input__inner{
background: #F1F6FF ;
color: #007BE9;
font-weight: bold;
padding-left: 8px;
}
.has-value ::v-deep .el-input__icon{
color: #007BE9;
}
.priceSel{
display: flex;
justify-content: space-around;
align-items: center;
color: #C0C4CC;
.iconSpan{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 8px;
}
}
.has-price{
color: #007BE9 !important;
}
}
.wrap_cls {
width: 100%;
padding-left: 13px;
padding-right: 13px;
box-sizing: border-box;
}
.common_cls {
color: #fff;
padding: 4px 8px;
border-radius: 3px;
margin-right: 6px;
}
.default_cls {
background-color: #B0BEC5;
}
.danger_cls {
background-color: red;
}
.success_cls {
background-color: #4CAF50;
}
.info_cls {
background-color: #FF9800;
}
.main_cls {
background-color: #6C9BFF;
}
.arrow_position {
position: absolute;
right: 5px;
top: 50px;
font-size: 20px;
opacity: 0.6;
}
.carType,.zdJuhe{
opacity: .7;
@include fontWeightSize(400,12px);
margin-right: 8px;
}
/*.zdJuhe {
width: 80px;
text-align: right;
margin-left: -10px;
}*/
.flex-between{
line-height: 24px;
}
</style>

View File

@@ -9,7 +9,7 @@
<img class="title1" src="@/assets/supplier/title1.png" alt="">
<div class="credentials_wrap">
<div class="credentials_item">
<div class="credentials_title">1. 法人身份证正面</div>
<div class="credentials_title">1.法人身份证人像页</div>
<van-uploader
accept="image/*"
v-model="idFrontPhotoList"
@@ -22,7 +22,7 @@
</van-uploader>
</div>
<div class="credentials_item ml2">
<div class="credentials_title">2. 法人身份证反面</div>
<div class="credentials_title">2.法人身份证国徽页</div>
<van-uploader
accept="image/*"
v-model="idBackPhotoList"
@@ -35,7 +35,7 @@
</van-uploader>
</div>
<div class="credentials_item ml2">
<div class="credentials_title">3. 营业执照</div>
<div class="credentials_title">3.营业执照</div>
<van-uploader
accept="image/*"
v-model="companyPhotoList"
@@ -47,6 +47,19 @@
</div>
</van-uploader>
</div>
<div class="credentials_item">
<div class="credentials_title">4.开户许可证/基本存款账户信息</div>
<van-uploader
accept="image/*"
v-model="licensePhotoList"
:after-read="licensePhotoHandler"
:max-size="5 * 1024 * 1024"
max-count="1">
<div class="custom-background">
<img src="@/assets/supplier/licensePhoto.png" alt="">
</div>
</van-uploader>
</div>
</div>
</div>
<div class="company_info">
@@ -57,6 +70,7 @@
<van-field :border="true" readonly v-model="form.legalName" class="required" name="legalName" label="法人姓名" placeholder="请输入" :rules="[{ required: true, message: '请输入法人姓名' }]" />
<van-field :border="true" v-model="form.linkName" class="required" name="linkName" label="联系人姓名" placeholder="请输入" :rules="[{ required: true, message: '请输入联系人姓名' }]" />
<van-field :border="true" v-model="form.linkPhone" class="required" name="linkPhone" label="联系电话" placeholder="请输入" :rules="phoneVerify" />
<van-field :border="true" v-model="form.linkEmail" class="required" name="linkEmail" label="邮箱" placeholder="请输入" :rules="emailVerify" />
<van-field :border="true" class="required" name="serviceType" label="服务能力">
<template #input>
<el-tree
@@ -121,13 +135,16 @@
return {
clickFlag: true,
phoneVerify: [{ required: true, message: '请输入联系电话' }, { validator: value => { return /^1[3456789]\d{9}$/.test(value) }, message: '联系电话格式不正确' }],
emailVerify: [{ required: true, message: '请输入邮箱' }, { validator: value => { return /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/.test(value) }, message: '邮箱格式不正确' }],
id:'', //车辆Id
idBackPhotoList: [],
idBackPhoto: '',
idFrontPhotoList: [],
idFrontPhoto: '',
companyPhotoList: [],
licensePhotoList: [],
companyPhoto: '',
licensePhoto:'',
form: {
name: '',
legalName: '',
@@ -138,6 +155,23 @@
trailCount: '',
minorCount: '',
serviceAreaCode: [],
linkEmail:'',
accountInfoDTO:{
dutyParagraph:'',
accountNumber:'',
accountName:'',
accountType:'',
invoiceType:'',
settlementType:'',
shouldRate:'',
realRate:'',
unitName:'',
bankNo:'',
billingPhone:'',
billingAddress:'',
billHead:'',
companyType:'',
}
},
qrCodeUrl: '',
qrCode: '',
@@ -158,7 +192,7 @@
children: 'children',
label: 'title',
},
areaProps: { multiple: true, checkStrictly: true, value: 'id',label: 'title', emitPath: false, },
areaProps: { multiple: true, checkStrictly: true, value: 'id',label: 'title', emitPath: false},
configId: '',
wechatId: '',
}
@@ -195,6 +229,7 @@
this.idFrontPhoto = _data?.idCardFrontUrl;
this.idBackPhoto = _data?.idCardBackUrl;
this.companyPhoto = _data?.businessLicense;
this.licensePhoto = _data?.accountUrl;
this.wechatId = _data?.wechatId
if(this.idFrontPhoto) {
this.idFrontPhotoList = [{ url : this.idFrontPhoto }];
@@ -205,6 +240,9 @@
if(this.companyPhoto) {
this.companyPhotoList = [{ url : this.companyPhoto }];
}
if(this.licensePhoto){
this.licensePhotoList= [{ url : this.licensePhoto }];
}
if( _data.service ) {
this.$refs.tree.setCheckedKeys(_data.service.split(','))
}
@@ -215,21 +253,26 @@
},
async applyAdd() {
if( !this.idFrontPhoto ) {
this.$toast('法人身份证正面照未上传')
this.$toast('法人身份证人像页未上传')
return
}
if( !this.idBackPhoto ) {
this.$toast('法人身份证反面照未上传')
this.$toast('法人身份证国徽页未上传')
return
}
if( !this.companyPhoto ) {
this.$toast('营业执照未上传')
return
}
if( !this.licensePhoto ) {
this.$toast('开户许可证未上传')
return
}
if( !(this.form.serviceAreaCode.length > 0) ) {
this.$toast('服务区域不能为空')
return
}
// this.wechatId='wmOTNXBwAABrvKkE_Fh8ZN8Xm2S9v2wQ'
if(!this.wechatId) {
await this.QrCodeResult();
if( !this.wechatId ) {
@@ -237,7 +280,6 @@
return
}
}
if( this.$refs.tree.getCheckedKeys().length > 0 ) {
await this.saveHandler()
} else {
@@ -261,22 +303,15 @@
_node.map(item => {
checkArr.push(item.data.id)
})
/* let treeArr = this.$refs.tree.getCheckedKeys();
let childrenTreeArr = [];
treeArr.map(item => {
let _arr = this.supplierServiceList.filter(_item => _item.id == item) || [] // 获取对应的大类
_arr[0]?.children?.map(childItem => {
childrenTreeArr?.push(childItem?.id)
})
})
let allArr = [...treeArr, ...childrenTreeArr]*/
let res = await saveSupplier({
id: this.id,
...this.form,
accountInfoJson:JSON.stringify(this.form.accountInfoDTO),
accountInfoDTO:{},
idCardFrontUrl: this.idFrontPhoto,
idCardBackUrl: this.idBackPhoto,
businessLicense: this.companyPhoto,
accountUrl:this.licensePhoto,
service: this.$refs.tree.getCheckedKeys().join(',') ,
serviceAreaCode: checkArr.join(','),
wechatId: this.wechatId,
@@ -286,16 +321,28 @@
message: "操作成功"
}).then(async () => {
this.goPage('supplierAddResult', { id : res?.data })
this.clickFlag = true
setTimeout(() => {
this.clickFlag = true
}, 1000)
});
} finally {
this.clickFlag = true
setTimeout(() => {
this.clickFlag = true
}, 1000)
}
}
},
async getAreaTree() {
let res = await getArea();
this.areaList = res?.data;
let result = res?.data
result?.map(item=>{
if( item.parentId == 0 ) {
this.$set(item , 'disabled' ,true)
} else {
this.$set(item , 'disabled' ,false)
}
})
this.areaList = result;
},
/*async getSupplierServiceTree(){
let res = await supplierServicePartTree({
@@ -370,8 +417,26 @@
})
this.form.name = res?.data?.name;
this.form.areaName = res?.data?.address
this.form.accountInfoDTO.dutyParagraph=res?.data?.regNum
this.form.accountInfoDTO.unitName = res?.data?.name
this.form.accountInfoDTO.companyType=res?.data?.type
await this.QrCodeHandler();
},
async licensePhotoHandler(file){// 开户许可证
const formData = new FormData();
formData.append("file" , file.file);
let res = await uploadImage(formData);
this.licensePhoto = res.data
await this.licenseOcrHandler()
},
async licenseOcrHandler(){// 开户许可证ocr识别
let res = await ocrHandler({
ocrType: 15,
imageUrl: this.licensePhoto,
})
this.form.accountInfoDTO.accountNumber=res?.data?.accountNumber
this.form.accountInfoDTO.accountName = res?.data?.accountBank
},
},
computed: {
regionText() {
@@ -401,12 +466,13 @@
padding: 0 20px;
}
.credentials_info {
height: 170px;
//height: 170px;
}
.credentials_wrap {
display: flex;
width: 100%;
margin-top: 10px;
flex-wrap: wrap;
.credentials_item {
width: 32%;
text-align: center;

View File

@@ -28,17 +28,22 @@
<div class="common_title">证件照信息</div>
<div class="credentials_wrap">
<div class="credentials_item">
<div class="credentials_title">1. 法人身份证正面</div>
<div class="credentials_title">1.法人身份证人像页</div>
<img :src="supplierInfo?.idCardFrontUrl" alt="">
</div>
<div class="credentials_item ml2">
<div class="credentials_title">2. 法人身份证反面</div>
<div class="credentials_title">2.法人身份证国徽页</div>
<img :src="supplierInfo?.idCardBackUrl" alt="">
</div>
<div class="credentials_item ml2">
<div class="credentials_title">3. 营业执照</div>
<div class="credentials_title">3.营业执照</div>
<img :src="supplierInfo?.businessLicense" alt="">
</div>
<div class="credentials_item">
<div class="credentials_title">4.开户许可证/基本存款账户信息</div>
<img v-if="supplierInfo?.accountUrl" :src="supplierInfo?.accountUrl" alt="">
<div v-else class="empty"></div>
</div>
</div>
</div>
<div class="company_info">
@@ -72,6 +77,10 @@
<div class="label">联系电话</div>
<div class="content">{{supplierInfo?.linkPhone}}</div>
</div>
<div class="info_item">
<div class="label">邮箱</div>
<div class="content">{{supplierInfo?.linkEmail}}</div>
</div>
<div class="info_item">
<div class="label">服务能力</div>
<div class="content service_color">{{supplierInfo?.serviceCategoryName}}</div>
@@ -191,7 +200,6 @@
id: this.id,
});
this.supplierInfo = res?.data;
console.log('res', res)
},
}
}
@@ -259,7 +267,7 @@
margin-top: 5px;
}
.supplier_content {
padding: 0 24px;
padding: 0 20px;
width: 100%;
box-sizing: border-box;
}
@@ -282,6 +290,7 @@
display: flex;
width: 100%;
margin-top: 10px;
flex-wrap: wrap;
.credentials_item {
width: 32%;
text-align: center;
@@ -290,10 +299,14 @@
color: #4A4A4A;
padding: 10px 0;
}
img {
img,.empty{
width: 97px;
height: 64px;
}
.empty{
border: 1px solid silver;
margin-left: 8px;
}
}
.ml2 {
margin-left: 2%;

View File

@@ -8,10 +8,10 @@
:border="false"
:fixed="true"
:safe-area-inset-top="true"
@click-left="h5GoBack"
@click-left="back"
/>
</div>
<div class="addContentWrap">
<div class="addContentWrap">
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
@@ -20,6 +20,7 @@
<van-uploader
v-model="vehicleLicenseFrontList"
:after-read="vehicleLicenseFrontHandler"
@oversize="onOversize"
:max-size="5 * 1024 * 1024"
max-count="1"
:preview-size="54"
@@ -34,6 +35,7 @@
<van-uploader
v-model="vehicleLicenseBackList"
:after-read="vehicleLicenseBackHandler"
@oversize="onOversize"
:max-size="5 * 1024 * 1024"
max-count="1"
:preview-size="54"
@@ -48,6 +50,7 @@
<van-uploader
v-model="vehicleLicenseCarPhotoList"
:after-read="vehicleLicenseCarHandler"
@oversize="onOversize"
:max-size="5 * 1024 * 1024"
max-count="1"
:preview-size="54"
@@ -62,6 +65,8 @@
<van-uploader
v-model="vehicleFrontPhotoList"
:after-read="vehicleFrontPhotoHandler"
@oversize="onOversize"
:max-size="5 * 1024 * 1024"
max-count="1"
:preview-size="54"
accept="image "
@@ -193,7 +198,7 @@
</div>
<div class="isJoin">
<van-radio-group v-model="vehicleStatus" :class="{ 'disabled-tree': vehicleInfoDisabled }" :disabled="vehicleInfoDisabled" @change="isVehicleChange" class="joinWrap">
<van-radio :name="1" style="margin-right: 26px">
<van-radio :name="1" style="margin-right: 14px">
启用
<img
slot="icon"
@@ -201,7 +206,7 @@
:src="props.checked ? activeIcon : inactiveIcon"
>
</van-radio>
<van-radio :name="2">
<van-radio :name="2" style="margin-right: 14px">
停用
<img
slot="icon"
@@ -209,6 +214,14 @@
:src="props.checked ? activeIcon : inactiveIcon"
>
</van-radio>
<van-radio :name="12" disabled>
-服务商停用
<img
slot="icon"
slot-scope="props"
:src="props.checked ? activeIcon : inactiveIcon"
>
</van-radio>
</van-radio-group>
</div>
</div>
@@ -238,7 +251,7 @@
</van-radio-group>
</div>
</div>
<div class="item_content_btn">
<div class="item_content_btn" v-if="[2,3].includes(this.liabilityInsuranceAudit)">
<span @click="goContinueInsurance">续保更新</span>
</div>
<span style="color: red">如有投保未投保选不必上传交强险或者商业险等其他保单</span>
@@ -256,6 +269,7 @@
:deletable="!disabledShow"
max-count="1"
:preview-size="54"
@click-preview="clickPreview"
/>
</div>
<div class="lineBot"></div>
@@ -297,9 +311,83 @@
</template>
</el-input>
</div>
<div class="lineBot" v-if="permissonList?.includes('hasInsuranceAudit')"></div>
<div class="itemContent" v-if="permissonList?.includes('hasInsuranceAudit')">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>保费</span>
</div>
<van-field
type="number"
class="vanIpt"
v-model="liabilityInsuranceAmount"
input-align="right"
>
<template slot="right-icon" >
<span style="white-space: nowrap;"></span>
</template>
</van-field>
</div>
<div class="lineBot" v-if="permissonList?.includes('hasInsuranceAudit')"></div>
<div class="itemContent" v-if="permissonList?.includes('hasInsuranceAudit')">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>保额</span>
</div>
<div style="display:flex;align-items: center;justify-content: flex-end">
<el-select
v-model="liabilityInsuranceQuota"
value-key="name"
class="elSelect"
collapse-tags="collapse-tags"
placeholder="请选择" style="width: 55%"
>
<el-option
v-for="item in liabilityQuotaOptions"
:key="item.value"
:label="item.value"
:value="item.value"
>
</el-option>
</el-select><span style="margin-right: 16px;opacity: .5;">万元</span>
</div>
<!-- <van-field
type="number"
class="vanIpt"
v-model="liabilityInsuranceQuota"
input-align="right"
>
<template slot="right-icon" >
<span style="white-space: nowrap;">万元 </span>
</template>
</van-field>-->
</div>
<!-- <div class="lineBot"></div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>保单号(救援)</span>
</div>
<van-field
class="vanIpt"
style="width: 72%"
v-model="insuranceCode"
input-align="right"
> </van-field>
</div>
<div class="lineBot"></div>
<div class="itemContent">
<div class="titleType"> <span>保单号(中道物流)</span> </div>
<van-field
class="vanIpt"
style="width: 65%"
v-model="insuranceCodeZd"
input-align="right"
> </van-field>
</div>
<div class="lineBot"></div>-->
</template>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
@@ -330,10 +418,33 @@
</div>
</div>-->
</div>
<two-common-btn class="btn" @cancelClick="h5GoBack" @submitClick="submitBtn" />
<two-common-btn class="btn" @cancelClick="back" @submitClick="submitBtn" />
<van-calendar v-model="showDatePicker" :min-date="minDate"
:max-date="maxDate" type="range" @confirm="onConfirm" />
</div>
<van-popup ref="success" v-model="approvalDialogShow">
<div class="pop_wrap">
<div class="pop_title">车辆提交审批</div>
<div class="pop_content">
<div class="iptWrap" v-if="vehicleInfoChange">
<div>车辆和服务备注</div>
<el-input placeholder="请输入车辆和服务备注" type="textarea" show-word-limit maxlength="200"
v-model="approvalForm.serviceRemark"></el-input>
</div>
<div class="iptWrap" v-if="insuranceChange">
<div>保单备注</div>
<el-input placeholder="请输入保单备注" type="textarea" show-word-limit maxlength="200"
v-model="approvalForm.insuranceRemark"></el-input>
</div>
</div>
<div class="tip_button_wrap">
<div class="continue close" @click="closeApproval">取消</div>
<div class="continue" :class="{'loading': loading}" @click="submitApprovalHandle">提交审批</div>
</div>
</div>
</van-popup>
</div>
</template>
@@ -342,7 +453,8 @@
import {Dialog} from "vant";
import {formatDate1} from "@/utils/common"
import {myMixins} from "@/utils/myMixins"
import {vehicleTypeList,saveVehicle,getInfoById,supplierServiceTree, uploadImage, ocrHandler, userOperationPermissions} from "@/api/mine"
import {vehicleTypeList,saveVehicle,getInfoById,supplierServiceTree, uploadImage, ocrHandler,
userOperationPermissions,saveSupplierApproval} from "@/api/mine"
import TwoCommonBtn from "@/components/twoBtnCommon.vue"
import CellGroup from "@/components/cellGroup.vue";
export default {
@@ -373,6 +485,7 @@ export default {
typeList:[],//车辆类型列表
selectedOption:[],//车辆类型
id:'',//车辆Id
supplierId:'',//服务商Id
serviceIds:[],//车辆服务种类,
supplierServiceList:[],
oldSupplierServiceList:[],
@@ -500,7 +613,29 @@ export default {
}, {
name: '其他',
value: 14
}]
}],
liabilityQuotaOptions:[{value:10},{value:20},{value:30},{value:50},{value:70},{value:80},{value:100},{value:200}],
approvalForm:{
type:2,
supplierId:'',
vehicleId:'',
serviceChange:null,
serviceRemark:'',
insuranceChange:null,
insuranceRemark:'',
vehicleOtherChange:null,
vehicleOtherRemark:'',
},
originData:{},//记录更改之前的数据
changedFields: [], // 用于存储变更过的字段
approvalDialogShow:false,
vehicleInfoChange:false,
insuranceChange:false,
loading: false,
liabilityInsuranceAmount:'',
liabilityInsuranceQuota:'',
insuranceCode:'',
insuranceCodeZd:'',
}
},
computed: {
@@ -548,13 +683,25 @@ export default {
this.setDefault();
},
},
async mounted() {
this.id=this.$route.params?.id
this.approvalForm.supplierId=this.$route.params?.supplierId
await this.getSupplierServiceTree();
await this.getTypeList();
if( this.id){
await this.vehicleInfo()
}else{
this.originData={
vehicleType:'',
vehicleStatus:'',
virtualVehicle:'',
serviceIds:[],
hasLiabilityInsurance:'',
insuranceCorp:'',
liabilityInsuranceStartTime:'',
liabilityInsuranceEndTime:'',
insurancePicturePhoto:''
}
}
},
methods:{
@@ -587,6 +734,10 @@ export default {
const [start, end] = date;
this.showDatePicker = false;
this.dateVal = `${this.formatDate(start)} - ${this.formatDate(end)}`;
console.log('this.dateVal',this.dateVal)
let timeObj = this.formatDateTimeRange(this.dateVal)
console.log('timeObj',timeObj)
},
formatDateTimeRange(str) {
const [startStr, endStr] = str.split(' - ').map(s => s.trim());
@@ -637,6 +788,9 @@ export default {
this.vehicleLicense = 5
}
},
onOversize() {
this.$toast(`文件大小不能超过5M`)
},
async vehicleLicenseFrontHandler(file) { // 上传 行驶证首页
const formData = new FormData();
formData.append("file" , file.file);
@@ -665,11 +819,19 @@ export default {
this.vehicleFrontPhoto = res.data;
await this.ocrCarFrontHandler()
},
clickPreview(){
console.log(' this.insurancePicturePhoto', this.insurancePicturePhoto)
if(this.insurancePicturePhoto && this.insurancePicturePhoto.indexOf('.pdf') !== -1){
let url=this.insurancePicturePhoto?.split(',')[0]
window.open(url.replace("http://", "https://"))
}
},
async insurancePictureFilesHandler(file) {
const formData = new FormData();
formData.append("file" , file.file);
let res = await uploadImage(formData);
this.insurancePicturePhoto = res.data;
console.log('insurancePictureFilesHandler',this.insurancePictureFiles)
},
async ocrCarFrontHandler() { // 车辆正面 orc 识别
let res = await ocrHandler({
@@ -736,6 +898,8 @@ export default {
this.vehicleLicenseInfo.overallDimension = '';
this.vehicleLicenseInfo.tractionWeight = '';
this.vehicleLicenseInfo.backPlateNo = ''; // 行驶证副页 车牌号,仅用来判断 ocr 识别是否成功
this.vehicleLicenseInfo.energySign = '';
this.vehicleLicenseInfo.marks = ''
let res = await ocrHandler({
ocrType: 3,
imageUrl: this.vehicleLicenseBack,
@@ -751,6 +915,8 @@ export default {
this.vehicleLicenseInfo.overallDimension = backInfo?.externalSize;
this.vehicleLicenseInfo.tractionWeight = backInfo?.TotalQuasiMass;
this.vehicleLicenseInfo.backPlateNo = backInfo?.plateNo;
this.vehicleLicenseInfo.energySign = backInfo?.fuelType;
this.vehicleLicenseInfo.marks = backInfo?.marks
}
console.log('this.vehicle', this.vehicleLicenseInfo)
},
@@ -793,6 +959,7 @@ export default {
let result=res.data;
console.log("result",result)
this.id=result.vehicleId
this.approvalForm.vehicleId=this.id
this.carNum=result.plateNumber
this.isJoin=result.hasPolymerization.code
this.selectedOption=result.vehicleType?.split(',').map((item)=>{
@@ -814,8 +981,9 @@ export default {
this.insuranceCorp = result.insuranceCorp;
this.liabilityInsuranceAudit = result.liabilityInsuranceAudit
if(this.insurancePicturePhoto) {
this.insurancePictureFiles = [{url: this.insurancePicturePhoto}]
this.insurancePictureFiles = [{url: this.insurancePicturePhoto?.split(',')[0]}]
}
console.log('this.insurancePictureFiles',this.insurancePictureFiles)
if( this.vehicleLicenseFront ) {
this.vehicleLicenseFrontList = [{ url : this.vehicleLicenseFront }];
}
@@ -834,6 +1002,17 @@ export default {
if( result.liabilityInsuranceEndTime && result.liabilityInsuranceStartTime ) {
this.dateVal = formatDate1(result.liabilityInsuranceStartTime) + ' - ' + formatDate1(result.liabilityInsuranceEndTime)
}
this.originData={
vehicleType:result.vehicleType,
vehicleStatus:result.vehicleStatus,
virtualVehicle:result.virtualVehicle,
serviceIds:result.serviceIds,
hasLiabilityInsurance:result.hasLiabilityInsurance,
insuranceCorp:result.insuranceCorp,
liabilityInsuranceStartTime:result.liabilityInsuranceStartTime,
liabilityInsuranceEndTime:result.liabilityInsuranceEndTime,
insurancePicturePhoto:result.insurancePicturePhoto
}
},
isChange(e){
this.hasLiabilityInsurance=e
@@ -851,11 +1030,200 @@ export default {
isVehicleChange(e) {
this.vehicleStatus=e
},
async submitBtn(){
if( !this.vehicleLicenseFront ) {
this.$toast('行驶证主页照片不能为空')
/* async submitAuditHandle(){//提交审核
let urls=[]
this.insurancePictureFiles?.forEach(item => urls.push(item.url))
let time =this.dateVal ? this.formatDateTimeRange(this.dateVal) : ''
// let time1=time.endTime?.split(' ')
let newFormValue={
vehicleStatus:this.vehicleStatus,
virtualVehicle:this.virtualVehicle,
serviceIds:this.$refs.tree.getCheckedKeys(true) || [],
vehicleType:this.selectedOption?.join(',') || '',
hasLiabilityInsurance:this.hasLiabilityInsurance,
insuranceCorp:this.insuranceCorp,
insurancePicturePhoto:urls?.join(','),
// liabilityInsuranceStartTime:time.startTime ? time.startTime : '',
liabilityInsuranceEndTime:time.endTime ? time.endTime : '',
}
console.log('newFormValue',newFormValue)
let oldFormValue={...this.originData,}
console.log('oldFormValue',oldFormValue)
this.compareObjects(newFormValue,oldFormValue);
if( this.changedFields && this.changedFields.length>0){
const arr1Set = new Set(this.changedFields);
const change1=['vehicleStatus','virtualVehicle','vehicleType','serviceIds']
const change2=['hasLiabilityInsurance','insurancePicturePhoto','insuranceCorp','liabilityInsuranceStartTime','liabilityInsuranceEndTime']
const serviceChangeItems = change1.filter(item => arr1Set.has(item));
console.log('serviceChangeItems',serviceChangeItems)
if(serviceChangeItems?.length>0){
this.vehicleInfoChange=true
this.approvalForm.serviceChange={}
change1.forEach(key => {
this.approvalForm.serviceChange[key] = this[key];
if(key=='serviceIds'){
this.approvalForm.serviceChange[key]=this.$refs.tree.getCheckedKeys(true)
}
if(key=='vehicleType'){
this.approvalForm.serviceChange[key]=this.selectedOption?.join(',') || ''
}
});
}else {
this.vehicleInfoChange=false
this.approvalForm.serviceChange=null
}
const insuranceChangeItems = change2.filter(item => arr1Set.has(item));
console.log('insuranceChangeItems',insuranceChangeItems)
if(insuranceChangeItems?.length>0){
this.insuranceChange=true
this.approvalForm.insuranceChange={}
change2.forEach(key => {
this.approvalForm.insuranceChange[key] =this[key];
let time =this.dateVal ? this.formatDateTimeRange(this.dateVal) : ''
if(key=='liabilityInsuranceStartTime' && time && time.startTime){
this.approvalForm.insuranceChange[key]=time.startTime
}
if(key=='liabilityInsuranceEndTime' && time && time.endTime){
// let time1=time.endTime?.split(' ')
this.approvalForm.insuranceChange[key]= time.endTime
}
if(key=='insurancePicturePhoto' && this.insurancePictureFiles?.length>0){
let urls=[]
this.insurancePictureFiles?.forEach(item => urls.push(item.url))
this.approvalForm.insuranceChange[key]=urls?.join(',')
}
});
}else {
this.insuranceChange=false
this.approvalForm.insuranceChange=null
}
}
if((this.vehicleInfoChange || this.insuranceChange) && this.id){
// this.approvalDialogShow=true
this.submitApprovalHandle()
console.log('11111',this.approvalForm.serviceChange)
console.log('2222',this.approvalForm.insuranceChange)
}else {
this.approvalDialogShow=false
await this.submitBtn();
}
},*/
async submitApprovalHandle(){//提交审批-走接口
if(this.vehicleInfoChange){
if(!(this.selectedOption.length > 0)){
this.$toast('车辆类别不能为空')
return
}
let res = this.checkDisabledItems();
if(!res) {
return false
}
if( !this.virtualVehicle ) {
this.$toast('车辆属性不能为空')
return
}
if( !this.vehicleStatus ) {
this.$toast('车辆状态不能为空')
return
}
/* if(!this.approvalForm.serviceRemark){
this.$toast('车辆和服务备注不能为空')
return
}*/
}
if(this.insuranceChange){
if(this.hasLiabilityInsurance === null || this.hasLiabilityInsurance === undefined || this.hasLiabilityInsurance === '') { // 有职业责任险,就需要有保单照片
this.$toast('救援职业责任险不能为空')
return
}
if(!this.insurancePicturePhoto && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('保单照片不能为空')
return
}
if(this.selectedOption?.length==1 && this.selectedOption.includes(1)){//选择小修车时需清除原本的拖车服务类型
let data=this.oldSupplierServiceList.filter(item => item.name ==='拖车服务')
this.serviceIds = this.serviceIds.filter(item => !data[0].children.some(obj => obj.id === item));
}
if(!this.insuranceCorp && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('承保保司不能为空')
return
}
if(!this.dateVal && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('保单有效期不能为空')
return
}
/* if(!this.approvalForm.insuranceRemark){
this.$toast('保单备注不能为空')
return
}*/
}
try {
this.loading = true;
await saveSupplierApproval({...this.approvalForm})
} finally {
this.loading = false;
}
this.$toast('提交审批成功')
this.closeApproval()
setTimeout(()=>{
this.$router.back();
},2000)
},
compareObjects(newObj, oldObj) {
this.changedFields=[]
for (let key in newObj) {
if( Array.isArray(newObj[key])){
if(newObj[key]?.length != oldObj[key]?.length){
this.changedFields.push(key);
}else {
// console.log('newObj[key]',newObj[key])
if(newObj[key][0]?.id){
let flag=newObj[key].every((item, index) => item.id == oldObj[key][index]?.id);
// console.log('flag',flag)
if(!flag){
this.changedFields.push(key);
}
}else{
let flag=newObj[key].every((value, index) => value == oldObj[key][index]);
if(!flag){
this.changedFields.push(key);
}
}
}
}else{
let isEqual = (
(newObj[key] == null && oldObj[key] == 0) ||
(newObj[key] == 0 && oldObj[key] == null) ||
(newObj[key] == oldObj[key])
);
if (!isEqual) {
this.changedFields.push(key);
}
}
}
},
closeApproval(){
this.approvalForm.serviceRemark=''
this.approvalForm.insuranceRemark=''
this.approvalForm.vehicleOtherRemark=''
this.approvalDialogShow=false
this.vehicleInfoChange=false
this.insuranceChange=false
},
back() {
this.$router.push({
name:'vehicleManage',
params:{
id: this.id
}
})
},
async submitBtn(){
if( !this.vehicleLicenseFront ) {
this.$toast('行驶证主页照片不能为空')
return
}
if( !this.vehicleLicenseBack ) {
this.$toast('行驶证副页照片不能为空')
return
@@ -912,11 +1280,27 @@ export default {
this.$toast('保单有效期不能为空')
return
}
if( this.hasLiabilityInsurance == 1){
if(this.permissonList.includes('hasInsuranceAudit') && !this.liabilityInsuranceAmount && this.liabilityInsuranceAmount!=0){
this.$toast('保费不能为空')
return
}
if(this.permissonList.includes('hasInsuranceAudit') && !this.liabilityInsuranceQuota){
this.$toast('保额不能为空')
return
}
/* if(!this.insuranceCode){
this.$toast('保单号(救援)不能为空')
return
}*/
}
let timeObj;
if(this.dateVal) {
timeObj = this.formatDateTimeRange(this.dateVal)
}
this.serviceIds = this.$refs.tree.getCheckedKeys(true)
// console.log('1122',this.vehicleStatus)
// return
await saveVehicle({
vehicleId:this.id ? this.id : '',
plateNumber:this.carNum ? this.carNum :'',
@@ -936,6 +1320,12 @@ export default {
liabilityInsuranceStartTime: this.hasLiabilityInsurance == 1 ? (timeObj?.startTime || '' ) : '',
liabilityInsuranceEndTime: this.hasLiabilityInsurance == 1 ? (timeObj?.endTime || '') : '',
virtualVehicle: this.virtualVehicle,
canSubmitApproval:true,
vehicleStatus:this.vehicleStatus,
liabilityInsuranceAmount:this.liabilityInsuranceAmount,
liabilityInsuranceQuota:this.liabilityInsuranceQuota,
insuranceCode:this.insuranceCode,
insuranceCodeZd:this.insuranceCodeZd,
})
if(this.id){
this.$toast('修改成功')
@@ -1076,4 +1466,77 @@ export default {
pointer-events: none;
opacity: 0.6;
}
.pop_wrap {
width: 340px;
box-sizing: border-box;
padding: 10px 15px;
position: relative;
.pop_title {
font-size: 14px;
font-weight: 500;
}
.pop_content {
width: 304px;
font-size: 14px;
color: #4C5361;
margin-left: 6px;
line-height: 25px;
.iptWrap{
margin: 10px 0;
}
}
.tip_button_wrap {
display: flex;
justify-content: flex-end;
.close {
border: 1px solid #ccc;
color: black;
background-color: #FFFFFF;
}
.continue {
padding: 6px 10px;
border-radius: 4px;
}
div:last-child{
background-color: #3266be;
color: #FFFFFF;
margin-left: 8px;
margin-right: 4px;
}
}
}
.loading {
position: relative;
}
.loading::after{
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border: 2px solid #000;
border-top-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.vanIpt{
width: 80%;
display: flex;
align-items: center;
}
.vanIpt1{
width: 72%;
}
</style>

View File

@@ -21,7 +21,7 @@
<van-search
v-model="searchVal"
show-action
placeholder="车辆名称/车牌号/车架号"
placeholder="请输入车辆名称车牌号车架号"
@search="resetHandler"
>
<template #action>
@@ -39,6 +39,14 @@
></i>
</template>
</el-input>-->
<el-select @change="resetHandler" multiple :collapse-tags="true" v-model="authStates" placeholder="认证状态" :class="{'customSel':true , 'has-value': authStates }" clearable>
<el-option
v-for="item in authStatesOptions"
:key="item.value"
:label="item.name"
:value="item.value">
</el-option>
</el-select>
<el-select @change="resetHandler" multiple :collapse-tags="true" v-model="inputStatusList" placeholder="录入状态" :class="{'customSel':true , 'has-value': inputStatusList }" clearable>
<el-option
v-for="item in inputStatusListOptions"
@@ -49,14 +57,6 @@
</el-select>
<!-- </div>-->
<!-- <div>-->
<el-select @change="resetHandler" multiple :collapse-tags="true" v-model="authStates" placeholder="认证状态" :class="{'customSel':true , 'has-value': authStates }" clearable>
<el-option
v-for="item in authStatesOptions"
:key="item.value"
:label="item.name"
:value="item.value">
</el-option>
</el-select>
<el-select @change="resetHandler" multiple :collapse-tags="true" v-model="liabilityInsuranceAuditList" placeholder="职业责任险" :class="{'customSel':true , 'has-value': liabilityInsuranceAuditList }" clearable>
<el-option
v-for="item in insuresOptions"
@@ -79,11 +79,12 @@
<div class="carItem" v-for="(item,index) in vehicleList" :key="index" @click.stop="updateVehicle(item)">
<div class="carCode">
<div class="codeLeft">{{item.plateNumber}} / {{item.vehicleTypeString}}
<span class="ml10" @click.stop="updateStatus(item)" :class="{'insuranceSuccess': item.vehicleStatus == 1, 'insuranceDanger': item.vehicleStatus == 2 ,'insuranceGray': !item.vehicleStatus }">{{ item.vehicleStatus == 1 ? '启用' :( item.vehicleStatus == 2 ? '停用' : '无状态') }} </span>
<span class="ml10" :class="{'insuranceSuccess': item.vehicleStatus == 1, 'insuranceDanger': item.vehicleStatus == 2 ,'insuranceGray': !item.vehicleStatus }">{{ item.vehicleStatus == 1 ? '启用' :( item.vehicleStatus == 2 ? '停用' : ( item.vehicleStatus == 12 ? '否-服务商停用' : '无状态')) }} </span>
</div>
<div class="codeRight" v-if="item.vehicleStatus == 1" @click.stop="updateStatus(item)">停用</div>
</div>
<div class="juhe flex-between">
<span class="zdJuhe">核验认证</span>
<span class="flex-right">
<span class="common_cls" v-if="item.inputStatusString" :class="getClass(item.inputStatusString)?.className">{{item.inputStatusString}}</span>
<span class="common_cls" v-if="item.authStateString" :class="getClass(item.authStateString)?.className">{{item.authStateString}}</span>
@@ -95,6 +96,12 @@
<span class="common_cls" @click.stop="showTip(item.liabilityInsuranceAuditMsg)" :class="getClass(item.rescueInsurance)?.className">{{item.rescueInsurance}}</span>
</span>
</div>
<!-- <div class="juhe flex-between">
<span class="zdJuhe">车辆信息</span>
<span class="flex-right" v-if="item.auditStatusStr">
<span class="common_cls" @click.stop="showTip(item.auditMsg)" :class="getClass(item.auditStatusStr)?.className">{{item.auditStatusStr}}</span>
</span>
</div>-->
<div class="juhe flex-between">
<span class="zdJuhe">最近登录时间</span>
<span class="flex-right">{{item.lastLoginTime}}</span>
@@ -243,7 +250,9 @@ export default {
},
async onLoad(){
await this.getVehicleList()
this.pageNum++;
if(this.total>10){
this.pageNum++;
}
// 加载状态结束
this.loading = false;
// 数据全部加载完成
@@ -326,7 +335,8 @@ export default {
this.$router.push({
name:'vehicleAdd',
params:{
id:item.vehicleId
id:item.vehicleId,
supplierId:item.supplierId
}
})
}
@@ -405,6 +415,12 @@ export default {
.codeLeft{
@include fontWeightSize(bold,14px)
}
.codeRight{
padding: 2px 8px;
border: 1px solid #DDDDDD;
border-radius: 3px;
font-weight: bold;
}
.twoBtn{
display: flex;
align-items: center;

View File

@@ -74,6 +74,7 @@
<div class="item">
<span class="leftTitle fontColor">服务师傅:</span><span class="rightContent">{{ orderDetailInfo.driverName }} {{orderDetailInfo.drivePhone ? '/' : ''}} {{orderDetailInfo.drivePhone }}
<span v-if="queryType == 5" class="driverPoiBtn" @click="noMultipleClicks(showMap)">查看司机位置</span>
<span v-if="orderDetailInfo.driverName && orderDetailInfo.proprietary?.code==1" class="driverPoiBtn" @click="noMultipleClicks(createDriverInfo)">生成司机信息</span>
</span>
</div>
<div class="item" v-if="queryType == 9 || queryType ==11 || queryType ==12">
@@ -114,7 +115,7 @@
</template>
<script>
import {myMixins} from '@/utils/myMixins'
import {getOrderDetail,showVehiclePositionInfo, getConfigByCode} from "@/api/order"
import {getOrderDetail,showVehiclePositionInfo, getConfigByCode,getDriverInfo} from "@/api/order"
import minePosition from '@/assets/minePosition.png';
import vehiclePosition from '@/assets/vehiclePosition.png';
import desitationPosition from '@/assets/desitationPosition.png'
@@ -161,6 +162,30 @@ export default {
});
this.carTypeList = res?.data?.userVehicleType
},
async getDriverInfo(){
let result=await getDriverInfo({
driverId:this.orderDetailInfo.driverId
})
if(result.data){
// console.log('--result--',result.data)
let params={
'姓名:':this.orderDetailInfo.driverName,
'身份证:':result.data.identityCardNumber,
'车牌:':this.orderDetailInfo.plateNumber,
'手机:':this.orderDetailInfo.driverPhone,
}
let data = {"action":"copyToClipboard","params":JSON.stringify(params)}
var u = navigator.userAgent;
var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
if(isiOS){
window.webkit.messageHandlers.nativeObject.postMessage(data);
}else {
window.android.copyToClipboard(JSON.stringify(params));
}
}else {
this.$toast('未获取到司机信息')
}
},
async getDetail(){
let result=await getOrderDetail({
queryType:this.queryType,
@@ -176,6 +201,9 @@ export default {
this.showPopup = true;
this.mapMarkers();
},
async createDriverInfo(){//生成司机信息
await this.getDriverInfo()
},
checkPhoto(){
let isAllowImage = this.queryType == 9 ? 0 : 1
let data = {

View File

@@ -0,0 +1,46 @@
<template>
<div class="wrap">
<el-table :data="tableData"
stripe
border
style="width: 100%"
height="100%"
v-loading="loading"
class="custom-table">
<el-table-column v-for="item in labelList" :key="item.prop" :label="item.label" :prop="item.prop" align="center">
<template slot-scope="scope">{{scope.row[item.prop]}} </template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: "fit-table",
props: ['tableData', 'labelList', 'loading', 'isMobile','active'],
data() {
return {}
},
mounted() {
},
methods: {
}
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
::v-deep .el-table{
font-size: 11px;
}
::v-deep .el-table thead{
color: #1D2129;
font-weight: bold;
}
::v-deep .el-table th.el-table__cell.is-leaf {
background-color: #E5E6EB;
}
::v-deep .el-table .el-table__cell{
padding: 4px 0 !important;
}
</style>

1203
src/views/kpi/kpiCaseNew.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@
<van-tabs v-model="active" sticky @click="tabClickHandle">
<van-tab v-for="(item,index) in tabArr" :key="index" :title="item.name"></van-tab>
<div v-if="isMobile && !([0,1,2,3].includes(active))" class="tipArrow left">{{ leftArr }}</div>
<div v-if="isMobile && !([9,10, 11].includes(active))" class="tipArrow right">>>></div>
<div v-if="isMobile && !([7,8, 9].includes(active))" class="tipArrow right">>>></div>
</van-tabs>
<div v-loading="loadingData" :class="{'contentWrap':true,'webcontentWrap':!isMobile}" v-if="active===0">
<!--<div :class="{'reciceOrder':true,'webCom':!isMobile}">
@@ -232,13 +232,13 @@
:total="total">
</el-pagination>
</div>
<div v-loading="loadingData" class="contentWrap monthTotal" v-if="[5,6,7,8,9,10, 11].includes(active)">
<div v-loading="loadingData" class="contentWrap monthTotal" v-if="[5,6,7,8,9].includes(active)">
<div :class="{'comTab':true,'detailTable':isMobile}">
<noFit-table :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"
></noFit-table>
</div>
<el-pagination
v-if="active !== 10"
v-if="active !== 8"
small
:page-sizes="[20, 50, 100]"
:current-page.sync="pageNum"
@@ -279,8 +279,8 @@ export default {
activeIndex: 0,
//
tabArr: [
{name: '总览',value:0}, {name: '月/总'}, {name: '日/总'},{name: '月/师傅'}, {name: '日/师傅'}, {name: '拒单明细'},
{name: '超时明细'}, {name: '催促明细'},{name: '投诉明细'}, {name: '不使用APP案件明细'}, {name: '车辆在线情况'} , {name: '聚合失败案件明细'}
{name: '总览',value:0}, {name: '月/总'}, {name: '日/总'},{name: '月/师傅'}, {name: '日/师傅'}, /*{name: '拒单明细'},*/
/*{name: '超时明细'},*/ {name: '催促明细'},{name: '投诉明细'}, {name: '不使用APP案件明细'}, {name: '车辆在线情况'} , {name: '聚合失败案件明细'}
],
list: [ {name: '服务评价'}, {name: 'APP使用情况'}, {name: ' 时效 '}],
// driverList: [{name: '得分总览'},{name: '接单情况'}, {name: '服务评价'}, {name: 'APP使用情况'}, {name: '时效 '}],
@@ -1096,7 +1096,7 @@ export default {
this.loading = false;
} */
else if ([ 5,6,7, 8,9,10,11].includes(this.active)) {
else if ([ 5,6,7, 8,9].includes(this.active)) {
this.detailList = []
this.labelList = []
let result = await getKpiDetailsData({
@@ -1113,7 +1113,7 @@ export default {
return {...item, date: formatVal};
});
this.loading = false
if (this.active === 5) {//拒单明细
/*if (this.active === 5) {//拒单明细
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'},
@@ -1133,7 +1133,8 @@ export default {
{label: '超时时间', prop: 'time'},
{label: '超时原因', prop: 'reason'},
]
} else if (this.active === 7) {//催促明细
} else */
if (this.active === 5) {//催促明细
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '二级合同名称', prop: 'contractName'},
@@ -1146,11 +1147,11 @@ export default {
{label: '服务车辆', prop: 'vehicleName'},
{label: '服务完成状况', prop: 'orderStatusString'},
{label: '案件完成时间', prop: 'finishTime'},
{label: '省(B)', prop: 'bProvince'},
{label: '市(B)', prop: 'bCity'},
{label: '省(B)', prop: 'bprovince'},
{label: '市(B)', prop: 'bcity'},
{label: '区(B)', prop: 'area'},
]
} else if (this.active === 8) {//投诉明细
} else if (this.active === 6) {//投诉明细
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'},
@@ -1159,7 +1160,7 @@ export default {
{label: '投诉类型', prop: 'complainTypeString'},
{label: '责任供应商扣罚金额', prop: 'compensateFee'},
]
} else if (this.active === 9) {//不使用App案件明细
} else if (this.active === 7) {//不使用App案件明细
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'},
@@ -1168,7 +1169,7 @@ export default {
{label: '案件创建时间', prop: 'orderCreateTime'},
{label: '事发地', prop: 'vehiclePointAddress'},
]
} else if (this.active === 10) {//车辆在线情况
} else if (this.active === 8) {//车辆在线情况
this.labelList = [
// {label: '总车辆数', prop: 'totalCount'},
{label: '在线车辆数(取每日的12点)', prop: 'twelveClockVehicleCount'},
@@ -1177,7 +1178,7 @@ export default {
{label: '在线率(在线车辆数/总车辆数)', prop: 'onlineRate'},
// {label: '车辆平均在线时长', prop: ''},
]
} else if (this.active === 11) {//聚合失败案件明细
} else if (this.active === 9) {//聚合失败案件明细
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'},
@@ -1326,7 +1327,8 @@ export default {
columnObj.label = item.month // 每一列的标题的名称
columnObj.prop = props + index //自定义每一列标题字段名称
this.etlLabelList.push(columnObj)
let mappings = [ 'complainOrderCount', 'complainOrderRate', 'pinganFavorableCount','pinganFavorableRate' ];
// 'pinganFavorableCount','pinganFavorableRate'
let mappings = [ 'complainOrderCount', 'complainOrderRate' ];
for (let i = 0; i < mappings.length; i++) {
this.$set(this.etlDetailList[i], columnObj.prop, item[mappings[i]]);
}
@@ -1605,32 +1607,32 @@ export default {
},
setType(type) {
switch (type) {
case 5:
/*case 5:
return 1;
case 6:
return 2;
case 7:
return 2;*/
case 5:
return 7;
case 8:
case 6:
return 3;
case 9:
case 7:
return 4;
case 10:
case 8:
return 5;
case 11:
case 9:
return 6;
}
},
getTitle(type) {
switch (type) {
case 0:
return '接单时效';
case 1:
return '客户评价';
case 2:
case 1:
return 'APP使用';
case 3:
case 2:
return '时效';
/* case 3:
return '时效';*/
}
},
}

View File

@@ -65,6 +65,7 @@ export default {
return {
showPoup: false,
used:false,
saveLoading: true,
}
},
async mounted() {
@@ -73,12 +74,19 @@ export default {
},
methods: {
async saveHandle() {
await sendInsuranceEmail();
this.$toast('操作成功');
this.showPoup=false
setTimeout(()=>{
this.goBack()
},1500)
if( this.saveLoading ) {
try {
this.saveLoading = false;
await sendInsuranceEmail();
this.$toast('操作成功');
this.showPoup = false
setTimeout(() => {
this.goBack()
}, 1500)
} finally {
this.saveLoading = true;
}
}
},
}
}

View File

@@ -0,0 +1,453 @@
<template>
<div class="wrap">
<div class="navBar">
<van-nav-bar
title="人员信息"
:left-arrow="Boolean(!isWebFunc())"
left-arrow-color="#FFFFFF"
:border="false"
:fixed="true"
:safe-area-inset-top="true"
@click-left="goBack"
/>
</div>
<div :class="{'tipWrap':true,'yelBg':type==3,'greBg':type==4}">
<img v-show="[1,2].includes(type)" src="@/assets/unpass.png" />
<img v-show="[3].includes(type)" src="@/assets/yelTip.png" />
<img v-show="[4].includes(type)" src="@/assets/greTip.png" />
<span v-if="type==1">请立即核对或修改以下信息姓名/身份/电话/微信号/邮箱</span>
<span v-else-if="type==2">角色无法修改如需修改角色请联系区域经理提交审批后请到管理人员模块进行查看</span>
<span v-else-if="type==3">当前信息正在确认中如需调整请联系区域经理</span>
<span v-else-if="type==4">当前信息已确认如需调整请联系区域经理</span>
</div>
<van-loading v-show="personList.length<=0" class="loadingWrap" type="spinner" color="#1989fa" />
<div class="contentWrap" v-show="personList.length>0"
:style="{'height':([1,2].includes(type) && isMaster) ? 'calc(100% - 160px)' : 'calc(100% - 90px)'}">
<div class="itemWrap" v-for="(item,index) in personList" :key="index">
<div class="opaCol">人员{{(index+1)}}:</div>
<div class="line"></div>
<div class="item">
<div class="left" :class="{'opaCol':type!=2}"><span class="star">*</span><span>姓名</span></div>
<div class="right">
<van-field :input-align="type==2 ? 'right' : 'left'" :readonly="type!=2" v-model="item.name" placeholder="请输入" />
</div>
</div>
<div class="item">
<div class="left opaCol"><span class="star" style="opacity: 0">*</span><span>角色</span></div>
<div class="right" :class="{'opaCol':type==2}">
<van-field :input-align="type==2 ? 'right' : 'left'" readonly v-model="item.roleName" placeholder="请输入" />
</div>
</div>
<div class="item">
<div class="left" :class="{'opaCol':type!=2}"><span class="star">*</span><span>电话1</span></div>
<div class="right">
<van-field :input-align="type==2 ? 'right' : 'left'" :readonly="type!=2" v-model.number="item.phone1" placeholder="请输入" @blur="blurHandle" />
</div>
</div>
<div class="item">
<div class="left" :class="{'opaCol':type!=2}"><span class="star" style="opacity: 0">*</span><span>电话2</span></div>
<div class="right">
<van-field :input-align="type==2 ? 'right' : 'left'" :readonly="type!=2" v-model.number="item.phone2" @blur="blurHandle" placeholder="请输入" />
</div>
</div>
<div class="item">
<div class="left" :class="{'opaCol':type!=2}"><span class="star">*</span><span>微信号</span></div>
<div class="right wechatRight">
<span class="reviseBtn" v-show="type==2" @click="wechatRevise(item,index)">修改</span>
<van-field :input-align="type==2 ? 'right' : 'left'" readonly v-model="item.wechatName" />
</div>
</div>
<div class="item">
<div class="left" :class="{'opaCol':type!=2}"><span class="star" style="opacity: 0">*</span><span>邮箱</span></div>
<div class="right">
<van-field :input-align="type==2 ? 'right' : 'left'" :readonly="type!=2" v-model="item.email" placeholder="请输入" />
</div>
</div>
</div>
</div>
<div class="btnWrap" v-if="isMaster && [1,2].includes(type)">
<div v-show="type==2" class="audit common" @click="auditHandle">提交审批</div>
<div v-show="type==1" class="revise common" @click="reviseHandle">立即修改</div>
<div v-show="type==1" class="confirm common" @click="confirmHandle">确认信息无误</div>
</div>
<van-dialog v-model="dialogShow" title="请将此二维码发送至相应人员进行添加,成功添加后将更新微信号。" show-cancel-button
confirmButtonText="确认已添加" confirmButtonColor="#354E93" className="customDialog"
@confirm="wechatConfirm" @cancel="cancelHandle">
<img v-if="wechatInfo?.qrCode" :src="wechatInfo.qrCode" style="width: 83%"/>
</van-dialog>
</div>
</template>
<script>
import {myMixins} from "@/utils/myMixins"
import {getConfirmPerson,getConfirmStatus,getQrCode,confirmAddWechat,confirm,submitConfirm} from "@/api/authentication";
import { Dialog } from 'vant';
export default {
name: "personList",
mixins:[myMixins],
data(){
return{
noClick:true,
id:'',
type:'',//1确认信息 2修改 3审批中 4已确认
personList:[],
dialogShow:false,
reviseIndex:-1,
supplierId:'',
wechatInfo:'',
personInfoId:'',
personInfo:'',
isMaster:false,
}
},
async mounted() {
window.addEventListener('message', (event) => {
if (event.data === 'dialogClosed') {
// console.log('Dialog 已关闭 // 执行关闭后的逻辑');
}
});
// console.log('isWebFunc()',this.isWebFunc())
const urlParams = new URLSearchParams(window.location.search);
this.id=this.$route.query.id || urlParams.get('id');
this.supplierId=this.$route.query.supplierId || urlParams.get('supplierId');
// this.supplierId=1128
console.log('1111',this.supplierId)
await this.getPersonList()
},
methods:{
blurHandle(e){
let val= e.target.value || ''
let flag=this.validatePhone(val)
// console.log('flag',flag)
if(!flag){
this.$toast('电话号码格式不正确')
}
},
validatePhone(val) {
console.log('val',val)
if(!val){
return true
}
const purePhone = String(val).trim()
const mobileReg = /^1[3-9]\d{9}$/ // 大陆手机号正则表达式
const hkMobileReg = /^[569]\d{3}[\s-]?\d{4}$/ // 香港手机号正则表达式,允许使用空格或 "-" 分隔
const macauMobileReg = /^6\d{3}[\s-]?\d{4}$/ // 澳门手机号正则表达式,允许使用空格或 "-" 分隔
const telReg = /^(0\d{2,3})([\s-])?\d{7,8}$/ // 大陆座机号正则表达式,允许区号和座机号用空格或 "-" 分开
const hkTelReg = /^\(?\d{2,4}\)?[\s-]?\d{4}[\s-]?\d{4}$/ // 香港座机号正则表达式,允许使用空格或 "-" 分隔
const macauTelReg = /^\(?\d{2,4}\)?[\s-]?\d{4}[\s-]?\d{4}$/ // 澳门座机号正则表达式,允许使用空格或 "-" 分隔
const phone400Reg = /^400[\s-]?\d{3}[\s-]?\d{4}$/ // 以 "400" 开头的号码正则表达式,允许使用空格或 "-" 分隔
// 情况112位且首位是0、第二位是1 → 去0后按大陆手机号校验
if (/^01[0-9]{10}$/.test(purePhone)) {
return true
}
// 匹配任意一种格式即为合法
if (mobileReg.test(purePhone) || hkMobileReg.test(purePhone) || macauMobileReg.test(purePhone) ||
telReg.test(purePhone) || hkTelReg.test(purePhone) || macauTelReg.test(purePhone) ||
phone400Reg.test(purePhone)) {
return true
}
// 其他情况 → 不合法
return false
},
async getPersonList(){
let result=await getConfirmStatus({supplierId:this.supplierId})
if(result.data?.wechatConfirmStatus==2){
this.type=3
}else if(result.data?.wechatConfirmStatus==3){
this.type=4
}else if(!result.data?.wechatConfirmStatus || result.data?.wechatConfirmStatus==1){
this.type=1
}
this.isMaster=result.data.isMaster
// this.type=2
let res=await getConfirmPerson({supplierId:this.supplierId})
if(res.data && res.data.length>0){
this.personList=res.data
}else{
this.personList=[]
}
},
async wechatConfirm(){//确认已添加微信按钮
Dialog.confirm({
message:'请确认相关人员是否已添加该二维码。',
confirmButtonText:'确认',
cancelButtonText:'取消',
confirmButtonColor:'#354E93',
}).then(async() => {
let res=await confirmAddWechat({supplierId:this.supplierId,supplierPersonInfoId:this.personInfoId})
console.log('res',this.personInfo,res.data?.name,this.personInfo.wechatName)
if((res.data?.name == this.personInfo.wechatName) || (!res.data && this.personInfo.wechatName)){
console.log('11111111')
this.$set(this.personList[this.reviseIndex],'isChange',false)
}else {
console.log('2222222')
this.$set(this.personList[this.reviseIndex],'isChange',true)
}
console.log('this.personList',this.personList)
let flagRevise=this.personList.some(item => 'isChange' in item && !item.isChange)
console.log('存在未修改',flagRevise)
if(res.data){
this.personList[this.reviseIndex].wechatId=res.data?.externalUserId
this.personList[this.reviseIndex].wechatName=res.data?.name
}
await this.cancelHandle()
}).catch(async () => {
await this.cancelHandle()
});
},
cancelHandle(){
this.dialogShow=false
this.wechatInfo=''
this.reviseIndex=-1
this.personInfoId=''
},
async wechatRevise(item,index){
console.log('item,index',item,index)
// console.log('item',item)
this.personInfoId=item.id
this.reviseIndex=index
this.personInfo=item
await this.getCode()
this.dialogShow=true
},
async getCode(){//获取二维码
let res = await getQrCode({supplierId:this.supplierId,supplierPersonInfoId:this.personInfoId})
// console.log('res--getCode',res)
this.wechatInfo=res.data || ''
},
async auditHandle(){//提交审批
let flag=this.personList.every(item => item.name && item.phone1 && item.wechatId && item.wechatName)
if(!flag){
this.$toast('必填项不可为空')
return
}
console.log('----',this.personList)
let flagRevise=this.personList.some(item => 'isChange' in item && !item.isChange)
console.log('存在未修改',flagRevise)
if(flagRevise){
this.$toast('您仍未修改微信号,请先修改再提交审批。')
return
}
let phone1Flag= this.personList.every(item => this.validatePhone(item.phone1))
let phone2Flag= this.personList.every(item => (item.phone2 && this.validatePhone(item.phone2) || !item.phone2))
console.log('phone1Flag',phone1Flag)
console.log('phone2Flag',phone2Flag)
if(!phone1Flag || !phone2Flag){
this.$toast('电话号码格式不正确')
return
}
await submitConfirm({supplierId:this.supplierId,infos:this.personList})
this.$toast('操作成功')
if(this.isWebFunc()){
setTimeout(()=>{
this.closeParentDialog()
},1500)
}else{
setTimeout(()=>{
this.goBack()
},1500)
}
},
async reviseHandle(){//修改
this.type=2
},
async confirmHandle(){//确认信息无误
let flag=this.personList.every(item => item.name && item.phone1 && item.wechatId && item.wechatName)
if(!flag){
this.$toast('必填项不可为空,请点击“立即修改”将信息补充完整')
return
}
Dialog.confirm({
message:'请务必仔细确认信息是否无误。',
confirmButtonText:'确认无误',
cancelButtonText:'返回',
confirmButtonColor:'#354E93',
}).then(async() => {
// console.log('确认无误')
await confirm({supplierId:this.supplierId})
this.$toast('操作成功')
if(this.isWebFunc()){
setTimeout(()=>{
this.closeParentDialog()
},1500)
}else{
setTimeout(()=>{
this.goBack()
},1500)
}
}).catch(() => {
});
},
}
}
</script>
<style lang="scss">
.customDialog {
.van-dialog__content{
text-align: center !important;
}
.van-dialog__header {
box-sizing: border-box;
padding: 12px;
}
}
</style>
<style scoped lang="scss">
@import "@/styles/common.scss";
@import '@/styles/mixin.scss';
.wrap {
@include wh(100%, 100%);
position: relative;
background-color: #F6F6F6;
}
.navBar{
margin-bottom: 46px;
}
.tipWrap{
width: 100%;
box-sizing: border-box;
padding: 8px 8px 8px 18px;
margin-bottom: 10px;
display: flex;
background-color: #FFF6F2;
img{
width: 14px;
height: 14px;
//vertical-align: middle;
margin-right: 5px;
margin-top: 2px;
}
span{
color: #FC3C06;
}
}
.yelBg{
background-color: #FFFFED ;
span{
color: #F36708;
}
}
.greBg{
background-color: #E8FFF3;
span{
color: #19AC43;
}
}
/*.headerWrap{
width: 100%;
box-sizing: border-box;
padding: 15px 16px;
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
.back{
@include wh(15px,15px);
}
span{
font-weight: bold;
font-size: 16px;
color: #203152;
}
}*/
.loadingWrap{
width: 100%;
height: calc(100% - 90px);
text-align: center;
line-height: 200px;
}
.contentWrap{
//height: calc(100% - 160px);
//height: calc(100% - 90px);//没有按钮时
overflow-y: auto;
.itemWrap{
font-size: 14px;
width: 100%;
margin-bottom: 10px;
background-color: #FFFFFF;
box-sizing: border-box;
padding:10px 15px 10px 20px;
.line{
width: 100%;
text-align: center;
border-bottom: 2px solid;
opacity: 0.16;
margin-top: 10px;
border-image: linear-gradient(270deg, rgba(217, 217, 217, 0.6), rgba(178, 178, 178, 1), rgba(178, 178, 178, 1), rgba(217, 217, 217, 0.6)) 1 1;
}
.item{
//line-height: 36px;
display: flex;
align-items: center;
.left{
width: 66px;
}
.right{
flex: 1;
}
.wechatRight{
display: flex;
align-items: center;
justify-content: space-between;
.reviseBtn{
display: inline-block;
width: 60px;
text-align: center;
border-radius: 4px;
background-color: #354D93;
color: #FFFFFF;
box-sizing: border-box;
padding: 2px 0;
}
}
}
.opaCol{
color: #323643;
opacity: .65;
}
.star{
font-weight: bold;
font-size: 13px;
color: #FF0808;
}
}
}
.btnWrap{
width: 100%;
position: fixed;
bottom: 0;
left: 0;
box-sizing: border-box;
padding: 10px 19px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #F6F6F6;
.common{
background: #354D93;
height: 48px;
line-height: 48px;
text-align: center;
border-radius: 5px;
font-weight: 500;
font-size: 14px;
color: #FFFFFF;
cursor: pointer;
}
.audit{
width: 100%;
}
.revise{
width: 30%;
background-color: #FFFFFF;
color: #354D93;
box-sizing: border-box;
border: 1px solid #354D93;
}
.confirm{
width: 67%;
}
}
</style>

View File

@@ -2,7 +2,7 @@
<div class="bottom_wrap">
<div class="report_item" v-for="(item, index) in reportList" @click="goReportHandler(item)" :key="index">
<img class="report_icon" :src="item?.iconUrl" alt="">
<div class="report_title">{{item.name}}</div>
<!-- <div class="report_title">{{item.name}}</div>-->
</div>
</div>
</template>
@@ -43,8 +43,8 @@ export default {
margin-top: 20px;
text-align: center;
.report_icon {
width: 45px;
height: 45px;
width: 60px;
height: 60px;
}
.report_title {
color: rgba(53, 53, 53, 0.67);

View File

@@ -0,0 +1,581 @@
<template>
<!-- <van-pull-refresh v-model="isLoading" class="custom-pull-refresh safe-pull-refresh" @refresh="onRefresh">-->
<div class="main_wrap">
<div class="top_bg" :class="{'dispatch' : type == 1 , 'driver' : type != 1}">
<!-- <div class="title">报备中心</div>-->
</div>
<div class="service_wrap">
<service-item :order-info="orderInfo" @refresh="onRefreshHandler"></service-item>
</div>
<div class="chat_list" ref="chatList" >
<!--ai对话框 s -->
<div v-for="(item, index) in recordList" :key="index">
<template v-if="item.messageType == 4">
<div class="top_tip_wrap">为了快速解决问题请选择下方咨询类型</div>
<div class="ai_chat" >
<div class="ai_left">
<img class="report_ai" src="@/assets/report/report_ai.png" alt="">
</div>
<div class="ai_right">
<div class="ai_title">{{ orderInfo?.driverName }}你好请选择报备内容</div>
<ul>
<li v-for="_item in item.content" :key="_item.id" @click="addReportHandler(_item)">{{_item.name}}</li>
</ul>
</div>
</div>
</template>
<template v-if="[2, 3].includes(Number(item.messageType))">
<!--ai对话框 e -->
<div class="tip_wrap">{{item.createTime}}</div>
<!--客服对话框 s -->
<div class="customer_item mb20">
<div class="customer_left">
<img class="profile_photo mr" src="@/assets/report/report_customer.png" alt="">
</div>
<div class="common_right customer_cls">
<div class="customer_status">{{item.reportConfigName}}</div>
<div class="response_con">{{item.remark}}</div>
</div>
</div>
</template>
<template v-if="item.messageType == 1">
<!--服务人员对话框 e -->
<div class="tip_wrap">{{item.createTime}}</div>
<!--服务人员对话框 s -->
<div class="server_item mb20">
<div class="common_right service_cls">
<div class="service_status">{{item.reportConfigName}}</div>
<div class="response_con">{{item.remark}}</div>
</div>
<div class="customer_left">
<img class="profile_photo ml" v-if="type == 1" src="@/assets/report/report_dispatch.png" alt="">
<img class="profile_photo ml" v-else src="@/assets/report/report_driver.png" alt="">
</div>
</div>
<!--服务人员对话框 e -->
</template>
</div>
</div>
<report-list-item :report-list.sync="reportList" @getReport="getReportHandler" />
<van-popup ref="addReportModal" v-model="addReportShow" :close-on-click-overlay="false" closeable duration="0" round position="bottom">
<div class="dialog_wrap">
<img class="add_report_bg" src="@/assets/report/add_report_bg.png" alt="">
<div class="report_content">
<template v-if="currentInfo.component == 'time'">
<!--预约时间报备 s -->
<div class="report_title">
<img class="dot" src="@/assets/report/add_report_dot.png" alt="">
<span>预约时间</span>
</div>
<div class="report_time_content" @click="showDatetime = true">
<div class="report_time_left">
<img class="add_report_time" src="@/assets/report/add_report_time.png" alt="">
<span>{{appointTime || '请选择时间'}}</span>
</div>
<img class="report_arrow" src="@/assets/report/add_report_arrow.png" alt="">
</div>
<!--预约时间报备 e -->
</template>
<template v-if="currentInfo.component == 'address'">
<!--修改地址报备 s -->
<div class="report_title">
<img class="dot" src="@/assets/report/add_report_dot.png" alt="">
<span>修改地址</span>
</div>
<div class="report_time_content" @click="goPage('addressMap')">
<div class="report_time_left">
<img class="add_report_time" src="@/assets/report/add_report_address.png" alt="">
<span>{{address || '请选择地址'}}</span>
</div>
<img class="report_arrow" src="@/assets/report/add_report_arrow.png" alt="">
</div>
<!--修改地址报备 e -->
</template>
<div class="report_title">
<img class="dot" src="@/assets/report/add_report_dot.png" alt="">
<span>补充报备内容</span>
</div>
<div class="report_common_content">
<textarea class="report_textarea" id="text-input" rows="4" placeholder="点击这里输入补充报备内容" v-model.trim="remark"></textarea>
<!-- <van-field-->
<!-- v-model="remark"-->
<!-- class="report_textarea"-->
<!-- placeholder="点击这里输入补充报备内容" />-->
</div>
</div>
<div class="report_btn" @click="saveHandler">
提交报备
</div>
</div>
</van-popup>
<van-popup v-model="showDatetime" round position="bottom">
<van-datetime-picker
class="dataTime"
v-if="showDatetime"
v-model="appointTime"
type="datetime"
:min-date="minDate"
:filter="filter"
@confirm="onConfirm"
@cancel="showDatetime = false"
/>
</van-popup>
</div>
<!-- </van-pull-refresh>-->
</template>
<script>
import { getReportListByOrder, reportHistory, newOrderReporting, getOrderInfo } from "@/api/report"
import { myMixins} from "@/utils/myMixins";
import {timeFormat} from "@/utils/common";
import reportListItem from '@/views/report/components/report-list-item'
import serviceItem from "@/views/report/components/service-item"
import {Dialog, Toast} from "vant";
export default {
name: "reportIndex",
components: { serviceItem, reportListItem },
mixins: [myMixins],
data() {
return {
addReportShow: false,
remark: '',
showDatetime: false,
appointTime: '',
minDate: new Date(),
address: '',
lat: '',
lng: '',
reportList: [],
recordList: [],
orderInfo: {},
type: '', // 1 调度 2 司机
currentInfo: '',
userOrderId: '',
userOrderCode: '',
driverId: '',
clickFlag: true,
isLoading: false,
}
},
async mounted() {
const urlParams = new URLSearchParams(window.location.search);
this.userOrderId = this.$route.query.userOrderId || urlParams.get('userOrderId');
this.userOrderCode = this.$route.query.userOrderCode || urlParams.get('userOrderCode');
this.type = this.$route.query.type || urlParams.get('type');
this.driverId = this.$route.query.driverId || urlParams.get('driverId');
await this.getReportList();
await this.getDetail();
await this.getRecordList();
await this.scrollToBottom();
},
activated() {
let addressSession = sessionStorage.getItem('reportAddress');
if(addressSession) {
let _obj = JSON.parse(addressSession)
this.address = _obj.startPoiAddress;
this.lat = _obj.startLat;
this.lng = _obj.startLng;
this.adCode = _obj.adCode;
}
},
methods: {
filter(type, options) {
if (type === 'minute') {
return options.filter((option) => option % 5 === 0);
}
return options;
},
onRefreshHandler() {
setTimeout(async () => {
try {
this.recordList = [];
await this.getReportList();
await this.getDetail();
await this.getRecordList();
await this.scrollToBottom();
} finally {
this.isLoading = false; // 结束加载状态
}
}, 100)
},
// 添加滚动方法
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.chatList
container.scrollTop = container.scrollHeight
})
},
async getRecordList() {
let recordList = await reportHistory({
userOrderId: this.userOrderId,
userOrderCode: this.userOrderCode,
driverId: this.driverId,
source: Number(this.type),
})
this.recordList = recordList.data;
},
async saveHandler() {
if( this.clickFlag ) {
let data = {
source: Number(this.type),
reportContent: this.remark,
userOrderId: Number(this.userOrderId),
reportConfigId: this.currentInfo.id,
userOrderCode: this.userOrderCode,
driverId: this.driverId,
}
if(this.currentInfo.component == 'time') {
if( !this.appointTime ) {
Toast('预约时间不能为空')
return
}
if( new Date(this.appointTime).getTime() <= new Date().getTime() ) {
Toast('预约时间不能小于当前时间')
return
}
data.time = this.appointTime
}
if(this.currentInfo.component == 'address') {
if( !this.address ) {
Toast('地址不能为空')
return
}
data.address = this.address
data.lat = this.lat;
data.lng = this.lng;
}
if( !this.remark ) {
Toast('备注内容不能为空')
return
}
this.clickFlag = false;
try {
await newOrderReporting(data)
Dialog.alert({
title: '提示',
message: "报备添加成功"
}).then(async () => {
await this.getRecordList()
this.addReportShow = false
this.scrollToBottom();
this.initDialogData();
this.clickFlag = true
});
} finally {
this.clickFlag = true
}
}
},
initDialogData() {
this.appointTime = '';
this.address = '';
this.lat = '';
this.lng = '';
this.remark = '';
},
addReportHandler(data) { // 点击添加报备弹框
console.log('currentInfocurrentInfo', data)
this.currentInfo = data;
this.remark = '';
this.addReportShow = true
},
getReportHandler(data) { // 点击获取当前报备信息
console.log('dadadada', data.child)
this.recordList.push({
messageType: 4,
content: data.child
})
this.scrollToBottom();
if( data.child?.length == 1 ) {
this.addReportHandler(data.child[0])
}
},
async getDetail(){ // 订单详情
if(this.userOrderId) {
let result = await getOrderInfo({
userOrderId: this.userOrderId,
userOrderCode: this.userOrderCode
})
this.orderInfo = result.data
} else {
this.orderInfo = {}
}
},
async getReportList() { // 报备大类
let res = await getReportListByOrder({
orderId: this.userOrderId
});
this.reportList = res.data
},
onConfirm(data) {
this.appointTime = timeFormat(new Date(data))
this.showDatetime = false;
}
}
}
</script>
<style lang="scss">
.custom-pull-refresh {
height: 100% !important;
}
</style>
<style lang="scss" scoped>
#app {
background: #F9FCFF !important;
height: 100%;
}
.main_wrap {
width: 100%;
height: 100%;
background: #F9FCFF !important;
display: flex;
flex-direction: column;
// 添加以下样式确保正确的高度计算
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.top_bg {
width: 100%;
height: 87px;
/*.title {
color: #fff;
text-align: center;
padding-top: 52px;
font-size: 17px;
line-height: 24px;
}*/
}
.dispatch { // 调度app头部背景色
background: #354683;
}
.driver { // 司机app头部背景色
background: #334885;
}
.service_wrap {
margin-top: -75px;
/*margin-bottom: 20px;*/
}
.chat_list {
flex: 1;
/*overflow: auto;*/
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch; // 为了iOS平滑滚动
padding-top: 10px;
/* 修复 iOS 滚动问题 */
transform: translateZ(0);
.top_tip_wrap {
color: #7C8698;
text-align: center;
font-size: 13px;
}
.tip_wrap {
color: #7C8698;
text-align: center;
font-size: 13px;
margin-bottom: 10px;
}
.ai_chat {
padding: 25px 10px 20px;
display: flex;
.ai_left {
.report_ai {
width: 44px;
height: 44px;
margin-right: 12px;
}
}
.ai_right {
.ai_title {
color: #203152;
font-size: 15px;
font-weight: 600;
margin-bottom: 15px;
}
li {
font-size: 13px;
color: #267EF0;
line-height: 18px;
margin-bottom: 15px;
font-weight: bold;
}
li::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
background-color: #5680FA;
border-radius: 50%; /* 圆形 */
margin-right: 10px;
}
}
}
}
/*客服人员,服务人员对话弹框样式 s*/
.profile_photo {
width: 44px;
height: 44px;
}
.mr {
margin-right: 10px;
}
.ml {
margin-left: 10px;
}
.mb20 {
margin-bottom: 20px;
}
.customer_item {
display: flex;
padding-left: 10px;
padding-right: 64px;
.customer_left {
}
}
.common_right {
padding: 11px 13px;
flex: 1;
.customer_status::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
background-color: #5680FA;
border-radius: 50%; /* 圆形 */
margin-right: 10px;
}
.customer_status {
font-size: 14px;
color: #267EF0;
line-height: 20px;
font-weight: bold;
}
.service_status {
font-size: 14px;
color: #F07926;
line-height: 20px;
font-weight: bold;
}
.service_status::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
background-color: #F07926;
border-radius: 50%; /* 圆形 */
margin-right: 10px;
}
}
.customer_cls {
border-radius: 0px 6px 6px 6px;
background: #E3F0FF;
}
.service_cls {
background: #FAEBD8;
border-radius: 0px 6px 6px 6px;
}
.server_item {
display: flex;
padding-right: 10px;
padding-left: 64px;
}
.response_con {
font-size: 13px;
color: #242A37;
line-height: 18px;
font-weight: bold;
margin-top: 5px;
}
/*客服人员,服务人员对话弹框样式 e*/
.dialog_wrap {
.add_report_bg {
width: 100%;
height: 76px;
}
.report_content {
width: calc(100% - 12px);
margin: -30px auto 0;
padding-right: 14px;
padding-left: 20px;
box-sizing: border-box;
background: #fff;
position: relative;
padding-top: 25px;
border-radius: 10px;
.report_title {
font-size: 16px;
font-weight: 600;
color: #267EF0;
line-height: 22px;
margin-bottom: 10px;
.dot {
width: 7px;
height: 6px;
margin-right: 12px;
}
}
.report_time_content {
padding: 13px;
background: #F2F7FF;
border-radius: 6px;
border: 1px solid #E2EAF6;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
.report_time_left {
font-size: 14px;
font-weight: bold;
color: rgba(0, 0, 0, 0.9);
display: flex;
align-items: flex-start;
}
.add_report_time {
width: 18px;
height: 18px;
margin-right: 8px;
}
.report_arrow {
width: 8px;
height: 12px;
margin-left: 5px;
}
}
.report_common_content {
margin-bottom: 27px;
.report_textarea {
width: 100%;
box-sizing: border-box;
background: #F8FAFC;
border-radius: 6px;
min-height: 95px;
border: 1px solid #E2EAF6;
padding: 10px;
font-size: 13px;
}
}
}
.report_btn {
width: calc(100% - 80px);
height: 46px;
line-height: 46px;
background: #005DD5;
border-radius: 23px;
color: #FFFFFF;
font-size: 15px;
font-weight: bold;
text-align: center;
margin: 0 auto 40px;
}
}
::v-deep .van-popup__close-icon {
color: #0F458E;
}
</style>

View File

@@ -57,16 +57,30 @@
</div>
</div>
<report-list-item :report-list.sync="reportList" @getReport="getReportHandler" />
<van-popup ref="addReportModal" v-model="addReportShow" :close-on-click-overlay="false" closeable duration="0" round position="bottom">
<van-popup ref="addReportModal" v-model="addReportShow" :close-on-click-overlay="false" z-index="500" closeable duration="0" round position="bottom">
<div class="dialog_wrap">
<img class="add_report_bg" src="@/assets/report/add_report_bg.png" alt="">
<div class="report_content">
<div class="report_title" style="margin-bottom: 10px;"><span>{{currentInfo.name}}</span></div>
<template v-if="currentInfo.component == 'time'">
<!--预约时间报备 s -->
<div class="report_title">
<img class="dot" src="@/assets/report/add_report_dot.png" alt="">
<span>预约时间</span>
</div>
<!--<el-date-picker class="report_time_content owner_time high-zindex-picker"
v-model="appointTime"
type="datetime"
:picker-options="pickerOptions"
:minutes-step="5"
format="yyyy-MM-dd HH:mm:00"
placeholder="选择日期时间">
</el-date-picker>-->
<!--<el-date-picker
v-model="appointTime"
type="datetime"
placeholder="选择日期时间">
</el-date-picker>-->
<div class="report_time_content" @click="showDatetime = true">
<div class="report_time_left">
<img class="add_report_time" src="@/assets/report/add_report_time.png" alt="">
@@ -91,17 +105,15 @@
</div>
<!--修改地址报备 e -->
</template>
<div class="report_title">
<img class="dot" src="@/assets/report/add_report_dot.png" alt="">
<span>补充报备内容</span>
</div>
<div class="report_common_content">
<textarea class="report_textarea" id="text-input" rows="4" placeholder="点击这里输入补充报备内容" v-model.trim="remark"></textarea>
<!-- <van-field-->
<!-- v-model="remark"-->
<!-- class="report_textarea"-->
<!-- placeholder="点击这里输入补充报备内容" />-->
</div>
<template v-if="currentInfo.component != 'time'">
<div class="report_title">
<img class="dot" src="@/assets/report/add_report_dot.png" alt="">
<span>补充反馈内容</span>
</div>
<div class="report_common_content">
<textarea class="report_textarea" id="text-input" rows="4" placeholder="点击这里输入补充反馈内容" v-model.trim="remark"></textarea>
</div>
</template>
</div>
<div class="report_btn" @click="saveHandler">
提交报备
@@ -109,15 +121,16 @@
</div>
</van-popup>
<van-popup v-model="showDatetime" round position="bottom">
<van-popup v-model="showDatetime" round position="bottom" :close-on-click-overlay="false">
<van-datetime-picker
class="dataTime"
v-if="showDatetime"
v-model="appointTime"
type="datetime"
:min-date="minDate"
:filter="filter"
@confirm="onConfirm"
@cancel="showDatetime = false"
@cancel="cancelHandler"
/>
</van-popup>
</div>
@@ -155,6 +168,12 @@
driverId: '',
clickFlag: true,
isLoading: false,
pickerOptions: {
// 可选:限制预约时间不能小于当前时间(复用你原有逻辑)
disabledDate: (time) => {
return time.getTime() <= new Date().getTime();
}
}
}
},
async mounted() {
@@ -179,6 +198,12 @@
}
},
methods: {
filter(type, options) {
if (type === 'minute') {
return options.filter((option) => option % 5 === 0);
}
return options;
},
onRefreshHandler() {
setTimeout(async () => {
try {
@@ -238,10 +263,10 @@
data.lat = this.lat;
data.lng = this.lng;
}
if( !this.remark ) {
/*if( !this.remark ) {
Toast('备注内容不能为空')
return
}
}*/
this.clickFlag = false;
try {
await newOrderReporting(data)
@@ -268,15 +293,21 @@
this.remark = '';
},
addReportHandler(data) { // 点击添加报备弹框
console.log('currentInfocurrentInfo', data)
this.currentInfo = data;
this.remark = '';
this.addReportShow = true
},
getReportHandler(data) { // 点击获取当前报备信息
console.log('dadadada', data.child)
this.recordList.push({
messageType: 4,
content: data.child
})
this.scrollToBottom();
if( data.child?.length == 1 ) {
this.addReportHandler(data.child[0])
}
},
async getDetail(){ // 订单详情
if(this.userOrderId) {
@@ -299,6 +330,10 @@
onConfirm(data) {
this.appointTime = timeFormat(new Date(data))
this.showDatetime = false;
},
cancelHandler() {
this.appointTime = ''
this.showDatetime = false
}
}
}
@@ -535,6 +570,9 @@
margin-left: 5px;
}
}
.owner_time {
padding: 0 !important;
}
.report_common_content {
margin-bottom: 27px;
.report_textarea {
@@ -565,4 +603,22 @@
::v-deep .van-popup__close-icon {
color: #0F458E;
}
::v-deep .el-date-editor.el-input,::v-deep .el-date-editor.el-input__inner {
width: 100%;
}
::v-deep .el-input__inner {
background: none !important;
}
::v-deep .el-picker-panel {
z-index: 100000000 !important;
}
.el-picker-panel{
z-index: 100000000 !important;
}
::v-deep .high-zindex-picker {
z-index: 9999 !important; // 高于 van-popup 默认层级(通常 1000-2000
}
::v-deep .el-date-editor {
overflow: visible !important;
}
</style>

View File

@@ -478,6 +478,21 @@ export default {
otherPhoto:this.otherImgSrc?.join(',')
})
this.clearStorageFormInfo()
if(res.code == 200 && !res.msg){
this.$toast('发布成功')
if(this.isWebFunc()){
setTimeout(()=>{
this.closeParentDialog()
},1000)
}else{
setTimeout(()=>{
this.$router.push({ name: "mineRelease"})
sessionStorage.setItem('mineActiveTab',String(1) )
},1000)
}
}else{
this.$toast(res.msg)
}
console.log("车源发布publishCarInfo",res)
} catch (e){
console.log('e',e)