Compare commits

..
Author SHA1 Message Date
zxl beb97a0762 kpi总览界面,隐藏两个平安的指标 2025-09-17 15:33:11 +08:00
zxl 425c4c2043 实名认证接口加参数ump:1 2025-09-15 13:18:53 +08:00
zxl 2e9f2a6fa1 VConsole调试删除 2025-09-15 09:23:33 +08:00
zxl 93aaaacd06 实名认证链接decode测试 2025-09-15 09:23:32 +08:00
zxl 4fd27cfa0e 实名认证调整 2025-09-15 09:23:03 +08:00
zxl f4a2c6d6fa 认证页的返回都调转列表页 2025-09-15 09:22:09 +08:00
zxl 5592ad773e 实名认证页,认证结果esignFlag做缓存,添加打印 2025-09-15 09:14:20 +08:00
zxl ff53323c3f 实名认证页,认证结果esignFlag做缓存,添加打印 2025-09-15 09:14:18 +08:00
zxl 1df54434c9 实名认证页,认证结果esignFlag做缓存,添加打印 2025-09-15 09:14:16 +08:00
zxl 210eb41ca6 实名认证页,认证结果esignFlag做缓存,添加打印 2025-09-15 09:14:13 +08:00
zxl 436a35ddb5 实名认证页,认证结果esignFlag做缓存,添加打印 2025-09-15 09:14:11 +08:00
zxl b33522708e 实名认证页,认证结果esignFlag做缓存,添加打印 2025-09-15 09:14:09 +08:00
zxl a9938d03fa 实名认证页,认证结果esignFlag做缓存 2025-09-15 09:14:06 +08:00
zxl e7ce0721ca 实名认证页返回都到列表页 2025-09-15 09:14:03 +08:00
zxl 3cab49c0d4 实名认证页代码优化 2025-09-15 09:14:01 +08:00
zxl 0d14a9add6 实名认证页代码优化 2025-09-15 09:13:58 +08:00
zxl 6bc78f76ad 认证页增加双击不放大功能,滚动条丝滑滚动优化 2025-09-15 09:13:55 +08:00
zxl fe5e85b8d5 认证页下一步添加接口 2025-09-15 09:13:49 +08:00
zxl d799cf7b93 实名认证esignFlag缓存添加 2025-09-15 09:13:46 +08:00
zxl 64240414b5 实名认证操作成功后返回到认证列表页 2025-09-15 09:13:43 +08:00
zxl 0aac318448 实名认证界面加缓存数据 2025-09-15 09:13:40 +08:00
zxl 121435ff3e 实名认证界面加缓存数据 2025-09-15 09:13:36 +08:00
zxl 2dbf353113 实名认证接口加token参数 2025-09-15 09:13:33 +08:00
zxl 728063ca86 实名认证加token参数 2025-09-15 09:13:29 +08:00
zxl ad548db437 实名认证加token参数 2025-09-15 09:13:26 +08:00
zxl ad5f16acc9 story#7189,跳转加token参数 2025-09-15 09:13:24 +08:00
zxl 39921aa2bb story#7189,高度调整 2025-09-15 09:13:21 +08:00
zxl 3d56f91575 story#7189,type默认'full' 2025-09-15 09:13:19 +08:00
zxl fda0139a06 story#7189,认证页radio回显处理 2025-09-15 09:13:17 +08:00
zxl 620e6b5ad4 story#7189,回程车小程序司机端-迁移认证,跳转新的url 2025-09-15 09:13:14 +08:00
zxl 4c0ad71e89 story#7189,回程车小程序司机端-迁移认证,添加调试 2025-09-15 09:13:11 +08:00
zxl 7f8c482df1 story#7189,回程车小程序司机端-迁移认证 2025-09-15 09:13:08 +08:00
zxl ceff5e008c story#7189,回程车小程序司机端-迁移认证页面 2025-09-15 09:13:03 +08:00
zxl 16924fb22e story#7189,回程车小程序司机端-迁移认证页面 2025-09-15 09:13:02 +08:00
zxl 6a8b6b7730 司机端培训资料,关键字接受字段更换,切换优化加loading 2025-09-09 13:34:39 +08:00
zxl bdc78351e4 司机端培训资料,关键字接受字段更换,切换优化加loading 2025-09-09 13:34:38 +08:00
zlf 25cc195f1e 三分钟联系客户率 2025-09-09 10:55:23 +08:00
zlf 97ba1500ff 好评率好评量删除 2025-09-09 10:55:23 +08:00
zlf 93f0e9317c kpi率乘以一百 2025-09-08 10:18:33 +08:00
zlf 2c83b1ea60 kpi字段替换 2025-09-08 09:41:42 +08:00
zlf 872021eca5 平安聚合时效 2025-09-05 11:44:00 +08:00
zlf 6beda9f6f1 服务商KPI 2025-09-04 10:01:16 +08:00
96 changed files with 595 additions and 232895 deletions
-2
View File
@@ -17,8 +17,6 @@ pnpm-debug.log*
# Editor directories and files # Editor directories and files
.idea .idea
.vscode .vscode
.claude
.kimi-code
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj
-3
View File
@@ -3,7 +3,4 @@
<component name="JavaScriptSettings"> <component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" /> <option name="languageLevel" value="ES6" />
</component> </component>
<component name="WebPackConfiguration">
<option name="mode" value="DISABLED" />
</component>
</project> </project>
@@ -1,501 +0,0 @@
# 调度App H5行驶证拍摄取景引导框 — 需求文档
---
## 需求文档摘要
**一句话概括**:在调度App H5车辆管理页(`vehicleAdd.vue`)的行驶证上传流程中,拦截现有 `van-uploader` 的点击事件,先进入自定义拍摄引导页(WebRTC取景+浅色背景+灰色边框引导框+蓝色角标+Hero提示卡+取景要点卡+预览确认),拍摄确认后将照片传回现有的压缩→上传→OCR流程,实现拍摄环节的引导增强。
**核心要点**
| 维度 | 内容 |
| --- | --- |
| **背景** | 调度App H5车辆管理页行驶证上传使用 `van-uploader` 直接调起系统选择器,无拍摄规范提示,导致卡套反光、证件歪斜、文字模糊等问题频发 |
| **目标** | 拦截行驶证槽位点击,进入拍摄引导页(浅色背景+相机预览+引导框+Hero提示卡+取景要点卡),拍摄后预览确认,确认后回到现有压缩→上传→OCR流程 |
| **现有流程** | `van-uploader` 点击 → 系统选择器 → `after-read` 回调 → `compressFileItem` 压缩 → `uploadImage` 上传 → `ocrHandler` OCR识别 |
| **改造方案** | 行驶证槽位点击 → **拍摄引导页(新增)** → 预览确认(新增)→ `compressFileItem` 压缩 → `uploadImage` 上传 → `ocrHandler` OCR识别 |
| **界面改动** | 新增拍摄引导页组件 + 预览确认页组件;改造 `vehicleAdd.vue` 中行驶证三个槽位的点击逻辑 |
| **数据规则** | 无新增字段;引导框样式与文案为前端静态资源;后端接口完全不变 |
| **原型** | [调度App-H5行驶证拍摄取景引导原型.html](https://sinoassist.yuque.com/attachments/yuque/0/2026/html/34748803/1785205633529-2dd10b98-a855-4a6b-b2a1-894fc14e3ca3.html) |
**技术要点**
+ 后端:无需变更,复用现有 `POST /order/uploadImage``POST /agg-api/tencent-ocr/unifiedOCR`
+ 前端:`supplier-dispatch-h5` 项目 `vehicleAdd.vue` 改造 + 新增拍摄引导组件
+ 数据库:无需变更
---
## 一、基本信息
| 项目 | 内容 |
| --- | --- |
| **需求名称** | 调度App H5行驶证拍摄取景引导框 |
| **提出人** | |
| **产品负责人** | 宋志领 |
| **所属模块** | 调度App H5 - 车辆管理(`vehicleAdd.vue`- 行驶证上传 |
| **需求类型** | 优化 |
| **目标版本** | |
| **紧急程度** | 中 |
## 二、需求背景
### 2.1 现有流程
调度App H5车辆管理页面(`https://crm1.sino-assist.com/h5/supplier/dispatch/vehicleManage`)中,用户在录入或修改车辆信息时,需要上传行驶证主页、副页、车辆照片页。
当前上传流程基于 Vant `van-uploader` 组件实现:
```plain
用户点击 van-uploader 上传槽位
系统调起相册/相机选择器(accept="image/*"
after-read 回调触发(vehicleLicenseFrontHandler / vehicleLicenseBackHandler / vehicleLicenseCarHandler
compressFileItem() 前端压缩(目标 < 5MBJPEG 格式)
uploadImage() 上传 → POST /order/uploadImagemultipart/form-data
ocrHandler() OCR 识别 → POST /agg-api/tencent-ocr/unifiedOCR
回填识别结果到表单字段(车牌号、车架号、所有人等)
```
**现有代码位置**
| 文件 | 关键方法/组件 |
| --- | --- |
| `supplier-dispatch-h5/src/views/index/vehicleAdd.vue` | `van-uploader` 组件(行驶证三个槽位) |
| `supplier-dispatch-h5/src/views/index/vehicleAdd.vue` | `vehicleLicenseFrontHandler()` — 行驶证主页上传 |
| `supplier-dispatch-h5/src/views/index/vehicleAdd.vue` | `vehicleLicenseBackHandler()` — 行驶证副页上传 |
| `supplier-dispatch-h5/src/views/index/vehicleAdd.vue` | `vehicleLicenseCarHandler()` — 行驶证车辆照片上传 |
| `supplier-dispatch-h5/src/views/index/vehicleAdd.vue` | `compressFileItem()` — 图片压缩 |
| `supplier-dispatch-h5/src/api/mine.js` | `uploadImage()` — 上传接口 |
| `supplier-dispatch-h5/src/api/mine.js` | `ocrHandler()` — OCR 识别接口 |
### 2.2 问题分析
当前流程中,用户点击上传槽位后直接调起系统相册/相机选择器,**没有任何拍摄规范提示**。实际采集到的照片普遍存在以下问题:
1. **卡套/保护膜未取出**:证件装在透明卡套或覆膜中拍摄,塑料/玻璃表面反光,遮盖号牌、车架号等关键字段,导致OCR无法识别。
2. **摆放不规整**:歪斜、弯曲、四角缺角、未对准画面中央,导致自动识别定位失败或字段错位。
3. **反光与阴影**:在强光或灯光直射下拍摄,出现高光斑、阴影遮挡文字,造成文字模糊。
上述问题直接造成行驶证OCR识别失败、审核驳回率升高,服务商需反复重传,拉长车辆录入周期。
### 2.3 改造方向
参考业界成熟的证件拍摄方案(如银行卡、身份证识别),在拍摄预览界面上叠加半透明取景引导框,配合文字提示,能从源头规范用户拍摄行为,显著提升照片合格率。
**改造原则**:仅改造拍摄环节(系统选择器 → 自定义拍摄引导页),后续的压缩→上传→OCR流程完全保持不变。
---
## 三、需求目标
1. 用户点击行驶证任一上传槽位时,**不再直接调起系统选择器**,而是进入自定义拍摄引导页。
2. 拍摄引导页在相机预览画面上**自动叠加半透明取景引导框**,框的形状和比例匹配行驶证实际尺寸。
3. 引导框区域显示明确的文字提示:「请将行驶证从卡套内取出后拍摄,避免反光」「请将证件对准框内,确保边缘完整、文字清晰」。
4. 引导框**适配不同手机屏幕尺寸**,在主流移动端设备上显示正常。
5. 用户点击拍摄后,引导框消失,进入**照片预览确认步骤**,支持重拍或确认使用。
6. 用户确认使用后,照片**传回现有的 **`compressFileItem`** → **`uploadImage`** → **`ocrHandler`** 流程**,后续逻辑完全不变。
7. 车头照、保单等非行驶证槽位**保持现有 **`van-uploader`** 直接调起选择器的逻辑**,不受影响。
---
## 四、功能说明
### 功能点 1:拦截行驶证槽位点击,进入拍摄引导页
**功能概述**
| 项目 | 内容 |
| --- | --- |
| **功能名称** | 行驶证槽位点击拦截 |
| **涉及页面** | `vehicleAdd.vue` — 行驶证主页、副页、车辆照片页的 `van-uploader` 组件 |
| **触发条件** | 用户在车辆管理页点击行驶证任一上传槽位(首次上传或重新上传)时触发 |
| **处理逻辑** | 拦截 `van-uploader` 的默认点击行为(调起系统选择器),改为打开拍摄引导页组件 |
| **输出结果** | 进入拍摄引导页,展示相机预览 + 引导框 |
**改造前后对比**
| 步骤 | 改造前(现有流程) | 改造后 |
| --- | --- | --- |
| 1 | 点击 `van-uploader` 槽位 | 点击 `van-uploader` 槽位 |
| 2 | 系统调起相册/相机选择器 | **拦截点击 → 进入拍摄引导页(新增)** |
| 3 | 选择/拍摄照片 | 相机预览 + 引导框 + 文字提示 |
| 4 | `after-read` 回调 | 点击拍摄 → **预览确认页(新增)** |
| 5 | `compressFileItem` 压缩 | 确认使用 → **构造 file 对象,触发 **`after-read`** 回调** |
| 6 | `uploadImage` 上传 | `compressFileItem` 压缩(**不变** |
| 7 | `ocrHandler` OCR 识别 | `uploadImage` 上传(**不变** |
| 8 | 回填表单 | `ocrHandler` OCR 识别(**不变**)→ 回填表单 |
**关键改造点**:拍摄引导页确认使用后,需将 Canvas 捕获的图片 Blob 构造为 `van-uploader``after-read` 回调所期望的 `{ file: File, content: dataURL }` 格式,从而无缝衔接现有的压缩→上传→OCR流程。
---
### 功能点 2:拍摄引导页 — 相机预览 + 引导框
**功能概述**
| 项目 | 内容 |
| --- | --- |
| **功能名称** | 拍摄取景引导框 |
| **涉及页面** | 新增拍摄引导页组件 |
| **触发条件** | 从行驶证槽位点击进入 |
| **处理逻辑** | 使用 WebRTC `getUserMedia` 获取摄像头视频流,在 `video` 元素上全屏展示;叠加浅色背景层(`#F5F5F5`+ 中央镂空引导框(灰色实线边框 `#C2C8D2` + 蓝色 L 形角标 `#354D93` + 入场渐显动画)+ 顶部 Hero 提示卡(白底+蓝色图标)+ 取景要点图标卡(do/don't)+ 底部白色拍摄栏 |
| **输出结果** | 用户在规范引导下完成拍摄 |
**界面设计**(拍摄引导页示意):
```plain
┌─────────────────────────────────────────┐
│ ← 拍摄行驶证主页 │ ← 顶部导航栏(深蓝 #2C395F)
├─────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────┐ │ ← Hero 提示卡(白底+蓝色图标)
│ │ [📁] 请将行驶证从卡套内取出后拍摄 │ │
│ │ 避免反光,确保文字清晰 │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────┐ │
│ │└── ──┘ │ │ ← 蓝色 L 形角标(#354D93
│ │ 中华人民共和国机动车行驶证 │ │ ← 证件标题轮廓(半透明蓝色)
│ │ │ │
│ │ │ │ ← 页面其余区域空白
│ │ │ │
│ │ ▢ 印章 │ │ ← 印章位置轮廓(红色方形)
│ │└── ──┘ │ │
│ └───────────────────────────────────┘ │ ← 灰色实线边框(#C2C8D2)
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ ← 取景要点图标卡(白底)
│ │ [✓] │ │ [✓] │ │ [✗] │ │
│ │ 边缘完整 │ │ 文字清晰 │ │ 避免反光 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
├─────────────────────────────────────────┤
│ ┌──────────┐ │
│ │ ◉ 拍摄 │ │ ← 拍摄按钮(蓝色 #354D93)
│ └──────────┘ │ ← 底部白色拍摄栏
└─────────────────────────────────────────┘
```
> 说明:全屏相机预览(`video` 元素)作为底层背景,整体页面背景为浅色(`#F5F5F5`),营造明亮的拍摄环境。屏幕中央为镂空透明区域(匹配行驶证宽高比约 1.43:1),镂空区域外围为浅色背景(区别于镂空区域的相机预览画面)。镂空区域外圈增加灰色实线边框(`#C2C8D2`,3px,带入场渐显动画),四角显示蓝色 L 形角标(`#354D93`,带入场渐显动画),辅助用户对齐证件边缘。**引导框上方设置 Hero 提示卡**(白底圆角卡片+蓝色图标+主标题"请将行驶证从卡套内取出后拍摄"+副标题"避免反光,确保文字清晰"),**引导框下方设置取景要点图标卡**(3 个白底小卡片,分别用 ✓/✗ 图标标注"边缘完整""文字清晰""避免反光")。**引导框内叠加极简证件模板轮廓**(半透明蓝色,透明度约 20%-35%):主页仅展示顶部一行标题"中华人民共和国机动车行驶证"和左下角红色方形印章,其余区域空白;副页仅展示右下角条形码图案(长度与真实条形码一致)及数字编号,其余区域空白。底部为白色拍摄栏,居中放置蓝色圆形拍摄按钮。
>
**操作按钮**
| 按钮 | 触发条件 | 操作结果 |
| --- | --- | --- |
| 拍摄 | 点击 | 通过 Canvas 捕获当前 `video` 帧,生成图片 Blob,进入预览确认页 |
| 返回(左上角) | 点击 | 关闭相机流,退出拍摄引导页,返回车辆管理页,不上传照片 |
| 点击背景区(非引导框内) | 点击 | 无响应,保持拍摄引导页(防止误触) |
---
### 功能点 3:三页差异化引导
**功能概述**
| 项目 | 内容 |
| --- | --- |
| **功能名称** | 分页面差异化引导 |
| **涉及页面** | 行驶证主页、副页、车辆照片页拍摄引导页 |
| **触发条件** | 按用户点击的具体槽位进入对应拍摄引导页 |
| **处理逻辑** | 三页共用相同的引导框比例和通用提示文案;差异体现在页面标题和**引导框内的证件模板轮廓** |
| **输出结果** | 用户明确知道当前拍摄的是行驶证的哪一页,并能按模板轮廓对齐证件内容 |
**各页引导差异**
| 页面 | 对应槽位 | 对应 Handler | 页面标题 | 模板轮廓内容 |
| --- | --- | --- | --- | --- |
| 行驶证主页 | `vehicleLicenseFrontList` | `vehicleLicenseFrontHandler` | 拍摄行驶证主页 | 仅展示:顶部一行标题"中华人民共和国机动车行驶证" + 左下红色方形印章 |
| 行驶证副页 | `vehicleLicenseBackList` | `vehicleLicenseBackHandler` | 拍摄行驶证副页 | 仅展示:右下角条形码图案(长度与真实条形码一致)+ 条形码数字编号 |
| 行驶证车辆照片 | `vehicleLicenseCarPhotoList` | `vehicleLicenseCarHandler` | 拍摄行驶证车辆照片 | "车辆照片"标题 + 车辆SVG轮廓(车身/车窗/车轮),引导居中拍摄 |
> **说明**:引导框内的证件模板轮廓为半透明叠层(透明度约 20%-35%),仅作视觉对齐辅助,不遮挡相机预览画面。模板轮廓使用 CSS/SVG 静态实现,不依赖后端数据。三种模板通过 `renderLicenseTemplate(type)` 函数根据槽位类型动态切换。
>
---
### 功能点 4:照片预览确认
**功能概述**
| 项目 | 内容 |
| --- | --- |
| **功能名称** | 照片预览确认 |
| **涉及页面** | 拍摄完成后进入的预览确认页 |
| **触发条件** | 用户在拍摄引导页点击"拍摄"按钮后触发 |
| **处理逻辑** | 展示刚拍摄的照片,提供"重拍"和"确认使用"两个操作 |
| **输出结果** | 确认使用 → 照片传回 `van-uploader``after-read` 回调,走现有压缩→上传→OCR流程;重拍则返回拍摄引导页 |
**界面设计**(预览确认页示意):
```plain
┌─────────────────────────────────────────┐
│ ← 返回 │
├─────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ │ 刚拍摄的照片预览 │ │
│ │ (全宽自适应展示) │ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 重拍 │ │ 确认使用 │ │
│ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────┘
```
**操作按钮**
| 按钮 | 触发条件 | 操作结果 |
| --- | --- | --- |
| 重拍 | 点击 | 返回拍摄引导页,重新进入相机预览 |
| 确认使用 | 点击 | 将照片 Blob 构造为 `{ file: File, content: dataURL }` 格式,调用对应槽位的 `after-read` Handler(如 `vehicleLicenseFrontHandler`),进入现有压缩→上传→OCR流程 |
| 返回 | 点击 | 关闭相机流,返回车辆管理页,不保存照片 |
**数据衔接说明**
确认使用后,需构造以下对象传给现有的 Handler 方法:
```plain
{
file: new File([blob], `vehicle_license_${type}_${Date.now()}.jpg`, { type: 'image/jpeg' }),
content: dataURL // base64 格式,供 van-uploader 预览使用
}
```
传入后,现有的 `compressFileItem()``uploadImage()``ocrHandler()` 链路完全不变。
---
## 五、业务规则
### 规则 1:触发范围
引导框仅对 `vehicleAdd.vue` 中标题含"行驶证"的三个 `van-uploader` 槽位生效(主页、副页、车辆照片);**车头照**(`vehicleFrontPhotoList`)、**保单**`insurancePictureFiles`)等其他槽位不进入拍摄引导页,保持现有 `van-uploader` 直接调起选择器的交互。
### 规则 2:引导框比例
引导框的宽高比固定为行驶证实物比例(约 1.43:1,横向),在不同屏幕尺寸下按屏幕宽度自适应缩放(宽度约为屏幕宽度的 80%),左右留白均匀,确保用户无需过度调整手机位置即可将证件放入框内。
### 规则 3:后续流程不变
引导框仅做"拍摄环节的视觉辅助",**不改变原上传与OCR识别流程**。用户确认使用后,照片传入现有的 `compressFileItem``uploadImage``ocrHandler` 链路,识别失败仍按现有方式提示重传。
### 规则 4:屏幕适配
引导框在竖屏模式下以横向矩形展示(行驶证为横向证件)。若用户将手机横置,引导框应自适应旋转为纵向矩形以匹配证件方向。优先保证竖屏体验,横屏为可选适配。
### 规则 5:降级策略
若设备不支持 WebRTC `getUserMedia`(部分低版本 Android WebView 或非 HTTPS 环境),**降级为现有的 **`van-uploader`** 直接调起系统选择器**,不阻断用户上传流程。降级时引导框不展示,用户走原有流程。
---
## 六、边界与异常场景
| 场景编号 | 场景描述 | 预期处理 |
| --- | --- | --- |
| 1 | 用户未对准引导框就点击拍摄 | 系统不拦截,正常拍摄并进入预览;用户在预览页可点击"重拍"重新对准 |
| 2 | 相机权限未授权 | 拍摄引导页加载前检测权限;未授权时提示"请允许使用相机权限",并提供"从相册选择"降级入口 |
| 3 | 弱光/暗光环境拍摄 | 引导框正常展示;若设备支持,可自动开启屏幕补光(或提示用户到光线充足处拍摄) |
| 4 | 用户旋转手机(横竖屏切换) | 引导框自适应旋转,保持与行驶证方向一致;拍摄按钮和提示文字跟随屏幕方向调整位置 |
| 5 | 小屏幕手机(如 iPhone SE) | 引导框按比例缩小,提示文字自动换行,确保所有元素在可视区域内完整展示 |
| 6 | 大屏幕手机(如 iPhone Pro Max) | 引导框按比例放大,不超过屏幕安全区域,保持舒适的单手操作距离 |
| 7 | 设备不支持 getUserMedia | 自动降级为 `van-uploader` 直接调起系统选择器,不展示引导框 |
| 8 | iOS 微信内置浏览器权限受限 | 检测到微信环境且 getUserMedia 不可用时,降级为系统选择器 |
| 9 | 拍摄后未进入预览(相机被中断) | 检测到相机关闭/中断事件,自动返回车辆管理页,不上传照片 |
| 10 | 非行驶证槽位(如车头照)点击 | 不进入拍摄引导页,维持现有 `van-uploader` 直接调起选择器的交互 |
| 11 | 修改车辆时重新上传行驶证 | 同样进入拍摄引导页,拍摄确认后替换原照片,走现有压缩→上传→OCR流程 |
---
## 七、影响范围
### 前端
| 文件 | 变更类型 | 变更内容 |
| --- | --- | --- |
| `supplier-dispatch-h5/src/views/index/vehicleAdd.vue` | **改造** | 行驶证三个 `van-uploader` 槽位(`vehicleLicenseFrontList`/`vehicleLicenseBackList`/`vehicleLicenseCarPhotoList`)增加点击拦截逻辑:点击时打开拍摄引导页组件,而非直接调起系统选择器;拍摄确认后将照片传回现有的 `vehicleLicenseFrontHandler`/`vehicleLicenseBackHandler`/`vehicleLicenseCarHandler` |
| `supplier-dispatch-h5/src/views/index/vehicleAdd.vue` | **改造** | 车头照(`vehicleFrontPhotoList`)、保单(`insurancePictureFiles`)槽位**不变**,保持现有 `van-uploader` 直接调起选择器的逻辑 |
| `supplier-dispatch-h5/src/components/CameraGuide.vue` | **新增** | 拍摄引导页组件:WebRTC 相机预览 + 浅色背景层(`#F5F5F5`+ 镂空引导框 + 灰色实线边框(`#C2C8D2`,入场渐显动画) + 四角蓝色 L 形角标(`#354D93`,入场渐显动画) + Hero 提示卡(白底+蓝色图标) + 取景要点图标卡(do/don't + **证件模板轮廓叠层**(主页/副页/车辆照片三种模板动态切换) + 底部白色拍摄栏(蓝色圆形拍摄按钮) |
| `supplier-dispatch-h5/src/components/CameraGuide.vue` | **新增** | 预览确认页:照片展示 + 重拍按钮 + 确认使用按钮 |
| `supplier-dispatch-h5/src/api/mine.js` | **不变** | `uploadImage()``ocrHandler()` 完全复用 |
| `supplier-dispatch-h5/src/utils/` | **不变** | `compressFileItem()` 等工具方法完全复用 |
### 后端
| 文件 | 变更内容 |
| --- | --- |
| 无 | 无需变更,复用现有 `POST /order/uploadImage``POST /agg-api/tencent-ocr/unifiedOCR` |
### 数据库
| 表名 | 变更内容 |
| --- | --- |
| 无 | 无需变更 |
---
## 八、验收标准
### 8.1 拍摄引导页展示
- [ ] 点击行驶证主页/副页/车辆照片任一上传槽位,进入拍摄引导页(非直接调起系统选择器)
- [ ] 拍摄引导页展示全屏相机预览背景(`video` 元素实时画面)
- [ ] 屏幕中央显示镂空引导框,框的宽高比约 1.43:1(横向)
- [ ] 引导框外围为浅色背景(`#F5F5F5`),与镂空区域的相机预览画面形成对比
- [ ] 引导框外圈显示灰色实线边框(`#C2C8D2`3px,带入场渐显动画)
- [ ] 引导框四角显示蓝色 L 形定位角标(`#354D93`,带入场渐显动画)
- [ ] 引导框上方显示 Hero 提示卡(白底圆角卡片+蓝色图标),主标题"请将行驶证从卡套内取出后拍摄",副标题"避免反光,确保文字清晰"
- [ ] 引导框下方显示取景要点图标卡(3 个白底小卡片):边缘完整(✓ 蓝色)、文字清晰(✓ 蓝色)、避免反光(✗ 红色)
- [ ] 页面底部显示白色拍摄栏,居中放置蓝色圆形拍摄按钮(`#354D93`),按钮样式醒目、易于点击
- [ ] 页面标题根据槽位类型显示对应文字("拍摄行驶证主页"/"拍摄行驶证副页"/"拍摄行驶证车辆照片"
- [ ] 引导框内显示证件模板轮廓(半透明,不遮挡相机预览)
- [ ] 主页模板包含:顶部一行标题"中华人民共和国机动车行驶证" + 左下红色方形印章,其余区域空白
- [ ] 副页模板包含:右下角条形码图案(长度与真实条形码一致)+ 条形码数字编号,其余区域空白
- [ ] 车辆照片模板包含:车辆SVG轮廓(车身/车窗/车轮)
- [ ] 切换不同槽位时,模板轮廓自动切换为对应内容
### 8.2 预览确认页展示
- [ ] 点击"拍摄"后进入预览确认页,展示刚拍摄的照片
- [ ] 预览页提供"重拍"按钮,点击后返回拍摄引导页
- [ ] 预览页提供"确认使用"按钮,点击后照片传入现有的 `after-read` Handler
- [ ] 预览页左上角提供"返回"按钮,点击后放弃当前照片,返回车辆管理页
### 8.3 现有流程衔接
- [ ] 确认使用后,照片经 `compressFileItem()` 压缩(目标 < 5MB
- [ ] 压缩后经 `uploadImage()` 上传到服务器(`POST /order/uploadImage`
- [ ] 上传后经 `ocrHandler()` 进行 OCR 识别(`POST /agg-api/tencent-ocr/unifiedOCR`
- [ ] OCR 识别结果正确回填到表单字段(车牌号、车架号、所有人等)
- [ ] 上传槽位显示拍摄的照片缩略图
### 8.4 非行驶证槽位不受影响
- [ ] 点击车头照上传槽位,保持现有 `van-uploader` 直接调起系统选择器的逻辑
- [ ] 点击保单上传槽位,保持现有 `van-uploader` 直接调起系统选择器的逻辑
- [ ] 车头照上传后的 `ocrCarFrontHandler()` OCR 识别正常
### 8.5 屏幕适配
- [ ] 在 iPhone 各型号(SE/标准版/Plus/Pro Max)上引导框比例正确、文字完整显示
- [ ] 在 Android 主流机型(华为/小米/OPPO/vivo 等)上引导框比例正确、文字完整显示
- [ ] 竖屏模式下引导框为横向矩形,横屏模式下自适应为纵向矩形
### 8.6 降级场景
- [ ] 设备不支持 `getUserMedia` 时,降级为 `van-uploader` 直接调起系统选择器
- [ ] 降级后压缩→上传→OCR流程正常
- [ ] iOS 微信内置浏览器权限受限时,降级为系统选择器
---
## 九、非功能要求
+ **性能**:拍摄引导页从点击槽位到展示引导框的耗时 < 500ms;拍摄按钮响应 < 200msCanvas 捕获帧 < 100ms。
+ **兼容性**:支持 iOS Safari 12+、Android Chrome 80+、微信/企业微信内置浏览器;需兼容 WebView 环境。
+ **适配性**:支持屏幕宽度 320px - 430px 的主流手机;引导框和文字在不同尺寸下自适应,不出现截断或重叠。
+ **安全性**:引导内容为纯前端静态展示,不涉及用户照片上传前的任何数据采集与存储;相机流在页面关闭时立即释放。
+ **降级方案**:若设备不支持 WebRTC/getUserMedia,降级为现有的 `van-uploader` 直接调起系统选择器,不阻断用户上传流程。
---
## 流程图
### 改造后完整流程
```plain
用户在 vehicleAdd.vue 点击上传槽位
┌─────────────────────┐
│ 是否为行驶证三个 │
│ 槽位之一? │
│ (主页/副页/车辆照片)│
└─────────────────────┘
│ │
否 是
│ ▼
│ ┌─────────────────────┐
│ │ 检测设备是否支持 │
│ │ getUserMedia
│ └─────────────────────┘
│ │ │
│ 否 是
│ │ │
│ ▼ ▼
│ 降级: 进入拍摄引导页
│ van-uploader WebRTC相机预览 +
│ 直接调起 浅色背景 + 引导框 +
│ 系统选择器 Hero卡 + 要点卡 + 角标)
│ │ │
│ │ ┌──────┼──────┐
│ │ ▼ ▼ ▼
│ │ 返回 拍摄 点击
│ │ 车辆 按钮 背景
│ │ 管理页 │ (无响应)
│ │ ▼
│ │ ┌──────────────┐
│ │ │ 照片预览确认 │
│ │ │ 页面 │
│ │ └──────────────┘
│ │ │
│ │ ┌─────┼─────┐
│ │ ▼ ▼ ▼
│ │ 重拍 确认 返回
│ │ │ 使用 车辆
│ │ │ │ 管理页
│ │ │ ▼
│ │ │ 构造 { file, content }
│ │ │ 传入 after-read Handler
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────┐
│ 现有流程(完全不变) │
│ compressFileItem() 压缩 │
│ │ │
│ ▼ │
│ uploadImage() 上传 │
│ POST /order/uploadImage │
│ │ │
│ ▼ │
│ ocrHandler() OCR识别 │
│ POST /agg-api/ │
│ tencent-ocr/unifiedOCR │
│ │ │
│ ▼ │
│ 回填表单字段 │
│ (车牌号/车架号/所有人等) │
└─────────────────────────┘
```
---
## 影响范围说明
| 维度 | 影响说明 |
| --- | --- |
| **功能** | 仅新增行驶证拍摄前的取景引导层,不改变车辆管理页其他功能和OCR识别流程 |
| **数据** | 无新增业务数据;不修改现有上传接口的入参和出参 |
| **接口** | 无接口变更,复用现有 `POST /order/uploadImage``POST /agg-api/tencent-ocr/unifiedOCR` |
| **配置** | 无变更 |
| **用户习惯** | 用户上传行驶证时多了一步引导页+预览确认,但流程轻量,拍摄确认后无缝衔接现有压缩→上传→OCR流程,整体体验提升 |
## 测试关注点
| 测试类型 | 关注点 |
| --- | --- |
| **功能测试** | 三页槽位均正确进入拍摄引导页;引导框比例正确;提示文字完整显示;拍摄后进入预览确认;确认后正确回填到 `van-uploader` 并触发现有 Handler |
| **流程衔接测试** | 确认使用后 `compressFileItem` 压缩正常;`uploadImage` 上传成功;`ocrHandler` OCR 识别结果正确回填表单;行驶证主页 OCR 回填车牌号/车架号/所有人等;行驶证副页 OCR 回填档案编号/核载人数等 |
| **兼容测试** | iPhone SE/标准版/Plus/Pro Max;华为/小米/OPPO/vivo 主流机型;微信/企业微信内置浏览器;Safari/Chrome |
| **回归测试** | 车头照等非行驶证槽位保持原有直接调选择器逻辑;车辆管理页其他功能不受影响;OCR 识别与审核流程正常;修改车辆时重新上传行驶证正常 |
| **降级测试** | 不支持 `getUserMedia` 的设备降级为 `van-uploader` 系统选择器;降级后压缩→上传→OCR 流程正常 |
| **适配测试** | 竖屏/横屏切换;320px 窄屏与 430px 宽屏的显示效果;文字不截断、按钮可点击 |
File diff suppressed because one or more lines are too long
+35 -297
View File
@@ -15,10 +15,9 @@
"dayjs": "^1.8.14", "dayjs": "^1.8.14",
"decimal.js": "^10.4.3", "decimal.js": "^10.4.3",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-ui": "^2.15.13", "element-ui": "^2.15.9",
"less": "^4.1.3", "less": "^4.1.3",
"less-loader": "^11.1.3", "less-loader": "^11.1.3",
"qrcode": "^1.5.4",
"qs": "^6.11.2", "qs": "^6.11.2",
"sass": "^1.65.1", "sass": "^1.65.1",
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
@@ -3351,6 +3350,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -4801,15 +4801,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/decimal.js": { "node_modules/decimal.js": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz", "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz",
@@ -5029,12 +5020,6 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true "dev": true
}, },
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/dir-glob": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -5225,7 +5210,8 @@
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
}, },
"node_modules/emojis-list": { "node_modules/emojis-list": {
"version": "3.0.0", "version": "3.0.0",
@@ -6229,6 +6215,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"dependencies": { "dependencies": {
"locate-path": "^5.0.0", "locate-path": "^5.0.0",
"path-exists": "^4.0.0" "path-exists": "^4.0.0"
@@ -6383,6 +6370,7 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": { "engines": {
"node": "6.* || 8.* || >= 10.*" "node": "6.* || 8.* || >= 10.*"
} }
@@ -7004,6 +6992,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -7419,6 +7408,7 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"dependencies": { "dependencies": {
"p-locate": "^4.1.0" "p-locate": "^4.1.0"
}, },
@@ -8455,6 +8445,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"dependencies": { "dependencies": {
"p-try": "^2.0.0" "p-try": "^2.0.0"
}, },
@@ -8469,6 +8460,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"dependencies": { "dependencies": {
"p-limit": "^2.2.0" "p-limit": "^2.2.0"
}, },
@@ -8493,6 +8485,7 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@@ -8589,6 +8582,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -8669,15 +8663,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/portfinder": { "node_modules/portfinder": {
"version": "1.0.32", "version": "1.0.32",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
@@ -9409,131 +9394,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/qrcode/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/qrcode/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/qrcode/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/qrcode/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/qrcode/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/qrcode/node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.11.2", "version": "6.11.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
@@ -9783,6 +9643,7 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -9796,12 +9657,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/requires-port": { "node_modules/requires-port": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -10178,12 +10033,6 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -10475,6 +10324,7 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@@ -10488,6 +10338,7 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
}, },
@@ -11851,12 +11702,6 @@
"which": "bin/which" "which": "bin/which"
} }
}, },
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/wildcard": { "node_modules/wildcard": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
@@ -14573,7 +14418,8 @@
"ansi-regex": { "ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
}, },
"ansi-styles": { "ansi-styles": {
"version": "3.2.1", "version": "3.2.1",
@@ -15627,11 +15473,6 @@
} }
} }
}, },
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="
},
"decimal.js": { "decimal.js": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz", "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz",
@@ -15786,11 +15627,6 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true "dev": true
}, },
"dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
},
"dir-glob": { "dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -15947,7 +15783,8 @@
"emoji-regex": { "emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
}, },
"emojis-list": { "emojis-list": {
"version": "3.0.0", "version": "3.0.0",
@@ -16708,6 +16545,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": { "requires": {
"locate-path": "^5.0.0", "locate-path": "^5.0.0",
"path-exists": "^4.0.0" "path-exists": "^4.0.0"
@@ -16812,7 +16650,8 @@
"get-caller-file": { "get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
}, },
"get-intrinsic": { "get-intrinsic": {
"version": "1.2.1", "version": "1.2.1",
@@ -17249,7 +17088,8 @@
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
}, },
"is-glob": { "is-glob": {
"version": "4.0.3", "version": "4.0.3",
@@ -17559,6 +17399,7 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": { "requires": {
"p-locate": "^4.1.0" "p-locate": "^4.1.0"
} }
@@ -18343,6 +18184,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": { "requires": {
"p-try": "^2.0.0" "p-try": "^2.0.0"
} }
@@ -18351,6 +18193,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": { "requires": {
"p-limit": "^2.2.0" "p-limit": "^2.2.0"
} }
@@ -18368,7 +18211,8 @@
"p-try": { "p-try": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
}, },
"param-case": { "param-case": {
"version": "3.0.4", "version": "3.0.4",
@@ -18448,7 +18292,8 @@
"path-exists": { "path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
}, },
"path-is-absolute": { "path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
@@ -18505,11 +18350,6 @@
"find-up": "^4.0.0" "find-up": "^4.0.0"
} }
}, },
"pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
},
"portfinder": { "portfinder": {
"version": "1.0.32", "version": "1.0.32",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
@@ -18995,96 +18835,6 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
}, },
"qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"requires": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"qs": { "qs": {
"version": "6.11.2", "version": "6.11.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
@@ -19271,7 +19021,8 @@
"require-directory": { "require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true
}, },
"require-from-string": { "require-from-string": {
"version": "2.0.2", "version": "2.0.2",
@@ -19279,11 +19030,6 @@
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true "dev": true
}, },
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"requires-port": { "requires-port": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -19547,11 +19293,6 @@
"send": "0.18.0" "send": "0.18.0"
} }
}, },
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"setprototypeof": { "setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -19793,6 +19534,7 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": { "requires": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@@ -19803,6 +19545,7 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": { "requires": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
} }
@@ -20781,11 +20524,6 @@
"isexe": "^2.0.0" "isexe": "^2.0.0"
} }
}, },
"which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
},
"wildcard": { "wildcard": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
+1 -2
View File
@@ -16,10 +16,9 @@
"dayjs": "^1.8.14", "dayjs": "^1.8.14",
"decimal.js": "^10.4.3", "decimal.js": "^10.4.3",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-ui": "^2.15.13", "element-ui": "^2.15.9",
"less": "^4.1.3", "less": "^4.1.3",
"less-loader": "^11.1.3", "less-loader": "^11.1.3",
"qrcode": "^1.5.4",
"qs": "^6.11.2", "qs": "^6.11.2",
"sass": "^1.65.1", "sass": "^1.65.1",
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
+1 -5
View File
@@ -32,10 +32,6 @@
</script> </script>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=2560bbf04daef66c810c5e6a97e8c508&plugin=AMap.AutoComplete,AMap.PlaceSearch,AMap.Geolocation,AMap.Geocoder,AMap.Marker,AMap.Driving"></script> <script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=2560bbf04daef66c810c5e6a97e8c508&plugin=AMap.AutoComplete,AMap.PlaceSearch,AMap.Geolocation,AMap.Geocoder,AMap.Marker,AMap.Driving"></script>
<!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>--> <!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>-->
<script>
window.selfLink = window.location.href;
window.selfSearch = window.location.search;
</script>
<script> <script>
// VConsole 默认会挂载到 `window.VConsole` 上 // VConsole 默认会挂载到 `window.VConsole` 上
// let vConsole = new window.VConsole(); // let vConsole = new window.VConsole();
@@ -56,7 +52,7 @@
if (isMobile) {// 是移动端不变 if (isMobile) {// 是移动端不变
// console.log("是移动端不变") // console.log("是移动端不变")
}else{ }else{
if(window.location.pathname=='/h5/supplier/dispatch/kpiIndex' || window.location.pathname=='/h5/supplier/dispatch/kpiCaseNew'){ if(window.location.pathname=='/h5/supplier/dispatch/kpiIndex'){
// console.log("执行了执行了") // console.log("执行了执行了")
win.addEventListener(resizeEvt, recalc, false); win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false);
-160
View File
@@ -1,160 +0,0 @@
# 年终总结看板 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化,与页面分离
-47
View File
@@ -1,47 +0,0 @@
{
"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
}
}
}
-47
View File
@@ -1,47 +0,0 @@
{
"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
-119
View File
@@ -1,119 +0,0 @@
{
"name": "data",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "data",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"xlsx": "^0.18.5"
}
},
"node_modules/adler-32": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/cfb": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"crc-32": "~1.2.0"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/codepage": {
"version": "1.15.0",
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/frac": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"license": "Apache-2.0",
"dependencies": {
"frac": "~1.1.2"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/wmf": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/word": {
"version": "0.3.0",
"resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"cfb": "~1.2.1",
"codepage": "~1.15.0",
"crc-32": "~1.2.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
}
}
}
@@ -1,16 +0,0 @@
{
"name": "data",
"version": "1.0.0",
"description": "",
"main": "read-excel.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"xlsx": "^0.18.5"
}
}
@@ -1,30 +0,0 @@
const XLSX = require('xlsx');
const fs = require('fs');
const path = require('path');
// Excel文件路径
const excelPath = 'C:\\Users\\chenhaid\\Documents\\WXWork\\1688858118476511\\Cache\\File\\2026-02\\供应商年度KPIv5.xlsx';
// 输出JSON文件路径
const outputPath = path.join(__dirname, 'kpi.json');
// 读取Excel文件
const workbook = XLSX.readFile(excelPath);
// 获取第一个工作表名称
const sheetName = workbook.SheetNames[0];
console.log('工作表列表:', workbook.SheetNames);
// 获取工作表
const worksheet = workbook.Sheets[sheetName];
// 转换为JSON
const jsonData = XLSX.utils.sheet_to_json(worksheet);
console.log('读取到', jsonData.length, '条数据');
console.log('第一条数据示例:', JSON.stringify(jsonData[0], null, 2));
// 写入JSON文件
fs.writeFileSync(outputPath, JSON.stringify(jsonData, null, 2), 'utf8');
console.log('已保存到:', outputPath);
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.
File diff suppressed because it is too large Load Diff
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

-48
View File
@@ -70,51 +70,3 @@ export function driverRealName( data) {
contentType: 'application/json' 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'
})
}
-14
View File
@@ -1,14 +0,0 @@
import request from '@/utils/http'
/**
* 查询 H5 损伤照片
* @param {Object} data - { userOrderId, taskOrderId }
*/
export function getDamagePhotos(data) {
return request({
url: '/api/h5-interface/damagePhotos',
method: 'POST',
contentType: 'application/json',
data
})
}
-10
View File
@@ -1,10 +0,0 @@
import request from '@/utils/http'
export function getBeforeDispatchImage(data) {
return request({
url: '/supplierAppV2/dispatchApp/order/getBeforeDispatchImage',
method: 'POST',
contentType: 'application/json',
data
})
}
-37
View File
@@ -82,40 +82,3 @@ export function getDriverName(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
});
}
+2 -122
View File
@@ -18,42 +18,6 @@ export function saveVehicle(data){
data data
}) })
} }
//车辆属性预判(行驶证OCR识别出车辆所有人后调用,命中服务商信息则锁定为自有车辆)
export function preJudgeVirtualVehicle(data){
return request({
url:'/supplierAppV2/dispatchApp/user/preJudgeVirtualVehicle',
method:'POST',
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({
url:'/supplierAppV2/dispatchApp/user/enableVehicle',
method:'POST',
contentType:'application/json',
data
})
}
//编辑车辆信息
export function updateInsurance(data){
return request({
url:'/supplierAppV2/dispatchApp/user/updateInsurance',
method:'POST',
data
})
}
// 获取车辆信息(用于修改时回显的值,只需传vehicleId) // 获取车辆信息(用于修改时回显的值,只需传vehicleId)
export function getInfoById(data){ export function getInfoById(data){
return request({ return request({
@@ -80,87 +44,12 @@ export function vehicleTypeList(){
}) })
} }
// 服务商服务能力 // 服务商服务能力
export function supplierServiceTree(data){ export function supplierServiceTree(){
console.log('ddddddd0', data)
return request({ return request({
url:'/supplierAppV2/dispatchApp/user/supplierServiceTree', url:'/supplierAppV2/dispatchApp/user/supplierServiceTree',
method:'POST', method:'POST'
data
}) })
} }
// 服务商服务能力
export function supplierServicePartTree(data){
return request({
url:'/supplier/approval/getSupplierServiceTree',
method:'POST',
data
})
}
// 申请加入
export function saveSupplier(data){
return request({
url:'/supplier/potential/save',
method:'POST',
data
})
}
// 申请加入
export function auditSupplier(data){
return request({
url:'/supplier/potential/audit',
method:'POST',
data
})
}
// 修改申请信息
export function getSupplierInfo(key){
return request({
url:'/supplier/potential/getById',
method:'GET',
params:key
})
}
// 根据id查询潜在服务商
export function supplierSelectById(params){
return request({
url:'/supplier/potential/supplierSelectById',
method:'GET',
params
})
}
// 获取企微二维码
export function getOrCodeInfo(data){
return request({
url:'/agg-api/wxcp/createContactQrCode',
method:'POST',
data
})
}
// 获取是否添加企微结果
export function getContactQrCodeResult(data){
return request({
url:'/agg-api/wxcp/getContactQrCodeResult',
method:'POST',
data
})
}
// 添加企微
export function updateWechatId(data){
return request({
url:'/supplier/potential/updateWechatId',
method:'POST',
data
})
}
//司机列表 //司机列表
export function driverList(data){ export function driverList(data){
return request({ return request({
@@ -429,20 +318,11 @@ export function unifiedOCRWithCompress(data){
}) })
} }
export function getArea(data){
return request({
url:'/base/area/tree',
method:'POST',
data
})
}
// 服务商是否培训 // 服务商是否培训
export function jumpPage(data){ export function jumpPage(data){
return request({ return request({
url:'/supplierAppV2/dispatchApp/user/jumpPage', url:'/supplierAppV2/dispatchApp/user/jumpPage',
method:'POST', method:'POST',
contentType:'application/json',
data data
}) })
} }
+1 -46
View File
@@ -23,7 +23,7 @@ export function toDoAudit(data){
data data
}) })
} }
// // 工单详情
export function getOrderDetail(data){ export function getOrderDetail(data){
return request({ return request({
url:'/supplierAppV2/dispatchApp/order/getOrderDetail', url:'/supplierAppV2/dispatchApp/order/getOrderDetail',
@@ -77,15 +77,6 @@ export function updateOrderSettlement(data){
data data
}) })
} }
// 获取司机信息
export function getDriverInfo(data){
return request({
url:'/supplierAppV2/dispatchApp/order/getDriverInfo',
method:'POST',
contentType:'application/json',
data
})
}
// 电瓶数量列表 // 电瓶数量列表
export function batteryCountList(){ export function batteryCountList(){
return request({ return request({
@@ -160,40 +151,4 @@ export function dealWithAlarm(data) {
}) })
} }
//获取当前任务信息
export function getConfigByCode(data){
return request({
url: '/base/baseConfig/getConfigByCode',
method:'POST',
data
})
}
//获取最新版本
export function getNewVersion(key){
return request({
url: '/driverApp/app/updateVersion',
method:'GET',
params: key
})
}
export function getLog(data){
// 添加log
return request({
url: '/ureport/pageView/save',
method:'POST',
contentType:'application/json',
data
})
}
//获取最新版本
export function getUserBO(){
return request({
url: '/base/user/getUserBO',
method:'POST',
})
}
+1 -1
View File
@@ -54,7 +54,7 @@ export function userFeedback(data){
data data
}) })
} }
//擦亮 //二手拖车信息擦亮
export function usedCarPolish(data){ export function usedCarPolish(data){
return request({ return request({
url: `/toc-user/car-app/usedCarPolish/${data}`, url: `/toc-user/car-app/usedCarPolish/${data}`,
-43
View File
@@ -1,43 +0,0 @@
import request from '@/utils/http'
/**
* 查询当前服务商分时段电话列表和审批状态
* @param {Object} params
* @param {number} params.supplierId 服务商 ID
*/
export function getSupplierPhoneList(params) {
return request({
url: '/supplierAppV2/dispatchApp/supplierPhone/list',
method: 'GET',
params
})
}
/**
* 查询当前服务商电话时段变更审批状态
* @param {Object} params
* @param {number} params.supplierId 服务商 ID
*/
export function getSupplierPhoneApprovalStatus(params) {
return request({
url: '/supplierAppV2/dispatchApp/supplierPhone/approvalStatus',
method: 'GET',
params
})
}
/**
* 提交服务商电话时段变更审批
* @param {Object} data
* @param {number} data.supplierId 服务商 ID
* @param {string} [data.modifyReason] 修改原因
* @param {Array} data.phoneList 电话时段草稿列表
*/
export function submitSupplierPhoneApproval(data) {
return request({
url: '/supplierAppV2/dispatchApp/supplierPhone/submit',
method: 'POST',
contentType: 'application/json',
data
})
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 B

-706
View File
@@ -1,706 +0,0 @@
<template>
<div class="cameraGuide" v-if="visible">
<!-- ===== 拍摄引导页 ===== -->
<div class="cameraPage" v-show="step==='guide'">
<div class="cameraNav">
<van-icon name="arrow-left" class="backBtn" @click="handleClose" />
<span class="navTitle">{{ navTitle }}</span>
</div>
<div class="guideLayer">
<!-- 顶部主指令卡 -->
<div class="guideHero">
<div class="guideHeroIcon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="5" width="18" height="14" rx="2"></rect>
<path d="M3 9h18"></path>
<circle cx="8" cy="13.5" r="1.6"></circle>
<path d="M11 12.5h7M11 15h5"></path>
</svg>
</div>
<div class="guideHeroText">
<div class="guideHeroTitle">{{ heroTitle }}</div>
<div class="guideHeroSub">从卡套内拿出避免反光</div>
</div>
</div>
<!-- 取景框 -->
<div class="guideFrameOuter">
<span class="corner tl"></span>
<span class="corner tr"></span>
<span class="corner bl"></span>
<span class="corner br"></span>
<div class="guideFrameBorder"></div>
<div class="guideHole">
<!-- 相机画面仅在取景框内显示 -->
<video ref="video" class="cameraVideo" autoplay playsinline webkit-playsinline muted></video>
<!-- 主页模板标题 + 印章 -->
<div v-if="type==='front'" class="licenseTemplate ltFrontPage">
<div class="ltInner">
<div class="ltTitle">
<div class="ltTitleLine">中华人民共和国机动车行驶证</div>
</div>
<div class="ltSeal">印章</div>
</div>
</div>
<!-- 副页模板条形码 + 编号 -->
<div v-else-if="type==='back'" class="licenseTemplate ltBackPage">
<div class="ltInner">
<div class="ltBarcodeArea">
<div class="ltBarcodeStrip">
<div v-for="(bar, i) in barcodeBars" :key="i" class="ltBar" :class="bar"></div>
</div>
<div class="ltBarcodeNum">3701001234567</div>
</div>
</div>
</div>
<!-- 车辆照片模板标题 + 车辆轮廓 -->
<div v-else class="licenseTemplate ltCarPage">
<div class="ltInner">
<div class="ltTitle">
<div class="ltTitleLine">车辆照片</div>
</div>
<div class="ltCarOutline">
</div>
</div>
</div>
</div>
</div>
<!-- 底部取景要点 -->
<div class="guideTips">
<div class="tipItem ok">
<span class="tipIco">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 8V4h4M16 4h4v4M20 16v4h-4M8 20H4v-4"></path>
</svg>
</span>
<span class="tipLabel">四角入框</span>
</div>
<div class="tipItem ok">
<span class="tipIco">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 12h16M7 7h10M7 17h10"></path>
</svg>
</span>
<span class="tipLabel">平放正向</span>
</div>
<div class="tipItem warn">
<span class="tipIco">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M5 19l2-2"></path>
</svg>
</span>
<span class="tipLabel">避免反光</span>
</div>
</div>
</div>
<div class="shootBar">
<div class="shootBtn" @click="takePhoto"></div>
<div class="shootLabel">点击拍照</div>
</div>
</div>
<!-- ===== 预览确认页 ===== -->
<div class="previewPage" v-show="step==='preview'">
<div class="cameraNav">
<van-icon name="arrow-left" class="backBtn" @click="handlePreviewBack" />
<span class="navTitle">照片预览</span>
</div>
<div class="previewImgWrap">
<img class="previewImg" :src="previewDataURL" alt="预览" />
</div>
<div class="previewBtns">
<button class="previewBtn btnRetake" @click="retake">重拍</button>
<button class="previewBtn btnConfirm" @click="confirmPhoto">确认使用</button>
</div>
</div>
</div>
</template>
<script>
// 拍摄引导组件:WebRTC 取景 + 引导框 + 分页面证件模板 + 预览确认
// props: type(front/back/car), visible
// emit: confirm({blob, dataURL})、cancel、fallback(权限拒绝/相机不可用,父组件降级系统选择器)
export default {
name: 'CameraGuide',
props: {
type: {
type: String,
default: 'front'
},
visible: {
type: Boolean,
default: false
},
// 父组件前置探测时已拿到的相机流,传入则直接复用(iOS 上避免二次 getUserMedia 黑屏)
stream: {
type: Object,
default: null
}
},
data() {
const heights = ['tall', 'short', 'medium wide', 'tall', 'short', 'medium', 'tall wide', 'short',
'medium', 'tall', 'short wide', 'medium', 'tall', 'short', 'medium wide', 'tall',
'short', 'medium', 'tall wide', 'short', 'medium', 'tall', 'short wide', 'medium',
'tall', 'short', 'medium wide', 'tall', 'short', 'medium', 'tall wide', 'short',
'medium', 'tall', 'short wide', 'medium', 'tall', 'short', 'medium wide', 'tall']
return {
step: 'guide', // guide 拍摄引导 | preview 预览确认
activeStream: null,
previewBlob: null,
previewDataURL: '',
barcodeBars: heights
}
},
computed: {
navTitle() {
return { front: '拍摄行驶证主页', back: '拍摄行驶证副页', car: '拍摄行驶证车辆照片' }[this.type] || '拍摄行驶证'
},
heroTitle() {
return { front: '请将主页对准取景框', back: '请将副页对准取景框', car: '请将车辆对准取景框' }[this.type] || '请将证件对准取景框'
}
},
watch: {
visible(val) {
if (val) {
this.step = 'guide'
this.previewBlob = null
this.previewDataURL = ''
this.$nextTick(() => this.startCamera())
} else {
this.stopCamera()
}
}
},
beforeDestroy() {
this.stopCamera()
},
methods: {
async startCamera() {
try {
// 优先复用父组件探测时拿到的流,未传入才自行申请
const stream = this.stream || await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' },
audio: false
})
// 等待期间组件已被关闭或销毁,立即释放
if (!this.visible || this._isDestroyed || this._isBeingDestroyed) {
stream.getTracks().forEach(t => t.stop())
return
}
this.activeStream = stream
console.log('[CameraGuide] stream acquired, 来源:', this.stream ? '父组件复用' : '组件自申请', 'active:', stream.active,
'tracks:', stream.getTracks().map(t => `${t.kind}/${t.readyState}/enabled:${t.enabled}`))
await this.$nextTick()
const video = this.$refs.video
if (video) {
// iOS 自动播放加固:muted/playsinline 属性与特性同时设置,元数据就绪后再 play
video.muted = true
video.setAttribute('playsinline', 'true')
video.setAttribute('webkit-playsinline', 'true')
video.srcObject = stream
const logVideoState = (tag) => {
console.log(`[CameraGuide] ${tag}:`,
'paused:', video.paused,
'readyState:', video.readyState,
'currentTime:', video.currentTime,
'videoWidth:', video.videoWidth,
'videoHeight:', video.videoHeight,
'streamActive:', stream.active,
'muted:', video.muted)
}
const playVideo = () => {
video.play()
.then(() => logVideoState('play resolved'))
.catch(e => {
console.warn('[CameraGuide] video play error:', e && (e.name || e.message || e))
logVideoState('play rejected')
})
}
video.onloadedmetadata = () => {
logVideoState('loadedmetadata')
playVideo()
}
video.onplaying = () => logVideoState('playing')
video.onerror = () => {
console.warn('[CameraGuide] video element error:', video.error && `${video.error.code}/${video.error.message}`)
}
if (video.readyState >= 1) {
playVideo()
}
// 2 秒后再看一次:currentTime 是否增长(判断是真在播还是黑屏死流)
setTimeout(() => logVideoState('after 2s'), 2000)
}
} catch (e) {
// 权限拒绝或相机不可用 → 通知父组件降级为系统选择器
console.warn('getUserMedia error:', e && (e.name || e.message || e))
this.$emit('fallback', e)
}
},
stopCamera() {
if (this.activeStream) {
this.activeStream.getTracks().forEach(t => t.stop())
this.activeStream = null
}
},
takePhoto() {
const video = this.$refs.video
if (!video || !video.videoWidth) {
this.$toast('相机尚未就绪,请稍候')
return
}
// 按取景框比例(约 1.43:1)从视频中心裁剪,与框内可视区域(object-fit: cover)保持一致
const frameAspect = 1.43
const vw = video.videoWidth
const vh = video.videoHeight
let sw, sh
if (vw / vh > frameAspect) {
sh = vh
sw = Math.round(vh * frameAspect)
} else {
sw = vw
sh = Math.round(vw / frameAspect)
}
const sx = Math.round((vw - sw) / 2)
const sy = Math.round((vh - sh) / 2)
const canvas = document.createElement('canvas')
canvas.width = sw
canvas.height = sh
canvas.getContext('2d').drawImage(video, sx, sy, sw, sh, 0, 0, sw, sh)
const dataURL = canvas.toDataURL('image/jpeg', 0.92)
canvas.toBlob(blob => {
if (!blob) {
this.$toast('拍摄失败,请重试')
return
}
this.previewBlob = blob
this.previewDataURL = dataURL
this.step = 'preview'
}, 'image/jpeg', 0.92)
},
retake() {
// 重拍回到引导页,相机流保持
this.step = 'guide'
},
confirmPhoto() {
this.$emit('confirm', { blob: this.previewBlob, dataURL: this.previewDataURL })
},
handleClose() {
this.$emit('cancel')
},
handlePreviewBack() {
// 预览页返回 = 放弃拍摄,关闭并释放相机流
this.$emit('cancel')
}
}
}
</script>
<style scoped lang="scss">
.cameraGuide {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 3000;
background: #F5F5F5;
}
.cameraPage {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(160deg, #F7F9FC 0%, #EEF1F6 50%, #E6EAF1 100%);
overflow: hidden;
}
.cameraVideo {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.cameraNav {
position: relative;
z-index: 10;
display: flex;
align-items: center;
height: 52px;
padding-left: 14px;
padding-top: env(safe-area-inset-top);
background: #2C395F;
color: #fff;
.backBtn {
font-size: 22px;
width: 36px;
flex-shrink: 0;
}
.navTitle {
flex: 1;
text-align: left;
font-size: 18px;
font-weight: bolder;
letter-spacing: .5px;
padding-left: 4px;
}
}
.guideLayer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 5;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 80px;
/* 避让底部拍照栏(约 142px 高),防止小屏下提示被遮挡 */
padding-bottom: 150px;
padding-bottom: calc(150px + env(safe-area-inset-bottom));
pointer-events: none;
}
.guideHero {
display: flex;
align-items: center;
gap: 12px;
background: #FFFFFF;
border: 1px solid #E9E9EA;
border-radius: 6px;
padding: 12px 15px;
margin-bottom: 24px;
max-width: 322px;
width: calc(100% - 48px);
box-sizing: border-box;
box-shadow: 0 2px 10px rgba(20, 30, 50, .08);
.guideHeroIcon {
width: 42px;
height: 42px;
flex-shrink: 0;
border-radius: 6px;
background: rgba(53, 77, 147, .10);
display: flex;
align-items: center;
justify-content: center;
color: #354D93;
svg {
width: 23px;
height: 23px;
}
}
.guideHeroTitle {
color: #222;
font-size: 16px;
font-weight: 700;
line-height: 1.3;
letter-spacing: .3px;
}
.guideHeroSub {
color: #5B606E;
font-size: 12.5px;
margin-top: 4px;
line-height: 1.4;
}
}
/* 取景框:宽约为屏宽 80%,宽高比约 1.43:1;小屏按可视高度自适应缩小,避免遮挡上下元素 */
.guideFrameOuter {
position: relative;
width: 80%;
/* 纵向可用高度 ≈ 100vh - (导航80 + 拍照栏150 + 提示卡92 + 底部提示92) ≈ 100vh - 420px
换算成宽度(×1.43)并限制在 170~340px 之间,大屏表现与原 80%/340px 一致 */
width: clamp(170px, min(80%, calc((100vh - 420px) * 1.43)), 340px);
width: clamp(170px, min(80%, calc((100dvh - 420px) * 1.43)), 340px);
/* 宽高比约 1.43:1(与拍照裁剪比例一致);不用 padding-top 百分比——它相对父级宽度,无法跟随自适应宽度 */
aspect-ratio: 1.43 / 1;
flex-shrink: 0;
/* 不用 filter: drop-shadowiOS 上父级 filter 会导致 video 合成层不绘制(黑屏) */
box-shadow: 0 4px 16px rgba(20, 30, 50, .12);
}
/* 兼容不支持 aspect-ratio 的旧浏览器:padding-top 相对父级宽度,80% / 1.43 ≈ 55.94% */
@supports not (aspect-ratio: 1 / 1) {
.guideFrameOuter {
padding-top: 55.94%;
}
}
.corner {
position: absolute;
width: 30px;
height: 30px;
border-color: #354D93;
border-style: solid;
z-index: 10;
animation: cornerFadeIn .45s ease both;
&.tl { top: -4px; left: -4px; border-width: 4px 0 0 4px; border-radius: 4px 0 0 0; }
&.tr { top: -4px; right: -4px; border-width: 4px 4px 0 0; border-radius: 0 4px 0 0; }
&.bl { bottom: -4px; left: -4px; border-width: 0 0 4px 4px; border-radius: 0 0 0 4px; }
&.br { bottom: -4px; right: -4px; border-width: 0 4px 4px 0; border-radius: 0 0 4px 0; }
}
@keyframes cornerFadeIn {
from { opacity: 0; transform: scale(1.15); }
to { opacity: 1; transform: scale(1); }
}
.guideFrameBorder {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 3px solid #C2C8D2;
border-radius: 8px;
pointer-events: none;
z-index: 9;
}
.guideHole {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 8px;
overflow: hidden;
}
/* 证件模板叠层(仅视觉辅助,不拦截事件) */
.licenseTemplate {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 6;
border: 1.5px solid rgba(53, 77, 147, .55);
border-radius: 4px;
overflow: hidden;
pointer-events: none;
background: rgba(53, 77, 147, .06);
}
.ltInner {
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
display: flex;
flex-direction: column;
}
.ltTitle {
text-align: center;
margin-bottom: 4px;
}
.ltTitleLine {
font-size: 10px;
color: rgba(53, 77, 147, .9);
font-weight: bold;
letter-spacing: 1px;
white-space: nowrap;
}
.ltSeal {
position: absolute;
left: 10px;
bottom: 10px;
width: 34px;
height: 34px;
border: 1.5px dashed rgba(255, 85, 59, .85);
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
font-size: 5.5px;
color: rgba(255, 85, 59, .85);
}
.ltFrontPage .ltInner {
justify-content: flex-start;
}
.ltBackPage .ltInner {
justify-content: flex-end;
}
.ltBarcodeArea {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
padding-bottom: 6px;
padding-right: 8px;
}
.ltBarcodeStrip {
display: flex;
align-items: flex-end;
height: 28px;
gap: 1px;
.ltBar {
background: rgba(53, 77, 147, .6);
width: 1.5px;
border-radius: 0.5px;
&.wide { width: 2.8px; }
&.short { height: 50%; }
&.medium { height: 70%; }
&.tall { height: 100%; }
}
}
.ltBarcodeNum {
font-size: 8px;
color: rgba(53, 77, 147, .9);
letter-spacing: 2px;
font-family: 'Courier New', monospace;
}
.ltCarPage .ltCarOutline {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
svg {
width: 60%;
opacity: .6;
color: #354D93;
}
}
.guideTips {
display: flex;
gap: 10px;
margin-top: 22px;
padding: 0 14px;
max-width: 340px;
width: 100%;
box-sizing: border-box;
}
.tipItem {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 7px;
background: #FFFFFF;
border-radius: 6px;
padding: 11px 4px 9px;
border: 1px solid #E9E9EA;
box-shadow: 0 2px 8px rgba(20, 30, 50, .06);
.tipIco {
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
svg {
width: 17px;
height: 17px;
}
}
&.ok .tipIco {
background: rgba(53, 77, 147, .10);
color: #354D93;
}
&.warn .tipIco {
background: rgba(255, 85, 59, .12);
color: #FF553B;
}
.tipLabel {
color: #5B606E;
font-size: 11.5px;
line-height: 1.3;
text-align: center;
}
}
.shootBar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 12px;
padding-bottom: calc(32px + env(safe-area-inset-bottom));
background: #FFFFFF;
border-top: 1px solid #E9E9EA;
}
.shootBtn {
width: 72px;
height: 72px;
border-radius: 50%;
border: 4px solid #354D93;
background: #FFFFFF;
position: relative;
box-shadow: 0 0 0 2px rgba(53, 77, 147, .22), 0 6px 16px rgba(20, 30, 50, .18);
&:active {
transform: scale(.92);
}
&::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 58px;
height: 58px;
border-radius: 50%;
background: #354D93;
}
}
.shootLabel {
color: #5B606E;
font-size: 12px;
margin-top: 10px;
letter-spacing: 1px;
}
/* ===== 预览确认页 ===== */
.previewPage {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #F5F5F5;
display: flex;
flex-direction: column;
z-index: 20;
}
.previewImgWrap {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
overflow: hidden;
}
.previewImg {
max-width: 100%;
max-height: 100%;
border-radius: 8px;
object-fit: contain;
}
.previewBtns {
display: flex;
gap: 16px;
padding: 0 20px 32px;
padding-bottom: calc(32px + env(safe-area-inset-bottom));
}
.previewBtn {
flex: 1;
height: 48px;
border-radius: 24px;
border: none;
font-size: 16px;
font-weight: 600;
&:active {
opacity: .8;
}
}
.btnRetake {
background: #FFFFFF;
color: #354D93;
border: 1px solid #354D93;
}
.btnConfirm {
background: #354D93;
color: #fff;
}
</style>
-136
View File
@@ -1,136 +0,0 @@
<template>
<div class="step-bar">
<div
v-for="(step, index) in steps"
:key="index"
class="step-item"
:class="getStepClass(index)"
>
<div class="step-circle" @click="onStepClick(index)">
<span v-if="getStatus(index) === 'finish'" class="step-check"></span>
<span v-else>{{ index + 1 }}</span>
</div>
<div class="step-label">{{ step }}</div>
<div v-if="index < steps.length - 1" class="step-line" :class="{'step-line-done': getStatus(index) === 'finish'}"></div>
</div>
</div>
</template>
<script>
export default {
name: 'StepBar',
props: {
current: {
type: Number,
default: 1
},
steps: {
type: Array,
default: () => ['基本信息', '添加企微', '车辆信息', '驾驶员信息']
}
},
methods: {
getStatus(index) {
const stepNum = index + 1;
if (stepNum < this.current) return 'finish';
if (stepNum === this.current) return 'process';
return 'wait';
},
getStepClass(index) {
return `step-${this.getStatus(index)}`;
},
onStepClick(index) {
const stepNum = index + 1;
if (this.getStatus(index) === 'finish') {
this.$emit('step-click', stepNum);
}
}
}
}
</script>
<style scoped lang="scss">
.step-bar {
display: flex;
align-items: flex-start;
justify-content: center;
padding: 14px 10px 10px;
background: #fff;
position: relative;
}
.step-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
position: relative;
}
.step-circle {
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: bold;
z-index: 1;
position: relative;
}
.step-finish .step-circle {
background: #0E76F4;
color: #fff;
cursor: pointer;
}
.step-process .step-circle {
background: #0E76F4;
color: #fff;
box-shadow: 0 0 0 3px rgba(14, 118, 244, 0.2);
}
.step-wait .step-circle {
background: #e0e0e0;
color: #999;
}
.step-label {
font-size: 11px;
margin-top: 5px;
text-align: center;
white-space: nowrap;
}
.step-finish .step-label {
color: #0E76F4;
}
.step-process .step-label {
color: #0E76F4;
font-weight: bold;
}
.step-wait .step-label {
color: #999;
}
.step-line {
position: absolute;
top: 13px;
left: calc(50% + 13px);
right: calc(-50% + 13px);
height: 2px;
background: #e0e0e0;
}
.step-line-done {
background: #0E76F4;
}
.step-check {
font-size: 14px;
}
</style>
-8
View File
@@ -39,13 +39,5 @@ const authenticationRouter = [
title: '银行卡信息认证', title: '银行卡信息认证',
} }
}, },
{
path: '/personList',
name: 'personList',
component: () => import('@/views/mine/personList.vue'),
meta:{
title: '人员信息',
}
},
] ]
export default authenticationRouter export default authenticationRouter
+1 -90
View File
@@ -11,54 +11,6 @@ const routes = [
path: '/', path: '/',
redirect: '/index' redirect: '/index'
}, },
{
path: '/supplierAdd',
name: 'supplierAdd',
component: () => import('@/views/index/supplierAdd'),
meta:{
title: '信息录入',
}
},
{
path: '/supplierAddResult',
name: 'supplierAddResult',
component: () => import('@/views/index/supplierAddResult'),
meta:{
title: '审核中',
}
},
{
path: '/addWechat',
name: 'addWechat',
component: () => import('@/views/index/addWechat'),
meta:{
title: '添加微信',
}
},
{
path: '/supplierInfo',
name: 'supplierInfo',
component: () => import('@/views/index/supplierInfo'),
meta:{
title: '信息查看',
}
},
{
path: '/supplierTimePhone',
name: 'supplierTimePhone',
component: () => import('@/views/index/supplierTimePhone'),
meta:{
title: '供应商时段电话',
}
},
{
path: '/supplierTimePhoneEdit',
name: 'supplierTimePhoneEdit',
component: () => import('@/views/index/supplierTimePhoneEdit'),
meta:{
title: '编辑时段电话',
}
},
{ {
path: '/toDoList', path: '/toDoList',
name: 'toDoList', name: 'toDoList',
@@ -180,22 +132,6 @@ const routes = [
title: '查看发票' title: '查看发票'
} }
}, },
{
path: '/damagePhotos',
name: 'damagePhotoList',
component: () => import('@/views/damagePhoto/damagePhotoList.vue'),
meta: {
title: '损伤照片'
}
},
{
path: '/environmentPhotos',
name: 'environmentPhotoList',
component: () => import('@/views/environmentPhoto/environmentPhotoList.vue'),
meta: {
title: '环境照片'
}
},
{ {
path: "/vehicleManage", path: "/vehicleManage",
name: 'vehicleManage', name: 'vehicleManage',
@@ -209,24 +145,7 @@ const routes = [
name: 'vehicleAdd', name: 'vehicleAdd',
component: () => import('@/views/index/vehicleAdd'), component: () => import('@/views/index/vehicleAdd'),
meta: { meta: {
title: '车辆新增或修改', title: '车辆新增或修改'
cache: true // keep-alive 缓存:跳转拍摄页返回时保留表单,再次从其他页面进入时由 activated 重新初始化
}
},
{
path: "/cameraGuide",
name: 'cameraGuide',
component: () => import('@/views/index/cameraGuide'),
meta: {
title: '拍摄行驶证'
}
},
{
path: "/continueInsurance",
name: 'continueInsurance',
component: () => import('@/views/index/continueInsurance'),
meta: {
title: '续保更新'
} }
}, },
{ {
@@ -373,14 +292,6 @@ const routes = [
title:'电瓶详情' title:'电瓶详情'
} }
}, },
{
path: '/goToApp',
name: 'goToApp',
component:()=>import('@/views/goToApp/goToApp.vue'),
meta: {
title:'小板运输司机端'
}
},
...kpiRouter, ...kpiRouter,
...invoiceRouter, ...invoiceRouter,
...secondHandRouter, ...secondHandRouter,
-8
View File
@@ -7,13 +7,5 @@ const kpiRouter = [
title: 'kpi首页', title: 'kpi首页',
} }
}, },
{
path: '/kpiCaseNew',
name: 'kpiCaseNew',
component: () => import('@/views/kpi/kpiCaseNew'),
meta:{
title: 'kpi服务商案件&车辆情况',
}
},
] ]
export default kpiRouter export default kpiRouter
-22
View File
@@ -1,22 +0,0 @@
// 拍摄页与表单页之间的内存中转:blob 无法走路由 query,返回时由表单页取出并清空
export default {
namespaced: true,
state: {
result: null, // 拍照确认结果 { type, blob, dataURL }
fallback: null // 相机不可用降级信息 { type, error }
},
mutations: {
setResult(state, payload) {
state.result = payload
state.fallback = null
},
setFallback(state, payload) {
state.fallback = payload
state.result = null
},
clear(state) {
state.result = null
state.fallback = null
}
}
}
-11
View File
@@ -23,17 +23,6 @@
div { div {
padding-top: 8px; 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 { .active {
color: #3678FF; color: #3678FF;
position: relative; position: relative;
+1 -1
View File
@@ -1,6 +1,6 @@
@import "@/styles/mixin.scss"; @import "@/styles/mixin.scss";
::v-deep .van-nav-bar__content{ ::v-deep .van-nav-bar__content{
//background-color: #3A3A3A !important; background-color: #3A3A3A !important;
} }
.wrap{ .wrap{
@include wh(100%,100%); @include wh(100%,100%);
+6 -13
View File
@@ -19,7 +19,6 @@ if( token ) {
service.interceptors.request.use( service.interceptors.request.use(
config => { config => {
let reqUrl=config.url let reqUrl=config.url
// console.log('config',config)
config.data = config.contentType ? config.data : qs.stringify(config.data) config.data = config.contentType ? config.data : qs.stringify(config.data)
if (config.testFlag) { if (config.testFlag) {
config.data = qs.stringify(config.data, {arrayFormat: 'indices', allowDots: true}) config.data = qs.stringify(config.data, {arrayFormat: 'indices', allowDots: true})
@@ -28,17 +27,12 @@ service.interceptors.request.use(
let token = localStorage.getItem('token'); let token = localStorage.getItem('token');
// let token='eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJBTkNIQU5HIiwidXNlcklkIjo0NTY3MSwibmFtZSI6IuWuieeVhSIsInVzZXJOYW1lIjoiQU5DSEFORyIsInN1cHBsaWVySWQiOjExMjgsImlzWmQiOjAsImV4cCI6MTc1NTQyMjUyNX0.xzDZhaANJFnbeViIHJA0SEtOyTv7Ja3rKmXqRKRuFkc' // let token='eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJBTkNIQU5HIiwidXNlcklkIjo0NTY3MSwibmFtZSI6IuWuieeVhSIsInVzZXJOYW1lIjoiQU5DSEFORyIsInN1cHBsaWVySWQiOjExMjgsImlzWmQiOjAsImV4cCI6MTc1NTQyMjUyNX0.xzDZhaANJFnbeViIHJA0SEtOyTv7Ja3rKmXqRKRuFkc'
// let token='eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJTSEhQWEIiLCJ1c2VySWQiOjU0NzI2LCJuYW1lIjoiI-a1i-ivleWwj-eZveeZvSIsInVzZXJOYW1lIjoiU0hIUFhCIiwic3VwcGxpZXJJZCI6MTAwMDE2NSwiZXhwIjoxNzQ0NTEwNzkwfQ.JPk0OA7slYJN3FIi_uhW4Y0CiWRvl6R1dK8MRTbyhD8' // let token='eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJTSEhQWEIiLCJ1c2VySWQiOjU0NzI2LCJuYW1lIjoiI-a1i-ivleWwj-eZveeZvSIsInVzZXJOYW1lIjoiU0hIUFhCIiwic3VwcGxpZXJJZCI6MTAwMDE2NSwiZXhwIjoxNzQ0NTEwNzkwfQ.JPk0OA7slYJN3FIi_uhW4Y0CiWRvl6R1dK8MRTbyhD8'
// if(!(reqUrl=='/supplier/supplierTraining/trainingTask' || reqUrl=='/supplier/supplierTraining/normalList' || reqUrl=='/supplier/supplierTraining/trainingList')){ if(!(reqUrl=='/supplier/supplierTraining/trainingTask' || reqUrl=='/supplier/supplierTraining/normalList' || reqUrl=='/supplier/supplierTraining/trainingList')){
if ((reqUrl=='/supplier/supplierTraining/trainingList' || reqUrl=='/supplier/supplierTraining/normalList') && config?.params?.type==1){ if(token) {
console.log('司机app使用该请求不挂token') config.headers['Authorization'] = `${token}`;
} else { config.headers['token'] = `${token}`;
if(token) { }
config.headers['Authorization'] = `${token}`; }
config.headers['token'] = `${token}`;
}
}
// }
return config return config
}, },
error => { error => {
@@ -48,7 +42,6 @@ service.interceptors.request.use(
service.interceptors.response.use( service.interceptors.response.use(
response => { response => {
console.log('response', response.data)
const res = response.data const res = response.data
if ( res.code === 401 || res.code === 400 || res.code == 500) { if ( res.code === 401 || res.code === 400 || res.code == 500) {
Toast(res.msg || 'Error') Toast(res.msg || 'Error')
-102
View File
@@ -1,102 +0,0 @@
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, ',');
},
}
}
-9
View File
@@ -1,6 +1,3 @@
import { getLog } from "@/api/order"
let _setLogTimer = null
export const myMixins = { export const myMixins = {
data() { data() {
@@ -8,11 +5,6 @@ export const myMixins = {
} }
}, },
methods: { methods: {
setLogHandler(data) {
if (_setLogTimer) return
_setLogTimer = setTimeout(() => { _setLogTimer = null }, 1000)
getLog(data)
},
showFun() { showFun() {
if( localStorage.getItem('infoVerify') == 8 || localStorage.getItem('infoVerify') == 12 ) { if( localStorage.getItem('infoVerify') == 8 || localStorage.getItem('infoVerify') == 12 ) {
return false return false
@@ -102,7 +94,6 @@ export const myMixins = {
isWebFunc(){ isWebFunc(){
let res=false let res=false
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
console.log('isMobile',isMobile)
if (!isMobile) {// 是移动端不变 if (!isMobile) {// 是移动端不变
res=true res=true
} }
-51
View File
@@ -1,51 +0,0 @@
/**
* 校验电话号码
* 支持:大陆手机号、大陆固话、400电话、港澳手机号
* @param {string} phone
* @returns {boolean}
*/
export function isValidPhone(phone) {
if (!phone) return false
// 大陆手机号
const mainlandMobile = /^1[3-9]\d{9}$/
// 大陆固话:区号(可选) + 7~8位号码
const mainlandLandline = /^(0\d{2,3}-?)\d{7,8}$/
// 400电话
const phone400 = /^400-?\d{3}-?\d{4}$/
// 港澳手机号(8位,首位5/6/8/9)
const hkMacaoMobile = /^[5689]\d{7}$/
// 港澳固话
const hkMacaoLandline = /^(\d{4}-?)?\d{4}$/
return (
mainlandMobile.test(phone) ||
mainlandLandline.test(phone) ||
phone400.test(phone) ||
hkMacaoMobile.test(phone) ||
hkMacaoLandline.test(phone)
)
}
/**
* 校验时段格式 HH:mm 及起止关系
* @param {string} startTime
* @param {string} endTime
* @returns {{valid: boolean, message?: string}}
*/
export function validateTimeRange(startTime, endTime) {
const timeRegex = /^([0-1]\d|2[0-3]):([0-5]\d)$/
if (!startTime || !endTime) {
return { valid: false, message: '请填写完整的起止时段' }
}
if (!timeRegex.test(startTime) || !timeRegex.test(endTime)) {
return { valid: false, message: '时段格式不正确,应为 00:00 ~ 23:59' }
}
const [startH, startM] = startTime.split(':').map(Number)
const [endH, endM] = endTime.split(':').map(Number)
const start = startH * 60 + startM
const end = endH * 60 + endM
if (start > end) {
return { valid: false, message: '开始时间不得晚于结束时间' }
}
return { valid: true }
}
-312
View File
@@ -1,312 +0,0 @@
<template>
<div class="wrap damage-photo-wrap">
<div class="navBar">
<van-nav-bar
title="损伤照片"
left-arrow
left-arrow-color="#FFFFFF"
:border="false"
:fixed="true"
:safe-area-inset-top="true"
@click-left="h5GoBack"
/>
</div>
<div class="content-wrap" v-if="loaded">
<!-- 案件信息 -->
<div class="info-card">
<div class="info-item">
<span class="info-label">案件号</span>
<span class="info-value">{{ info.caseNo || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">车辆标识</span>
<span class="info-value">{{ info.vehicleIdentity || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">起止路线</span>
<span class="info-value">{{ info.route || '-' }}</span>
</div>
</div>
<!-- 照片列表 -->
<div class="photo-list" v-if="photoList.length">
<div class="photo-card" v-for="(item, index) in photoList" :key="index">
<div class="photo-title">
<span>{{ item.title || '-' }}</span>
<span
v-if="item.replaced || (item.historyList && item.historyList.length)"
class="replaced-tag"
@click="showHistory(item)"
>被替换</span>
</div>
<div class="photo-image" @click="previewCurrent(item.currentUrl)">
<van-image
:src="item.currentUrl"
fit="cover"
width="100%"
height="160"
:error-icon="defaultImg"
error-icon-size="48"
/>
</div>
</div>
</div>
<!-- 空态 -->
<div class="empty-state" v-else>
<van-empty description="暂无损伤照片" />
</div>
</div>
<!-- 历史照片弹框 -->
<van-popup
v-model="historyShow"
position="bottom"
round
closeable
:style="{ height: '70%' }"
@closed="currentHistory = []"
>
<div class="history-popup">
<div class="history-title">历史照片</div>
<div class="history-list" v-if="currentHistory.length">
<div
class="history-item"
v-for="(item, index) in currentHistory"
:key="index"
@click="previewHistory(item.url)"
>
<van-image
:src="item.url"
fit="cover"
width="100%"
height="120"
:error-icon="defaultImg"
error-icon-size="48"
/>
<div class="history-time" v-if="item.replaceTime">替换时间{{ item.replaceTime }}</div>
</div>
</div>
<div class="history-empty" v-else>
<van-empty description="暂无历史照片" />
</div>
</div>
</van-popup>
</div>
</template>
<script>
import { getDamagePhotos } from '@/api/damagePhoto'
import { myMixins } from '@/utils/myMixins'
import { ImagePreview, Toast } from 'vant'
export default {
name: 'damagePhotoList',
mixins: [myMixins],
data() {
return {
loaded: false,
info: {
caseNo: '',
vehicleIdentity: '',
route: ''
},
photoList: [],
historyShow: false,
currentHistory: [],
defaultImg: require('@/assets/empty.png')
}
},
async mounted() {
await this.fetchDamagePhotos()
},
methods: {
async fetchDamagePhotos() {
const userOrderId = this.$route.query.userOrderId
const taskOrderId = this.$route.query.taskOrderId
if (!userOrderId || !taskOrderId) {
Toast('缺少必要参数')
this.loaded = true
return
}
Toast.loading({
message: '加载中...',
forbidClick: true,
duration: 0
})
try {
const res = await getDamagePhotos({
userOrderId: Number(userOrderId),
taskOrderId: Number(taskOrderId)
})
this.info = {
caseNo: res.data?.caseNo || '',
vehicleIdentity: res.data?.vehicleIdentity || '',
route: res.data?.route || ''
}
this.photoList = Array.isArray(res.data?.photos) ? res.data.photos : []
} finally {
Toast.clear()
this.loaded = true
}
},
previewCurrent(url) {
if (!url) return
ImagePreview({
images: [url],
startPosition: 0,
closeable: true
})
},
showHistory(item) {
this.currentHistory = Array.isArray(item.historyList) ? item.historyList : []
this.historyShow = true
},
previewHistory(url) {
if (!url) return
ImagePreview({
images: [url],
startPosition: 0,
closeable: true
})
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.wrap {
@include wh(100%, 100%);
background-color: #F4F5F7;
overflow-y: auto;
}
.navBar {
height: 46px;
::v-deep .van-nav-bar--fixed {
z-index: 9999 !important;
}
}
.content-wrap {
padding: 10px 15px 20px;
}
.info-card {
background: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
.info-item {
display: flex;
line-height: 24px;
font-size: 13px;
&:not(:last-child) {
margin-bottom: 8px;
}
.info-label {
color: rgba(0, 0, 0, 0.5);
width: 70px;
flex-shrink: 0;
}
.info-value {
color: #323643;
flex: 1;
word-break: break-all;
}
}
}
.photo-list {
.photo-card {
background: #fff;
border-radius: 8px;
padding: 12px 12px 15px;
margin-bottom: 10px;
.photo-title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
font-weight: bold;
color: #323643;
margin-bottom: 10px;
.replaced-tag {
font-size: 12px;
font-weight: normal;
color: #fff;
background-color: #ee0a24;
padding: 2px 8px;
border-radius: 4px;
}
}
.photo-image {
width: 100%;
border-radius: 4px;
overflow: hidden;
}
}
}
.empty-state {
margin-top: 30px;
}
.history-popup {
height: 100%;
display: flex;
flex-direction: column;
.history-title {
font-size: 16px;
font-weight: bold;
color: #323643;
text-align: center;
padding: 15px 0;
border-bottom: 1px solid #E9E9EA;
}
.history-list {
flex: 1;
overflow-y: auto;
padding: 15px;
.history-item {
margin-bottom: 15px;
border-radius: 4px;
overflow: hidden;
.history-time {
font-size: 12px;
color: rgba(0, 0, 0, 0.5);
margin-top: 6px;
}
}
}
.history-empty {
flex: 1;
}
}
</style>
<style>
/* 确保图片预览及其遮罩层级高于底部弹框 */
.van-image-preview {
z-index: 10000000003 !important;
}
.van-overlay.van-image-preview__overlay {
z-index: 10000000002 !important;
}
</style>
+8 -73
View File
@@ -1,28 +1,6 @@
<template> <template>
<div class="wrap"> <div class="wrap">
<div class="navBar"> <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 <van-nav-bar
:border="false" :border="false"
:fixed="true" :fixed="true"
@@ -46,8 +24,7 @@
</template> </template>
</van-nav-bar> </van-nav-bar>
</div> </div>
--> <div class="statisticContainer">
<!-- <div class="statisticContainer">
<div class="statisticWrap"> <div class="statisticWrap">
<div class="line1"> <div class="line1">
<span>培训统计</span> <span>培训统计</span>
@@ -66,12 +43,11 @@
</div> </div>
</div> </div>
</div> </div>
</div>--> </div>
<div class="tab_wrap"> <div class="driver_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}"
@click="changeTab(index)"> @click="changeTab(index)">
{{ item.name }}<span v-if="item.count" :class="{'commonNum':true,'notFinish':index==2}">{{item.count}}</span> {{ item.name }}
<!-- {{ item.name }}-->
</div> </div>
</div> </div>
<div class="contentWrap" v-show="!showEmpty"> <div class="contentWrap" v-show="!showEmpty">
@@ -108,14 +84,12 @@ export default {
mixins:[myMixins], mixins:[myMixins],
data(){ data(){
return{ return{
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}],
// tabArr: [{name: '车型技术参数', status: 1}, {name: '中道服务规范', status:2},{name: '中道小课堂', status: 3}],
activeIndex: 0, activeIndex: 0,
pageList:[], pageList:[],
totalList:[], totalList:[],
numInfo:'', numInfo:'',
keyword:'', keyword:'',
serviceTypeName:'',
show:false, show:false,
showEmpty:false, showEmpty:false,
isLoading:false, isLoading:false,
@@ -128,7 +102,6 @@ export default {
this.driverId = urlParams.get('driverId'); this.driverId = urlParams.get('driverId');
this.time=timeFormat(Date.now()) this.time=timeFormat(Date.now())
this.keyword=urlParams.get('keyword') || '' this.keyword=urlParams.get('keyword') || ''
this.serviceTypeName=urlParams.get('serviceTypeName') || ''
this.getTrainingList(); this.getTrainingList();
document.addEventListener('visibilitychange', async ( ) => { document.addEventListener('visibilitychange', async ( ) => {
let state = document.visibilityState let state = document.visibilityState
@@ -140,10 +113,6 @@ export default {
}); });
}, },
methods:{ methods:{
initShow(){
this.keyword= ''
this.getTrainingList()
},
onRefresh() { onRefresh() {
this.getTrainingList() this.getTrainingList()
setTimeout(() => { setTimeout(() => {
@@ -160,42 +129,21 @@ export default {
type:1, type:1,
id:this.driverId, id:this.driverId,
text:this.keyword || '', text:this.keyword || '',
serviceTypeName:this.serviceTypeName || ''
}) })
this.totalList=[] this.totalList=[]
this.pageList=[] this.pageList=[]
this.tabArr[0].count=0
this.tabArr[1].count=0
this.tabArr[2].count=0
this.numInfo=res.data this.numInfo=res.data
if(res.data.totalNum){ this.totalList=res.data.list
this.tabArr[0].count=res.data.totalNum let result=[]
}
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){ if(this.activeIndex === 0){
result=this.totalList?.filter(q => q.title === '车型技术参数'); result=this.totalList?.filter(q => q.title === '车型技术参数');
}else if(this.activeIndex === 1){ }else if(this.activeIndex === 1){
result=this.totalList?.filter(q => q.title === '中道服务规范'); result=this.totalList?.filter(q => q.title === '中道服务规范');
}else if(this.activeIndex === 2){ }else if(this.activeIndex === 2){
result=this.totalList?.filter(q => q.title === '中道小课堂'); 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){ if(result){
this.pageList=result this.pageList=result[0].materials
this.time=timeFormat(Date.now()) this.time=timeFormat(Date.now())
} }
if(this.pageList?.length === 0){ if(this.pageList?.length === 0){
@@ -212,19 +160,6 @@ export default {
@import "@/styles/mixin.scss"; @import "@/styles/mixin.scss";
@import "@/styles/docment.scss"; @import "@/styles/docment.scss";
@import "@/styles/driverDocment.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{ .read{
padding: 2px 8px; padding: 2px 8px;
border-radius: 5px; border-radius: 5px;
+1 -4
View File
@@ -94,7 +94,6 @@ export default {
showEmpty:false, showEmpty:false,
supplierId:'', supplierId:'',
driverId:'', driverId:'',
serviceTypeName:'',
} }
}, },
mounted() { mounted() {
@@ -104,7 +103,6 @@ export default {
if(this.driverId){ if(this.driverId){
this.keyword=urlParams.get('keyword') || '' this.keyword=urlParams.get('keyword') || ''
} }
this.serviceTypeName=urlParams.get('serviceTypeName') || ''
this.getNormalList() this.getNormalList()
}, },
methods:{ methods:{
@@ -130,7 +128,6 @@ export default {
type:this.supplierId ? 2 : 1, type:this.supplierId ? 2 : 1,
id: this.supplierId ? this.supplierId : this.driverId, id: this.supplierId ? this.supplierId : this.driverId,
text:this.keyword || '', text:this.keyword || '',
serviceTypeName:this.serviceTypeName || ''
}) })
this.totalList=res.data.list this.totalList=res.data.list
let result=[] let result=[]
@@ -154,7 +151,7 @@ export default {
this.isLoading = false this.isLoading = false
}, },
initShow(){ initShow(){
this.keyword= '' this.keyword= '',
this.getNormalList() this.getNormalList()
} }
} }
+9 -46
View File
@@ -22,9 +22,9 @@
</van-nav-bar> </van-nav-bar>
</div> </div>
<div class="tab_wrap"> <div class="tab_wrap">
<div v-for="(item, index) in tabArr" :key="index" :class="{'active' : activeIndex == index,'active1':activeIndex==0}" <div v-for="(item, index) in tabArr" :key="index" :class="{'active' : activeIndex == index}"
@click="changeTab(index)"> @click="changeTab(index)">
{{ item.name }}<span v-if="item.count" :class="{'commonNum':true,'notFinish':index==2}">{{item.count}}</span> {{ item.name }}
</div> </div>
</div> </div>
<div class="contentWrap" v-show="!showEmpty"> <div class="contentWrap" v-show="!showEmpty">
@@ -61,30 +61,22 @@ export default {
mixins:[myMixins], mixins:[myMixins],
data(){ data(){
return{ return{
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, activeIndex: 0,
pageNum:1, pageNum:1,
pageSize:10, pageSize:10,
pageList:[], pageList:[],
totalList:[], totalList:[],
keyword:'', keyword:'',
serviceTypeName:'',
show:false, show:false,
showEmpty:false, showEmpty:false,
isLoading:false, isLoading:false,
supplierId:'', supplierId:'',
numInfo:'',
} }
}, },
activated() {
console.log('activated')
},
mounted() { mounted() {
console.log('mounted')
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
this.supplierId = urlParams.get('supplierId'); this.supplierId = urlParams.get('supplierId');
this.keyword=urlParams.get('keyword') || ''
this.serviceTypeName=urlParams.get('serviceTypeName') || ''
this.getTrainingList(); this.getTrainingList();
document.addEventListener('visibilitychange', async ( ) => { document.addEventListener('visibilitychange', async ( ) => {
let state = document.visibilityState let state = document.visibilityState
@@ -112,39 +104,20 @@ export default {
type:2, type:2,
id: this.supplierId, id: this.supplierId,
text:this.keyword || '', text:this.keyword || '',
serviceTypeName:this.serviceTypeName || ''
}) })
this.totalList=[] this.totalList=[]
this.pageList=[] this.pageList=[]
this.tabArr[0].count=0 this.totalList=res.data.list
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=[] let result=[]
if(this.activeIndex === 0){ if(this.activeIndex === 0){
result=this.totalList || [] result=this.totalList?.filter(q => q.title === '车型技术参数');
// result=this.totalList?.filter(q => q.title === '车型技术参数');
}else if(this.activeIndex === 1){ }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){ }else if(this.activeIndex === 2){
result=this.totalList?.filter(item => item.alreadyRead!=1) || [] result=this.totalList?.filter(q => q.title === '中道小课堂');
// result=this.totalList?.filter(q => q.title === '中道小课堂');
} }
if(result){ if(result){
this.pageList=result this.pageList=result[0].materials
} }
if(this.pageList?.length === 0){ if(this.pageList?.length === 0){
this.showEmpty = true this.showEmpty = true
@@ -153,7 +126,7 @@ export default {
} }
}, },
initShow(){ initShow(){
this.keyword= '' this.keyword= '',
this.getTrainingList() this.getTrainingList()
} }
} }
@@ -163,16 +136,6 @@ export default {
@import "@/styles/common.scss"; @import "@/styles/common.scss";
@import "@/styles/mixin.scss"; @import "@/styles/mixin.scss";
@import "@/styles/docment.scss"; @import "@/styles/docment.scss";
.tab_wrap {
.active:after {
width: 37px;
left: 0%;
transform: translateX(0%);
}
.active1:after {
width:48px !important;
}
}
.read{ .read{
padding: 2px 8px; padding: 2px 8px;
border-radius: 5px; border-radius: 5px;
@@ -1,118 +0,0 @@
<template>
<div class="wrap environment-photo-wrap">
<div v-if="loaded && photoList.length === 0" class="empty-tip">暂无环境照片</div>
<div v-for="(item, index) in photoList" :key="index" class="photo-item">
<div class="media-wrap">
<video
v-if="item.tag && item.tag.includes('video')"
:src="item.imageUrl"
controls
playsinline
class="media"
/>
<img
v-else-if="item.tag && item.tag.includes('img')"
:src="item.imageUrl"
class="media"
@click="previewImage(index)"
/>
</div>
<p v-if="item.title" class="photo-name">{{ item.title }}</p>
</div>
</div>
</template>
<script>
import { Toast, ImagePreview } from 'vant'
import { getBeforeDispatchImage } from '@/api/environmentPhoto'
export default {
name: 'EnvironmentPhotoList',
data() {
return {
photoList: [],
loaded: false
}
},
async mounted() {
await this.fetchPhotos()
},
methods: {
async fetchPhotos() {
const userOrderId = this.$route.query.userOrderId
if (!userOrderId) {
Toast('缺少必要参数')
this.loaded = true
return
}
Toast.loading({
message: '加载中...',
forbidClick: true,
duration: 0
})
try {
const res = await getBeforeDispatchImage({ userOrderId: Number(userOrderId) })
this.photoList = Array.isArray(res.data) ? res.data : []
} finally {
Toast.clear()
this.loaded = true
}
},
previewImage(index) {
const urls = this.photoList
.filter(item => item.tag && item.tag.includes('img'))
.map(item => item.imageUrl)
ImagePreview({ images: urls, startPosition: index })
}
}
}
</script>
<style scoped>.wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #F4F5F7;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.environment-photo-wrap {
padding: 12px;
}
.empty-tip {
text-align: center;
color: #999;
margin-top: 60px;
font-size: 14px;
}
.photo-item {
background: #fff;
border-radius: 6px;
margin-bottom: 12px;
overflow: hidden;
}
.media-wrap {
position: relative;
width: 100%;
padding-top: 56.25%;
}
.photo-item img,
.photo-item video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.photo-name {
font-size: 13px;
color: #333;
padding: 8px 10px;
margin: 0;
}
</style>
-136
View File
@@ -1,136 +0,0 @@
<template>
<div class="wrap">
<div class="content">
<div class="goToBtn" @click="goAppHandler">立即打开</div>
<div class="content_tip">
<div class="font_cls">如未安装请点击 <span @click="downLoadHandler">立即下载</span></div>
</div>
</div>
<!-- 微信内置浏览器引导 -->
<div v-if="showGuide" class="guide-mask" @click.self="closeGuide">
<img class="guide-img" src="@/assets/sucai.png" alt="引导在浏览器打开" />
<div class="guide-btn" @click="closeGuide">我知道了</div>
</div>
</div>
</template>
<script>
import { getNewVersion, getLog } from "@/api/order"
export default {
name: "goToApp",
data() {
return {
showGuide: false
}
},
mounted() {
getLog({
source: 'App下载页',
event: '页面浏览',
page: '/h5/supplier/dispatch/goToApp'
})
this.showGuide = this.isWeixin()
},
methods: {
isWeixin() {
const ua = navigator.userAgent.toLowerCase()
return ua.indexOf('micromessenger') !== -1
},
closeGuide() {
this.showGuide = false
},
goAppHandler() {
getLog({
source: 'App下载页',
event: '直接打开',
page: '/h5/supplier/dispatch/goToApp'
})
window.location.href = "rvdriver://page/pagesLogin/phoneLogin?source=h5Link";
},
async downLoadHandler() {
getLog({
source: 'App下载页',
event: '立即下载',
page: '/h5/supplier/dispatch/goToApp'
});
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;
}
}
}
.guide-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
background: rgba(0, 0, 0, 0.85);
}
.guide-img {
width: 90%;
margin-top: 0;
object-fit: contain;
}
.guide-btn {
width: 140px;
height: 40px;
margin-top: 40px;
border: 1px solid #FFFFFF;
border-radius: 20px;
font-size: 15px;
color: #FFFFFF;
line-height: 40px;
text-align: center;
}
</style>
-183
View File
@@ -1,183 +0,0 @@
<template>
<div class="wrap">
<div class="top-wrap"></div>
<div class="center-info">
<div class="wei_code_wrap">
<div class="wei_title">请先添加企业微信保证申请流程正常进行</div>
<div class="wei_code_bg">
<img v-if="qrCodeUrl" :src="qrCodeUrl" alt="企业微信二维码" />
</div>
</div>
</div>
<div class="btn_wrap">
<div class="btn" @click="nextStep">下一步</div>
</div>
</div>
</template>
<script>
import QRCode from 'qrcode'
import {getOrCodeInfo, getContactQrCodeResult, saveSupplier} from "@/api/mine"
import {myMixins} from "@/utils/myMixins"
import {Toast, Dialog} from "vant";
export default {
name: "addWechat",
mixins: [myMixins],
data() {
return {
name: '',
potentialFlag: '',
formPayload: null,
qrCodeUrl: '',
qrCode: '',
configId: '',
wechatId: '',
clickFlag: true,
}
},
async mounted() {
const urlParams = new URLSearchParams(window.location.search);
this.potentialFlag = this.$route.query.potentialFlag || urlParams.get('potentialFlag') || '';
const cached = localStorage.getItem('supplierAddForm');
if (cached) {
this.formPayload = JSON.parse(cached);
this.wechatId = this.formPayload.wechatId;
this.name = this.formPayload.name;
}
if (this.name) {
await this.QrCodeHandler();
}
},
methods: {
async QrCodeHandler() {
await this.QrCodeInfo();
await this.getQrCode();
},
async QrCodeInfo() {
let res = await getOrCodeInfo({ name: this.name });
this.configId = res?.configId;
this.qrCode = res?.qrCode;
},
async getQrCode() {
try {
this.qrCodeUrl = await QRCode.toDataURL(this.qrCode, {
width: 150,
margin: 2,
color: { dark: '#000000', light: '#FFFFFF' },
});
} catch (error) {
console.error('生成二维码失败:', error);
}
},
async nextStep() {
if (!this.clickFlag) return;
if(!this.wechatId) {
let res = await getContactQrCodeResult({ configId: this.configId, name: this.name });
this.wechatId = res?.data?.wechatId;
}
/*if (!this.wechatId) {
Toast('请先添加企微再继续');
return;
}*/
try {
this.clickFlag = false;
const res = await saveSupplier({
...this.formPayload,
wechatId: this.wechatId,
});
if (res?.noToast) {
if (res?.code != 0) {
Dialog.alert({ title: '提示', message: '该服务商已存在,查看信息' }).then(() => {
localStorage.removeItem('supplierAddForm');
const target = { name: 'supplierInfo', query: { id: res?.code, potentialFlag: this.potentialFlag } };
localStorage.setItem('lastRoute', JSON.stringify(target));
this.goPage('supplierInfo', { id: res?.code, potentialFlag: this.potentialFlag });
});
} else {
Toast('该服务商已存在');
}
} else {
localStorage.removeItem('supplierAddForm');
const supplierId = res?.data;
const potentialFlag = this.potentialFlag;
const target = { name: 'supplierAddResult', query: { id: supplierId, potentialFlag } };
localStorage.setItem('lastRoute', JSON.stringify(target));
Dialog.confirm({
title: '提示',
message: '添加车辆和驾驶员会加快审核进度,是否去添加?',
confirmButtonText: '去添加',
cancelButtonText: '取消',
}).then(() => {
localStorage.setItem('lastRoute', JSON.stringify({ name: 'vehicleManage', query: { id: supplierId, potentialFlag } }));
this.goPage('vehicleManage', { id: supplierId, potentialFlag });
}).catch(() => {
localStorage.setItem('lastRoute', JSON.stringify({ name: 'supplierAddResult', query: { id: supplierId, potentialFlag } }));
this.goPage('supplierAddResult', { id: supplierId, potentialFlag });
});
}
} catch (e) {
Toast(e?.message || '提交失败');
} finally {
setTimeout(() => { this.clickFlag = true; }, 1000);
}
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.wrap {
@include wh(100%, 100%);
box-sizing: border-box;
padding-bottom: 100px;
overflow-y: auto;
background: linear-gradient(180deg, #FBFDFE 0%, #F6FBFF 73%, #F0F7FF 100%);
backdrop-filter: blur(5.602678571428572px);
}
.top-wrap {
width: 100%;
height: 175px;
background: linear-gradient(180deg, #DAECFF 0%, rgba(233, 243, 255, 0) 100%);
}
.center-info {
text-align: center;
}
.wei_code_wrap {
margin-top: 10px;
.wei_title {
font-size: 15px;
color: #FF553B;
text-align: center;
}
.wei_code_bg {
width: 163px;
height: 173px;
background-image: url("~@/assets/supplier/weiCodeBg.png");
background-size: 100% 100%;
margin: 8px auto;
display: flex;
align-items: center;
justify-content: center;
}
}
.btn_wrap {
width: 100%;
padding: 15px 0;
position: fixed;
bottom: 10px;
z-index: 10000 !important;
.btn {
width: calc(100% - 80px);
margin-left: 40px;
height: 46px;
background: #0E76F4;
border-radius: 5px;
font-size: 15px;
color: #FFFFFF;
text-align: center;
line-height: 46px;
}
}
</style>
-47
View File
@@ -1,47 +0,0 @@
<template>
<!-- 独立拍摄页 vehicleAdd 跳转进入query.type = front/back/car
确认/取消/降级后 $router.back() 返回 vehicleAdd -->
<camera-guide
:visible="show"
:type="type"
@confirm="onConfirm"
@cancel="goBack"
@fallback="onFallback"
/>
</template>
<script>
import CameraGuide from "@/components/CameraGuide.vue";
export default {
name: 'cameraGuidePage',
components: { CameraGuide },
data() {
return {
show: false // 挂载后置 true,触发组件 visible 侦听器启动相机
}
},
computed: {
type() {
const t = this.$route.query.type
return ['front', 'back', 'car'].includes(t) ? t : 'front'
}
},
mounted() {
this.show = true
},
methods: {
onConfirm({ blob, dataURL }) { // 拍照确认:结果存入 store,返回 vehicleAdd 消费
this.$store.commit('camera/setResult', { type: this.type, blob, dataURL })
this.$router.back()
},
onFallback(e) { // 相机不可用:降级信息存入 store,返回 vehicleAdd 走系统选择器
this.$store.commit('camera/setFallback', { type: this.type, error: e })
this.$router.back()
},
goBack() {
this.$router.back()
}
}
}
</script>
-494
View File
@@ -1,494 +0,0 @@
<template>
<div class="wrap vehicleAdd">
<div class="navBar">
<van-nav-bar
title="续保更新"
left-arrow
left-arrow-color="#FFFFFF"
:border="false"
:fixed="true"
:safe-area-inset-top="true"
@click-left="back"
/>
</div>
<div class="addContentWrap">
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>有无投保救援职业责任险或货运险</span>
</div>
<div class="isJoin">
<van-radio-group v-model="hasLiabilityInsurance" @change="isChange" class="joinWrap">
<van-radio :name="1" style="margin-right: 26px">
<img
slot="icon"
slot-scope="props"
:src="props.checked ? activeIcon : inactiveIcon"
>
</van-radio>
<van-radio :name="0">
<img
slot="icon"
slot-scope="props"
:src="props.checked ? activeIcon : inactiveIcon"
>
</van-radio>
</van-radio-group>
</div>
</div>
<span style="color: red">如有投保未投保选不必上传交强险或者商业险等其他保单</span>
<div class="lineBot"></div>
<template v-if="hasLiabilityInsurance == 1">
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span style="line-height: 20px">请上传救援职业责任险或货运险保单PDF或者关键信息页截图</span>
</div>
<van-uploader
v-model="insurancePictureFiles"
:after-read="insurancePictureFilesHandler"
max-count="1"
:preview-size="54"
accept="image "
/>
</div>
<div class="lineBot"></div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>承保保司</span>
</div>
<el-select
v-model="insuranceCorp"
value-key="name"
class="elSelect"
collapse-tags="collapse-tags"
placeholder="请选择" style="width: 55%"
>
<el-option
v-for="item in insuranceOptions"
:key="item.name"
:label="item.name"
:value="item.value"
>
</el-option>
</el-select>
</div>
<div class="lineBot"></div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>保险有效期</span>
</div>
<el-input style="width: 55%" readonly @focus="showDatePicker = true" v-model="dateVal" placeholder="请选择发布日期" :class="{'customSel':true,'customInput':true , 'has-value': dateVal }" >
<template #suffix>
<i
v-if="dateVal"
class="el-icon-circle-close el-input__icon"
@click="dateVal = ''"
></i>
</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"
:max-date="maxDate" type="range" @confirm="onConfirm" />
</div>
</div>
</template>
<script>
import {Dialog} from "vant";
import {myMixins} from "@/utils/myMixins"
import {formatDate1} from "@/utils/common"
import { uploadImage, updateInsurance, getInfoById,userOperationPermissions} from "@/api/mine"
import CommonBtn from "@/components/commonBtn.vue"
export default {
name: "vehicleAdd",
mixins:[myMixins],
data(){
return{
defaultProps: {
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,
dateVal: '',
hasLiabilityInsurance: 1, // 有无责任险
activeIcon: require('@/assets/check.png'),
inactiveIcon: require('@/assets/uncheck.png'),
// result: [],
checked: true,
show:false,
imageUrl: require('@/assets/arr_right.png'),
insurancePictureFiles: [],
insurancePicturePhoto: '', // 保单照片
isMultiple: false, // 是否支持多选
insuranceCorp: '',
liabilityInsuranceAmount:'',
liabilityInsuranceQuota:'',
permissonList:[],
insuranceOptions: [{
name: '太平洋',
value: 1
}, {
name: '人保',
value: 2
}, {
name: '平安',
value: 3
}, {
name: '中华联合',
value: 4
}, {
name: '大地',
value: 5
}, {
name: '阳光',
value: 6
}, {
name: '太平',
value: 7
}, {
name: '人寿',
value: 8
}, {
name: '永安',
value: 9
}, {
name: '华安',
value: 10
}, {
name: '申能',
value: 11
}, {
name: '大家',
value: 12
}, {
name: '富德',
value: 13
}, {
name: '其他',
value: 14
}]
}
},
computed: {
},
watch:{
},
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
})
let result=res.data;
this.id=result.vehicleId
this.insurancePicturePhoto = result.insurancePicturePhoto;
this.hasLiabilityInsurance = result.hasLiabilityInsurance
this.insuranceCorp = result.insuranceCorp;
if(this.insurancePicturePhoto) {
this.insurancePictureFiles = [{url: this.insurancePicturePhoto}]
}
if( result.liabilityInsuranceEndTime && result.liabilityInsuranceStartTime ) {
this.dateVal = formatDate1(result.liabilityInsuranceStartTime) + ' - ' + formatDate1(result.liabilityInsuranceEndTime)
}
},
formatDate(date) {
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
},
onConfirm(date) {
const [start, end] = date;
this.showDatePicker = false;
this.dateVal = `${this.formatDate(start)} - ${this.formatDate(end)}`;
},
formatDateTimeRange(str) {
const [startStr, endStr] = str.split(' - ').map(s => s.trim());
const startDate = new Date(startStr);
const startTime = `${startDate.getFullYear()}-${(startDate.getMonth() + 1).toString().padStart(2, '0')}-${startDate.getDate().toString().padStart(2, '0')} 00:00:00`;
const endDate = new Date(endStr);
const endTime = `${endDate.getFullYear()}-${(endDate.getMonth() + 1).toString().padStart(2, '0')}-${endDate.getDate().toString().padStart(2, '0')} 23:59:59`;
return {
startTime,
endTime
};
},
async insurancePictureFilesHandler(file) {
const formData = new FormData();
formData.append("file" , file.file);
let res = await uploadImage(formData);
this.insurancePicturePhoto = res.data;
},
isChange(e){
this.hasLiabilityInsurance=e
if (e === 0) {
Dialog.confirm({
title: '提醒',
message: '有救援职业责任险车辆会提升案件聚合能力和接单能力。',
confirmButtonText: '我已知晓', // 确认按钮文字,默认为“确认”
showCancelButton: false // 是否显示取消按钮,默认为 true。如果不需要取消按钮,可以设置为 false。
})
.then(() => { /* 确认后的操作 */ })
.catch(() => { /* 取消后的操作 */ });
}
},
async submitBtn(){
if(this.hasLiabilityInsurance === null || this.hasLiabilityInsurance === undefined || this.hasLiabilityInsurance === '') { // 有职业责任险,就需要有保单照片
this.$toast('救援职业责任险不能为空')
return
}
if(!this.insurancePicturePhoto && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('保单照片不能为空')
return
}
if(!this.insuranceCorp && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('承保保司不能为空')
return
}
if(!this.dateVal && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
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)
}
let params = {
id: this.id ? this.id : '',
renewInsureInfo: JSON.stringify({
hasLiabilityInsurance: this.hasLiabilityInsurance,
insurancePictureUrls: this.hasLiabilityInsurance == 1 ? this.insurancePicturePhoto : '',
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)
if(this.id){
this.$toast('修改成功')
}else{
this.$toast('添加成功')
}
setTimeout(()=>{
// this.$router.back();
this.back()
},2000)
}
},
components:{
CommonBtn
}
}
</script>
<style>
.vehicleAdd .van-field__control:disabled {
color: #323643 !important;
-webkit-text-fill-color: #323643 !important;
}
</style>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.mySelect{
@include fontWeightSize(bold,13px);
color: #323643;
appearance: none;
padding-right: 16px;
background: url('@/assets/arrow_bot.png') no-repeat right center / 15px 14px;
}
.checkbox-group {
display: none;
}
.navBar{
margin-bottom: 46px;
}
.wrap{
@include wh(100%,100%);
overflow-y: auto;
}
.addContentWrap{
margin-left: 16px;
.item_content_btn {
padding-right: 25px;
text-align: right;
span {
display: inline-block;
background: #354D93;
padding: 5px 10px;
font-size: 12px;
font-weight: bold;
color: #fff;
border-radius: 4px;
margin-bottom: 5px;
}
}
.itemContent{
@include flexColBet;
line-height: 56px;
box-sizing: border-box;
padding-right: 25px;
.titleType{
@include fontWeightSize(bold,14px);
color: #323643;
}
.isJoin{
display: flex;
.joinWrap{
display: flex;
}
img{
@include widHeiMar(16px,16px,6px)
}
}
.startImg{
@include widHeiMar(6px,6px,3px);
vertical-align: super;
}
input{
border: none;
text-align: right;
@include fontWeightSize(bold,13px);
opacity: .5;
}
}
.lineBot{
@include wh(100%,1px);
background: #E9E9EA;
opacity: 0.6;
}
.serviceType{
@include fontWeightSize(bold,13px);
margin-left: 15px;
.trailerService{
margin-bottom: 10px;
}
.serviceline{
@include flexCenter;
margin-bottom: 10px;
}
.item{
margin: 20px 0;
@include flexCenter;
}
.radioWrap{
margin-left: 10px;
}
.service{
@include colorOpa(#323643,0.7);
line-height: 18px;
white-space: nowrap;
}
.line{
display: inline-block;
@include wh(240px,2px);
opacity: 0.16;
border-bottom: 1px solid;
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;
}
img{
@include widHeiMar(16px,16px,12px)
}
}
.btn{
width: 90%;
margin: 40px 0 30px 8px;
}
}
.elSelect ::v-deep .el-input__inner{
border: none !important;
text-align: right !important;
}
.disabled-tree {
pointer-events: none;
opacity: 0.6;
}
</style>
+8 -27
View File
@@ -67,7 +67,7 @@
accept="image " accept="image "
/> />
</div> </div>
<!-- <div class="lineBot"></div> <div class="lineBot"></div>
<div class="itemContent"> <div class="itemContent">
<div class="titleType"> <div class="titleType">
<img class="startImg" src="@/assets/start.png" /> <img class="startImg" src="@/assets/start.png" />
@@ -80,12 +80,11 @@
:preview-size="54" :preview-size="54"
accept="image " accept="image "
/> />
</div>--> </div>
<div class="lineBot"></div> <div class="lineBot"></div>
<div class="itemContent"> <div class="itemContent">
<div class="titleType"> <div class="titleType">
<img class="startImg" src="@/assets/start.png" /> <span>头像</span>
<span>免冠正面照</span>
</div> </div>
<van-uploader <van-uploader
v-model="iconList" v-model="iconList"
@@ -122,7 +121,7 @@
<span>司机状态</span> <span>司机状态</span>
</div> </div>
<div class="isJoin"> <div class="isJoin">
<van-radio-group v-model="states" @change="isUse" :disabled="statesDisabled" :class="{ 'disabled-tree': statesDisabled }" class="joinWrap"> <van-radio-group v-model="states" @change="isUse" class="joinWrap">
<van-radio :name="1" style="margin-right: 26px" class="item"> <van-radio :name="1" style="margin-right: 26px" class="item">
启用 启用
<img <img
@@ -163,7 +162,6 @@ export default {
// baseURL:'', // baseURL:'',
// licenseOcrInfo:'', // licenseOcrInfo:'',
id:'', id:'',
supplierId: '',
driverName:"", driverName:"",
driverPhone:"", driverPhone:"",
identityCardNumber:"", identityCardNumber:"",
@@ -185,7 +183,6 @@ export default {
iconList: [], iconList: [],
driverInfo: {}, driverInfo: {},
drivingLicenceName: '', drivingLicenceName: '',
potentialFlag: '',
idCardAuthority:'', idCardAuthority:'',
idCardValidStartTime:'', idCardValidStartTime:'',
idCardValidEndTime:'', idCardValidEndTime:'',
@@ -206,7 +203,6 @@ export default {
let token=localStorage.getItem('token') let token=localStorage.getItem('token')
this.authorization.Authorization=token;*/ this.authorization.Authorization=token;*/
this.id = this.$route.params?.id; this.id = this.$route.params?.id;
this.supplierId = this.$route.params?.supplierId || '';
this.driverName = this.$route.params?.name; this.driverName = this.$route.params?.name;
this.driverPhone = this.$route.params?.phone; this.driverPhone = this.$route.params?.phone;
this.drivingModel = this.$route.params?.drivingModel; this.drivingModel = this.$route.params?.drivingModel;
@@ -223,11 +219,6 @@ export default {
this.identityCardFront = this.$route.params?.identityCardFront; this.identityCardFront = this.$route.params?.identityCardFront;
this.driverInfo.lssueDate = this.$route.params?.lssueDate; this.driverInfo.lssueDate = this.$route.params?.lssueDate;
this.drivingLicenceName = this.$route.params?.drivingLicenceName; this.drivingLicenceName = this.$route.params?.drivingLicenceName;
this.potentialFlag = this.$route.params?.potentialFlag || '';
console.log('this.potentialFlagthis.potentialFlag', this.potentialFlag)
if(!this.id && this.potentialFlag === '1') {
this.states = 0;
}
if( this.identityCardFront ) { if( this.identityCardFront ) {
this.identityCardFrontList = [{ url : this.identityCardFront }]; this.identityCardFrontList = [{ url : this.identityCardFront }];
} }
@@ -248,7 +239,7 @@ export default {
const selectedValue = selectElement.value; const selectedValue = selectElement.value;
this.drivingModel=selectedValue this.drivingModel=selectedValue
}); });
console.log('statesDisabled', this.statesDisabled)
}, },
methods:{ methods:{
isUse(e){ isUse(e){
@@ -316,7 +307,7 @@ export default {
let res = await uploadImage(formData) let res = await uploadImage(formData)
this.drivingLicenceContrary = res.data; this.drivingLicenceContrary = res.data;
}, },
async iconListHandler(file) { // 上传免冠正面照 async iconListHandler(file) { // 上传头像
const formData = new FormData(); const formData = new FormData();
formData.append("file" , file.file); formData.append("file" , file.file);
let res = await uploadImage(formData) let res = await uploadImage(formData)
@@ -408,14 +399,10 @@ export default {
this.$toast('驾驶证首页未上传') this.$toast('驾驶证首页未上传')
return return
} }
/* if(!this.drivingLicenceContrary) { if(!this.drivingLicenceContrary) {
this.$toast('驾驶证副页未上传') this.$toast('驾驶证副页未上传')
return return
}*/ }
if(!this.icon) {
this.$toast('免冠正面照未上传')
return
}
let drivingLicenceName=this.drivingLicenceName.replace(/[^\u4e00-\u9fff]+/g, ''); let drivingLicenceName=this.drivingLicenceName.replace(/[^\u4e00-\u9fff]+/g, '');
let driverName=this.driverName.replace(/[^\u4e00-\u9fff]+/g, ''); let driverName=this.driverName.replace(/[^\u4e00-\u9fff]+/g, '');
@@ -434,7 +421,6 @@ export default {
} }
await saveDriver({ await saveDriver({
driverId:this.id ? this.id : '', driverId:this.id ? this.id : '',
supplierId: this.supplierId || undefined,
driverName:this.driverName , driverName:this.driverName ,
driverPhone:this.driverPhone, driverPhone:this.driverPhone,
identityCardNumber:this.identityCardNumber, identityCardNumber:this.identityCardNumber,
@@ -462,11 +448,6 @@ export default {
},2000) },2000)
}, },
}, },
computed: {
statesDisabled() {
return !this.id && this.potentialFlag === '1';
}
},
components:{ components:{
TwoCommonBtn, TwoCommonBtn,
CellGroup CellGroup
+55 -355
View File
@@ -3,14 +3,14 @@
<div class="navBar"> <div class="navBar">
<van-nav-bar <van-nav-bar
title="司机管理" title="司机管理"
:left-arrow="!supplierId" left-arrow
left-arrow-color="#FFFFFF" left-arrow-color="#FFFFFF"
:border="false" :border="false"
:fixed="true" :fixed="true"
:safe-area-inset-top="true" :safe-area-inset-top="true"
@click-left="goBack" @click-left="goBack"
> >
<template slot="right" v-if="permissonList.includes('driverAddBtn') || supplierId"> <template slot="right" v-if="permissonList.includes('driverAddBtn')">
<div class="rightWrap" @click="addDriver" > <div class="rightWrap" @click="addDriver" >
<img src="@/assets/addImg.png" /> <img src="@/assets/addImg.png" />
<span class="addTxt">添加</span> <span class="addTxt">添加</span>
@@ -18,112 +18,51 @@
</template> </template>
</van-nav-bar> </van-nav-bar>
</div> </div>
<van-search <van-pull-refresh v-model="isLoading" @refresh="onRefresh">
v-model="searchVal" <van-list
show-action v-model="loading"
placeholder="请输入司机姓名、电话、身份证号" :finished="finished"
@search="resetHandler" finished-text="没有更多了"
> @load="onLoad"
<template #action> >
<div @click="resetHandler">搜索</div> <div class="itemWrap" v-for="(item,index) in driverList" :key="index">
</template> <div class="name-status">
</van-search> <div class="namephone">{{ item.driverName }} / {{ item.driverPhone }}</div>
<div class="filterWrap"> <div class="twoBtn">
<el-select @change="resetHandler" multiple :collapse-tags="true" v-model="idCardStatusList" placeholder="身份证录入状态" :class="{'customSel':true , 'has-value': idCardStatusList }" clearable> <button v-if="permissonList.includes('driverModifyBtn')" class="del" @click="handleStatus(item)">{{ item.states?.label == '启用' ? '停用' : '启用' }}</button>
<el-option <button v-if="permissonList.includes('driverModifyBtn')" class="revise" @click="updateDriver(item)">修改</button>
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="!isMiniProgram(item) && 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="!isMiniProgram(item) && 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="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="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="!isMiniProgram(item) && 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>
</van-list> <div class="" style="display: flex">
</van-pull-refresh> <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>
<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>
</div>
<div class="btn_wrap" v-if="supplierId">
<div class="btn" @click="nextToResult">下一步</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import {myMixins} from "@/utils/myMixins" 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 { export default {
name: "driverManage", name: "driverManage",
mixins:[myMixins], mixins:[myMixins],
@@ -134,108 +73,24 @@ export default {
total:'', total:'',
driverList:[], driverList:[],
states:'', states:'',
supplierId: '',
routeId: '',
initialized: false,
potentialFlag: '',
supplierType:'', supplierType:'',
count: 0, count: 0,
isLoading: false, isLoading: false,
loading: false, loading: false,
finished: false, finished: false,
permissonList:[], 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() { mounted() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
this.supplierType = urlParams.get('supplierType'); this.supplierType = urlParams.get('supplierType');
const rawId = this.$route.query.id || urlParams.get('id'); this.getPermissions();
this.supplierId = rawId ? Number(rawId) : '';
const potentialId = this.$route.query.potentialId || urlParams.get('potentialId');
this.routeId = potentialId;
this.potentialFlag = this.$route.query.potentialFlag || urlParams.get('potentialFlag') || '';
if(!rawId) {
this.getPermissions();
}
// this.getDriverList() // this.getDriverList()
}, },
methods:{ methods:{
nextToResult() {
localStorage.setItem('lastRoute', JSON.stringify({ name: 'supplierAddResult', query: { id: this.routeId, potentialFlag: this.potentialFlag } }));
this.goPage('supplierAddResult', { id: this.routeId, potentialFlag: this.potentialFlag });
},
isMiniProgram(item) {
return item.registerFrom === '小程序' || (item.jobNumber && item.jobNumber.includes('小程序'))
},
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(){ async onLoad(){
await this.getDriverList() await this.getDriverList()
if (this.total>10){ this.pageNum++;
this.pageNum++;
}
// 加载状态结束 // 加载状态结束
this.loading = false; this.loading = false;
// 数据全部加载完成 // 数据全部加载完成
@@ -257,19 +112,13 @@ export default {
}else{ }else{
this.$router.push({ this.$router.push({
name: 'driverAdd', name: 'driverAdd',
params: { supplierId: this.supplierId || undefined, potentialFlag: this.potentialFlag || undefined }
}) })
} }
}, },
async getDriverList(){ async getDriverList(){
let res = await driverList({ let res = await driverList({
pageNum:this.pageNum, pageNum:this.pageNum,
pageSize:this.pageSize, pageSize:this.pageSize
searchVal: this.searchVal,
idCardStatusList: this.idCardStatusList,
driverLicenseStatusList: this.driverLicenseStatusList,
authStatusList: this.authStatusList,
supplierId: this.supplierId || undefined,
}); });
if(res.code == 200){ if(res.code == 200){
this.total=res.total this.total=res.total
@@ -281,11 +130,12 @@ export default {
this.driverList = preList.concat(arr) this.driverList = preList.concat(arr)
} }
} }
console.log('this.driverList',this.driverList)
}, },
async getPermissions(){ async getPermissions(){
let res = await userOperationPermissions(); let res = await userOperationPermissions();
this.permissonList = res.data this.permissonList = res.data
// console.log("司机管理",this.permissonList.includes('driverAddBtn'))
}, },
async handleStatus(item){ async handleStatus(item){
if(item.states.code === 0){ if(item.states.code === 0){
@@ -293,23 +143,14 @@ export default {
}else{ }else{
this.states = 0 this.states = 0
} }
Dialog.confirm({ await enableAction({
message: '当前状态为启用,是否要改为停用?', driverId:item.driverId,
}).then(async () => { states:this.states
await enableAction({ })
driverId:item.driverId, this.pageNum = 1;
states:this.states await this.getDriverList();
})
this.pageNum = 1;
await this.getDriverList();
}).catch(() => {
});
}, },
updateDriver(item){ updateDriver(item){
if(!this.permissonList.includes('driverModifyBtn')){//有权限才能修改
return
}
// 在当前组件中进行路由跳转并传递参数对象 // 在当前组件中进行路由跳转并传递参数对象
this.$router.push({ this.$router.push({
name: 'driverAdd', // 目标路由的名称 name: 'driverAdd', // 目标路由的名称
@@ -344,7 +185,7 @@ export default {
.wrap { .wrap {
background: #F4F5F7; background: #F4F5F7;
//@include sizingPadding(13px,13px); @include sizingPadding(13px,13px);
@include wh(100%, 100%); @include wh(100%, 100%);
overflow-y: auto; overflow-y: auto;
} }
@@ -367,7 +208,7 @@ export default {
} }
.itemWrap { .itemWrap {
@include wh(100%, 140px); @include wh(100%, 104px);
@include radiusSizing(6px); @include radiusSizing(6px);
@include fontWeightSize(400, 12px); @include fontWeightSize(400, 12px);
@include flexBetween; @include flexBetween;
@@ -375,7 +216,6 @@ export default {
box-shadow: 0px 2px 10px 0px rgba(216, 216, 216, 0.5); box-shadow: 0px 2px 10px 0px rgba(216, 216, 216, 0.5);
margin-bottom: 10px; margin-bottom: 10px;
padding: 11px 13px 9px 15px; padding: 11px 13px 9px 15px;
position: relative;
.halfOpci { .halfOpci {
opacity: .5; opacity: .5;
margin-right: 5px; margin-right: 5px;
@@ -384,15 +224,15 @@ export default {
opacity: 1; opacity: 1;
} }
.statusNo { .statusNo {
color: red; color: #FF0000;
} }
.statusYes { .statusYes {
color: green color: #09B820
} }
} }
.name-status { .name-status {
@include flexBetCen; @include flexColBet;
.namephone { .namephone {
@include fontWeightSize(bold, 14px) @include fontWeightSize(bold, 14px)
} }
@@ -416,146 +256,6 @@ export default {
margin-left: 15px; 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;
}
.btn_wrap {
width: 100%;
padding: 15px 0;
position: fixed;
bottom: 10px;
z-index: 10000;
.btn {
width: calc(100% - 80px);
margin-left: 40px;
height: 46px;
background: #0E76F4;
border-radius: 5px;
font-size: 15px;
color: #FFFFFF;
text-align: center;
line-height: 46px;
}
} }
</style> </style>
+2 -9
View File
@@ -153,17 +153,12 @@ export default {
if (this.notifyList.length >= this.total) { if (this.notifyList.length >= this.total) {
this.finished = true; this.finished = true;
} }
} else {
//
this.loading = false;
this.finished = true;
} }
}, },
async changeTabHandler() { async changeTabHandler() {
this.pageNum = 1; this.pageNum = 1;
this.notifyList = []; this.notifyList = [];
this.checkList = []; this.checkList = [];
this.finished = false;
if (this.active == 1) { // if (this.active == 1) { //
this.status = ''; this.status = '';
this.dispatchAppSearchInvoiced = ''; this.dispatchAppSearchInvoiced = '';
@@ -180,7 +175,7 @@ export default {
confirmButtonColor: '#0E76F4', confirmButtonColor: '#0E76F4',
showCancelButton: false, showCancelButton: false,
}).then(async() => { }).then(async() => {
this.goPage('newTrainingList', { supplierId : this.supplierId, type: 'invoice' }) this.goPage('newTrainingList', { supplierId : this.supplierId })
}).catch(() => { }).catch(() => {
// on cancel // on cancel
}); });
@@ -194,9 +189,7 @@ export default {
this.getNotifyList() this.getNotifyList()
}, },
async getJumpHandler() { async getJumpHandler() {
let res = await jumpPage({ let res = await jumpPage();
type: 'invoice'
});
return res return res
}, },
getMonth(item) { getMonth(item) {
-713
View File
@@ -1,713 +0,0 @@
<template>
<div class="wrap">
<div class="error_bar" v-if="errorMsg">{{ errorMsg }}</div>
<div class="top_banner">
<img src="@/assets/supplier/topBg.png" alt="">
</div>
<van-form @submit="applyAdd">
<div class="content_wrap">
<div class="credentials_info">
<img class="title1" src="@/assets/supplier/title1.png" alt="">
<div class="credentials_wrap">
<div class="credentials_item">
<div class="credentials_title">1.法人身份证人像页</div>
<van-uploader
accept="image/*"
v-model="idFrontPhotoList"
:after-read="idFrontPhotoHandler"
:max-size="5 * 1024 * 1024"
max-count="1">
<div class="custom-background">
<img src="@/assets/supplier/idCardPerson.png" alt="">
</div>
</van-uploader>
</div>
<div class="credentials_item ml2">
<div class="credentials_title">2.法人身份证国徽页</div>
<van-uploader
accept="image/*"
v-model="idBackPhotoList"
:after-read="idBackPhotoHandler"
:max-size="5 * 1024 * 1024"
max-count="1">
<div class="custom-background">
<img src="@/assets/supplier/idCardBack.png" alt="">
</div>
</van-uploader>
</div>
<div class="credentials_item ml2">
<div class="credentials_title">3.营业执照</div>
<van-uploader
accept="image/*"
v-model="companyPhotoList"
:after-read="companyPhotoHandler"
:max-size="5 * 1024 * 1024"
max-count="1">
<div class="custom-background">
<img src="@/assets/supplier/companyPhoto.png" alt="">
</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">
<img class="title2" src="@/assets/supplier/title2.png" alt="">
<div class="company_wrap">
<van-field :border="true" readonly v-model="form.name" class="required" name="name" label="服务商名称" placeholder="请输入" :rules="[{ required: true, message: '请输入服务商名称' }]" />
<van-field :border="true" readonly v-model="form.areaName" class="required" name="areaName" label="注册地址" placeholder="请输入" :rules="[{ required: true, message: '请输入注册地址' }]" />
<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
show-checkbox
node-key="id"
ref="tree"
:highlight-current="true"
:expand-on-click-node="false"
:data="bigServiceList"
:props="defaultProps">
</el-tree>
</template>
</van-field>
<van-field :border="true" v-model="form.trailCount" type="digit" class="required" name="trailCount" label="拖车数量" placeholder="请输入" :rules="countVerify" />
<van-field :border="true" v-model="form.minorCount" type="digit" class="required" name="minorCount" label="抢修车数量" placeholder="请输入" :rules="countVerify" />
<van-field :border="true" class="required" label="服务区域" placeholder="请选择">
<template #input>
<el-cascader
v-model="form.serviceAreaCode"
:options="areaList"
:props="areaProps"
ref="areaCascader"
popper-class="responsive-cascader"
:popper-append-to-body="false"
filterable
clearable></el-cascader>
</template>
</van-field>
<!-- <div class="wei_code_wrap" v-if="form.name && !id">
<div class="wei_title">请先添加企业微信保证申请流程正常进行</div>
<div class="wei_code_bg">
<img v-if="qrCodeUrl" :src="qrCodeUrl" alt="企业微信二维码" />
</div>
</div>-->
</div>
</div>
</div>
<div class="btn_wrap">
<van-button class="btn" block type="info" :native-type="'submit'">提交申请</van-button>
</div>
</van-form>
<van-popup
v-model="areaShow"
position="bottom"
>
<van-area title="服务区域" :area-list="areaList" :columns-num="3" closeable="true" :value="form.areaCode ? String(form.areaCode) : ''"
@cancel="areaShow = false" @confirm="confirmHandle"/>
</van-popup>
</div>
</template>
<script>
import {leftCopy} from "@/utils/common"
import QRCode from 'qrcode'
import { uploadImage, ocrHandler, unifiedOCRWithCompress, getArea, saveSupplier, getSupplierInfo, getOrCodeInfo, getContactQrCodeResult } from "@/api/mine"
import {myMixins} from "@/utils/myMixins"
import {Dialog} from "vant";
export default {
name: "supplierAdd",
mixins:[myMixins],
data() {
return {
clickFlag: true,
errorMsg: '',
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: '邮箱格式不正确' }],
countVerify: [{ required: true, message: '请输入数量' }, { validator: value => /^\d+$/.test(value) && Number(value) >= 0, message: '请输入大于等于0的整数' }],
id:'', //Id
idBackPhotoList: [],
idBackPhoto: '',
idFrontPhotoList: [],
idFrontPhoto: '',
companyPhotoList: [],
licensePhotoList: [],
companyPhoto: '',
licensePhoto:'',
form: {
name: '',
legalName: '',
areaName: '',
linkName: '',
linkPhone: '',
serviceType: '',
trailCount: '',
minorCount: '',
serviceAreaCode: [],
linkEmail:'',
person:'',
accountInfoDTO:{
dutyParagraph:'',
accountNumber:'',
accountName:'',
accountType:'',
invoiceType:'',
settlementType:'',
shouldRate:'',
realRate:'',
unitName:'',
bankNo:'',
billingPhone:'',
billingAddress:'',
billHead:'',
companyType:'',
}
},
qrCodeUrl: '',
qrCode: '',
areaShow: false,
areaList: [],
supplierServiceList: [],
bigServiceList: [{
id: 1002,
title: '拖车服务'
}, {
id: 1003,
title: '抢修服务'
}, {
id: 1004,
title: '困境救援'
}],
defaultProps: {
children: 'children',
label: 'title',
},
areaProps: { multiple: true, checkStrictly: true, value: 'id',label: 'title', emitPath: false},
configId: '',
wechatId: '',
}
},
async mounted() {
const urlParams = new URLSearchParams(window.location.search);
const lastRoute = localStorage.getItem('lastRoute');
this.id = this.$route.query.id || urlParams.get('id');
if (lastRoute && !this.id) {
const { name, query } = JSON.parse(lastRoute);
this.$router.replace({ name, query });
return;
}
await this.getAreaTree();
if(this.id) {
await this.getInfoHandler();
}
// await this.getSupplierServiceTree();
},
methods: {
async QrCodeHandler() {
await this.QrCodeInfo()
await this.getQrCode();
},
async QrCodeInfo() {
let res = await getOrCodeInfo({
name: this.form.name
});
this.configId = res?.configId;
this.qrCode = res?.qrCode
},
async getInfoHandler() {
let res = await getSupplierInfo({
id: this.id
});
let _data = res?.data
leftCopy(this.form, {..._data})
this.idFrontPhoto = _data?.idCardFrontUrl;
this.idBackPhoto = _data?.idCardBackUrl;
this.companyPhoto = _data?.businessLicense;
this.licensePhoto = _data?.accountUrl;
this.wechatId = _data?.wechatId;
this.form.person = _data.legalName;
if( _data.failReason ) {
this.showError(_data.failReason)
}
if(this.idFrontPhoto) {
this.idFrontPhotoList = [{ url : this.idFrontPhoto }];
}
if(this.idBackPhoto) {
this.idBackPhotoList = [{ url : this.idBackPhoto }];
}
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(','))
}
if(_data?.serviceAreaCode) {
this.$set(this.form, 'serviceAreaCode', _data?.serviceAreaCode.split(',').map(item => Number(item)));
}
},
showError(msg) {
this.errorMsg = msg;
},
async applyAdd() {
this.errorMsg = '';
if( !this.idFrontPhoto ) {
this.showError('法人身份证人像页未上传')
return
}
if( !this.idBackPhoto ) {
this.showError('法人身份证国徽页未上传')
return
}
if( !this.companyPhoto ) {
this.showError('营业执照未上传')
return
}
if(this.form.person != this.form.legalName){
this.showError('身份证法人姓名和营业执照法人不一致')
return
}
if( !this.licensePhoto ) {
this.showError('开户许可证未上传')
return
}
if( !(this.$refs.tree.getCheckedKeys().length > 0) ) {
this.showError('服务能力不能为空')
return
}
if( !(this.form.serviceAreaCode.length > 0) ) {
this.showError('服务区域不能为空')
return
}
if( this.$refs.tree.getCheckedKeys().length > 0 ) {
let _node = this.$refs.areaCascader.getCheckedNodes();
let checkArr = [];
_node.map(item => { checkArr.push(item.data.id) });
const payload = {
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,
};
localStorage.setItem('supplierAddForm', JSON.stringify(payload));
this.goPage('addWechat', { potentialFlag: 1 });
}
},
async QrCodeResult() { //
let res = await getContactQrCodeResult({
configId: this.configId
});
this.wechatId = res?.data?.wechatId
},
async saveHandler() {
if( this.clickFlag ) {
try {
this.clickFlag = false
let _node = this.$refs.areaCascader.getCheckedNodes();
let checkArr = [];
_node.map(item => {
checkArr.push(item.data.id)
})
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(','),
});
console.log('rererere', res)
if( res?.noToast ) { //
this.showError('当前服务商已存在')
if( res?.code != 0 ) {
Dialog.alert({
title: '提示',
message: "该服务商已存在,查看信息"
}).then(async () => {
const target = { name: 'supplierAddResult', query: { id: res?.code } };
localStorage.setItem('lastRoute', JSON.stringify(target));
this.goPage('supplierAddResult', { id : res?.code })
setTimeout(() => {
this.clickFlag = true
}, 1000)
});
}
} else {
Dialog.alert({
title: '提示',
message: "操作成功"
}).then(async () => {
const target = { name: 'supplierAddResult', query: { id: res?.data } };
localStorage.setItem('lastRoute', JSON.stringify(target));
this.goPage('supplierAddResult', { id : res?.data })
setTimeout(() => {
this.clickFlag = true
}, 1000)
});
}
} catch (e) {
this.showError(e)
} finally {
setTimeout(() => {
this.clickFlag = true
}, 1000)
}
}
},
async getAreaTree() {
let res = await getArea();
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({
serviceTypes: '1002,1003,1004',
treeType: 2
});
this.supplierServiceList=res.data
this.bigServiceList = [];
res.data.map(item => {
let obj = {...item, children: null}
this.bigServiceList.push(obj)
})
},*/
confirmHandle(val){
this.region=[]
val?.forEach(item => this.region.push(item.name))
let code=[]
val?.forEach(item => code.push(item.code))
this.form.areaCode=code[code.length - 1]
this.areaShow=false
},
async getQrCode() {
try {
this.qrCodeUrl = await QRCode.toDataURL(this.qrCode, {
width: 150,
margin: 2,
color: {
dark: '#000000',
light: '#FFFFFF'
},
})
} catch (error) {
console.error('生成二维码失败:', error)
}
},
async idBackPhotoHandler(file) {
const formData = new FormData();
formData.append("file" , file.file);
let res = await uploadImage(formData);
this.idBackPhoto = res.data
},
async idFrontPhotoHandler(file) {
const formData = new FormData();
formData.append("file" , file.file);
let res = await uploadImage(formData);
this.idFrontPhoto = res.data;
await this.idCardOcrHandler();
},
async idCardOcrHandler() { // ocr
this.form.legalName = '';
let res = await ocrHandler({
ocrType: 1,
imageUrl: this.idFrontPhoto,
cardSide: 'FRONT'
})
//
this.form.legalName = res?.data?.name
},
async companyPhotoHandler(file) {
const formData = new FormData();
formData.append("file" , file.file);
let res = await uploadImage(formData);
this.companyPhoto = res.data
await this.companyOcrHandler()
},
async companyOcrHandler() { // ocr
this.form.name = '';
this.form.areaName = '';
let res = await unifiedOCRWithCompress({
ocrType: 7,
imageUrl: this.companyPhoto,
cardSide: 'FRONT'
})
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
//
this.form.person = res?.data?.person
// 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() {
return this.region.length ? `${this.region[0]}/${this.region[1]}/${this.region[2]}` : '请选择所在地区';
}
},
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.wrap{
@include wh(100%,100%);
box-sizing: border-box;
padding-bottom: 100px;
overflow-y: auto;
}
.error_bar {
position: sticky;
top: 0;
z-index: 9999;
background-color: #e53935;
color: #ffffff;
font-size: 14px;
padding: 10px 16px;
text-align: center;
line-height: 1.5;
}
.top_banner {
width: 100%;
img {
width: 100%;
height: 169px;
}
}
.content_wrap {
padding: 0 20px;
}
.credentials_info {
//height: 170px;
}
.credentials_wrap {
display: flex;
width: 100%;
margin-top: 10px;
flex-wrap: wrap;
.credentials_item {
width: 32%;
text-align: center;
.credentials_title {
font-size: 12px;
color: #4A4A4A;
padding: 10px 0;
}
.custom-background img {
width: 97px;
height: 64px;
}
}
.ml2 {
margin-left: 2%;
}
}
::v-deep .van-uploader__preview-image {
width: 97px;
height: 64px;
}
.title1 {
width: 106px;
height: 19px;
margin-top: 25px;
}
.title2 {
width: 90px;
height: 18px;
margin-top: 25px;
margin-bottom: 10px;
}
.van-cell.required::before{
content: "";
position: absolute;
color: #F00;
left: 0px;
top: .25rem;
font-size: .3rem;
}
.wei_code_wrap {
margin-top: 10px;
.wei_title {
font-size: 15px;
color: #FF553B;
text-align: center;
}
.wei_code_bg {
width: 163px;
height: 173px;
background-image: url("~@/assets/supplier/weiCodeBg.png");
background-size: 100% 100%;
margin: 8px auto;
display: flex;
align-items: center;
justify-content: center;
}
}
.btn_wrap {
width: 100%;
padding: 15px 0;
position: fixed;
bottom: 0;
background: #F7F7F9;
box-shadow: 0px -7px 24px 0px rgba(0, 0, 0, 0.11);
z-index: 10000 !important;
.btn {
width: calc(100% - 80px);
margin-left: 40px;
height: 46px;
background: #0E76F4;
border-radius: 5px;
font-size: 15px;
color: #FFFFFF;
text-align: center;
line-height: 46px;
}
}
/*::v-deep .el-cascader {
width: 100% !important;
}
::v-deep .el-input__inner {
width: 100% !important;
border: none !important;
padding-left: 0 !important;
background: transparent !important;
}
::v-deep .el-input {
width: 100% !important;
}
::v-deep .el-input__suffix {
right: 0 !important;
}*/
</style>
<style>
.responsive-cascader .el-cascader-menu {
min-width: 100px !important;
}
.responsive-cascader .el-cascader-panel {
font-size: 12px !important;
}
.responsive-cascader .el-cascader-node {
padding-left: 5px !important;
padding-right: 4px !important;
}
/* !* *!
.responsive-cascader {
width: 90vw !important;
max-width: 400px !important;
left: 50% !important;
transform: translateX(-50%) !important;
}
.responsive-cascader .el-cascader-panel {
display: flex !important;
width: 100% !important;
max-height: 60vh !important;
}
.responsive-cascader .el-cascader-node {
padding: 12px 16px !important;
font-size: 12px !important;
height: 20px !important;
!*min-height: 44px !important;*!
display: flex !important;
align-items: center !important;
}
.responsive-cascader .el-cascader-node__label {
font-size: 12px !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
flex: 1 !important;
}
!* 移动端触摸优化 *!
.responsive-cascader .el-cascader-node {
padding: 6px 6px !important;
}
!* 选中状态 *!
.responsive-cascader .el-cascader-node.in-active-path,
.responsive-cascader .el-cascader-node.is-active {
color: #1989fa !important;
background-color: #f2f8ff !important;
}
!* 移动端适配 *!
@media (max-width: 768px) {
.responsive-cascader {
width: 95vw !important;
max-width: none !important;
}
.responsive-cascader .el-cascader-menu {
width: 33.33% !important;
flex: none !important;
}
.responsive-cascader .el-cascader-node__label {
font-size: 13px !important;
}
}*/
</style>
-113
View File
@@ -1,113 +0,0 @@
<template>
<div class="wrap">
<div class="top-wrap"></div>
<div class="center-info">
<img class="wait_icon" src="@/assets/supplier/successIcon.png" alt="">
<div class="info-tip">信息录入完成请等待审核结果</div>
</div>
<div class="btn_wrap">
<div class="btn" @click="searchInfo">信息查看</div>
</div>
</div>
</template>
<script>
import {myMixins} from "@/utils/myMixins"
export default {
name: "supplierAddResult",
mixins:[myMixins],
data() {
return {
id: '',
potentialFlag: '',
name: '',
qrCodeUrl: '',
qrCode: '',
configId: '',
wechatId: '',
}
},
async mounted() {
const urlParams = new URLSearchParams(window.location.search);
this.id = this.$route.query.id || urlParams.get('id');
this.potentialFlag = this.$route.query.potentialFlag || urlParams.get('potentialFlag') || '';
if (this.id) {
localStorage.setItem('lastRoute', JSON.stringify({ name: 'supplierAddResult', query: { id: this.id, potentialFlag: this.potentialFlag } }));
}
},
methods: {
async searchInfo() {
localStorage.setItem('lastRoute', JSON.stringify({ name: 'supplierInfo', query: { id: this.id, potentialFlag: this.potentialFlag } }));
this.goPage('supplierInfo', { id: this.id, potentialFlag: this.potentialFlag })
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.wrap{
@include wh(100%,100%);
box-sizing: border-box;
padding-bottom: 100px;
overflow-y: auto;
background: linear-gradient( 180deg, #FBFDFE 0%, #F6FBFF 73%, #F0F7FF 100%);
backdrop-filter: blur(5.602678571428572px);
}
.top-wrap {
width: 100%;
height: 175px;
background: linear-gradient( 180deg, #DAECFF 0%, rgba(233,243,255,0) 100%);
}
.center-info {
text-align: center;
.wait_icon {
width: 54px;
height: 54px;
margin-bottom: 15px;
}
.info-tip {
font-size: 20px;
font-weight: 500;
color: #2C2A53;
}
}
.btn_wrap {
width: 100%;
padding: 15px 0;
position: fixed;
bottom: 10px;
z-index: 10000 !important;
.btn {
width: calc(100% - 80px);
margin-left: 40px;
height: 46px;
background: #0E76F4;
border-radius: 5px;
font-size: 15px;
color: #FFFFFF;
text-align: center;
line-height: 46px;
}
}
.wei_code_wrap {
margin-top: 10px;
.wei_title {
font-size: 15px;
color: #FF553B;
text-align: center;
}
.wei_code_bg {
width: 163px;
height: 173px;
background-image: url("~@/assets/supplier/weiCodeBg.png");
background-size: 100% 100%;
margin: 8px auto;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
-372
View File
@@ -1,372 +0,0 @@
<template>
<div class="wrap">
<div class="nav_bar_bg" :class="{ 'status_wait': supplierInfo?.state == 0, 'status_danger': supplierInfo?.state == 99, 'status_success': supplierInfo?.state == 1 }">
<template v-if="supplierInfo?.state == 0">
<div class="status_wrap">
<img class="wait_icon" src="@/assets/supplier/waitIcon.png" alt="">
<span class="wait_status">审批中</span>
</div>
</template>
<template v-if="supplierInfo?.state == 1">
<div class="status_wrap">
<img class="wait_icon" src="@/assets/supplier/successIcon.png" alt="">
<span class="wait_status success_status">恭喜你审核通过</span>
</div>
</template>
<template v-if="supplierInfo?.state == 99">
<div class="status_wrap">
<img class="wait_icon" src="@/assets/supplier/failIcon.png" alt="">
<span class="wait_status danger_status">审核不通过</span>
</div>
<div class="danger_tip">
{{supplierInfo?.failReason}}
</div>
</template>
</div>
<div class="supplier_content">
<div class="credentials_info">
<div class="common_title">证件照信息</div>
<div class="credentials_wrap">
<div class="credentials_item">
<div class="credentials_title">1.法人身份证人像页</div>
<img :src="supplierInfo?.idCardFrontUrl" alt="">
</div>
<div class="credentials_item ml2">
<div class="credentials_title">2.法人身份证国徽页</div>
<img :src="supplierInfo?.idCardBackUrl" alt="">
</div>
<div class="credentials_item ml2">
<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">
<div class="common_title">公司信息</div>
<div class="info_wrap">
<div class="info_item">
<div class="label">服务商名称</div>
<div class="content">{{supplierInfo?.name}}</div>
</div>
<div class="info_item" v-if="supplierInfo?.abbr">
<div class="label">服务商简称</div>
<div class="content">{{supplierInfo?.abbr}}</div>
</div>
<div class="info_item" v-if="supplierInfo?.supplierCode">
<div class="label">服务商编号</div>
<div class="content">{{supplierInfo?.supplierCode}}</div>
</div>
<div class="info_item" v-if="supplierInfo?.areaName">
<div class="label">注册地址</div>
<div class="content">{{supplierInfo?.areaName}}</div>
</div>
<div class="info_item">
<div class="label">法人姓名</div>
<div class="content">{{supplierInfo?.legalName}}</div>
</div>
<div class="info_item">
<div class="label">联系人姓名</div>
<div class="content">{{supplierInfo?.linkName}}</div>
</div>
<div class="info_item">
<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>
</div>
<div class="info_item">
<div class="label">拖车数量</div>
<div class="content">{{supplierInfo.trailCount>=0 ? supplierInfo.trailCount : ''}}</div>
</div>
<div class="info_item">
<div class="label">抢修车数量</div>
<div class="content">{{supplierInfo.minorCount >= 0 ? supplierInfo.minorCount : ''}}</div>
</div>
<div class="info_item">
<div class="label">服务区域</div>
<div class="content">{{supplierInfo?.serviceAreaName}}</div>
</div>
<template v-if="type == 'audit' && supplierInfo?.state != 1">
<div class="info_item">
<div class="label"><span style="color: red;margin-right: 2px">*</span>审批</div>
<div class="content">
<van-radio-group v-model="form.state" direction="horizontal" icon-size="18px">
<van-radio name="1">审批通过</van-radio>
<van-radio name="99">审批不通过</van-radio>
</van-radio-group>
</div>
</div>
<div class="info_item" v-if="form.state == 1">
<div class="label"><span style="color: red;margin-right: 2px">*</span>服务商简称</div>
<div class="content">
<el-input style="width: 100%" v-model.trim="form.abbr" placeholder="请输入服务商简称"></el-input>
</div>
</div>
<div class="info_item" v-if="form.state == 99">
<div class="label"><span style="color: red;margin-right: 2px">*</span>不通过原因</div>
<div class="content">
<textarea class="report_textarea" rows="4" maxlength="200" show-word-limit placeholder="点击这里输入不通过原因" v-model.trim="form.failReason"></textarea>
</div>
</div>
</template>
</div>
</div>
<!-- <div class="btn_wrap" style="margin-bottom: 5px">
<div class="btn" @click="goAddVehicle">完善信息</div>
</div>-->
<div class="btn_wrap" v-if="type == 'show' && supplierInfo?.state == 99 && origin != 'web'">
<div class="btn" @click="goModifyPage">修改信息</div>
</div>
<div class="btn_wrap" v-if="type == 'audit' && supplierInfo?.state != 1">
<div class="btn" v-if="type == 'audit'" @click="auditSupplier"> </div>
</div>
</div>
</div>
</template>
<script>
import { getSupplierInfo, auditSupplier } from "@/api/mine"
import {myMixins} from "@/utils/myMixins"
export default {
name: "supplierInfo",
mixins:[myMixins],
data() {
return {
id:'', //Id
form: {
state: '',
abbr: '',
failReason: '',
},
supplierInfo: {},
type: 'show',
origin: '',
potentialFlag: '',
}
},
async mounted() {
const urlParams = new URLSearchParams(window.location.search);
this.id=this.$route.query.id || urlParams.get('id');
this.type=this.$route.query.type || urlParams.get('type') || 'show';
this.origin = this.$route.query.origin || ''
this.potentialFlag = this.$route.query.potentialFlag || urlParams.get('potentialFlag') || '';
await this.getInfoHandler();
},
methods: {
goAddVehicle() {
const supplierId = this.id
localStorage.setItem('lastRoute', JSON.stringify({ name: 'vehicleManage', query: { id: supplierId, potentialFlag: this.potentialFlag } }));
this.goPage('vehicleManage', { id: supplierId, potentialFlag: this.potentialFlag });
},
goModifyPage() {
this.goPage('supplierAdd', { id : this.id })
},
async auditSupplier() {
if( !this.form.state ) {
this.$toast('请选择审批结果')
return
}
if( this.form.state == 99 && !this.form.failReason ) {
this.$toast('请输入不通过原因')
return
}
if( this.form.state == 1 ) {
const reg = /^[\u4e00-\u9fa5]{2}$/;
if(!this.form.abbr) {
this.$toast('请输入服务商简称')
return
}
if(!(reg.test(this.form.abbr))) {
this.$toast('服务商简称请输入两位汉字')
return
}
}
try {
await auditSupplier({
id: this.id,
...this.form,
wechatId: 'test', //
})
} finally {
this.closeParentDialog();
}
},
async getInfoHandler() {
let res = await getSupplierInfo({
id: this.id,
});
this.supplierInfo = res?.data;
},
}
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.report_textarea {
width: 100%;
box-sizing: border-box;
border-radius: 6px;
min-height: 95px;
border: 1px solid #E2EAF6;
padding: 10px;
font-size: 13px;
}
.wrap{
@include wh(100%,100%);
box-sizing: border-box;
padding-bottom: 100px;
overflow-y: auto;
}
.nav_bar_bg {
width: 100%;
height: 125px;
padding-top: 50px;
}
.status_wait {
background: linear-gradient( 180deg, #FFE4C9 0%, rgba(255,248,233,0) 100%);
}
.status_success {
background: linear-gradient( 180deg, #DAFFF8 0%, rgba(237,255,233,0) 100%);
}
.status_danger {
background: linear-gradient( 180deg, #FFE8DA 0%, rgba(255,233,233,0) 100%);
}
.status_wrap {
display: flex;
align-items: center;
.wait_icon {
width: 34px;
height: 34px;
margin-left: 31px;
}
.wait_status {
color: #E69B0B;
font-weight: 600;
font-size: 16px;
margin-left: 11px;
}
.success_status {
color: #37B864;
}
.danger_status {
color: #ED440C;
}
}
.danger_tip {
font-size: 12px;
color: #FF553B;
line-height: 18px;
padding-left: 38px;
padding-right: 23px;
margin-top: 5px;
}
.supplier_content {
padding: 0 20px;
width: 100%;
box-sizing: border-box;
}
.common_title {
font-weight: 500;
font-size: 14px;
color: rgba(50, 54, 67, 0.66);
padding-left: 2px;
}
.common_title::after {
content: '';
display: block;
width: 100%;
border: 1px solid;
margin-top: 10px;
opacity: 0.16;
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;
}
.credentials_wrap {
display: flex;
width: 100%;
margin-top: 10px;
flex-wrap: wrap;
.credentials_item {
width: 32%;
text-align: center;
.credentials_title {
font-size: 12px;
color: #4A4A4A;
padding: 10px 0;
}
img,.empty{
width: 97px;
height: 64px;
}
.empty{
border: 1px solid silver;
margin-left: 8px;
}
}
.ml2 {
margin-left: 2%;
}
}
.company_info {
margin-top: 20px;
/*.company_wrap {*/
.info_wrap {
margin-top: 10px;
.info_item {
display: flex;
padding: 10px 2px;
.label {
font-size: 13px;
color: rgba(50, 54, 67, 0.6);
width: 90px;
flex-shrink: 0;
}
.content {
font-size: 13px;
color: #323643;
/*font-weight: bold;*/
flex: 1;
}
.service_color {
color: rgba(230, 155, 11, 0.9);
}
}
/*}*/
}
}
.btn_wrap {
width: 100%;
padding: 15px 0;
position: fixed;
bottom: 0;
background: #F7F7F9;
box-shadow: 0px -7px 24px 0px rgba(0, 0, 0, 0.11);
z-index: 10000 !important;
left: 0;
.btn {
width: calc(100% - 80px);
margin-left: 40px;
height: 46px;
background: #0E76F4;
border-radius: 5px;
font-size: 15px;
color: #FFFFFF;
text-align: center;
line-height: 46px;
}
}
</style>
-417
View File
@@ -1,417 +0,0 @@
<template>
<div class="wrap">
<van-nav-bar
title="供应商时段电话"
left-arrow
@click-left="onNavBack"
fixed
placeholder
class="nav-bar"
/>
<div class="content">
<!-- 审批状态提示条 -->
<div v-if="inApproval" class="status-tip status-tip--pending">{{approvalStateName }}</div>
<div v-else-if="rejected" class="status-tip status-tip--danger">
<div class="status-tip__title">审批被驳回</div>
<div v-if="approvalRemark" class="status-tip__desc">{{ approvalRemark }}</div>
</div>
<!-- 空态 -->
<div v-if="!phoneList.length" class="empty-wrap">
<div class="empty-text">暂无分时段电话点击新增时段电话</div>
</div>
<!-- 时段卡片列表 -->
<div v-else class="card-list">
<div v-for="(item, index) in phoneList" :key="index" class="time-card" @click="goEdit(index)">
<div class="card-head">
<span class="period">{{ item.startTime }} {{ item.endTime }}</span>
<div class="ops" @click.stop v-if="!inApproval">
<span class="op-edit" @click="goEdit(index)">编辑</span>
<span class="op-del" @click="deleteItem(index)">删除</span>
</div>
</div>
<div class="divider"></div>
<div class="phone-rows">
<div v-for="phone in visiblePhones(item)" :key="phone.index" class="phone-row">
<span class="phone-icon"></span>
<span class="phone-num">{{ phone.num }}</span>
<span v-if="phone.desc" class="tag">{{ phone.desc }}</span>
<span v-if="phone.wx" class="tag tag-wx">:{{ phone.wx }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="footer">
<button v-if="!inApproval" class="btn btn-add" @click="goAdd"> 新增时段</button>
<button
class="btn btn-submit"
:disabled="inApproval || !phoneList.length"
@click="onSubmitClick"
>
{{ inApproval ? '审核中' : '提交审批' }}
</button>
</div>
</div>
</template>
<script>
import { getSupplierPhoneList, submitSupplierPhoneApproval } from '@/api/supplierPhone'
import { myMixins } from '@/utils/myMixins'
import { Dialog, Toast } from 'vant'
const DRAFT_KEY_PREFIX = 'supplierTimePhoneDraft_'
function getSupplierIdFromToken() {
const token = localStorage.getItem('token')
if (!token) return null
try {
const payload = JSON.parse(atob(token.split('.')[1]))
return payload.supplierId || null
} catch (e) {
return null
}
}
function getDraftKey(supplierId) {
return `${DRAFT_KEY_PREFIX}${supplierId}`
}
export default {
name: 'supplierTimePhone',
mixins: [myMixins],
data() {
return {
supplierId: null,
supplierName: '',
approvalId: null,
approvalState: null,
approvalStateName: '',
inApproval: false,
rejected: false,
approvalRemark: '',
phoneList: []
}
},
mounted() {
const urlParams = new URLSearchParams(window.location.search);
this.supplierId = urlParams.get('supplierId') || this.$route.query.supplierId || getSupplierIdFromToken()
if (!this.supplierId) {
Toast('未获取到服务商信息')
return
}
this.loadData()
},
methods: {
async loadData() {
Toast.loading({ message: '加载中...', forbidClick: true, duration: 0 })
try {
const result = await getSupplierPhoneList({ supplierId: this.supplierId })
const res=result?.data || ''
this.supplierName = res?.supplierName || ''
this.approvalId = res?.approvalId || null
this.approvalState = res?.approvalState || null
this.approvalStateName = res?.approvalStateName || ''
this.inApproval = !!res?.inApproval
this.rejected = !!res?.rejected
this.approvalRemark = res?.approvalRemark || ''
const cached = this.getCachedDraft()
if (cached && Array.isArray(cached.phoneList)) {
this.phoneList = cached.phoneList
} else {
this.phoneList = res?.phoneList || []
this.saveDraft()
}
} catch (e) {
console.error(e)
} finally {
Toast.clear()
}
},
visiblePhones(item) {
const list = []
for (let i = 1; i <= 3; i++) {
const num = item['phone' + i]
if (num) {
list.push({
index: i,
num,
desc: item['phone' + i + 'Desc'],
wx: item['phone' + i + 'WechatId']
})
}
}
return list
},
getCachedDraft() {
try {
const raw = localStorage.getItem(getDraftKey(this.supplierId))
return raw ? JSON.parse(raw) : null
} catch (e) {
return null
}
},
saveDraft() {
const draft = {
approvalId: this.approvalId,
approvalState: this.approvalState,
phoneList: this.phoneList
}
localStorage.setItem(getDraftKey(this.supplierId), JSON.stringify(draft))
},
onNavBack() {
localStorage.removeItem(getDraftKey(this.supplierId))
this.goBack()
},
goAdd() {
this.saveDraft()
this.goPage('supplierTimePhoneEdit', { supplierId: this.supplierId, index: -1 })
},
goEdit(index) {
this.saveDraft()
this.goPage('supplierTimePhoneEdit', { supplierId: this.supplierId, index })
},
deleteItem(index) {
Dialog.confirm({
title: '提示',
message: '确定删除该时段电话?'
}).then(() => {
this.phoneList.splice(index, 1)
this.saveDraft()
}).catch(() => {})
},
// HH:mm
timeToMinutes(timeStr) {
if (!timeStr) return null
const normalized = String(timeStr).trim().replace(//g, ':')
const [h, m] = normalized.split(':').map(Number)
if (isNaN(h) || isNaN(m)) return null
return h * 60 + m
},
// 00:00 23:59 1
validateTimePeriods(list) {
const periods = list.map(item => ({
start: this.timeToMinutes(item.startTime),
end: this.timeToMinutes(item.endTime)
}))
if (periods.some(p => p.start === null || p.end === null)) {
return '存在格式不正确的时间段'
}
periods.sort((a, b) => a.start - b.start)
if (periods[0].start !== 0) {
return '时间段未从 00:00 开始!'
}
if (periods[periods.length - 1].end !== 23 * 60 + 59) {
return '时间段未到 23:59 结束!'
}
for (let i = 1; i < periods.length; i++) {
if (periods[i].start < periods[i - 1].end) {
return '时间段重叠'
}
if (periods[i].start - periods[i - 1].end > 1) {
return '时间段有遗漏'
}
}
return null
},
async onSubmitClick() {
if (this.inApproval) return
if (!this.phoneList.length) {
Toast('请先添加至少一个时段电话')
return
}
if (!this.phoneList.every(item => item.phone1)) {
Toast('每条时段电话1均必填')
return
}
const timeError = this.validateTimePeriods(this.phoneList)
if (timeError) {
Toast(timeError)
return
}
Toast.loading({ message: '提交中...', forbidClick: true, duration: 0 })
try {
await submitSupplierPhoneApproval({
supplierId: this.supplierId,
modifyReason: '',
phoneList: this.phoneList
})
Toast.success('提交成功')
localStorage.removeItem(getDraftKey(this.supplierId))
this.loadData()
} catch (e) {
Toast(e?.message || '提交失败,请重试')
} finally {
Toast.clear()
}
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.wrap {
@include wh(100%, 100%);
box-sizing: border-box;
background: #f4f5f7;
display: flex;
flex-direction: column;
}
::v-deep .nav-bar {
.van-nav-bar {
background: #2b6cff;
}
.van-nav-bar__title,
.van-nav-bar__arrow {
color: #fff;
}
}
.content {
flex: 1;
overflow-y: auto;
padding: 12px;
}
.status-tip {
border-radius: 10px;
padding: 10px 12px;
font-size: 13px;
line-height: 1.5;
margin-bottom: 12px;
&--pending {
background: #eef3ff;
color: #2b6cff;
}
&--danger {
background: #fff2f0;
color: #ff4d4f;
}
&__title {
font-weight: 600;
}
&__desc {
margin-top: 4px;
font-size: 12px;
}
}
.empty-wrap {
text-align: center;
padding: 60px 0;
.empty-text {
font-size: 13px;
color: #999;
}
}
.card-list {
.time-card {
background: #fff;
border-radius: 14px;
padding: 12px 14px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.period {
font-size: 15px;
font-weight: 700;
color: #1a1a1a;
background: #eef3ff;
padding: 4px 10px;
border-radius: 8px;
}
.ops {
font-size: 13px;
color: #2b6cff;
span {
margin-left: 14px;
cursor: pointer;
}
.op-del {
color: #ff4d4f;
}
}
.divider {
height: 1px;
background: #f0f0f0;
margin: 10px 0;
}
.phone-row {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 0;
font-size: 13.5px;
}
.phone-icon {
color: #2b6cff;
font-size: 14px;
}
.phone-num {
font-weight: 600;
color: #1a1a1a;
letter-spacing: 0.3px;
}
.tag {
font-size: 11px;
padding: 2px 7px;
border-radius: 6px;
background: #f2f3f5;
color: #666;
}
.tag-wx {
background: #e8f8ee;
color: #1a8a4a;
}
}
.footer {
padding: 10px 12px;
background: #fff;
border-top: 1px solid #eee;
flex-shrink: 0;
display: flex;
gap: 10px;
.btn {
flex: 1;
height: 46px;
border-radius: 12px;
border: none;
font-size: 15px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.btn-add {
flex: 0 0 44%;
background: #fff;
color: #2b6cff;
border: 1.5px solid #2b6cff;
}
.btn-submit {
background: #2b6cff;
color: #fff;
&:disabled {
background: #b9c8ee;
cursor: not-allowed;
}
}
}
</style>
-393
View File
@@ -1,393 +0,0 @@
<template>
<div class="wrap">
<van-nav-bar
:title="isEdit ? '编辑时段电话' : '新增时段电话'"
left-arrow
@click-left="h5GoBack"
fixed
placeholder
class="nav-bar"
/>
<div class="content">
<div class="field">
<label class="field-label">时段开始 结束格式 00:00 ~ 23:59</label>
<div class="period-row">
<input
type="text"
readonly
:value="form.startTime"
placeholder="开始时间"
@click="openTimePicker('startTime')"
/>
<span class="sep"></span>
<input
type="text"
readonly
:value="form.endTime"
placeholder="结束时间"
@click="openTimePicker('endTime')"
/>
</div>
<div v-if="periodErr" class="err-msg">{{ periodErr }}</div>
</div>
<div class="phone-block" v-for="n in 3" :key="n">
<div class="phone-block__head">
<span>电话 {{ n }}{{ n === 1 ? '(必填)' : '(选填)' }}</span>
</div>
<input
type="text"
v-model="form['phone' + n]"
:placeholder="'电话号码'"
/>
<div class="sub-grid">
<input
type="text"
v-model="form['phone' + n + 'Desc']"
placeholder="备注/负责人"
/>
<input
type="text"
v-model="form['phone' + n + 'WechatId']"
placeholder="微信号"
/>
</div>
<div v-if="phoneErr[n - 1]" class="err-msg">{{ phoneErr[n - 1] }}</div>
</div>
</div>
<div class="footer">
<button class="btn btn-save" @click="save">保存</button>
</div>
<van-popup v-model="showTimePicker" round position="bottom">
<van-datetime-picker
v-if="showTimePicker"
v-model="pickerValue"
type="time"
:formatter="timeFormatter"
@confirm="onTimeConfirm"
@cancel="showTimePicker = false"
/>
</van-popup>
</div>
</template>
<script>
import { myMixins } from '@/utils/myMixins'
import { isValidPhone, validateTimeRange } from '@/utils/validate'
import { Toast } from 'vant'
const DRAFT_KEY_PREFIX = 'supplierTimePhoneDraft_'
function getSupplierIdFromToken() {
const token = localStorage.getItem('token')
if (!token) return null
try {
const payload = JSON.parse(atob(token.split('.')[1]))
return payload.supplierId || null
} catch (e) {
return null
}
}
function getDraftKey(supplierId) {
return `${DRAFT_KEY_PREFIX}${supplierId}`
}
function emptyRecord() {
return {
startTime: '',
endTime: '',
phone1: '',
phone1Desc: '',
phone1WechatId: '',
phone2: '',
phone2Desc: '',
phone2WechatId: '',
phone3: '',
phone3Desc: '',
phone3WechatId: ''
}
}
export default {
name: 'supplierTimePhoneEdit',
mixins: [myMixins],
data() {
return {
supplierId: null,
index: -1,
form: emptyRecord(),
periodErr: '',
phoneErr: ['', '', ''],
showTimePicker: false,
pickerField: 'startTime',
pickerValue: ''
}
},
computed: {
isEdit() {
return this.index >= 0
}
},
mounted() {
this.supplierId = this.$route.query.supplierId || getSupplierIdFromToken()
this.index = Number(this.$route.query.index)
if (Number.isNaN(this.index)) {
this.index = -1
}
if (this.isEdit) {
this.loadRecord()
}
},
methods: {
validate() {
this.periodErr = ''
this.phoneErr = ['', '', '']
let valid = true
const timeRes = validateTimeRange(this.form.startTime, this.form.endTime)
if (!timeRes.valid) {
this.periodErr = timeRes.message
valid = false
}
if (!this.form.phone1) {
this.phoneErr[0] = '电话 1 为必填项'
valid = false
} else if (!isValidPhone(this.form.phone1)) {
this.phoneErr[0] = '电话 1 格式不正确'
valid = false
} else if (!this.form.phone1Desc) {
this.phoneErr[0] = '电话 1 备注为必填项'
valid = false
}
if (this.form.phone2) {
if (!isValidPhone(this.form.phone2)) {
this.phoneErr[1] = '电话 2 格式不正确'
valid = false
} else if (!this.form.phone2Desc) {
this.phoneErr[1] = '电话 2 备注为必填项'
valid = false
}
}
if (this.form.phone3) {
if (!isValidPhone(this.form.phone3)) {
this.phoneErr[2] = '电话 3 格式不正确'
valid = false
} else if (!this.form.phone3Desc) {
this.phoneErr[2] = '电话 3 备注为必填项'
valid = false
}
}
return valid
},
loadRecord() {
const key = getDraftKey(this.supplierId)
const raw = localStorage.getItem(key)
if (!raw) {
Toast('未找到时段数据')
this.h5GoBack()
return
}
try {
const draft = JSON.parse(raw)
const list = draft.phoneList || []
if (this.index >= 0 && this.index < list.length) {
this.form = { ...list[this.index] }
} else {
Toast('时段数据不存在')
this.h5GoBack()
}
} catch (e) {
console.error(e)
Toast('数据读取失败')
this.h5GoBack()
}
},
save() {
if (!this.validate()) return
try {
const key = getDraftKey(this.supplierId)
const draft = JSON.parse(localStorage.getItem(key) || '{}')
const list = draft.phoneList || []
const record = {
startTime: this.form.startTime,
endTime: this.form.endTime,
phone1: this.form.phone1,
phone1Desc: this.form.phone1Desc,
phone1WechatId: this.form.phone1WechatId,
phone2: this.form.phone2,
phone2Desc: this.form.phone2Desc,
phone2WechatId: this.form.phone2WechatId,
phone3: this.form.phone3,
phone3Desc: this.form.phone3Desc,
phone3WechatId: this.form.phone3WechatId
}
if (this.isEdit) {
list[this.index] = record
} else {
list.push(record)
}
draft.phoneList = list
localStorage.setItem(key, JSON.stringify(draft))
Toast('已保存')
this.h5GoBack()
} catch (e) {
Toast('保存失败')
console.error(e)
}
},
openTimePicker(field) {
this.pickerField = field
this.pickerValue = this.form[field] || '00:00'
this.showTimePicker = true
},
onTimeConfirm(value) {
this.form[this.pickerField] = value
this.showTimePicker = false
},
timeFormatter(type, value) {
return value.toString().padStart(2, '0')
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.wrap {
@include wh(100%, 100%);
box-sizing: border-box;
background: #f4f5f7;
display: flex;
flex-direction: column;
}
::v-deep .nav-bar {
.van-nav-bar {
background: #2b6cff;
}
.van-nav-bar__title,
.van-nav-bar__arrow {
color: #fff;
}
}
.content {
flex: 1;
overflow-y: auto;
padding: 14px;
}
.field {
margin-bottom: 16px;
}
.field-label {
display: block;
font-size: 13px;
color: #333;
font-weight: 600;
margin-bottom: 7px;
}
.period-row {
display: flex;
align-items: center;
gap: 10px;
input {
flex: 1;
}
.sep {
color: #999;
}
}
input[type='text'] {
width: 100%;
height: 42px;
border: 1px solid #dfe1e6;
border-radius: 10px;
padding: 0 12px;
font-size: 14px;
color: #1a1a1a;
background: #fafbfc;
outline: none;
box-sizing: border-box;
&:focus {
border-color: #2b6cff;
background: #fff;
}
&[readonly] {
cursor: pointer;
}
}
.phone-block {
border: 1px solid #eef0f3;
border-radius: 12px;
padding: 12px;
margin-bottom: 12px;
background: #fcfcfd;
&__head {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #2b6cff;
font-weight: 600;
margin-bottom: 8px;
}
.sub-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 8px;
}
}
.err-msg {
color: #ff4d4f;
font-size: 12px;
margin-top: 6px;
}
.footer {
padding: 10px 12px;
background: #fff;
border-top: 1px solid #eee;
flex-shrink: 0;
display: flex;
flex-direction: row;
gap: 10px;
.btn {
flex: 1;
height: 46px;
border-radius: 12px;
border: none;
font-size: 15px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.btn-save {
background: #2b6cff;
color: #fff;
}
.btn-cancel {
background: #f2f3f5;
color: #666;
}
.btn-del {
background: #fff;
color: #ff4d4f;
border: 1.5px solid #ffccc7;
}
}
</style>
+2 -2
View File
@@ -287,10 +287,10 @@
if(!res.data.ocrInvoiceRes.data.sellerName.startsWith("国家税务总局") && if(!res.data.ocrInvoiceRes.data.sellerName.startsWith("国家税务总局") &&
conList.every(a => { conList.every(a => {
let items = a.itemName.split("*") let items = a.itemName.split("*")
return (!items[1].includes("生产生活服务") && !items[1].includes("交通运输服务")) || (!items[2].includes("拖车") && !items[2].includes("救援") && !items[2].includes("施救") && !items[2].includes("清障") && !items[2].includes("道路救援")) return (!items[1].includes("现代服务") && !items[1].includes("运输服务")) || (!items[2].includes("拖车") && !items[2].includes("救援") && !items[2].includes("施救") && !items[2].includes("清障") && !items[2].includes("道路救援"))
}) })
){ ){
errorStr = errorStr + "分类必须包含:生产生活服务/交通运输服务,服务名称必须包含:拖车/救援/施救/清障/道路救援 ;例如《*现代服务*拖车费》"; errorStr = errorStr + "分类必须包含:现代服务/运输服务,服务名称必须包含:拖车/救援/施救/清障/道路救援 ;例如《*现代服务*拖车费》";
} }
if( !flag ) { if( !flag ) {
File diff suppressed because it is too large Load Diff
+37 -483
View File
@@ -3,132 +3,53 @@
<div class="navBar"> <div class="navBar">
<van-nav-bar <van-nav-bar
title="车辆管理" title="车辆管理"
:left-arrow="!supplierId" left-arrow
left-arrow-color="#FFFFFF" left-arrow-color="#FFFFFF"
:border="false" :border="false"
:fixed="true" :fixed="true"
:safe-area-inset-top="true" :safe-area-inset-top="true"
@click-left="goBack" @click-left="goBack"
> >
<template slot="right" v-if="permissonList.includes('vehicleAddBtn') || supplierId"> <template slot="right" v-if="permissonList.includes('vehicleAddBtn')">
<div class="rightWrap" @click="goPage('vehicleAdd', supplierId ? { supplierId, potentialFlag } : {})"> <div class="rightWrap" @click="goPage('vehicleAdd')">
<img src="@/assets/addImg.png" /> <img src="@/assets/addImg.png" />
<span class="addTxt">添加</span> <span class="addTxt">添加</span>
</div> </div>
</template> </template>
</van-nav-bar> </van-nav-bar>
</div> </div>
<van-search <van-pull-refresh v-model="isLoading" @refresh="onRefresh">
v-model="searchVal" <van-list
show-action
placeholder="请输入车辆名称、车牌号、车架号"
@search="resetHandler"
>
<template #action>
<div @click="resetHandler">搜索</div>
</template>
</van-search>
<div class="filterWrap">
<!-- <div >-->
<!--<el-input v-model="searchVal" @blur="resetHandler" placeholder="车辆名称/车牌号/车架号" :class="{'customSel':true,'customInput':true , 'has-value': searchVal }" >
<template #suffix>
<i
v-if="searchVal"
class="el-icon-circle-close el-input__icon"
@click="searchVal = ''"
></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"
:key="item.value"
:label="item.name"
:value="item.value">
</el-option>
</el-select>
<!-- </div>-->
<!-- <div>-->
<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"
:key="item.value"
:label="item.name"
:value="item.value">
</el-option>
</el-select>
<!-- </div>-->
</div>
<div class="wrap_cls">
<van-pull-refresh v-model="isLoading" @refresh="onRefresh">
<van-list
v-if="initialized"
v-model="loading" v-model="loading"
:finished="finished" :finished="finished"
finished-text="没有更多了" finished-text="没有更多了"
@load="onLoad" @load="onLoad"
> >
<div class="carItem" v-for="(item,index) in vehicleList" :key="index" @click.stop="updateVehicle(item)"> <div class="carItem" v-for="(item,index) in vehicleList" :key="index">
<div class="carCode"> <div class="carCode">
<div class="codeLeft">{{item.plateNumber}} / {{item.vehicleTypeString}} <div class="codeLeft">{{item.plateNumber}} / {{item.vehicleTypeString}}</div>
<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 class="twoBtn">
</div> <button v-if="permissonList.includes('vehicleAddBtn')" class="del" @click="deleteItem(item.vehicleId)">删除</button>
<div class="btnWrap"> <button v-if="permissonList.includes('vehicleModifyBtn')" class="revise" @click="updateVehicle(item)">修改</button>
<div class="codeRight" v-if="permissonList.includes('vehicleAddBtn') || supplierId" @click.stop="copyAddVehicle(item)">复制新增</div>
<div class="codeRight" v-if="item.vehicleStatus == 1" @click.stop="updateStatus(item)">停用</div>
</div>
</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>
</span>
</div>
<div class="juhe flex-between">
<span class="zdJuhe">救援责任险</span>
<span class="flex-right" v-if="item.rescueInsurance">
<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>
</div>
<van-icon class="arrow_position" v-if="permissonList.includes('vehicleModifyBtn')" name="arrow" />
</div> </div>
<div class="carType">{{ item.serviceName }}</div>
<div class="juhe">
<span class="zdJuhe">是否参与中道聚合: </span>
<span :class="item.hasPolymerization.code == 0 ? 'isYes' :'isNo'">{{item.hasPolymerization.label}}</span>
</div>
</div>
</van-list> </van-list>
</van-pull-refresh> </van-pull-refresh>
</div>
<van-dialog v-model="show" title="确定删除吗" show-cancel-button @confirm="handleConfirm"></van-dialog> <van-dialog v-model="show" title="确定删除吗" show-cancel-button @confirm="handleConfirm"></van-dialog>
<div class="btn_wrap" v-if="supplierId">
<div class="btn" @click="nextToDriver">下一步</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import {Dialog} from "vant";
import { myMixins} from "@/utils/myMixins"; import { myMixins} from "@/utils/myMixins";
import {supplierVehicleList, deleteVehicle, userOperationPermissions, enableVehicle, supplierSelectById} from "@/api/mine" import {supplierVehicleList,deleteVehicle,userOperationPermissions} from "@/api/mine"
export default { export default {
name: "vehicleManage", name: "vehicleManage",
mixins:[myMixins], mixins:[myMixins],
@@ -137,165 +58,33 @@ export default {
vehicleList:[], vehicleList:[],
show:false, show:false,
id:'', id:'',
supplierId: '',
routeId: '',
initialized: false,
pageNum:1, pageNum:1,
pageSize:10, pageSize:10,
total:'', total:'',
isLoading: false, isLoading: false,
loading: false, loading: false,
finished: false, finished: false,
showPopover: false,
permissonList:[], permissonList:[],
inputStatusList: [],
searchVal: '',
authStates: [],
liabilityInsuranceAuditList: [],
potentialFlag: '',
inputStatusListOptions: [{
name: '行驶证未录入',
value: 0
},{
name: '行驶证核验通过',
value: 1
},{
name: '行驶证核验不通过',
value: 2
}],
authStatesOptions: [{
name: '车头照片未录入',
value: 1
},{
name: '车头照片认证通过',
value: 2
},{
name: '车头照片认证不通过',
value: 3
}],
insuresOptions: [{
name: '待审核',
value: 1
},{
name: '有责任险',
value: 2
},{
name: '有货物险',
value: 3
},{
name: '未通过',
value: 4
},{
name: '无',
value: 5
},{
name: '空',
value: 6
},{
name: '原责任险过期',
value: 8
},{
name: '原货物险过期',
value: 9
}],
} }
}, },
async mounted() { mounted() {
const urlParams = new URLSearchParams(window.location.search); // this.getVehicleList();
const rawId = this.$route.query.id || urlParams.get('id'); this.getPermissions();
this.potentialFlag = this.$route.query.potentialFlag || urlParams.get('potentialFlag') || '';
if (rawId) {
this.routeId = rawId;
await this.getSupplierById(Number(rawId));
} else {
this.initialized = true;
}
let _token = localStorage.getItem('token')
if( _token ) {
this.getPermissions();
}
}, },
methods:{ methods:{
nextToDriver() {
localStorage.setItem('lastRoute', JSON.stringify({ name: 'driverManage', query: { id: this.supplierId, potentialId: this.routeId, potentialFlag: this.potentialFlag } }));
this.goPage('driverManage', { id: this.supplierId, potentialId: this.routeId, potentialFlag: this.potentialFlag });
},
async getSupplierById(id) {
let res = await supplierSelectById({ id });
this.supplierId = res?.data?.supplierId || '';
this.initialized = true;
},
showTip(msg) {
Dialog.alert({
title: '未通过原因',
message: msg,
}).then(() => {
});
},
getClass(id) {
const ids = String(id)
const steps = {
'待审核': {
className: 'main_cls'
},
'有责任险': {
className: 'success_cls'
},
'有货物险': {
className: 'info_cls'
},
'未通过': {
className: 'danger_cls'
},
'原责任险过期': {
className: 'danger_cls'
},
'原货物险过期': {
className: 'danger_cls'
},
'无': {
className: 'default_cls'
},
'空': {
className: 'default_cls'
},
'行驶证未录入': {
className: 'default_cls'
},
'行驶证核验通过': {
className: 'success_cls'
},
'行驶证核验不通过': {
className: 'danger_cls'
},
'车头照未录入': {
className: 'default_cls'
},
'车头照认证通过': {
className: 'success_cls'
},
'车头照认证不通过': {
className: 'danger_cls'
}
}
return steps[ids] || { className: 'default_cls' }
},
async onLoad(){ async onLoad(){
await this.getVehicleList() await this.getVehicleList()
if(this.total>10){ this.pageNum++;
this.pageNum++;
}
// //
this.loading = false; this.loading = false;
// //
if (this.vehicleList.length >= this.total) { if (this.vehicleList.length >= this.total) {
this.finished = true; this.finished = true;
} else {
this.finished = false;
} }
}, },
async onRefresh() { onRefresh() {
await this.resetHandler() this.pageNum=1
// this.getVehicleList()
setTimeout(() => { setTimeout(() => {
this.$toast('刷新成功'); this.$toast('刷新成功');
this.isLoading = false; this.isLoading = false;
@@ -304,12 +93,7 @@ export default {
async getVehicleList(){ async getVehicleList(){
let result = await supplierVehicleList({ let result = await supplierVehicleList({
pageNum:this.pageNum, pageNum:this.pageNum,
pageSize:this.pageSize, pageSize:this.pageSize
searchVal: this.searchVal,
inputStatusList: this.inputStatusList,
authStates: this.authStates,
liabilityInsuranceAuditList: this.liabilityInsuranceAuditList,
supplierId: this.supplierId || undefined,
}) })
this.total=result.total this.total=result.total
if(this.pageNum == 1){// if(this.pageNum == 1){//
@@ -337,55 +121,13 @@ export default {
this.pageNum=1 this.pageNum=1
await this.getVehicleList(); await this.getVehicleList();
}, },
async resetHandler() {
this.pageNum=1
this.finished = false;
this.total = 0;
// this.vehicleList = [];
await this.getVehicleList();
},
updateStatus(item){
if(item.vehicleStatus != 1){
return
}
Dialog.confirm({
message: '当前状态为启用,是否要改为停用?',
}).then(async () => {
// on confirm
await enableVehicle({
vehicleId:item.vehicleId,
vehicleStatus:2,
})
this.pageNum=1
await this.getVehicleList()
console.log("item",item)
}).catch(() => {
// on cancel
});
},
copyAddVehicle(item){//// query
const query = {
copyFlag: 1,
vehicleType: item.vehicleType || '',
virtualVehicle: item.virtualVehicle || '',
serviceIds: (item.serviceIds || []).join(','),
}
if (this.supplierId) {//
query.supplierId = this.supplierId
query.potentialFlag = this.potentialFlag
}
this.goPage('vehicleAdd', query)
},
updateVehicle(item){// updateVehicle(item){//
if( this.permissonList.includes('vehicleModifyBtn') ) { this.$router.push({
this.$router.push({ name:'vehicleAdd',
name:'vehicleAdd', params:{
params:{ id:item.vehicleId
id:item.vehicleId, }
supplierId:item.supplierId })
}
})
}
}, },
} }
@@ -395,41 +137,12 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/mixin.scss"; @import "@/styles/mixin.scss";
@import "@/styles/common.scss"; @import "@/styles/common.scss";
.ml10 {
margin-left: 10px;
}
.wrap{ .wrap{
background: #F4F5F7; background: #F4F5F7;
@include sizingPadding(13px,13px);
@include wh(100%, 100%); @include wh(100%, 100%);
overflow-y: auto; overflow-y: auto;
} }
.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;
}
.navBar{ .navBar{
margin-bottom: 46px; margin-bottom: 46px;
.rightWrap{ .rightWrap{
@@ -448,35 +161,20 @@ export default {
} }
} }
.carItem{ .carItem{
@include wh(100%,120px); @include wh(100%,86px);
background: #FFFFFF; background: #FFFFFF;
box-shadow: 0px 2px 10px 0px rgba(216,216,216,0.5); box-shadow: 0px 2px 10px 0px rgba(216,216,216,0.5);
@include radiusSizing(6px); @include radiusSizing(6px);
padding: 11px 13px 11px 15px; padding: 11px 13px 11px 15px;
margin-bottom: 10px; margin-bottom: 10px;
@include flexBetween; @include flexBetween;
position: relative;
.carCode{ .carCode{
@include flexColBet; @include flexColBet;
.codeLeft{ .codeLeft{
@include fontWeightSize(bold,14px) @include fontWeightSize(bold,14px)
} }
.btnWrap{
display: flex;
align-items: center;
.codeRight + .codeRight{
margin-left: 8px;
}
}
.codeRight{
padding: 2px 8px;
border: 1px solid #DDDDDD;
border-radius: 3px;
font-weight: bold;
}
.twoBtn{ .twoBtn{
display: flex; display: flex;
align-items: center;
button{ button{
border: none; border: none;
padding: 4px 8px; padding: 4px 8px;
@@ -504,11 +202,6 @@ export default {
opacity: .7; opacity: .7;
@include fontWeightSize(400,12px) @include fontWeightSize(400,12px)
} }
.zdJuhe {
width: 80px;
text-align: right;
margin-left: -10px;
}
.isYes{ .isYes{
color: #09B820; color: #09B820;
@include fontWeightSize(400,12px); @include fontWeightSize(400,12px);
@@ -518,143 +211,4 @@ export default {
@include fontWeightSize(400,12px); @include fontWeightSize(400,12px);
} }
} }
.arrow_position {
position: absolute;
right: 5px;
top: 50px;
font-size: 20px;
opacity: 0.6;
}
.mr10 {
margin-right: 10px;
}
.insuranceMain {
color: #354D93;
}
.insuranceSuccess {
color: green;
}
.insuranceTip {
color: orange;
}
.insuranceDanger {
color: red;
}
.insuranceGray{
color: #aaa;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.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;
}
}
::v-deep .el-select .el-select__tags>span {
display: flex;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.flex-right {
display: flex;
flex: 1;
justify-content: flex-start;
margin-left: 10px;
}
.btn_wrap {
width: 100%;
padding: 15px 0;
position: fixed;
bottom: 10px;
z-index: 10000;
.btn {
width: calc(100% - 80px);
margin-left: 40px;
height: 46px;
background: #0E76F4;
border-radius: 5px;
font-size: 15px;
color: #FFFFFF;
text-align: center;
line-height: 46px;
}
}
</style> </style>
+34 -203
View File
@@ -4,54 +4,28 @@
<div class="title">基本信息:</div> <div class="title">基本信息:</div>
<div class="line"></div> <div class="line"></div>
<div class="infoWrap fontColor"> <div class="infoWrap fontColor">
<div class="infoRow"> <div class="leftTitle">
<span class="leftItem">工单编号:</span> <div class="leftItem">工单编号:</div>
<span class="rightItem" style="display: flex;align-items: center;"> <div class="leftItem">合同:</div>
<div class="leftItem">结算方式:</div>
<div class="leftItem">客户姓名:</div>
<div class="leftItem">客户电话:</div>
<div class="leftItem">车牌号:</div>
<div class="leftItem">车型品牌:</div>
</div>
<div class="rightContent">
<div class="rightItem" style="display: flex;align-items: center;">
<span id="copyText">{{orderDetailInfo.orderCode}}</span> <span id="copyText">{{orderDetailInfo.orderCode}}</span>
<img @click="copyText" src="@/assets/copy.png" style="width: 35px;height: 15px;margin-left: 10px"/> <img @click="copyText" src="@/assets/copy.png" style="width: 35px;height: 15px;margin-left: 10px"/>
</span>
</div>
<div class="infoRow" v-if="!([7,9,11,12].includes(Number(queryType)))">
<span class="leftItem">合同:</span>
<span class="rightItem">{{ orderDetailInfo.contractName }}</span>
</div>
<template v-if="orderDetailInfo.contractParentId == 110">
<div class="infoRow">
<span class="leftItem">机构名称:</span>
<span class="rightItem">{{ orderDetailInfo.organizeName }}</span>
</div> </div>
<div class="infoRow"> <div class="rightItem">{{ orderDetailInfo.contractName }}</div>
<span class="leftItem">销售人员:</span> <div class="rightItem">{{ orderDetailInfo.contractSettleType?.label }}</div>
<span class="rightItem">{{ orderDetailInfo.saleName }}</span> <div class="rightItem">{{ orderDetailInfo.userName }}</div>
</div> <div class="rightItem">{{ orderDetailInfo.userPhone }}</div>
<div class="infoRow"> <div class="rightItem">{{ orderDetailInfo.plateNumber }}</div>
<span class="leftItem">司机号码:</span> <!-- <div class="rightItem" >理想智动LXA6500SHEVM理想智动LXA6500SHEVM</div>-->
<span class="rightItem">{{ orderDetailInfo.driverPhone ? orderDetailInfo.driverPhone : ' '}}</span> <div class="rightItem" >{{orderDetailInfo.model}}{{ orderDetailInfo.brand }}</div>
</div>
</template>
<div class="infoRow">
<span class="leftItem">结算方式:</span>
<span class="rightItem">{{ orderDetailInfo.contractSettleType?.label }}</span>
</div>
<div class="infoRow">
<span class="leftItem">客户姓名:</span>
<span class="rightItem">{{ [7,9,11,12].includes(Number(queryType)) ? maskName(orderDetailInfo.userName) : orderDetailInfo.userName }}</span>
</div>
<div class="infoRow">
<span class="leftItem">客户电话:</span>
<span class="rightItem">{{ maskPhone(orderDetailInfo.userPhone) }}</span>
</div>
<div class="infoRow">
<span class="leftItem">车牌号:</span>
<span class="rightItem">{{ [7,9,11,12].includes(Number(queryType)) ? maskPlate(orderDetailInfo.plateNumber) : orderDetailInfo.plateNumber }}</span>
</div>
<div class="infoRow">
<span class="leftItem">车型品牌:</span>
<span class="rightItem">{{orderDetailInfo.model}}{{ orderDetailInfo.brand }}</span>
</div>
<div class="infoRow" v-if="!([7,9,11,12].includes(Number(queryType)))">
<span class="leftItem">车架号:</span>
<span class="rightItem">{{orderDetailInfo.vinNo}}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -62,9 +36,6 @@
<div class="item"> <div class="item">
<span class="leftTitle fontColor">服务类型:</span><span class="rightContent">{{ orderDetailInfo.taskServiceName }}</span> <span class="leftTitle fontColor">服务类型:</span><span class="rightContent">{{ orderDetailInfo.taskServiceName }}</span>
</div> </div>
<div class="item" v-if="orderDetailInfo?.userVehicleTypeString">
<span class="leftTitle fontColor">车辆类型:</span><span class="rightContent">{{ orderDetailInfo.userVehicleTypeString }}<van-icon class="icon" name="question" @click="showCarTypeShow = true" /></span>
</div>
<div class="item"> <div class="item">
<span class="leftTitle fontColor">车辆位于:</span><span class="rightContent">{{ orderDetailInfo.positionEnvironment?.label }}</span> <span class="leftTitle fontColor">车辆位于:</span><span class="rightContent">{{ orderDetailInfo.positionEnvironment?.label }}</span>
</div> </div>
@@ -90,38 +61,13 @@
<div class="item"> <div class="item">
<span class="leftTitle fontColor">服务师傅:</span><span class="rightContent">{{ orderDetailInfo.driverName }} {{orderDetailInfo.drivePhone ? '/' : ''}} {{orderDetailInfo.drivePhone }} <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="queryType == 5" class="driverPoiBtn" @click="noMultipleClicks(showMap)">查看司机位置</span>
<span v-if="orderDetailInfo.driverName && orderDetailInfo.proprietary?.code==1" class="driverPoiBtn" @click="noMultipleClicks(createDriverInfo)">生成司机信息</span>
</span> </span>
</div> </div>
<div class="item" v-if="queryType == 9 || queryType ==11 || queryType ==12 || queryType ==5"> <div class="item" v-if="queryType == 9 || queryType ==11 || queryType ==12">
<span class="leftTitle fontColor">工单照片:</span><span class="rightContent"> <span class="leftTitle fontColor">工单照片:</span><span class="rightContent">
<span class="driverPoiBtn" @click="checkPhoto">查看照片</span> <span class="driverPoiBtn" @click="checkPhoto">查看照片</span>
<span class="driverPoiBtn" v-if="orderDetailInfo.beforeDispatchImages && orderDetailInfo.beforeDispatchImages.length > 0" @click="environmentPhoto">环境照片</span>
</span> </span>
</div> </div>
<div v-if="orderDetailInfo.firstAggregateFailInfo" class="aggregate-fail-card">
<div class="aggregate-fail-card__title"> 首次聚合未接单</div>
<div class="aggregate-fail-card__row">
<span class="aggregate-fail-card__label">师傅姓名</span>
<span class="aggregate-fail-card__value">{{ orderDetailInfo.firstAggregateFailInfo.driverName || '—' }}</span>
</div>
<div class="aggregate-fail-card__row">
<span class="aggregate-fail-card__label">师傅手机号</span>
<span class="aggregate-fail-card__value">{{ orderDetailInfo.firstAggregateFailInfo.driverPhone || '—' }}</span>
</div>
<div class="aggregate-fail-card__row">
<span class="aggregate-fail-card__label">车辆</span>
<span class="aggregate-fail-card__value">{{ aggregateFailVehicle }}</span>
</div>
<div class="aggregate-fail-card__row">
<span class="aggregate-fail-card__label">失败原因</span>
<span class="aggregate-fail-card__value aggregate-fail-card__value--warning">{{ orderDetailInfo.firstAggregateFailInfo.failReason || '—' }}</span>
</div>
<div class="aggregate-fail-card__row">
<span class="aggregate-fail-card__label">聚合时间</span>
<span class="aggregate-fail-card__value">{{ orderDetailInfo.firstAggregateFailInfo.dispatchTime || '—' }}</span>
</div>
</div>
</div> </div>
</div> </div>
<div class="serviceInfo common" v-if="orderDetailInfo.batteryNo"> <div class="serviceInfo common" v-if="orderDetailInfo.batteryNo">
@@ -139,15 +85,7 @@
</div> </div>
</div> </div>
</div> </div>
<van-popup v-model="showCarTypeShow" round position="center" class="popupCenter">
<table v-for="(item, index) in carTypeList" :key="index">
<tr>
<td width="50">{{item.type}}:</td>
<td>{{item.abbr}}</td>
</tr>
</table>
<!-- <div v-for="(item, index) in carTypeList" :key="index"><span style="margin-right: 5px">{{item.type}}:</span><span>{{item.abbr}}</span></div>-->
</van-popup>
<div class="map" v-show="showPopup" id="container"> <div class="map" v-show="showPopup" id="container">
<img @click="showPopup = false" src="@/assets/delKey.png" class="mapImg"> <img @click="showPopup = false" src="@/assets/delKey.png" class="mapImg">
</div> </div>
@@ -155,7 +93,7 @@
</template> </template>
<script> <script>
import {myMixins} from '@/utils/myMixins' import {myMixins} from '@/utils/myMixins'
import {getOrderDetail,showVehiclePositionInfo, getConfigByCode,getDriverInfo} from "@/api/order" import {getOrderDetail,showVehiclePositionInfo} from "@/api/order"
import minePosition from '@/assets/minePosition.png'; import minePosition from '@/assets/minePosition.png';
import vehiclePosition from '@/assets/vehiclePosition.png'; import vehiclePosition from '@/assets/vehiclePosition.png';
import desitationPosition from '@/assets/desitationPosition.png' import desitationPosition from '@/assets/desitationPosition.png'
@@ -173,18 +111,6 @@ export default {
map: '', map: '',
driverPoiInfo:{}, driverPoiInfo:{},
noClick:true, noClick:true,
showCarTypeShow: false,
carTypeList: []
}
},
computed:{
aggregateFailVehicle(){
const info = this.orderDetailInfo.firstAggregateFailInfo
if(!info) return '—'
const plate = info.licensePlate || ''
const type = info.vehicleTypeName || ''
if(plate && type) return `${plate}${type}`
return plate || type || '—'
} }
}, },
async mounted() { async mounted() {
@@ -195,7 +121,6 @@ export default {
this.userOrderId = urlParams.get('userOrderId'); this.userOrderId = urlParams.get('userOrderId');
this.orderCode=urlParams.get('orderCode'); this.orderCode=urlParams.get('orderCode');
this.taskOrderId=urlParams.get('taskOrderId') ? urlParams.get('taskOrderId') : ''; this.taskOrderId=urlParams.get('taskOrderId') ? urlParams.get('taskOrderId') : '';
await this.getConfigByCodeHandler();
await this.getDetail(); await this.getDetail();
if(this.queryType == 5){ if(this.queryType == 5){
await this.getDriverPoi(); await this.getDriverPoi();
@@ -203,35 +128,9 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.initMap(); this.initMap();
}); });
console.log('queryType', this.queryType)
}, },
methods:{ methods:{
async getConfigByCodeHandler() {
let res = await getConfigByCode({
code: 'userVehicleTypeConfig'
});
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}\n身份证:${result.data.identityCardNumber}\n车牌:${this.orderDetailInfo.vehiclePlateNumber}\n手机:${this.orderDetailInfo.driverPhone}`
// console.log('params',params)
let data = {"action":"copyToClipboard","params":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(params);
}
}else {
this.$toast('未获取到司机信息')
}
},
async getDetail(){ async getDetail(){
let result=await getOrderDetail({ let result=await getOrderDetail({
queryType:this.queryType, queryType:this.queryType,
@@ -247,9 +146,6 @@ export default {
this.showPopup = true; this.showPopup = true;
this.mapMarkers(); this.mapMarkers();
}, },
async createDriverInfo(){//
await this.getDriverInfo()
},
checkPhoto(){ checkPhoto(){
let isAllowImage = this.queryType == 9 ? 0 : 1 let isAllowImage = this.queryType == 9 ? 0 : 1
let data = { let data = {
@@ -269,12 +165,6 @@ export default {
window.android.sendMessage("orderPhoto,"+this.userOrderId+","+this.orderCode+","+this.taskOrderId+","+isAllowImage); window.android.sendMessage("orderPhoto,"+this.userOrderId+","+this.orderCode+","+this.taskOrderId+","+isAllowImage);
} }
}, },
environmentPhoto() {
this.$router.push({
path: '/environmentPhotos',
query: { userOrderId: this.userOrderId }
})
},
initMap() { initMap() {
this.map = new AMap.Map('container',{ this.map = new AMap.Map('container',{
zoom:14,//( 3-20 ) zoom:14,//( 3-20 )
@@ -318,21 +208,6 @@ export default {
this.map.add(marker2); this.map.add(marker2);
this.map.setFitView([marker,marker1,marker2]) this.map.setFitView([marker,marker1,marker2])
}, },
maskName(name) {
if (!name) return '';
return name.slice(0, 1) + '*'.repeat(name.length - 1);
},
maskPhone(phone) {
if (!phone) return '';
const str = String(phone);
if (str.length <= 7) return str;
return str.slice(0, 3) + '*'.repeat(str.length - 7) + str.slice(-4);
},
maskPlate(plate) {
if (!plate) return '';
if (plate.length <= 2) return plate;
return plate.slice(0, 1) + '*'.repeat(plate.length - 2) + plate.slice(-1);
},
async getDriverPoi(){ async getDriverPoi(){
let res=await showVehiclePositionInfo({ let res=await showVehiclePositionInfo({
userOrderId:this.userOrderId, userOrderId:this.userOrderId,
@@ -388,27 +263,19 @@ export default {
line-height: 24px; line-height: 24px;
} }
.baseInfo{ .baseInfo{
width: 100%; @include wh(100%,260px);
margin-bottom: 12px; margin-bottom: 12px;
.infoWrap{ .infoWrap{
.infoRow{ display: flex;
display: flex; .leftTitle{
align-items: center; margin-left: 5px;
min-height: 24px; margin-right: 6px;
.leftItem{ @include wh(60px,189px);
width: 60px; opacity: .5;
flex-shrink: 0; }
margin-left: 5px; .rightContent{
margin-right: 6px; height: 189px;
opacity: .5; width: calc(100% - 60px);
}
.rightItem{
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
} }
} }
@@ -451,38 +318,6 @@ export default {
} }
} }
} }
.aggregate-fail-card{
margin-top: 12px;
padding: 12px;
background: #FFF7F0;
border: 1px solid #FFBB96;
border-radius: 6px;
&__title{
@include fontWeightSize(bold,13px);
color: #FA541C;
margin-bottom: 8px;
}
&__row{
display: flex;
align-items: baseline;
line-height: 22px;
}
&__label{
width: 78px;
flex-shrink: 0;
@include fontWeightSize(400,12px);
color: #000000;
opacity: .65;
}
&__value{
flex: 1;
@include fontWeightSize(bold,12px);
color: #000000;
&--warning{
color: #FA541C;
}
}
}
.title{ .title{
@include fontWeightSize(bold,14px); @include fontWeightSize(bold,14px);
@include colorOpa(#323643,0.6); @include colorOpa(#323643,0.6);
@@ -495,8 +330,4 @@ export default {
margin: 7px 0; margin: 7px 0;
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; 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;
} }
.popupCenter {
padding: 20px 10px;
width: 80%;
}
</style> </style>
-46
View File
@@ -1,46 +0,0 @@
<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>
File diff suppressed because it is too large Load Diff
+240 -261
View File
@@ -39,13 +39,14 @@
<van-tabs v-model="active" sticky @click="tabClickHandle"> <van-tabs v-model="active" sticky @click="tabClickHandle">
<van-tab v-for="(item,index) in tabArr" :key="index" :title="item.name"></van-tab> <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 && !([0,1,2,3].includes(active))" class="tipArrow left">{{ leftArr }}</div>
<div v-if="isMobile && !([7,8, 9].includes(active))" class="tipArrow right">>>></div> <div v-if="isMobile && !([9,10, 11].includes(active))" class="tipArrow right">>>></div>
</van-tabs> </van-tabs>
<div v-loading="loadingData" :class="{'contentWrap':true,'webcontentWrap':!isMobile}" v-if="active===0"> <div v-loading="loadingData" :class="{'contentWrap':true,'webcontentWrap':!isMobile}" v-if="active===0">
<!--<div :class="{'reciceOrder':true,'webCom':!isMobile}"> <div :class="{'reciceOrder':true,'webCom':!isMobile}">
<div :class="{'title':true,'webTitle':!isMobile}">接单指标</div> <div :class="{'title':true,'webTitle':!isMobile}">接单指标</div>
<div class="reciceOrderIWrap"> <div class="reciceOrderIWrap">
<div class="left common"> <div class="left common">
<!-- indexData && -->
<div class="num">{{ formatCurrency(indexData?.receiveOrderCount) }}</div> <div class="num">{{ formatCurrency(indexData?.receiveOrderCount) }}</div>
<div class="itemTitle">承接案件量</div> <div class="itemTitle">承接案件量</div>
</div> </div>
@@ -58,7 +59,7 @@
<div class="itemTitle" style="cursor: pointer">超时率 ></div> <div class="itemTitle" style="cursor: pointer">超时率 ></div>
</div> </div>
</div> </div>
</div>--> </div>
<div :class="{'reciceOrder':true,'webCom':!isMobile,'evaluate':isMobile}"> <div :class="{'reciceOrder':true,'webCom':!isMobile,'evaluate':isMobile}">
<div class="title">客户评价</div> <div class="title">客户评价</div>
<div class="reciceOrderIWrap" v-if="indexData"> <div class="reciceOrderIWrap" v-if="indexData">
@@ -111,28 +112,38 @@
<circle-char v-show="showScoreChart" ref="Doughnut7" :data="indexData && indexData.score" :bg-color="Number(indexData?.score || 0) >= 60 ? 'green' : 'red'" :is-store="true"></circle-char> <circle-char v-show="showScoreChart" ref="Doughnut7" :data="indexData && indexData.score" :bg-color="Number(indexData?.score || 0) >= 60 ? 'green' : 'red'" :is-store="true"></circle-char>
<div v-show="!showScoreChart" class="detailScore"> <div v-show="!showScoreChart" class="detailScore">
<div class="left"> <div class="left">
<!-- <div><span class="defen">接单得分:</span></div>--> <div>
<div><span class="defen">接单时效得分:</span></div> <span class="defen">接单得分:</span>
<div><span class="defen">到达时效得分:</span></div> </div>
<div><span class="defen">聚合成功率得分:</span></div> <div>
<div><span class="defen">APP使用率得分:</span></div> <span class="defen">接单时效得分:</span>
<div><span class="defen">催促率得分:</span></div> </div>
<div><span class="defen">投诉率得分:</span></div> <div>
<div><span class="defen">超时率得分:</span></div> <span class="defen">接单时效得分:</span>
<div><span class="defen">拒单率得分:</span></div> </div>
<div><span class="defen">3'联系率得分:</span></div> <div><span class="defen">聚合成功率得分:</span>
</div>
<div><span class="defen">APP使用率得分:</span>
</div>
<div><span class="defen">催促率得分:</span>
</div>
<div><span class="defen">投诉率得分:</span>
</div>
</div> </div>
<div class="right"> <div class="right">
<!-- <div><span>{{ indexData.orderScore }}</span></div>--> <div>
<div><span>{{ indexData.orderAgeingScore }}</span></div> <span>{{ indexData.orderScore }}</span>
<div><span>{{ indexData.arriveAgeingScore }}</span></div> </div>
<div>
<span>{{ indexData.orderAgeingScore }}</span>
</div>
<div>
<span>{{ indexData.arriveAgeingScore }}</span>
</div>
<div> <span>{{ indexData.polymerizationSuccessScore }}</span></div> <div> <span>{{ indexData.polymerizationSuccessScore }}</span></div>
<div> <span>{{ indexData.appUseScore }}</span></div> <div> <span>{{ indexData.appUseScore }}</span></div>
<div> <span>{{ indexData.urgeScore }}</span></div> <div> <span>{{ indexData.urgeScore }}</span></div>
<div><span>{{ indexData.urgeScore }}</span></div> <div><span>{{ indexData.urgeScore }}</span></div>
<div> <span>{{ indexData.timeOutScore }}</span></div>
<div> <span>{{ indexData.refuseOrderScore }}</span></div>
<div><span>{{ indexData.threeMinutesContactSocre }}</span></div>
</div> </div>
</div> </div>
</div> </div>
@@ -153,7 +164,7 @@
<div v-if="active==1" style="width: 100%"> <div v-if="active==1" style="width: 100%">
<div id="chartWrap" style="width: 100%;height:300px"></div> <div id="chartWrap" style="width: 100%;height:300px"></div>
</div> </div>
<div v-if="activeIndex==2" class="selectWrap"> <div v-if="activeIndex==3" class="selectWrap">
<el-select v-model="value" @change="changeHandle"> <el-select v-model="value" @change="changeHandle">
<el-option <el-option
v-for="item in options" v-for="item in options"
@@ -165,7 +176,7 @@
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div v-if="[0,1].includes(activeIndex)"> <div v-if="[0,1,2].includes(activeIndex)">
<div id="chartWrap" style="width: 100%;height:300px"></div> <div id="chartWrap" style="width: 100%;height:300px"></div>
</div> </div>
<div v-else style="width: 100%;display: flex;justify-content: space-between"> <div v-else style="width: 100%;display: flex;justify-content: space-between">
@@ -189,8 +200,8 @@
<noFit-table v-else :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"></noFit-table> <noFit-table v-else :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"></noFit-table>
</div> </div>
</div> </div>
<div v-loading="loadingData" class="contentWrap monthTotal" v-if="[2,3,4].includes(active)"> <div v-loading="loadingData" class="contentWrap monthTotal" v-if="[2,4,5].includes(active)">
<div class="searchDriverName" v-if="[3,4].includes(active)"> <div class="searchDriverName" v-if="[5,4].includes(active)">
<el-select <el-select
v-model="driverName" v-model="driverName"
filterable filterable
@@ -212,12 +223,12 @@
</el-select> </el-select>
</div> </div>
<div :class="{'tabWrap':true,'webTabWrap':!isMobile,'mobileTab':isMobile}"> <div :class="{'tabWrap':true,'webTabWrap':!isMobile,'mobileTab':isMobile}">
<div v-for="(item,index) in (active===3 ? driverList : list)" :class="activeIndex===index ? 'active' : ''" <div v-for="(item,index) in (active===4 ? driverList : list)" :class="activeIndex===index ? 'active' : ''"
:key="index" :key="index"
@click="changeTab(index)">{{ item.name }} @click="changeTab(index)">{{ item.name }}
</div> </div>
</div> </div>
<div :class="{'comTab':active !== 3,'comTabActive3':active === 3}"> <div :class="{'comTab':active !== 4,'comTabActive3':active === 4}">
<noFit-table :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"></noFit-table> <noFit-table :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"></noFit-table>
</div> </div>
<el-pagination <el-pagination
@@ -232,13 +243,13 @@
:total="total"> :total="total">
</el-pagination> </el-pagination>
</div> </div>
<div v-loading="loadingData" class="contentWrap monthTotal" v-if="[5,6,7,8,9].includes(active)"> <div v-loading="loadingData" class="contentWrap monthTotal" v-if="[3,6,7,8,9,10, 11].includes(active)">
<div :class="{'comTab':true,'detailTable':isMobile}"> <div :class="{'comTab':true,'detailTable':isMobile}">
<noFit-table :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList" <noFit-table :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"
></noFit-table> ></noFit-table>
</div> </div>
<el-pagination <el-pagination
v-if="active !== 8" v-if="active !== 10"
small small
:page-sizes="[20, 50, 100]" :page-sizes="[20, 50, 100]"
:current-page.sync="pageNum" :current-page.sync="pageNum"
@@ -261,10 +272,8 @@ import {
getStatisticsKpi, getStatisticsKpi,
getSupplierId, getSupplierId,
getDriverName,getDriverStatisticsKpi, getDriverName,getDriverStatisticsKpi,
getRecentSupplierKpi, getRecentSupplierKpi, querySupplierDriverStatisticsScore
} from "@/api/kpi.js" } from "@/api/kpi.js"
import { getUserBO } from "@/api/order"
// querySupplierDriverStatisticsScore
import {myMixins} from "@/utils/myMixins" import {myMixins} from "@/utils/myMixins"
import CircleChar from "@/views/kpi/components/circleChar.vue"; import CircleChar from "@/views/kpi/components/circleChar.vue";
import NoFitTable from "@/views/kpi/components/noFit-table.vue"; import NoFitTable from "@/views/kpi/components/noFit-table.vue";
@@ -280,14 +289,11 @@ export default {
activeIndex: 0, activeIndex: 0,
// //
tabArr: [ tabArr: [
{name: '总览',value:0}, {name: '月/总'}, {name: '日/总'},{name: '月/师傅'}, {name: '日/师傅'}, /*{name: '拒单明细'},*/ {name: '总览'}, {name: '月/总'}, {name: '日/总'},{name: '师傅满意度'},{name: '月/师傅'}, {name: '日/师傅'}, {name: '拒单明细'},
/*{name: '超时明细'},*/ {name: '催促明细'},{name: '投诉明细'}, {name: '不使用APP案件明细'}, {name: '车辆在线情况'} , {name: '聚合失败案件明细'} {name: '超时明细'}, {name: '投诉明细'}, {name: '不使用APP案件明细'}, {name: '车辆在线情况'} , {name: '聚合失败案件明细'}
], ],
list: [ {name: '服务评价'}, {name: 'APP使用情况'}, {name: ' 时效 '}], list: [{name: '接单指标'}, {name: '客户评价'}, {name: 'APP使用'}, {name: ' 时效 '}],
// driverList: [{name: ''},{name: ''}, {name: ''}, {name: 'APP使'}, {name: ' '}], driverList: [{name: '得分总览'},{name: '接单情况'}, {name: '服务评价'}, {name: 'APP使用情况'}, {name: '时效 '}],
// {name: ''},
driverList: [{name: '服务评价'}, {name: 'APP使用情况'}, {name: '时效 '}],//story#7149,
startMonthTime: '', startMonthTime: '',
startTime: '', startTime: '',
endTime: '', endTime: '',
@@ -310,6 +316,7 @@ export default {
isMobile: false, isMobile: false,
isZd: '', isZd: '',
current:'2024-10', current:'2024-10',
// current: dayjs(new Date()).format('M'),
supplierName:'', supplierName:'',
value: '1', value: '1',
options: [ options: [
@@ -331,10 +338,6 @@ export default {
showScoreChart:true, showScoreChart:true,
continueMonthKpi:[], continueMonthKpi:[],
isBtn:false,// isBtn:false,//
source: 'App',
currentSupplierId: '',
userId: '',
enterTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
} }
}, },
created() { created() {
@@ -343,41 +346,13 @@ export default {
this.isZd = urlParams?.get('isZd') || '' this.isZd = urlParams?.get('isZd') || ''
this.supplierId = urlParams?.get('supplierId') || '' this.supplierId = urlParams?.get('supplierId') || ''
this.isBtn= Number(urlParams?.get('isBtn')) this.isBtn= Number(urlParams?.get('isBtn'))
this.source = urlParams?.get('source') || 'App'
}, },
async mounted() { async mounted() {
await this.checkMobile(); await this.checkMobile();
await this.initDate(); await this.initDate();
await this.selectSupplierNameHandle(); await this.selectSupplierNameHandle();
await this.getUserInfo();
document.addEventListener('visibilitychange', async ( ) => {
let state = document.visibilityState
if (state == 'hidden') { //
if( this.source == 'App' ) {
this.setLogHandler({
supplierId: this.currentSupplierId, // id
userId: this.userId, // userId
source: '服务商KPI',
event: this.source, // App system
openTime: this.enterTime, // log enter leave
leaveTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
});
}
}
if (state == 'visible') {
this.enterTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
}
});
}, },
methods: { methods: {
async getUserInfo() {
let res = await getUserBO();
this.userId = res.data.id;
this.currentSupplierId = res.data.supplierId
console.log('getUserBO', res)
},
applicateHandle() { applicateHandle() {
if (window.parent) { if (window.parent) {
const hasListener = window.parent.dispatchEvent(new Event('checkCloseDialog')); const hasListener = window.parent.dispatchEvent(new Event('checkCloseDialog'));
@@ -395,13 +370,21 @@ export default {
} }
}, },
monthChangeHandle(value){ monthChangeHandle(value){
console.log('valuevaluevalue', dayjs(new Date(value)).format('YYYY-MM-DD'))
if (value) { if (value) {
this.current = dayjs(new Date(value)).format('YYYY-MM') this.current = dayjs(new Date(value)).format('YYYY-MM')
const _tempDate = dayjs(new Date(value)).format('YYYY-MM-DD') const _tempDate = dayjs(new Date(value)).format('YYYY-MM-DD')
const lastDay = dayjs(_tempDate).endOf('month'); const lastDay = dayjs(_tempDate).endOf('month');
/*const year = value.getFullYear();
const month = value.getMonth() + 1; // 0 1
const yearAndMonth = year + '-' + (month > 9 ? month : '0' + month);
this.current = dayjs(new Date(yearAndMonth)).format('YYYY-MM')*/
this.startTime = `${this.current}-01 00:00:00`; this.startTime = `${this.current}-01 00:00:00`;
// const lastDay = new Date(year, month, 0).getDate(); //
this.endTime = `${this.current}-${this.padZero(lastDay.date())} 23:59:59`; this.endTime = `${this.current}-${this.padZero(lastDay.date())} 23:59:59`;
this.startMonthTime=this.getStartTimeFromEndTime(this.endTime) this.startMonthTime=this.getStartTimeFromEndTime(this.endTime)
console.log(" this.startMonthTime", this.startMonthTime)
this.selectSupplierNameHandle(); this.selectSupplierNameHandle();
if(this.active===1){ if(this.active===1){
this.tabClickHandle() this.tabClickHandle()
@@ -417,7 +400,7 @@ export default {
return return
} }
this.$refs.Doughnut1.initCircle() this.$refs.Doughnut1.initCircle()
// this.$refs.Doughnut2.initCircle() this.$refs.Doughnut2.initCircle()
this.$refs.Doughnut3.initCircle() this.$refs.Doughnut3.initCircle()
this.$refs.Doughnut4.initCircle() this.$refs.Doughnut4.initCircle()
this.$refs.Doughnut5.initCircle() this.$refs.Doughnut5.initCircle()
@@ -530,7 +513,7 @@ export default {
let myChart = echarts.init(document.getElementById('chartWrap')) let myChart = echarts.init(document.getElementById('chartWrap'))
let option; let option;
// //
/*let series1 = [{ let series1 = [{
name: '派遣案件量', name: '派遣案件量',
type: 'bar', type: 'bar',
data: this.v1?.map(item => Number(item?.toString()?.replace(/,/g, ''))), data: this.v1?.map(item => Number(item?.toString()?.replace(/,/g, ''))),
@@ -569,19 +552,19 @@ export default {
formatter: '{c}%' formatter: '{c}%'
}, },
}, },
// { /* {
// name: '', name: '客户取消率',
// type: "line", type: "line",
// data: this.v4, data: this.v4,
// yAxisIndex: 1, yAxisIndex: 1,
// smooth: true, smooth: true,
// label: { label: {
// show: true, show: true,
// position: 'top', position: 'top',
// formatter: '{c}%' formatter: '{c}%'
// }, },
// }, },*/
]*/ ]
let series2 = [ let series2 = [
{ {
name: '投诉率', name: '投诉率',
@@ -757,7 +740,7 @@ export default {
}, },
}, },
] ]
/*let yAxis1 = [ let yAxis1 = [
{type: 'value'}, {type: 'value'},
{ {
type: 'value', type: 'value',
@@ -765,7 +748,7 @@ export default {
formatter: '{value}%' formatter: '{value}%'
} }
}, },
]*/ ]
let yAxis2 = [ let yAxis2 = [
{ {
type: 'value', type: 'value',
@@ -796,17 +779,16 @@ export default {
let series = [] let series = []
let yAxis = [] let yAxis = []
if (this.active === 1) { if (this.active === 1) {
/* if (this.activeIndex === 0) { if (this.activeIndex === 0) {
series = series1 series = series1
yAxis = yAxis1 yAxis = yAxis1
} else*/ } else if (this.activeIndex === 1) {
if (this.activeIndex === 0) {
series = series2 series = series2
yAxis = yAxis2 yAxis = yAxis2
} else if (this.activeIndex === 1) { } else if (this.activeIndex === 2) {
series = [] series = []
yAxis = [] yAxis = []
} else if (this.activeIndex === 2) { } else if (this.activeIndex === 3) {
if (this.value == 1) { if (this.value == 1) {
series = series4 series = series4
yAxis = yAxis4 yAxis = yAxis4
@@ -818,7 +800,7 @@ export default {
} }
option = { option = {
title: { title: {
text: this.isMobile ? this.getTitle(this.activeIndex) : (this.active === 1 && this.activeIndex === 1) ? '' : '师傅接单时效', text: this.isMobile ? this.getTitle(this.activeIndex) : (this.active === 1 && this.activeIndex === 2) ? '' : '师傅接单时效',
textStyle: { textStyle: {
fontSize: 12 fontSize: 12
}, },
@@ -830,7 +812,7 @@ export default {
right: '1%', right: '1%',
itemWidth: this.isMobile ? 10 : 18, // itemWidth: this.isMobile ? 10 : 18, //
itemHeight: this.isMobile ? 6 : 10, itemHeight: this.isMobile ? 6 : 10,
width: (this.active == 1 && this.activeIndex == 1) ? '60%' : '100%', width: (this.active == 1 && this.activeIndex == 2) ? '60%' : '100%',
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
@@ -1003,7 +985,7 @@ export default {
async getKpiData() { async getKpiData() {
try { try {
this.loading = true this.loading = true
if (this.active === 0) {// if (this.active === 0) {
await this.allDataChart() await this.allDataChart()
let res = await getStatisticsKpiByMonth({ let res = await getStatisticsKpiByMonth({
startTime: this.startTime, startTime: this.startTime,
@@ -1034,7 +1016,7 @@ export default {
this.indexData[key] = this.processPercentage(this.indexData[key]); this.indexData[key] = this.processPercentage(this.indexData[key]);
} }
} }
} else if ([1, 2].includes(this.active)) {// } else if ([1, 2].includes(this.active)) {
let res = await getStatisticsKpi({ let res = await getStatisticsKpi({
startTime: this.active === 1 ? this.startMonthTime : this.startTime, startTime: this.active === 1 ? this.startMonthTime : this.startTime,
endTime: this.endTime, endTime: this.endTime,
@@ -1065,6 +1047,8 @@ export default {
item.minorFortyMinutesArrivalRate=this.formatPercentage(item.minorFortyMinutesArrivalRate) ; item.minorFortyMinutesArrivalRate=this.formatPercentage(item.minorFortyMinutesArrivalRate) ;
let formatVal = dayjs(item.statisticsDate).format('DD'); let formatVal = dayjs(item.statisticsDate).format('DD');
let formatVal1 = dayjs(item.statisticsDate).format('M'); let formatVal1 = dayjs(item.statisticsDate).format('M');
// console.log("formatVal1",formatVal1)
// return {...item, date: formatVal, month: (formatVal1 == this.current) ? '' : (formatVal1 + '')};
return {...item, date: formatVal, month: formatVal1 + '月'}; return {...item, date: formatVal, month: formatVal1 + '月'};
}); });
this.loading = false this.loading = false
@@ -1072,8 +1056,33 @@ export default {
this.xAxisArr.push(item.month) this.xAxisArr.push(item.month)
this.xAxisArr = [...new Set(this.xAxisArr)]; // this.xAxisArr = [...new Set(this.xAxisArr)]; //
}) })
console.log(" this.xAxisArr", this.xAxisArr)
// this.xAxisArr[this.xAxisArr.length - 1] = ''
await this.twoTabHanldeData() await this.twoTabHanldeData()
} else if ([3,4].includes(this.active)) {// }else if (this.active === 3) {
let res = await querySupplierDriverStatisticsScore({
startTime: this.startTime ,
endTime: this.endTime,
statisticsType: 1,
supplierId: this.supplierId,
driverId: this.driverId,
driverName:this.driverName,
pageNum: this.pageNum,
pageSize: this.pageSize
})
this.total = res.total
this.detailList = res.data?.map(item => {
let formatVal = dayjs(item.statisticsDate).format('DD');
return {...item, date: formatVal};
});
this.labelList = [
{label: '师傅姓名', prop: 'driverName'},
{label: '案件总量', prop: 'orderCount'},
{label: '满意度得分 ', prop: 'driverSatisfaction'},
]
this.loading = false;
}
else if ([4,5].includes(this.active)) {
let res = await getDriverStatisticsKpi({ let res = await getDriverStatisticsKpi({
startTime: this.startTime , startTime: this.startTime ,
endTime: this.endTime, endTime: this.endTime,
@@ -1104,32 +1113,13 @@ export default {
return {...item, date: formatVal}; return {...item, date: formatVal};
}); });
this.loading = false; this.loading = false;
/* if(this.active===5 && !this.driverName){ if(this.active===5 && !this.driverName){
this.detailList=[] this.detailList=[]
this.total=0 this.total=0
}*/ }
await this.twoTabHanldeData(); await this.twoTabHanldeData();
} }
/* else if (this.active ===7) { else if ([ 6, 7, 8,9,10, 11].includes(this.active)) {
let res = await querySupplierDriverStatisticsScore({
startTime: this.startTime ,
endTime: this.endTime,
statisticsType: 1,
supplierId: this.supplierId,
driverId: this.driverId,
driverName:this.driverName,
pageNum: this.pageNum,
pageSize: this.pageSize
})
this.total = res.total
this.detailList = res.data?.map(item => {
let formatVal = dayjs(item.statisticsDate).format('DD');
return {...item, date: formatVal};
});
this.loading = false;
} */
else if ([ 5,6,7, 8,9].includes(this.active)) {
this.detailList = [] this.detailList = []
this.labelList = [] this.labelList = []
let result = await getKpiDetailsData({ let result = await getKpiDetailsData({
@@ -1146,7 +1136,7 @@ export default {
return {...item, date: formatVal}; return {...item, date: formatVal};
}); });
this.loading = false this.loading = false
/*if (this.active === 5) {//拒单明细 if (this.active === 6) {//
this.labelList = [ this.labelList = [
{label: '案件编号', prop: 'orderCode'}, {label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'}, {label: '服务内容', prop: 'serviceName'},
@@ -1156,7 +1146,7 @@ export default {
{label: '拒绝时间', prop: 'time'}, {label: '拒绝时间', prop: 'time'},
{label: '拒单原因', prop: 'reason'}, {label: '拒单原因', prop: 'reason'},
] ]
} else if (this.active === 6) {// } else if (this.active === 7) {//
this.labelList = [ this.labelList = [
{label: '案件编号', prop: 'orderCode'}, {label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'}, {label: '服务内容', prop: 'serviceName'},
@@ -1166,25 +1156,7 @@ export default {
{label: '超时时间', prop: 'time'}, {label: '超时时间', prop: 'time'},
{label: '超时原因', prop: 'reason'}, {label: '超时原因', prop: 'reason'},
] ]
} else */ } else if (this.active === 8) {//
if (this.active === 5) {//
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '二级合同名称', prop: 'contractName'},
{label: '案件创建时间', prop: 'orderCreateTime'},
{label: '催促创建时间 ', prop: 'urgeTime'},
{label: '服务商接单时间 ', prop: 'taskAcceptTime'},
{label: '备注内容(催促内容) ', prop: 'urgeRemark'},
{label: '催促次数', prop: 'urgeCount'},//
{label: '服务商名称', prop: 'supplierName'},
{label: '服务车辆', prop: 'vehicleName'},
{label: '服务完成状况', prop: 'orderStatusString'},
{label: '案件完成时间', prop: 'finishTime'},
{label: '省(B)', prop: 'bprovince'},
{label: '市(B)', prop: 'bcity'},
{label: '区(B)', prop: 'area'},
]
} else if (this.active === 6) {//
this.labelList = [ this.labelList = [
{label: '案件编号', prop: 'orderCode'}, {label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'}, {label: '服务内容', prop: 'serviceName'},
@@ -1193,7 +1165,7 @@ export default {
{label: '投诉类型', prop: 'complainTypeString'}, {label: '投诉类型', prop: 'complainTypeString'},
{label: '责任供应商扣罚金额', prop: 'compensateFee'}, {label: '责任供应商扣罚金额', prop: 'compensateFee'},
] ]
} else if (this.active === 7) {//使App } else if (this.active === 9) {//使App
this.labelList = [ this.labelList = [
{label: '案件编号', prop: 'orderCode'}, {label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'}, {label: '服务内容', prop: 'serviceName'},
@@ -1202,16 +1174,17 @@ export default {
{label: '案件创建时间', prop: 'orderCreateTime'}, {label: '案件创建时间', prop: 'orderCreateTime'},
{label: '事发地', prop: 'vehiclePointAddress'}, {label: '事发地', prop: 'vehiclePointAddress'},
] ]
} else if (this.active === 8) {//线 } else if (this.active === 10) {//线
this.labelList = [ this.labelList = [
// {label: '', prop: 'totalCount'}, {label: '日期', prop: 'date'},
{label: '在线车辆数(取每日的12点)', prop: 'twelveClockVehicleCount'}, {label: '0点在线车辆数', prop: 'zeroClockVehicleCount'},
{label: '在线拖车数(12点)', prop: 'trailCount'}, {label: '8点在线车辆数量', prop: 'eightClockVehicleCount'},
{label: '在线小修车数(12点)', prop: 'minorCount'}, {label: '12点在线车辆数量', prop: 'twelveClockVehicleCount'},
{label: '在线率(在线车辆数/总车辆数)', prop: 'onlineRate'}, {label: '16点在线车辆数', prop: 'sixteenClockVehicleCount'},
// {label: '线', prop: ''}, {label: '20点在线车辆数量', prop: 'twentyClockVehicleCount'},
{label: '22点在线车辆数量', prop: 'twentyTwoClockVehicleCount'},
] ]
} else if (this.active === 9) {// } else if (this.active === 11) {//线
this.labelList = [ this.labelList = [
{label: '案件编号', prop: 'orderCode'}, {label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'}, {label: '服务内容', prop: 'serviceName'},
@@ -1235,6 +1208,22 @@ export default {
}, },
// //
initDate() { initDate() {
/*let year=''
let month=''
if( this.current ) {
year = new Date(this.current).getFullYear();
month = new Date(this.current).getMonth() + 1;
} else {
year = new Date().getFullYear();
month = new Date().getMonth() + 1;
}
let yearAndMonth = year + '-' + (month > 9 ? month : '0' + month);
this.current = dayjs(new Date(yearAndMonth)).format('YYYY-MM')
this.startTime = dayjs(new Date()).format('YYYY-MM') + '-01 00:00:00'
this.endTime = dayjs(new Date()).format('YYYY-MM') + '-' + this.getDayLen()+' 23:59:59'*/
// test
// let _testDate = new Date('2024-11-01 00:00:01')
const today = dayjs(); // const today = dayjs(); //
const currentDay = today.date(); // 1-31 const currentDay = today.date(); // 1-31
let targetMonth = today; // let targetMonth = today; //
@@ -1251,6 +1240,7 @@ export default {
} }
this.endTime = endTime.format('YYYY-MM-DD HH:mm:ss'); this.endTime = endTime.format('YYYY-MM-DD HH:mm:ss');
this.startMonthTime=this.getStartTimeFromEndTime(this.endTime) this.startMonthTime=this.getStartTimeFromEndTime(this.endTime)
console.log(" this.startMonthTime", this.startMonthTime,this.startTime)
}, },
// //
getDayLen() { getDayLen() {
@@ -1305,12 +1295,12 @@ export default {
twoTabHanldeData() { twoTabHanldeData() {
this.etlDetailList=[] this.etlDetailList=[]
this.etlLabelList=[] this.etlLabelList=[]
if (this.active === 1) {// if (this.active === 1) {
const columnObj = {} // const columnObj = {} //
columnObj.label = 'KPI' // columnObj.label = 'KPI' //
columnObj.prop = 'month' // columnObj.prop = 'month' //
this.etlLabelList.push(columnObj) this.etlLabelList.push(columnObj)
/*if (this.activeIndex === 0) {//接单指标 if (this.activeIndex === 0) {//
this.etlDetailList=[{ 'month': '派遣量' },{ 'month': '承接量' },{ 'month': '完成量' }, this.etlDetailList=[{ 'month': '派遣量' },{ 'month': '承接量' },{ 'month': '完成量' },
{ 'month': '拒单量' },{ 'month': '拒单率(%)' },{ 'month': '超时接单量' },{ 'month': '超时率(%)' },{ 'month': '客户取消率(%)' }] { 'month': '拒单量' },{ 'month': '拒单率(%)' },{ 'month': '超时接单量' },{ 'month': '超时率(%)' },{ 'month': '客户取消率(%)' }]
let props = 'prop' // let props = 'prop' //
@@ -1346,22 +1336,20 @@ export default {
{label: '超时率(%)', prop: 'timeoutOrderRate'}, {label: '超时率(%)', prop: 'timeoutOrderRate'},
{label: '取消率(%)', prop: 'cancelRate'}, {label: '取消率(%)', prop: 'cancelRate'},
] ]
} else*/ } else if (this.activeIndex === 1) {
if (this.activeIndex === 0) { this.etlDetailList=[{ 'month': '投诉量' },{ 'month': '投诉率(%)' },{ 'month': '平安好评量' }, { 'month': '平安好评率' }]
// ,{ 'month': '' }, { 'month': '' } console.log('detailList', this.detailList)
this.etlDetailList=[{ 'month': '投诉量' },{ 'month': '投诉率(%)' }]
let props = 'prop' // let props = 'prop' //
this.detailList?.map((item,index) => { this.detailList?.map((item,index) => {
this.v1.push(item.complainOrderCount) this.v1.push(item.complainOrderCount)
this.v2.push(item.complainOrderRate?.replace('%', '')) this.v2.push(item.complainOrderRate?.replace('%', ''))
// this.v3.push(item?.pinganFavorableCount) this.v3.push(item?.pinganFavorableCount)
// this.v4.push(item.pinganFavorableRate?.replace('%', '')) this.v4.push(item.pinganFavorableRate?.replace('%', ''))
const columnObj = {} const columnObj = {}
columnObj.label = item.month // columnObj.label = item.month //
columnObj.prop = props + index // columnObj.prop = props + index //
this.etlLabelList.push(columnObj) this.etlLabelList.push(columnObj)
// 'pinganFavorableCount','pinganFavorableRate' let mappings = [ 'complainOrderCount', 'complainOrderRate', 'pinganFavorableCount','pinganFavorableRate' ];
let mappings = [ 'complainOrderCount', 'complainOrderRate' ];
for (let i = 0; i < mappings.length; i++) { for (let i = 0; i < mappings.length; i++) {
this.$set(this.etlDetailList[i], columnObj.prop, item[mappings[i]]); this.$set(this.etlDetailList[i], columnObj.prop, item[mappings[i]]);
} }
@@ -1370,23 +1358,22 @@ export default {
{label: '月份', prop: 'month'}, {label: '月份', prop: 'month'},
{label: '投诉量', prop: 'complainOrderCount'}, {label: '投诉量', prop: 'complainOrderCount'},
{label: '投诉率', prop: 'complainOrderRate'}, {label: '投诉率', prop: 'complainOrderRate'},
{label: '平安好评量', prop: 'pinganFavorableCount'},
{label: '平安好评率', prop: 'pinganFavorableRate'},
] ]
/* {label: '', prop: 'pinganFavorableCount'}, } else if (this.activeIndex === 2) {
{label: '平安好评率', prop: 'pinganFavorableRate'},*/ this.etlDetailList=[{ 'month': 'APP使用量' }, { 'month': 'APP使用率' }, { 'month': '平安聚合成功量' },{ 'month': '平安聚合成功率' },
} else if (this.activeIndex === 1) {
// { 'month': '' },{ 'month': '' },
this.etlDetailList=[{ 'month': 'APP使用量' }, { 'month': 'APP使用率' },
{ 'month': '中华联合聚合成功量' }, { 'month': '中华联合聚合成功率' }, { 'month': '整体聚合成功量' }, { 'month': '整体聚合成功率' }] { 'month': '中华联合聚合成功量' }, { 'month': '中华联合聚合成功率' }, { 'month': '整体聚合成功量' }, { 'month': '整体聚合成功率' }]
let props = 'prop' // let props = 'prop' //
this.detailList?.map((item,index) => { this.detailList?.map((item,index) => {
this.v1.push(item.appUseCount) this.v1.push(item.appUseCount)
this.v2.push(item.appRate.replace('%', '')) this.v2.push(item.appRate.replace('%', ''))
/*this.v3.push(item.pinganJuheSuccessCount) this.v3.push(item.pinganJuheSuccessCount)
this.v4.push(item.pinganPolymerizationSuccessRate.replace('%', ''))*/ this.v4.push(item.pinganPolymerizationSuccessRate.replace('%', ''))
this.v3.push(item.zhonghuaPolymerizationSuccessCount) this.v5.push(item.zhonghuaPolymerizationSuccessCount)
this.v4.push(item.zhonghuaPolymerizationSuccessRate.replace('%', '')) this.v6.push(item.zhonghuaPolymerizationSuccessRate.replace('%', ''))
this.v5.push(item.wholeJuheSuccessCount) this.v7.push(item.wholeJuheSuccessCount)
this.v6.push(item.wholeJuheSuccessRate.replace('%', '')) this.v7.push(item.wholeJuheSuccessRate.replace('%', ''))
const columnObj = {} const columnObj = {}
columnObj.label = item.month // columnObj.label = item.month //
columnObj.prop = props + index // columnObj.prop = props + index //
@@ -1396,20 +1383,19 @@ export default {
this.$set(this.etlDetailList[i], columnObj.prop, item[mappings[i]]); this.$set(this.etlDetailList[i], columnObj.prop, item[mappings[i]]);
} }
}) })
/*{label: '', prop: 'pinganJuheSuccessCount'},
{label: '平安聚合成功率', prop: 'pinganPolymerizationSuccessRate'},*/
this.labelList = [ this.labelList = [
{label: '月份', prop: 'month'}, {label: '月份', prop: 'month'},
{label: 'APP使用量', prop: 'appUseCount'}, {label: 'APP使用量', prop: 'appUseCount'},
{label: 'APP使用率', prop: 'appRate'}, {label: 'APP使用率', prop: 'appRate'},
{label: '平安聚合成功量', prop: 'pinganJuheSuccessCount'},
{label: '平安聚合成功率', prop: 'pinganPolymerizationSuccessRate'},
{label: '中华联合聚合成功量', prop: 'zhonghuaPolymerizationSuccessCount'}, {label: '中华联合聚合成功量', prop: 'zhonghuaPolymerizationSuccessCount'},
{label: '中华联合聚合成功率', prop: 'zhonghuaPolymerizationSuccessRate'}, {label: '中华联合聚合成功率', prop: 'zhonghuaPolymerizationSuccessRate'},
{label: '整体聚合成功量', prop: 'wholeJuheSuccessCount'}, {label: '整体聚合成功量', prop: 'wholeJuheSuccessCount'},
{label: '整体聚合成功率', prop: 'wholeJuheSuccessRate'}, {label: '整体聚合成功率', prop: 'wholeJuheSuccessRate'},
] ]
} else if (this.activeIndex === 2) { } else if (this.activeIndex === 3) {
// ,{'month':''} this.etlDetailList=[{ 'month': '接单时效' },{ 'month': '3分钟联系客户率' },{ 'month': '到达时效' },{'month':'平安聚合成功到达时效'}]
this.etlDetailList=[{ 'month': '接单时效' },{ 'month': '3分钟联系客户率' },{ 'month': '到达时效' }]
let props = 'prop' // let props = 'prop' //
if (this.isMobile) { if (this.isMobile) {
if (this.value == 1) { if (this.value == 1) {
@@ -1453,16 +1439,16 @@ export default {
}) })
} }
} }
// {label: '', prop: 'pinganJuheSuccessArriving'},
this.labelList = [ this.labelList = [
{label: '月份', prop: 'month'}, {label: '月份', prop: 'month'},
{label: '接单时效', prop: 'receiving'}, {label: '接单时效', prop: 'receiving'},
{label: '3分钟联系客户率', prop: 'threeMinutesContactRate'}, {label: '3分钟联系客户率', prop: 'threeMinutesContactRate'},
{label: '到达时效', prop: 'arriving'}, {label: '到达时效', prop: 'arriving'},
{label: '平安聚合成功到达时效', prop: 'pinganJuheSuccessArriving'},
] ]
} }
} else if (this.active === 2) {// } else if (this.active === 2) {
/*if (this.activeIndex === 0) {//接单指标 if (this.activeIndex === 0) {//
this.labelList = [ this.labelList = [
{label: '日', prop: 'date'}, {label: '日', prop: 'date'},
{label: '派单量', prop: 'dispatchOrderCount'}, {label: '派单量', prop: 'dispatchOrderCount'},
@@ -1474,8 +1460,7 @@ export default {
{label: '超时率', prop: 'timeoutOrderRate'}, {label: '超时率', prop: 'timeoutOrderRate'},
// {label: '(%)', prop: 'cancelRate'}, // {label: '(%)', prop: 'cancelRate'},
] ]
} else*/ } else if (this.activeIndex === 1) {
if (this.activeIndex === 0) {
this.labelList = [ this.labelList = [
{label: '日', prop: 'date'}, {label: '日', prop: 'date'},
{label: '投诉量', prop: 'complainOrderCount'}, {label: '投诉量', prop: 'complainOrderCount'},
@@ -1483,9 +1468,9 @@ export default {
/* {label: '', prop: 'favorableCount'}, /* {label: '', prop: 'favorableCount'},
{label: '好评率', prop: 'favorableRate'},*/ {label: '好评率', prop: 'favorableRate'},*/
{label: '平安评价率', prop: 'pinganEvaluateRate'}, {label: '平安评价率', prop: 'pinganEvaluateRate'},
// {label: '', prop: 'pinganFavorableRate'}, {label: '平安好评率', prop: 'pinganFavorableRate'},
] ]
} else if (this.activeIndex === 1) { } else if (this.activeIndex === 2) {
this.labelList = [ this.labelList = [
{label: '日', prop: 'date'}, {label: '日', prop: 'date'},
{label: 'APP使用量', prop: 'appUseCount'}, {label: 'APP使用量', prop: 'appUseCount'},
@@ -1497,17 +1482,21 @@ export default {
{label: '整体聚合成功量', prop: 'wholeJuheSuccessCount'}, {label: '整体聚合成功量', prop: 'wholeJuheSuccessCount'},
{label: '整体聚合成功率', prop: 'wholeJuheSuccessRate'}, {label: '整体聚合成功率', prop: 'wholeJuheSuccessRate'},
] ]
} else if (this.activeIndex === 2) { } else if (this.activeIndex === 3) {
this.labelList = [ this.labelList = [
{label: '日', prop: 'date'}, {label: '日', prop: 'date'},
{label: '接单时效(分)', prop: 'receiving'}, {label: '接单时效(分)', prop: 'receiving'},
{label: '3分钟联系客户率', prop: 'threeMinutesContactRate'}, {label: '3分钟联系客户率', prop: 'threeMinutesContactRate'},
{label: '到达时效(分)', prop: 'arriving'}, {label: '到达时效(分)', prop: 'arriving'},
// {label: '()', prop: 'pinganJuheSuccessArriving'}, {label: '平安聚合成功到达时效(分)', prop: 'pinganJuheSuccessArriving'},
] ]
} }
} else if (this.active === 3) {// }
/* if (this.activeIndex === 0) { /* else if(this.active === 3){
} */
else if (this.active === 4) {
if (this.activeIndex === 0) {
this.labelList = [ this.labelList = [
{label: '师傅姓名', prop: 'driverName'}, {label: '师傅姓名', prop: 'driverName'},
{label: '总分', prop: 'score'}, {label: '总分', prop: 'score'},
@@ -1535,100 +1524,92 @@ export default {
{label: '新手入列得分', prop: 'listedScore'}, {label: '新手入列得分', prop: 'listedScore'},
{label: '星级评分', prop: 'starRank'}, {label: '星级评分', prop: 'starRank'},
] ]
}else*/ }if (this.activeIndex === 1) {
/* if (this.activeIndex === 0) {
this.labelList = [ this.labelList = [
{label: '师傅姓名', prop: 'driverName'}, {label: '师傅姓名', prop: 'driverName'},
{label: '派单量', prop: 'dispatchOrderCount'}, {label: '承接案件量', prop: 'receiveOrderCount'},
{label: '接单量', prop: 'receiveOrderCount'},
{label: '拒单量', prop: 'refuseOrderCount'}, {label: '拒单量', prop: 'refuseOrderCount'},
{label: '拒单率(%)', prop: 'refuseOrderRate'}, {label: '拒单率(%)', prop: 'refuseOrderRate'},
{label: '超时量', prop: 'timeoutOrderCount'}, {label: '超时接单量', prop: 'timeoutOrderCount'},
{label: '超时率(%)', prop: 'timeoutOrderRate'}, {label: '超时率(%)', prop: 'timeoutOrderRate'},
] ]
} else */
if (this.activeIndex === 0) {
this.labelList = [
{label: '师傅姓名', prop: 'driverName'},
// {label: '', prop: 'receiveOrderCount'},
{label: '投诉量', prop: 'complainOrderCount'},
{label: '投诉率(%)', prop: 'complainOrderRate'},
{label: '平安评价量', prop: 'pinganEvaluateCount'},
{label: '平安评价率(%)', prop: 'pinganEvaluateRate'},
/* {label: '', prop: 'pinganFavorableCount'},
{label: '平安好评率(%)', prop: 'pinganFavorableRate'},*/
// {label: '(%)', prop: 'urgeRate'},
]
} else if (this.activeIndex === 1) {
this.labelList = [
{label: '师傅姓名', prop: 'driverName'},
{label: 'App使用量', prop: 'appUseCount'},
{label: 'App使用率(%)', prop: 'appRate'},
// {label: '3(%)', prop: 'threeMinutesContactRate'},
{label: '平安聚合成功量', prop: 'pinganPolymerizationSuccessCount'},
{label: '平安聚合成功率(%)', prop: 'pinganPolymerizationSuccessRate'},
{label: '中华联合聚合成功量', prop: 'zhonghuaPolymerizationSuccessCount'},
{label: '中华联合聚合成功率(%)', prop: 'zhonghuaPolymerizationSuccessRate'},
// {label: '(%)', prop: 'zdPolymerizationSuccessRate'},
{label: '整体聚合成功量', prop: 'wholeJuheSuccessCount'},
{label: '整体聚合成功率(%)', prop: 'wholeJuheSuccessRate'},
// {label: '线(h)', prop: 'onlineDuration'},
]
} else if (this.activeIndex === 2) { } else if (this.activeIndex === 2) {
this.labelList = [
{label: '师傅姓名', prop: 'driverName'},
{label: '承接案件量', prop: 'receiveOrderCount'},
{label: '投诉量', prop: 'complainOrderCount'},
{label: '投诉率(%)', prop: 'complainOrderRate'},
{label: '客户满意度(%)', prop: 'customerSatisfaction'},
{label: '客户评价率(%)', prop: 'customerEvaluateRate'},
{label: '催促率(%)', prop: 'urgeRate'},
]
} else if (this.activeIndex === 3) {
this.labelList = [
{label: '师傅姓名', prop: 'driverName'},
{label: '承接案件量', prop: 'receiveOrderCount'},
{label: 'App使用率(%)', prop: 'appRate'},
{label: '3′联系客户率(%)', prop: 'threeMinutesContactRate'},
{label: '平安聚合成功率(%)', prop: 'pinganPolymerizationSuccessRate'},
{label: '中华聚合成功率(%)', prop: 'zhonghuaPolymerizationSuccessRate'},
{label: '中道聚合成功率(%)', prop: 'zdPolymerizationSuccessRate'},
{label: '总聚合成功率(%)', prop: 'polymerizationSuccessRate'},
{label: '日均在线时长(h)', prop: 'onlineDuration'},
]
} else if (this.activeIndex === 4) {
this.labelList = [ this.labelList = [
{label: '师傅姓名', prop: 'driverName'}, {label: '师傅姓名', prop: 'driverName'},
{label: '接单时效(分)', prop: 'receiving'}, {label: '接单时效(分)', prop: 'receiving'},
{label: '3联系客户率(%)', prop: 'threeMinutesReceivingRate'}, {label: '3接单率(%)', prop: 'threeMinutesReceivingRate'},
{label: '到达时效(分)', prop: 'arriving'}, {label: '到达时效(分)', prop: 'arriving'},
// {label: '40(%)', prop: 'fortyMinutesArrivalRate'}, {label: '40′到达率(%)', prop: 'fortyMinutesArrivalRate'},
// {label: '()', prop: 'pinganJuheSuccessArriving'}, {label: '聚合成功到达时效(分)', prop: 'polymerizationSuccessArriving'},
] ]
} }
} else if (this.active === 4) {// } else if (this.active === 5) {
/*if (this.activeIndex === 0) {//接单指标 if (this.activeIndex === 0) {//
this.labelList = [ this.labelList = [
{label: '日', prop: 'date'}, {label: '日', prop: 'date'},
{label: '师傅姓名', prop: 'driverName'}, {label: '师傅姓名', prop: 'driverName'},
{label: '派量', prop: 'dispatchOrderCount'}, {label: '派遣案件量', prop: 'dispatchOrderCount'},
{label: '接单量', prop: 'receiveOrderCount'}, {label: '承接案件量', prop: 'receiveOrderCount'},
{label: '完成量', prop: 'finishOrderCount'},
{label: '拒单量', prop: 'refuseOrderCount'}, {label: '拒单量', prop: 'refuseOrderCount'},
{label: '拒单率(%)', prop: 'refuseOrderRate'}, {label: '拒单率(%)', prop: 'refuseOrderRate'},
{label: '超时量', prop: 'timeoutOrderCount'}, {label: '超时接单量', prop: 'timeoutOrderCount'},
{label: '超时率(%)', prop: 'timeoutOrderRate'}, {label: '超时率(%)', prop: 'timeoutOrderRate'},
] {label: '客户取消率(%)', prop: 'cancelRate'},
} else */
if (this.activeIndex === 0) {
this.labelList = [
{label: '日', prop: 'date'},
{label: '师傅姓名', prop: 'driverName'},
{label: '投诉量', prop: 'complainOrderCount'},
{label: '投诉率(%)', prop: 'complainOrderRate'},
{label: '平安评价量', prop: 'pinganEvaluateCount'},
{label: '平安评价率(%)', prop: 'pinganEvaluateRate'},
// {label: '', prop: 'pinganFavorableCount'},
// {label: '(%)', prop: 'pinganFavorableRate'},
] ]
} else if (this.activeIndex === 1) { } else if (this.activeIndex === 1) {
this.labelList = [ this.labelList = [
{label: '日', prop: 'date'}, {label: '日', prop: 'date'},
{label: '师傅姓名', prop: 'driverName'}, {label: '师傅姓名', prop: 'driverName'},
{label: 'App使用量', prop: 'appUseCount'}, {label: '投诉量', prop: 'complainOrderCount'},
{label: 'App使用率(%)', prop: 'appRate'}, {label: '投诉率(%)', prop: 'complainOrderRate'},
{label: '平安聚合成功量', prop: 'pinganPolymerizationSuccessCount'}, {label: '客户满意度(%)', prop: 'customerSatisfaction'},
{label: '平安聚合成功率(%)', prop: 'pinganPolymerizationSuccessRate'}, {label: '客户评价率(%)', prop: 'customerEvaluateRate'},
{label: '中华联合聚合成功量', prop: 'zhonghuaPolymerizationSuccessCount'}, {label: '催促率(%)', prop: 'urgeRate'},
{label: '中华联合聚合成功率(%)', prop: 'zhonghuaPolymerizationSuccessRate'},
{label: '整体聚合成功量', prop: 'wholeJuheSuccessCount'},
{label: '整体聚合成功率(%)', prop: 'wholeJuheSuccessRate'},
] ]
} else if (this.activeIndex === 2) { } else if (this.activeIndex === 2) {
this.labelList = [
{label: '日', prop: 'date'},
{label: '师傅姓名', prop: 'driverName'},
{label: '使用率(%)', prop: 'appRate'},
{label: '3′联系客户率(%)', prop: 'threeMinutesContactRate'},
{label: '平安聚合成功率(%)', prop: 'pinganPolymerizationSuccessRate'},
{label: '中华聚合成功率(%)', prop: 'zhonghuaPolymerizationSuccessRate'},
{label: '中道聚合成功率(%)', prop: 'zdPolymerizationSuccessRate'},
{label: '总聚合成功率(%)', prop: 'polymerizationSuccessRate'},
{label: '日均在线时长(h)', prop: 'onlineDuration'},
]
} else if (this.activeIndex === 3) {
this.labelList = [ this.labelList = [
{label: '日', prop: 'date'}, {label: '日', prop: 'date'},
{label: '师傅姓名', prop: 'driverName'}, {label: '师傅姓名', prop: 'driverName'},
{label: '接单时效(分)', prop: 'receiving'}, {label: '接单时效(分)', prop: 'receiving'},
{label: '3联系客户率(%)', prop: 'threeMinutesReceivingRate'}, {label: '3接单率(%)', prop: 'threeMinutesReceivingRate'},
{label: '到达时效(分)', prop: 'arriving'}, {label: '到达时效(分)', prop: 'arriving'},
// {label: '()', prop: 'pinganJuheSuccessArriving'}, {label: '40′到达率(%)', prop: 'fortyMinutesArrivalRate'},
{label: '聚合成功到达时效(分)', prop: 'polymerizationSuccessArriving'},
] ]
} }
} }
@@ -1640,32 +1621,30 @@ export default {
}, },
setType(type) { setType(type) {
switch (type) { switch (type) {
/*case 5: case 6:
return 1; return 1;
case 6:
return 2;*/
case 5:
return 7;
case 6:
return 3;
case 7: case 7:
return 4; return 2;
case 8: case 8:
return 5; return 3;
case 9: case 9:
return 4;
case 10:
return 5;
case 11:
return 6; return 6;
} }
}, },
getTitle(type) { getTitle(type) {
switch (type) { switch (type) {
case 0: case 0:
return '客户评价'; return '接单时效';
case 1: case 1:
return 'APP使用'; return '客户评价';
case 2: case 2:
return 'APP使用';
case 3:
return '时效'; return '时效';
/* case 3:
return '时效';*/
} }
}, },
} }
+20 -23
View File
@@ -13,20 +13,23 @@
</div> </div>
<div class="contentWrap"> <div class="contentWrap">
<div> <div>
为降低日常救援工作中的不可预期的车损赔付带来的运营风险和资金压力减少由此衍生而来的投诉顺应行业服务标准不断增长的局势中道救援自有车辆已全部投保了救援职业责任险 为降低日常救援工作中的不可预期的车损赔付带来的运营风险和资金压力减少由此衍生而来的可能投诉顺应行业服务标准不断增长的局势中道救援自有车辆已全部投保了救援职业责任险
</div> </div>
<div> <div>
通过交流了解我们的合作服务商日常运营中会遇到责任险或者货物险的投保理赔的问题为此我们与保司沟通协调特为与中道救援合作紧密的服务商提供救援职业责任险参保渠道解决大家的实际困难实现共同进步 通过日常的交流了解此险种对于服务商而言投保相对较很多服务商在无投保渠道的情况下选择了对于我们救援场景保障能力稍弱不太匹配的货物险从而从形成了现在很多救援车无保障或者特殊场景货物险理赔难的局面
</div>
<div>
为此中道救援经过与保司沟通协调特为与中道救援合作紧密的服务商提供救援职业责任险参保渠道提升服务商服务能力降低运营风险实现共同进步
</div> </div>
<div>救援职业责任险简介</div> <div>救援职业责任险简介</div>
<div> 1保额50万和100万两套方案可选</div> <div>1综合保额50万可全方位覆盖我们日常的经营活动</div>
<div> 2保费支付可采用救援服务费抵扣的方式降低资金压力</div> <div> 2保费支付可采用救援服务费抵扣的方式降低资金压力</div>
<div> 3其他详情如需了解请与辖区区域经理联系</div> <div> 3详细保险条款保费和其他问题如需了解请与辖区区域经理联系</div>
<div> 如有好的建议需要交流请与18621558505曲仁杰联系</div> <div> 其他意见建议如需交流请与18621558505曲仁杰联系</div>
<div> 有参保需要请点击下方参保按钮我们会与您联系</div> <div> 确认参保请点击下方参保按钮会有专人与贵司联系</div>
</div> </div>
<div class="upload_btn_wrap"> <div class="upload_btn_wrap">
<div class="btn_save" :style="{'backgroundColor':used ? '#cccccc' : '#354D93'}" @click="showPoup = true">我想咨询</div> <div class="btn_save" :style="{'backgroundColor':used ? '#cccccc' : '#354D93'}" @click="showPoup = true">参保</div>
</div> </div>
<div class="poupCommon" v-if="showPoup"> <div class="poupCommon" v-if="showPoup">
<div class="showPoupContainer"> <div class="showPoupContainer">
@@ -34,10 +37,12 @@
<img class="insure" src="@/assets/insured.png"> <img class="insure" src="@/assets/insured.png">
<div class="con"> <div class="con">
<template v-if="used"> <template v-if="used">
<div class="title">咨询已收到我们会与您联系</div> <div class="title">您已成功申请参保请等待</div>
<div class="title">专人与贵司联系</div>
</template> </template>
<template v-else> <template v-else>
<div class="title">请确认咨询</div> <div class="title">请确认</div>
<div class="title">是否参保救援职业责任险</div>
</template> </template>
<div class="btnWrap"> <div class="btnWrap">
<div v-if="used" class="btn confirmBtn" @click="showPoup = false">确认</div> <div v-if="used" class="btn confirmBtn" @click="showPoup = false">确认</div>
@@ -60,7 +65,6 @@ export default {
return { return {
showPoup: false, showPoup: false,
used:false, used:false,
saveLoading: true,
} }
}, },
async mounted() { async mounted() {
@@ -69,19 +73,12 @@ export default {
}, },
methods: { methods: {
async saveHandle() { async saveHandle() {
if( this.saveLoading ) { await sendInsuranceEmail();
try { this.$toast('操作成功');
this.saveLoading = false; this.showPoup=false
await sendInsuranceEmail(); setTimeout(()=>{
this.$toast('操作成功'); this.goBack()
this.showPoup = false },1500)
setTimeout(() => {
this.goBack()
}, 1500)
} finally {
this.saveLoading = true;
}
}
}, },
} }
} }
-496
View File
@@ -1,496 +0,0 @@
<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.roleDisplay" placeholder="请输入" />
</div>
</div>
<template v-if="item.hasRelationRole">
<div class="item" v-for="(row, rIndex) in item.relationRows" :key="'relation-' + rIndex">
<div class="left relationLeft" :class="{'opaCol':type!=2}"><span class="star" :style="{opacity: type==2 ? 1 : 0}">*</span><span>{{ item.relationRows.length > 1 ? row.base : '关系' }}</span></div>
<div class="right">
<van-field :input-align="type==2 ? 'right' : 'left'" :readonly="type!=2" v-model="row.relation" placeholder="请输入关系" />
</div>
</div>
</template>
<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';
// -
const RELATION_ROLES = ['其他', '法人关系人', '实控人关系人', '合伙人']
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" 使 "-"
// 11201 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.map(item => this.buildRoleDisplay(item))
}else{
this.personList=[]
}
},
// roleName `-` '-'
buildRoleDisplay(item){
const parts = (item.roleName || '').split(',').filter(Boolean).map(raw => {
const dashIndex = raw.indexOf('-')
if (dashIndex > -1 && RELATION_ROLES.includes(raw.substring(0, dashIndex))) {
return { raw, base: raw.substring(0, dashIndex), relation: raw.substring(dashIndex + 1) }
}
return { raw, base: raw, relation: null }
})
item.roleParts = parts
item.roleDisplay = parts.map(p => p.relation != null ? p.base : p.raw).join(',')
item.relationRows = parts.filter(p => p.relation != null)
item.hasRelationRole = item.relationRows.length > 0
return item
},
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
}
// roleDisplay + relation `-` roleName
for (const item of this.personList) {
if (item.hasRelationRole) {
for (const row of item.relationRows) {
if (!row.relation || !String(row.relation).trim()) {
this.$toast(`请填写${row.base}关系`)
return
}
}
item.roleName = item.roleParts.map(p => p.relation != null ? `${p.base}-${String(p.relation).trim()}` : p.raw).join(',')
}
}
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;
}
.item .left.relationLeft{
width: auto;
min-width: 66px;
white-space: nowrap;
padding-right: 4px;
}
.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>
@@ -78,11 +78,9 @@ export default {
isLoading:false, isLoading:false,
showPoup:true,// showPoup:true,//
isFinished:false, isFinished:false,
type: '',
} }
}, },
mounted() { mounted() {
this.type = this.$route.query?.type;
// this.onRefresh(); // this.onRefresh();
this.getList(); this.getList();
@@ -122,7 +120,6 @@ export default {
pageSize:this.pageSize, pageSize:this.pageSize,
docType:1, docType:1,
trainingType:4, trainingType:4,
type: this.type,
}) })
this.pageList=res.data?.list || []; this.pageList=res.data?.list || [];
if(res.data.assessState == 0){ if(res.data.assessState == 0){
@@ -2,7 +2,7 @@
<div class="bottom_wrap"> <div class="bottom_wrap">
<div class="report_item" v-for="(item, index) in reportList" @click="goReportHandler(item)" :key="index"> <div class="report_item" v-for="(item, index) in reportList" @click="goReportHandler(item)" :key="index">
<img class="report_icon" :src="item?.iconUrl" alt=""> <img class="report_icon" :src="item?.iconUrl" alt="">
<!-- <div class="report_title">{{item.name}}</div>--> <div class="report_title">{{item.name}}</div>
</div> </div>
</div> </div>
</template> </template>
@@ -43,8 +43,8 @@ export default {
margin-top: 20px; margin-top: 20px;
text-align: center; text-align: center;
.report_icon { .report_icon {
width: 60px; width: 45px;
height: 60px; height: 45px;
} }
.report_title { .report_title {
color: rgba(53, 53, 53, 0.67); color: rgba(53, 53, 53, 0.67);
-581
View File
@@ -1,581 +0,0 @@
<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>
+16 -72
View File
@@ -57,30 +57,16 @@
</div> </div>
</div> </div>
<report-list-item :report-list.sync="reportList" @getReport="getReportHandler" /> <report-list-item :report-list.sync="reportList" @getReport="getReportHandler" />
<van-popup ref="addReportModal" v-model="addReportShow" :close-on-click-overlay="false" z-index="500" closeable duration="0" round position="bottom"> <van-popup ref="addReportModal" v-model="addReportShow" :close-on-click-overlay="false" closeable duration="0" round position="bottom">
<div class="dialog_wrap"> <div class="dialog_wrap">
<img class="add_report_bg" src="@/assets/report/add_report_bg.png" alt=""> <img class="add_report_bg" src="@/assets/report/add_report_bg.png" alt="">
<div class="report_content"> <div class="report_content">
<div class="report_title" style="margin-bottom: 10px;"><span>{{currentInfo.name}}</span></div>
<template v-if="currentInfo.component == 'time'"> <template v-if="currentInfo.component == 'time'">
<!--预约时间报备 s --> <!--预约时间报备 s -->
<div class="report_title"> <div class="report_title">
<img class="dot" src="@/assets/report/add_report_dot.png" alt=""> <img class="dot" src="@/assets/report/add_report_dot.png" alt="">
<span>预约时间</span> <span>预约时间</span>
</div> </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_content" @click="showDatetime = true">
<div class="report_time_left"> <div class="report_time_left">
<img class="add_report_time" src="@/assets/report/add_report_time.png" alt=""> <img class="add_report_time" src="@/assets/report/add_report_time.png" alt="">
@@ -105,15 +91,17 @@
</div> </div>
<!--修改地址报备 e --> <!--修改地址报备 e -->
</template> </template>
<template v-if="currentInfo.component != 'time'"> <div class="report_title">
<div class="report_title"> <img class="dot" src="@/assets/report/add_report_dot.png" alt="">
<img class="dot" src="@/assets/report/add_report_dot.png" alt=""> <span>补充报备内容</span>
<span>补充反馈内容</span> </div>
</div> <div class="report_common_content">
<div class="report_common_content"> <textarea class="report_textarea" id="text-input" rows="4" placeholder="点击这里输入补充报备内容" v-model.trim="remark"></textarea>
<textarea class="report_textarea" id="text-input" rows="4" placeholder="点击这里输入补充反馈内容" v-model.trim="remark"></textarea> <!-- <van-field-->
</div> <!-- v-model="remark"-->
</template> <!-- class="report_textarea"-->
<!-- placeholder="点击这里输入补充报备内容" />-->
</div>
</div> </div>
<div class="report_btn" @click="saveHandler"> <div class="report_btn" @click="saveHandler">
提交报备 提交报备
@@ -121,16 +109,15 @@
</div> </div>
</van-popup> </van-popup>
<van-popup v-model="showDatetime" round position="bottom" :close-on-click-overlay="false"> <van-popup v-model="showDatetime" round position="bottom">
<van-datetime-picker <van-datetime-picker
class="dataTime" class="dataTime"
v-if="showDatetime" v-if="showDatetime"
v-model="appointTime" v-model="appointTime"
type="datetime" type="datetime"
:min-date="minDate" :min-date="minDate"
:filter="filter"
@confirm="onConfirm" @confirm="onConfirm"
@cancel="cancelHandler" @cancel="showDatetime = false"
/> />
</van-popup> </van-popup>
</div> </div>
@@ -168,12 +155,6 @@
driverId: '', driverId: '',
clickFlag: true, clickFlag: true,
isLoading: false, isLoading: false,
pickerOptions: {
//
disabledDate: (time) => {
return time.getTime() <= new Date().getTime();
}
}
} }
}, },
async mounted() { async mounted() {
@@ -198,12 +179,6 @@
} }
}, },
methods: { methods: {
filter(type, options) {
if (type === 'minute') {
return options.filter((option) => option % 5 === 0);
}
return options;
},
onRefreshHandler() { onRefreshHandler() {
setTimeout(async () => { setTimeout(async () => {
try { try {
@@ -263,10 +238,10 @@
data.lat = this.lat; data.lat = this.lat;
data.lng = this.lng; data.lng = this.lng;
} }
/*if( !this.remark ) { if( !this.remark ) {
Toast('备注内容不能为空') Toast('备注内容不能为空')
return return
}*/ }
this.clickFlag = false; this.clickFlag = false;
try { try {
await newOrderReporting(data) await newOrderReporting(data)
@@ -293,21 +268,15 @@
this.remark = ''; this.remark = '';
}, },
addReportHandler(data) { // addReportHandler(data) { //
console.log('currentInfocurrentInfo', data)
this.currentInfo = data; this.currentInfo = data;
this.remark = '';
this.addReportShow = true this.addReportShow = true
}, },
getReportHandler(data) { // getReportHandler(data) { //
console.log('dadadada', data.child)
this.recordList.push({ this.recordList.push({
messageType: 4, messageType: 4,
content: data.child content: data.child
}) })
this.scrollToBottom(); this.scrollToBottom();
if( data.child?.length == 1 ) {
this.addReportHandler(data.child[0])
}
}, },
async getDetail(){ // async getDetail(){ //
if(this.userOrderId) { if(this.userOrderId) {
@@ -330,10 +299,6 @@
onConfirm(data) { onConfirm(data) {
this.appointTime = timeFormat(new Date(data)) this.appointTime = timeFormat(new Date(data))
this.showDatetime = false; this.showDatetime = false;
},
cancelHandler() {
this.appointTime = ''
this.showDatetime = false
} }
} }
} }
@@ -570,9 +535,6 @@
margin-left: 5px; margin-left: 5px;
} }
} }
.owner_time {
padding: 0 !important;
}
.report_common_content { .report_common_content {
margin-bottom: 27px; margin-bottom: 27px;
.report_textarea { .report_textarea {
@@ -603,22 +565,4 @@
::v-deep .van-popup__close-icon { ::v-deep .van-popup__close-icon {
color: #0F458E; 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> </style>
@@ -109,7 +109,7 @@
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
console.log('window.location',window.location) console.log('window.location',window.location)
// console.log("urlParams",urlParams) // console.log("urlParams",urlParams)
console.log("decodeURIComponent( window.location.href)",decodeURIComponent( window.location.href)) // console.log("decodeURIComponent( window.location.href)",decodeURIComponent( window.location.href))
this.esignFlag = urlParams.get('esignFlag') || localStorage.getItem("esignFlag") this.esignFlag = urlParams.get('esignFlag') || localStorage.getItem("esignFlag")
if(window.location.search){ if(window.location.search){
console.log('有参数') console.log('有参数')
-15
View File
@@ -478,21 +478,6 @@ export default {
otherPhoto:this.otherImgSrc?.join(',') otherPhoto:this.otherImgSrc?.join(',')
}) })
this.clearStorageFormInfo() 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) console.log("车源发布publishCarInfo",res)
} catch (e){ } catch (e){
console.log('e',e) console.log('e',e)
+1 -36
View File
@@ -16,7 +16,7 @@
<img class="mine" src="@/assets/secondHandCar/mine.png" @click="goMine" /> <img class="mine" src="@/assets/secondHandCar/mine.png" @click="goMine" />
<el-input <el-input
@blur="getList" @blur="getList"
placeholder="牌照|车辆类型|落板|排放|品牌|描述|底座|刹车|自营" placeholder="请输入"
v-model.trim="topSearch"> v-model.trim="topSearch">
<img slot="suffix" class="search" src="@/assets/secondHandCar/search.png"/> <img slot="suffix" class="search" src="@/assets/secondHandCar/search.png"/>
</el-input> </el-input>
@@ -99,11 +99,6 @@
></i> ></i>
</template> </template>
</el-input> </el-input>
<div class="customSel wantSourceSel" @click.stop="toggleWantSource">
<img v-if="wantSource === 1" class="checkIcon" src="@/assets/secondHandCar/checked.png" />
<img v-else class="checkIcon" src="@/assets/secondHandCar/uncheck.png" />
<span :class="{'has-price': wantSource === 1}">想要的车源</span>
</div>
</div> </div>
<div class="contentWrap" v-show="!show"> <div class="contentWrap" v-show="!show">
<van-pull-refresh v-model="isLoading" @refresh="onRefresh" style="min-height:85vh"> <van-pull-refresh v-model="isLoading" @refresh="onRefresh" style="min-height:85vh">
@@ -228,7 +223,6 @@ export default {
showDatePicker: false, showDatePicker: false,
areaShow:false, areaShow:false,
isClearing:false, isClearing:false,
wantSource: 0,
minDate: new Date(1970, 0, 1), // 197011 minDate: new Date(1970, 0, 1), // 197011
maxDate: new Date(2099, 11, 31) // 20991231 maxDate: new Date(2099, 11, 31) // 20991231
} }
@@ -273,10 +267,6 @@ export default {
this.setSearchVal(); this.setSearchVal();
this.getList() this.getList()
}, },
wantSource(){
this.setSearchVal();
this.getList()
},
}, },
mounted() { mounted() {
this.getSearchVal(); this.getSearchVal();
@@ -291,9 +281,6 @@ export default {
}); });
}, },
methods:{ methods:{
toggleWantSource(){
this.wantSource = this.wantSource === 1 ? 0 : 1;
},
confirmHandle(val){ confirmHandle(val){
if(val[0].name.includes('市')){ if(val[0].name.includes('市')){
this.areaName=val[0].name this.areaName=val[0].name
@@ -369,7 +356,6 @@ export default {
sessionStorage.setItem('dateVal',this.dateVal); sessionStorage.setItem('dateVal',this.dateVal);
sessionStorage.setItem('brandModel',this.brandModel); sessionStorage.setItem('brandModel',this.brandModel);
sessionStorage.setItem('underpanBrand',this.underpanBrand); sessionStorage.setItem('underpanBrand',this.underpanBrand);
sessionStorage.setItem('wantSource',String(this.wantSource));
}, },
getSearchVal(){ getSearchVal(){
this.activeTab = Number(sessionStorage.getItem('indexActiveTab')) || 0; this.activeTab = Number(sessionStorage.getItem('indexActiveTab')) || 0;
@@ -385,7 +371,6 @@ export default {
this.dateVal=sessionStorage.getItem('dateVal') || ''; this.dateVal=sessionStorage.getItem('dateVal') || '';
this.brandModel=sessionStorage.getItem('brandModel') || ''; this.brandModel=sessionStorage.getItem('brandModel') || '';
this.underpanBrand=sessionStorage.getItem('underpanBrand') || ''; this.underpanBrand=sessionStorage.getItem('underpanBrand') || '';
this.wantSource=Number(sessionStorage.getItem('wantSource')) || 0;
}, },
goMine(){// goMine(){//
this.$router.push({ name: "mineRelease"}) this.$router.push({ name: "mineRelease"})
@@ -449,7 +434,6 @@ export default {
endTime:this.endTime, endTime:this.endTime,
brandModel:this.brandModel, brandModel:this.brandModel,
underpanBrand:this.underpanBrand, underpanBrand:this.underpanBrand,
wantSource:this.wantSource,
}) })
this.total=res.total this.total=res.total
if(this.pageNum == 1){// if(this.pageNum == 1){//
@@ -542,9 +526,6 @@ export default {
border-radius: 20px; border-radius: 20px;
border: 2px solid #264B94; border: 2px solid #264B94;
} }
::v-deep .el-input__inner::placeholder{
font-size: 10px;
}
::v-deep .el-input__suffix{ ::v-deep .el-input__suffix{
top: 12px; top: 12px;
right: 10px; right: 10px;
@@ -641,22 +622,6 @@ export default {
.has-price{ .has-price{
color: #007BE9 !important; color: #007BE9 !important;
} }
.wantSourceSel{
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
width: 68px;
cursor: pointer;
.checkIcon{
width: 13px;
height: 13px;
}
span{
font-size: 10px;
color: #323233;
}
}
} }
.contentWrap{ .contentWrap{
width: 100%; width: 100%;
+19 -101
View File
@@ -54,11 +54,6 @@
<span <span
class="content">{{ orderInfo.carClothingToolAmount }}</span> class="content">{{ orderInfo.carClothingToolAmount }}</span>
</div> </div>
<div class="item">
<span class="titleLeft">免拖</span>
<span
class="content">{{ orderInfo.customerDistanceLimit }} {{orderInfo.hedgingMileage && orderInfo.hedgingMileage > 0 ? '(超限对冲,只结免拖)' : ''}}</span>
</div>
<div class="item"> <div class="item">
<span class="titleLeft">换电瓶费用</span> <span class="titleLeft">换电瓶费用</span>
<span <span
@@ -77,35 +72,35 @@
<div class="inpInfo"> <div class="inpInfo">
<div class="titleContent"> <div class="titleContent">
<div class="flex-between" v-show="abState"> <div class="flex-between" v-show="abState">
<div><span style="color: red;">*</span> 到事发地距离:</div> <div> 到事发地距离:</div>
<div class="halfOpcity"><input @input="validateIntegerInput" class="ipt" type="number" v-model="form.supplierSettleMileageAb"/>公里</div> <div class="halfOpcity"><input @input="validateIntegerInput" class="ipt" type="number" v-model="form.supplierSettleMileageAb"/>公里</div>
</div> </div>
<div class="flex-between" v-show="bcState"> <div class="flex-between" v-show="bcState">
<div><span style="color: red;">*</span> 拖车里程:</div> <div> 拖车里程:</div>
<div class="halfOpcity"><input class="ipt" type="number" v-model="form.supplierSettleMileageBc"/>公里</div> <div class="halfOpcity"><input class="ipt" type="number" v-model="form.supplierSettleMileageBc"/>公里</div>
</div> </div>
<div class="flex-between" v-show="caState"> <div class="flex-between" v-show="caState">
<div><span style="color: red;">*</span>返程里程:</div> <div>返程里程:</div>
<div class="halfOpcity"><input class="ipt" type="number" v-model="form.supplierSettleMileageCa"/>公里</div> <div class="halfOpcity"><input class="ipt" type="number" v-model="form.supplierSettleMileageCa"/>公里</div>
</div> </div>
<div class="flex-between" v-show="bridgeAbState"> <div class="flex-between" v-show="bridgeAbState">
<div><span v-show="!isUnsuccessfulSettle" style="color: red;">*</span>ab路桥费:</div> <div>ab路桥费:</div>
<div class="halfOpcity"><input class="ipt" @input="validateIntegerInput" :disabled="isUnsuccessfulSettle" type="number" v-model="form.supplierBridgeAmountAb"/></div> <div class="halfOpcity"><input class="ipt" @input="validateIntegerInput" :disabled="isUnsuccessfulSettle" type="number" v-model="form.supplierBridgeAmountAb"/></div>
</div> </div>
<div class="flex-between" v-show="bridgeBcState"> <div class="flex-between" v-show="bridgeBcState">
<div><span v-show="!isUnsuccessfulSettle" style="color: red;">*</span>bc路桥费:</div> <div>bc路桥费:</div>
<div class="halfOpcity"><input class="ipt" @input="validateIntegerInput" :disabled="isUnsuccessfulSettle" type="number" v-model="form.supplierBridgeAmountBc"/></div> <div class="halfOpcity"><input class="ipt" @input="validateIntegerInput" :disabled="isUnsuccessfulSettle" type="number" v-model="form.supplierBridgeAmountBc"/></div>
</div> </div>
<div class="flex-between" v-show="bridgeCaState"> <div class="flex-between" v-show="bridgeCaState">
<div><span v-show="!isUnsuccessfulSettle" style="color: red;">*</span>ca路桥费:</div> <div>ca路桥费:</div>
<div class="halfOpcity"><input class="ipt" @input="validateIntegerInput" :disabled="isUnsuccessfulSettle" type="number" v-model="form.supplierBridgeAmountCa"/></div> <div class="halfOpcity"><input class="ipt" @input="validateIntegerInput" :disabled="isUnsuccessfulSettle" type="number" v-model="form.supplierBridgeAmountCa"/></div>
</div> </div>
<div class="flex-between" v-show="waitAmountState"> <div class="flex-between" v-show="waitAmountState">
<div><span style="color: red;">*</span>等候费:</div> <div>等候费:</div>
<div class="halfOpcity inputContent"><input class="ipt" @input="validateIntegerInput" type="number" v-model="form.supplierWaitAmount"/></div> <div class="halfOpcity inputContent"><input class="ipt" @input="validateIntegerInput" type="number" v-model="form.supplierWaitAmount"/></div>
</div> </div>
<div class="flex-between" v-show="tyreAmountState"> <div class="flex-between" v-show="tyreAmountState">
<div><span v-show="!isUnsuccessfulSettle" style="color: red;">*</span>小轮个数:</div> <div>小轮个数:</div>
<div class="halfOpcity inputContent"><input class="ipt" @input="validateIntegerInput" :disabled="isUnsuccessfulSettle" type="number" v-model="form.tyreNumber"/></div> <div class="halfOpcity inputContent"><input class="ipt" @input="validateIntegerInput" :disabled="isUnsuccessfulSettle" type="number" v-model="form.tyreNumber"/></div>
</div> </div>
<div class="flex-between" v-show="tyreAmountState"> <div class="flex-between" v-show="tyreAmountState">
@@ -113,11 +108,11 @@
<div class="halfOpcity inputContent"><input @input="validateIntegerInput" disabled type="number" v-model="form.supplierTyreAmount"/></div> <div class="halfOpcity inputContent"><input @input="validateIntegerInput" disabled type="number" v-model="form.supplierTyreAmount"/></div>
</div> </div>
<div class="flex-between" v-show="dilemmaAmountState && basePriceDisabled"> <div class="flex-between" v-show="dilemmaAmountState && basePriceDisabled">
<div><span style="color: red;">*</span>困境费:</div> <div>困境费:</div>
<div class="halfOpcity inputContent"><input class="ipt" @input="validateIntegerInput" type="number" v-model="form.supplierDilemmaAmount"/></div> <div class="halfOpcity inputContent"><input class="ipt" @input="validateIntegerInput" type="number" v-model="form.supplierDilemmaAmount"/></div>
</div> </div>
<div class="flex-between" v-show="basementFeeState"> <div class="flex-between" v-show="basementFeeState">
<div><span v-show="isBasementSettle && !isUnsuccessfulSettle" style="color: red;">*</span>地库费:</div> <div>地库费:</div>
<div class="halfOpcity inputContent"><input @input="validateIntegerInput" class="ipt" type="number" v-model="form.supplierBasementFee" :disabled="!isBasementSettle || isUnsuccessfulSettle" /></div> <div class="halfOpcity inputContent"><input @input="validateIntegerInput" class="ipt" type="number" v-model="form.supplierBasementFee" :disabled="!isBasementSettle || isUnsuccessfulSettle" /></div>
</div> </div>
<div class="flex-between"> <div class="flex-between">
@@ -125,7 +120,7 @@
<div class="halfOpcity inputContent"><input type="number" class="ipt" v-model="form.supplierCustomerAmount"/></div> <div class="halfOpcity inputContent"><input type="number" class="ipt" v-model="form.supplierCustomerAmount"/></div>
</div> </div>
<div class="flex-between bgDisable" :class=" { 'flex-between': true, 'bgDisable' : basePriceDisabled } "> <div class="flex-between bgDisable" :class=" { 'flex-between': true, 'bgDisable' : basePriceDisabled } ">
<div><span v-show="!basePriceDisabled && !isBasementSettle" style="color: red;">*</span>基本费用:</div> <div>基本费用:</div>
<div class="halfOpcity inputContent "><input class="disable" type="number" :readonly="basePriceDisabled || isBasementSettle" <div class="halfOpcity inputContent "><input class="disable" type="number" :readonly="basePriceDisabled || isBasementSettle"
v-model="form.supplierBasePrice"/><span></span></div> v-model="form.supplierBasePrice"/><span></span></div>
</div> </div>
@@ -498,7 +493,13 @@ export default {
console.log("ii", i) console.log("ii", i)
}, },
goback() { goback() {
this.$router.back() this.$router.push({
name: "workOrderReconciliation",
params: {
activeIndex: this.index,
queryTime: this.queryTime,
}
})
}, },
async compute() { async compute() {
if(this.orderInfo.contractSettleTypeString == '车主现收'){ if(this.orderInfo.contractSettleTypeString == '车主现收'){
@@ -656,49 +657,6 @@ export default {
this.goback() this.goback()
}, },
async save() { async save() {
const isBlank = (v) => v === '' || v === null || v === undefined || parseFloat(v) < 0
if (this.abState && isBlank(this.form.supplierSettleMileageAb)) {
this.$toast('请填写到事发地距离'); return;
}
if (this.bcState && isBlank(this.form.supplierSettleMileageBc)) {
this.$toast('请填写拖车里程'); return;
}
if (this.caState && isBlank(this.form.supplierSettleMileageCa)) {
this.$toast('请填写返程里程'); return;
}
if (this.bridgeAbState && !this.isUnsuccessfulSettle && isBlank(this.form.supplierBridgeAmountAb)) {
this.$toast('请填写ab路桥费'); return;
}
if (this.bridgeBcState && !this.isUnsuccessfulSettle && isBlank(this.form.supplierBridgeAmountBc)) {
this.$toast('请填写bc路桥费'); return;
}
if (this.bridgeCaState && !this.isUnsuccessfulSettle && isBlank(this.form.supplierBridgeAmountCa)) {
this.$toast('请填写ca路桥费'); return;
}
if (this.waitAmountState && isBlank(this.form.supplierWaitAmount)) {
this.$toast('请填写等候费'); return;
}
if (!this.auditFormShow && this.tyreAmountState && !this.isUnsuccessfulSettle && isBlank(this.form.tyreNumber)) {
this.$toast('请填写小轮个数'); return;
}
if (this.dilemmaAmountState && this.basePriceDisabled && isBlank(this.form.supplierDilemmaAmount)) {
this.$toast('请填写困境费'); return;
}
if (this.basementFeeState && this.isBasementSettle && !this.isUnsuccessfulSettle && isBlank(this.form.supplierBasementFee)) {
this.$toast('请填写地库费'); return;
}
if (!this.basePriceDisabled && !this.isBasementSettle && isBlank(this.form.supplierBasePrice)) {
this.$toast('请填写基本费用'); return;
}
if (
this.orderInfo.customerDistanceLimit &&
parseFloat(this.form.supplierSettleMileageBc) > parseFloat(this.orderInfo.customerDistanceLimit)
) {
if (!this.form.supplierCustomerAmount || parseFloat(this.form.supplierCustomerAmount) == 0) {
this.$toast('拖车里程超出免拖范围,请填写收取客户金额');
return;
}
}
if (this.form.supplierOtherAmount > 0) { if (this.form.supplierOtherAmount > 0) {
if (this.form.supplierOtherAmountRemark == '') { if (this.form.supplierOtherAmountRemark == '') {
this.$toast('请填写其他费用说明'); this.$toast('请填写其他费用说明');
@@ -736,7 +694,6 @@ export default {
await this.compute(); await this.compute();
const formData = new FormData(); const formData = new FormData();
let obj = this.form; let obj = this.form;
console.log('obj',obj)
Object.keys(obj).forEach((key) => { Object.keys(obj).forEach((key) => {
formData.append(key, obj[key]); formData.append(key, obj[key]);
}); });
@@ -768,45 +725,6 @@ export default {
this.$toast('已进入后续流程,不允许操作'); this.$toast('已进入后续流程,不允许操作');
return; return;
} }
const isBlank = (v) => v === '' || v === null || v === undefined || parseFloat(v) < 0
if (this.abState && isBlank(this.form.supplierSettleMileageAb)) {
this.$toast('请填写到事发地距离'); return;
}
if (this.bcState && isBlank(this.form.supplierSettleMileageBc)) {
this.$toast('请填写拖车里程'); return;
}
if (this.caState && isBlank(this.form.supplierSettleMileageCa)) {
this.$toast('请填写返程里程'); return;
}
if (this.bridgeAbState && !this.isUnsuccessfulSettle && isBlank(this.form.supplierBridgeAmountAb)) {
this.$toast('请填写ab路桥费'); return;
}
if (this.bridgeBcState && !this.isUnsuccessfulSettle && isBlank(this.form.supplierBridgeAmountBc)) {
this.$toast('请填写bc路桥费'); return;
}
if (this.bridgeCaState && !this.isUnsuccessfulSettle && isBlank(this.form.supplierBridgeAmountCa)) {
this.$toast('请填写ca路桥费'); return;
}
if (this.waitAmountState && isBlank(this.form.supplierWaitAmount)) {
this.$toast('请填写等候费'); return;
}
if (this.dilemmaAmountState && this.basePriceDisabled && isBlank(this.form.supplierDilemmaAmount)) {
this.$toast('请填写困境费'); return;
}
if (this.basementFeeState && this.isBasementSettle && !this.isUnsuccessfulSettle && isBlank(this.form.supplierBasementFee)) {
this.$toast('请填写地库费'); return;
}
if (!this.basePriceDisabled && !this.isBasementSettle && isBlank(this.form.supplierBasePrice)) {
this.$toast('请填写基本费用'); return;
}
if (
this.orderInfo.customerDistanceLimit &&
parseFloat(this.form.supplierSettleMileageBc) > parseFloat(this.orderInfo.customerDistanceLimit)
) {
if (!this.form.supplierCustomerAmount || parseFloat(this.form.supplierCustomerAmount) == 0) {
this.$toast('拖车里程超出免拖范围,请填写收取客户金额'); return;
}
}
this.loading1 = true; this.loading1 = true;
this.form.supplierExtraPrice = this.form.extraPrice || 0 this.form.supplierExtraPrice = this.form.extraPrice || 0
this.form.supplierBasePrice = this.form.basePrice || 0 this.form.supplierBasePrice = this.form.basePrice || 0
@@ -1024,7 +942,7 @@ img {
} }
.baseInfo { .baseInfo {
width: 100%; @include wh(100%, 344px);
} }