Files
supplier-dispatch-h5/src/views/index/vehicleAdd.vue
T

2041 lines
70 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="wrap vehicleAdd">
<div class="navBar">
<van-nav-bar
:title="id ? '修改车辆信息' : '添加车辆'"
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="uploadSlotWrap">
<van-uploader
ref="licenseFrontUploader"
v-model="vehicleLicenseFrontList"
:after-read="vehicleLicenseFrontHandler"
@delete="vehicleLicenseFrontDelete"
max-count="1"
:preview-size="54"
accept="image/*"
/>
<div class="slotClickMask" @click="onLicenseSlotClick('front')"></div>
</div>
</div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>行驶证副页</span>
</div>
<div class="uploadSlotWrap">
<van-uploader
ref="licenseBackUploader"
v-model="vehicleLicenseBackList"
:after-read="vehicleLicenseBackHandler"
@delete="vehicleLicenseBackDelete"
max-count="1"
:preview-size="54"
accept="image/*"
/>
<div class="slotClickMask" @click="onLicenseSlotClick('back')"></div>
</div>
</div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>行驶证车辆照片页</span>
</div>
<div class="uploadSlotWrap">
<van-uploader
ref="licenseCarUploader"
v-model="vehicleLicenseCarPhotoList"
:after-read="vehicleLicenseCarHandler"
@delete="vehicleLicenseCarPhotoDelete"
max-count="1"
:preview-size="54"
accept="image/*"
/>
<div class="slotClickMask" @click="onLicenseSlotClick('car')"></div>
</div>
</div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>车头照</span>
</div>
<van-uploader
v-model="vehicleFrontPhotoList"
:after-read="vehicleFrontPhotoHandler"
@delete="vehicleFrontPhotoDelete"
max-count="1"
:preview-size="54"
accept="image/*"
/>
</div>
<cell-group label="车牌号" placeholder="车牌号" disabled v-model="carNum" />
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>车辆类别</span>
</div>
<!--
<select
id="mySelect"
class="mySelect"
v-model="selectedOption"
multiple
>
<option
v-for="item in typeList"
:key="item"
:value="item"
:disabled="isOptionDisabled(item)"
>
{{ item }}
</option>
</select>
-->
<el-select
multiple
:multiple-limit="isMultiple ? 2 : 1"
v-model="selectedOption"
:disabled="vehicleInfoDisabled"
value-key="name"
class="elSelect"
collapse-tags="collapse-tags"
:popper-append-to-body="false"
placeholder="请选择" style="width: 55%"
>
<el-option
v-for="item in vehicleTypes"
:key="item.name"
:label="item.name"
:value="item.value"
:disabled="!item.disabled ? false : true"
>
</el-option>
</el-select>
</div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>车辆属性</span>
</div>
<div class="virtualVehicleField">
<el-select
v-model="virtualVehicle"
value-key="name"
class="elSelect"
collapse-tags="collapse-tags"
:disabled="vehicleInfoDisabled || virtualVehicleLocked"
placeholder="请选择" style="width: 55%"
>
<el-option
v-for="item in virtualVehicleOptions"
:key="item.name"
:label="item.name"
:value="item.value"
>
</el-option>
</el-select>
<span class="matchRoleArrow" :class="{expanded: matchRoleExpand}"
@click="matchRoleExpand = !matchRoleExpand"></span>
</div>
</div>
<div v-if="matchRoleExpand" class="matchRolePanel">
匹配角色{{ virtualVehicleMatchRole || '—' }}
</div>
<div class="lineBot"></div>
<div class="itemContent">
<div class="titleType">
<span>牌照</span>
</div>
<el-select
v-model="vehicleLicense"
disabled
value-key="name"
class="elSelect"
collapse-tags="collapse-tags"
placeholder="请选择" style="width: 55%"
>
<el-option
v-for="item in vehicleLicenseOptions"
: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>
<div class="isJoin">
<van-radio-group v-model="isJoin" @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>
<div class="lineBot"></div>-->
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>车辆状态</span>
</div>
<div class="isJoin">
<van-radio-group v-model="vehicleStatus" :class="{ 'disabled-tree': vehicleInfoDisabled || vehicleStatusDisabled }" :disabled="vehicleInfoDisabled || vehicleStatusDisabled" @change="isVehicleChange" class="joinWrap">
<van-radio :name="1" style="margin-right: 14px">
启用
<img
slot="icon"
slot-scope="props"
:src="props.checked ? activeIcon : inactiveIcon"
>
</van-radio>
<van-radio :name="2" style="margin-right: 14px">
停用
<img
slot="icon"
slot-scope="props"
:src="props.checked ? activeIcon : inactiveIcon"
>
</van-radio>
<van-radio :name="12" disabled>
-服务商停用
<img
slot="icon"
slot-scope="props"
:src="props.checked ? activeIcon : inactiveIcon"
>
</van-radio>
</van-radio-group>
</div>
</div>
<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" :disabled="disabledShow" 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>
<div class="item_content_btn" v-if="[2,3].includes(this.liabilityInsuranceAudit)">
<span @click="goContinueInsurance">续保更新</span>
</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"
:disabled="disabledShow"
:deletable="!disabledShow"
max-count="1"
:preview-size="54"
@click-preview="clickPreview"
/>
</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"
:disabled="disabledShow"
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="showDateHandler" v-model="dateVal" placeholder="请选择发布日期" :class="{'customSel':true,'customInput':true , 'has-value': dateVal }" >
<template #suffix>
<i
v-if="dateVal && !disabledShow"
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')">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>保费</span>
</div>
<van-field
type="number"
class="vanIpt"
v-model="liabilityInsuranceAmount"
input-align="right"
>
<template slot="right-icon" >
<span style="white-space: nowrap;"></span>
</template>
</van-field>
</div>
<div class="lineBot" v-if="permissonList?.includes('hasInsuranceAudit')"></div>
<div class="itemContent" v-if="permissonList?.includes('hasInsuranceAudit')">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>保额</span>
</div>
<div style="display:flex;align-items: center;justify-content: flex-end">
<el-select
v-model="liabilityInsuranceQuota"
value-key="name"
class="elSelect"
collapse-tags="collapse-tags"
placeholder="请选择" style="width: 55%"
>
<el-option
v-for="item in liabilityQuotaOptions"
:key="item.value"
:label="item.value"
:value="item.value"
>
</el-option>
</el-select><span style="margin-right: 16px;opacity: .5;">万元</span>
</div>
<!-- <van-field
type="number"
class="vanIpt"
v-model="liabilityInsuranceQuota"
input-align="right"
>
<template slot="right-icon" >
<span style="white-space: nowrap;">万元 </span>
</template>
</van-field>-->
</div>
<!-- <div class="lineBot"></div>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>保单号(救援)</span>
</div>
<van-field
class="vanIpt"
style="width: 72%"
v-model="insuranceCode"
input-align="right"
> </van-field>
</div>
<div class="lineBot"></div>
<div class="itemContent">
<div class="titleType"> <span>保单号(中道物流)</span> </div>
<van-field
class="vanIpt"
style="width: 65%"
v-model="insuranceCodeZd"
input-align="right"
> </van-field>
</div>
<div class="lineBot"></div>-->
</template>
<div class="itemContent">
<div class="titleType">
<img class="startImg" src="@/assets/start.png" />
<span>选择服务种类</span>
</div>
</div>
<div class="serviceType">
<el-tree
show-checkbox
node-key="id"
ref="tree"
:highlight-current="true"
:expand-on-click-node="false"
:class="{ 'disabled-tree': vehicleInfoDisabled }"
:data="supplierServiceList"
:props="defaultProps">
</el-tree>
<!--<div class="trailerService" v-for="(item,index) in supplierServiceList" :key="index">
<div class="serviceline" @click="loadAll(index)">
<img :ref="'serviceline' + index" :src="imageUrl" style="width: 20px;height: 15px" >
<span class="service">{{ item.name }}:</span>
<span class="line"></span>
</div>
<div :ref="'checkboxGroup' + index" class="checkbox-group">
<van-checkbox-group v-model="serviceIds" v-for="(item2,index2) in item.children" :key="index2" class="radioWrap">
<van-checkbox class="item" :name="item2.id">{{item2.name }}</van-checkbox>
</van-checkbox-group>
</div>
</div>-->
</div>
<two-common-btn class="btn" @cancelClick="back" @submitClick="submitBtn" />
<van-calendar v-model="showDatePicker" :min-date="minDate"
:max-date="maxDate" type="range" @confirm="onConfirm" />
</div>
<van-popup ref="success" v-model="approvalDialogShow">
<div class="pop_wrap">
<div class="pop_title">车辆提交审批</div>
<div class="pop_content">
<div class="iptWrap" v-if="vehicleInfoChange">
<div>车辆和服务备注</div>
<el-input placeholder="请输入车辆和服务备注" type="textarea" show-word-limit maxlength="200"
v-model="approvalForm.serviceRemark"></el-input>
</div>
<div class="iptWrap" v-if="insuranceChange">
<div>保单备注</div>
<el-input placeholder="请输入保单备注" type="textarea" show-word-limit maxlength="200"
v-model="approvalForm.insuranceRemark"></el-input>
</div>
</div>
<div class="tip_button_wrap">
<div class="continue close" @click="closeApproval">取消</div>
<div class="continue" :class="{'loading': loading}" @click="submitApprovalHandle">提交审批</div>
</div>
</div>
</van-popup>
</div>
</template>
<script>
// uploadImage
import {Dialog, ImagePreview} from "vant";
import {formatDate1} from "@/utils/common"
import {myMixins} from "@/utils/myMixins"
import {vehicleTypeList,saveVehicle,getInfoById,supplierServiceTree, uploadImage, ocrHandler,
userOperationPermissions,saveSupplierApproval,preJudgeVirtualVehicle as preJudgeVirtualVehicleApi} from "@/api/mine"
import TwoCommonBtn from "@/components/twoBtnCommon.vue"
import CellGroup from "@/components/cellGroup.vue";
export default {
name: "vehicleAdd",
mixins:[myMixins],
data(){
return{
defaultProps: {
children: 'children',
label: 'name'
},
minDate: new Date(1970, 0, 1), // 设置最小可选日期(1970年1月1日)
maxDate: new Date(2099, 11, 31), // 设置最大可选日期(2099年12月31日)
checkList: [],
virtualVehicle: '',
potentialFlag: new URLSearchParams(window.location.search).get('potentialFlag') || '',
vehicleStatus: new URLSearchParams(window.location.search).get('potentialFlag') === '1' ? 2 : '',
showDatePicker: false,
dateVal: '',
trailerService: '',//拖车服务
isJoin:'',//是否参与
hasLiabilityInsurance: '', // 有无责任险
liabilityInsuranceAudit: '',
activeIcon: require('@/assets/check.png'),
inactiveIcon: require('@/assets/uncheck.png'),
// result: [],
checked: true,
carNum:"",//车牌号
typeList:[],//车辆类型列表
selectedOption:[],//车辆类型
id:'',//车辆Id
supplierId:'',//服务商Id
serviceIds:[],//车辆服务种类,
supplierServiceList:[],
oldSupplierServiceList:[],
show:false,
imageUrl: require('@/assets/arr_right.png'),
cameraGuideType: 'front', // 当前拍摄槽位 front/back/car
vehicleLicenseFrontList: [],
vehicleLicenseBackList: [],
vehicleLicenseCarPhotoList: [],
vehicleFrontPhotoList: [],
insurancePictureFiles: [],
vehicleLicenseFront: '', // 行驶证首页
vehicleLicenseBack: '', // 行驶证副页
vehicleLicenseCarPhoto: '', // 行驶证车辆照片
vehicleFrontPhoto: '', // 车头照
insurancePicturePhoto: '', // 保单照片
vehicleLicenseInfo: {},
vehicleLicenseBackOcrFlag: false, // 行驶证副页 修改时默认不需要 ocr识别
isMultiple: false, // 是否支持多选
virtualVehicleLocked: false, // 车辆属性锁定(OCR所有人命中服务商信息时锁定为自有车辆)
virtualVehicleMatchRole: '', // 车辆属性自动判定命中的匹配角色
matchRoleExpand: false, // 匹配角色面板展开状态
vehicleLicense:'',//牌照
vehicleFrontLicensePlate:'',//车头照车牌号码
insuranceCorp: '',
auditStatus: '',
permissonList:[],
vehicleTypes:[{
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 },
],
vehicleLicenseOptions: [{
name: '蓝牌',
value: 1
}, {
name: '黄牌',
value: 2
}, {
name: '绿牌',
value: 3
}, {
name: '临牌',
value: 4
}, {
name: '其他',
value: 5
}, {
name: '黄绿牌',
value: 6
}],
virtualVehicleOptions: [{
name: '自有车辆',
value: 1
}, {
name: '挂靠车辆',
value: 2
}, {
name: '虚拟车辆',
value: 3
}, {
name: '借入车辆',
value: 4
}],
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
}],
liabilityQuotaOptions:[{value:10},{value:20},{value:30},{value:50},{value:70},{value:80},{value:100},{value:200}],
approvalForm:{
type:2,
supplierId:'',
vehicleId:'',
serviceChange:null,
serviceRemark:'',
insuranceChange:null,
insuranceRemark:'',
vehicleOtherChange:null,
vehicleOtherRemark:'',
},
originData:{},//记录更改之前的数据
changedFields: [], // 用于存储变更过的字段
approvalDialogShow:false,
vehicleInfoChange:false,
insuranceChange:false,
loading: false,
liabilityInsuranceAmount:'',
liabilityInsuranceQuota:'',
insuranceCode:'',
insuranceCodeZd:'',
}
},
computed: {
vehicleInfoDisabled() {
return [1,2].includes(this.auditStatus)
},
disabledShow() {
if (this.id) {
return !this.permissonList.includes('hasInsuranceAudit') && [2,3].includes(this.liabilityInsuranceAudit)
} else {
return false
}
},
vehicleStatusDisabled() {
return !this.id && this.potentialFlag === '1';
}
},
watch:{
selectedOption(newVal){
if(newVal == 4){
this.isMultiple = true
this.vehicleTypes[2].disabled=true
this.vehicleTypes[5].disabled=true
this.vehicleTypes[6].disabled=true
this.vehicleTypes[7].disabled=true
this.vehicleTypes[8].disabled=true
this.vehicleTypes[9].disabled=true
this.vehicleTypes[10].disabled=true
}else{
this.isMultiple = false
this.vehicleTypes[2].disabled=false
this.vehicleTypes[5].disabled=false
this.vehicleTypes[6].disabled=false
this.vehicleTypes[7].disabled=false
this.vehicleTypes[8].disabled=false
this.vehicleTypes[9].disabled=false
this.vehicleTypes[10].disabled=false
}
if (newVal == 1 || newVal == 7 || newVal == 9){
let arr = []
this.supplierServiceList.forEach((item)=>{
if(!(item.name == '拖车服务' || item.name == '大型车救援')){
arr.push(item)
}
})
this.supplierServiceList = arr
}else{
this.supplierServiceList = this.oldSupplierServiceList
}
this.setDefault();
},
},
async mounted() {
await this.initPage()
this.hasInited = true // 标记首次初始化完成(keep-alive 下 activated 据此判断再次进入需重新初始化)
},
activated() {
// 从拍摄页返回:取回拍照结果/降级信息,保留表单不重新初始化
const cam = this.$store.state.camera
if (cam.result || cam.fallback) {
const { result, fallback } = cam
this.$store.commit('camera/clear')
if (result) {
this.cameraGuideType = result.type
this.onCameraConfirm(result)
} else {
this.cameraGuideType = fallback.type
this.handleCameraFail(fallback.error)
}
return
}
// 从车辆管理等其他页面再次进入(keep-alive 缓存复用):按当前路由重新初始化
if (this.hasInited) {
this.initPage()
}
},
methods:{
async initPage() {
// 整体重置为初始 data(keep-alive 缓存复用时清空上一次的新增/修改数据,含 watcher 改过的 vehicleTypes.disabled),再按当前模式初始化
Object.assign(this.$data, this.$options.data.call(this, this))
this.id=this.$route.params?.id
this.approvalForm.supplierId=this.$route.params?.supplierId
const urlParams = new URLSearchParams(window.location.search);
const rawId = this.$route.query.supplierId || urlParams.get('supplierId');
this.supplierId = rawId ? Number(rawId) : '';
await this.getSupplierServiceTree();
await this.getTypeList();
if( this.id){
await this.vehicleInfo()
}else{
// 新增:清空 el-tree 勾选状态(缓存复用时可能残留上一辆车的服务种类勾选)
this.$nextTick(() => {
this.$refs.tree && this.$refs.tree.setCheckedKeys([])
})
this.originData={
vehicleType:'',
vehicleStatus:'',
virtualVehicle:'',
serviceIds:[],
hasLiabilityInsurance:'',
insuranceCorp:'',
liabilityInsuranceStartTime:'',
liabilityInsuranceEndTime:'',
insurancePicturePhoto:''
}
// 复制新增:从 query 预填类别/属性/服务种类(id 为空,提交仍走新增分支)
this.prefillFromQuery()
}
},
// 复制新增:从 query 解析并回填车辆类别/属性/服务种类 3 项,不调任何接口;
// 字段缺失或为空时等价普通新增兜底;其余字段(照片/OCR/责任险/保单/车辆状态)保持重置后的空值
// 注意:SPA 内 $router.push 跳转时 $route.query 保留原始类型(copyFlag 为数字 1),
// 整页加载/刷新时则为字符串 "1",此处统一 String() 兼容两种来源
prefillFromQuery() {
const urlParams = new URLSearchParams(window.location.search);
const copyFlag = this.$route.query.copyFlag || urlParams.get('copyFlag');
if (String(copyFlag) !== '1') {
return
}
const vehicleType = this.$route.query.vehicleType || urlParams.get('vehicleType');
const virtualVehicle = this.$route.query.virtualVehicle || urlParams.get('virtualVehicle');
const serviceIds = this.$route.query.serviceIds || urlParams.get('serviceIds');
if (vehicleType) {
this.selectedOption = String(vehicleType).split(',').map(Number)
}
if (virtualVehicle) {
this.virtualVehicle = Number(virtualVehicle)
}
// selectedOption 赋值触发 watch(类别 disabled 重置、小修车过滤拖车服务节点、setDefault),
// 服务树勾选必须在 watch 过滤之后执行,$nextTick 回调注册顺序保证清空勾选 → watch 过滤 → 预填勾选
this.$nextTick(() => {
if (serviceIds) {
this.$refs.tree && this.$refs.tree.setCheckedKeys(String(serviceIds).split(',').map(Number))
}
})
},
showDateHandler() {
if( this.id ) { // 修改
if( this.permissonList.includes('hasInsuranceAudit') ) {
this.showDatePicker = true
}
} else {
this.showDatePicker = true
}
},
async getPermissions(){
let res = await userOperationPermissions();
this.permissonList = res.data
},
goContinueInsurance() {
this.$router.push({
name:'continueInsurance',
params:{
id: this.id
}
})
},
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)}`;
console.log('this.dateVal',this.dateVal)
let timeObj = this.formatDateTimeRange(this.dateVal)
console.log('timeObj',timeObj)
},
formatDateTimeRange(str) {
const [startStr, endStr] = str.split(' - ').map(s => s.trim());
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
};
},
setDefault() {
if( this.vehicleLicenseInfo?.vehicleType?.includes('小型') || this.vehicleLicenseInfo?.vehicleType?.includes('微型') || this.vehicleLicenseInfo?.vehicleType?.includes('普通') || this.vehicleLicenseInfo?.vehicleType?.includes('摩托') ) {
this.vehicleTypes.map(item => {
if( item.value == 1 || item.value == 9 ) {
item.disabled = false;
} else {
item.disabled = true;
}
})
this.$forceUpdate();
} else if( this.vehicleLicenseInfo?.vehicleType?.includes('载货') || this.vehicleLicenseInfo?.vehicleType?.includes('中型') || this.vehicleLicenseInfo?.vehicleType?.includes('重型')) {
this.vehicleTypes.map(item => {
if( item.value == 1 || item.value == 9 ) {
item.disabled = true;
} else {
item.disabled = false;
}
})
} else {
this.vehicleTypes.map(item => {
item.disabled = false;
})
this.$forceUpdate();
}
},
getVehicleLicense(color) {
color = color || ''
if( color == '蓝' ) {
this.vehicleLicense = 1
} else if(color == '黄') {
this.vehicleLicense = 2
} else if(color == '临牌') {
this.vehicleLicense = 4
} else if(color.includes('黄绿')) {
this.vehicleLicense = 6
} else if(color.includes('绿')) {
this.vehicleLicense = 3
} else {
this.vehicleLicense = 5
}
},
onOversize() {
this.$toast(`文件大小不能超过5M`)
},
// 将 base64 dataURL 转为 Blob,用于 APP WebView 只返回 content 的场景
dataURLToBlob(dataURL) {
const arr = dataURL.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
blobToDataURL(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = () => reject(new Error('读取压缩图片失败'));
reader.readAsDataURL(blob);
});
},
compressImage(file, maxSize = 5 * 1024 * 1024) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const originWidth = img.width;
const originHeight = img.height;
const tryQuality = (quality = 0.9) => {
canvas.width = originWidth;
canvas.height = originHeight;
ctx.clearRect(0, 0, originWidth, originHeight);
ctx.drawImage(img, 0, 0, originWidth, originHeight);
canvas.toBlob((blob) => {
if (!blob) return reject(new Error('压缩失败'));
if (blob.size <= maxSize || quality <= 0.1) {
if (blob.size <= maxSize) {
resolve(blob);
} else {
tryResolution();
}
return;
}
tryQuality(quality - 0.1);
}, 'image/jpeg', quality);
};
const tryResolution = (scale = 0.9, quality = 0.8) => {
const width = Math.floor(originWidth * scale);
const height = Math.floor(originHeight * scale);
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob((blob) => {
if (!blob) return reject(new Error('压缩失败'));
if (blob.size <= maxSize || scale <= 0.1) {
if (blob.size <= maxSize) {
resolve(blob);
} else {
reject(new Error('超过5M'));
}
return;
}
tryResolution(scale - 0.1, quality);
}, 'image/jpeg', quality);
};
tryQuality();
};
img.onerror = () => reject(new Error('图片加载失败'));
img.src = e.target.result;
};
reader.onerror = () => reject(new Error('文件读取失败'));
reader.readAsDataURL(file);
});
},
async compressFileItem(fileItem, maxSize = 5 * 1024 * 1024) {
let rawFile = fileItem?.file;
const rawContent = fileItem?.content;
// 打印可序列化的基础信息,VConsole 中对象会显示为空,但 name/size/type 能看到
console.log('rawFile info:', {
name: rawFile?.name,
size: rawFile?.size,
type: rawFile?.type,
constructor: rawFile?.constructor?.name
});
console.log('rawContent prefix:', typeof rawContent === 'string' ? rawContent.slice(0, 60) : rawContent);
// 部分 APP WebView 返回的文件对象异常或为空,但 content 是 base64,此时用 content 兜底
if ((!rawFile || !(rawFile instanceof File) || rawFile.size === 0) &&
typeof rawContent === 'string' && rawContent.startsWith('data:')) {
try {
const blob = this.dataURLToBlob(rawContent);
const ext = blob.type === 'image/png' ? 'png' : 'jpg';
rawFile = new File([blob], `upload_${Date.now()}.${ext}`, { type: blob.type || 'image/jpeg' });
console.log('fallback file from content, size:', rawFile.size);
} catch (e) {
console.error('content to file error:', e);
throw new Error('文件解析失败,请重新上传');
}
}
if (!rawFile) {
throw new Error('未获取到文件,请重新上传');
}
if (rawFile.size <= maxSize) return rawFile;
const blob = await this.compressImage(rawFile, maxSize);
if (blob.size > maxSize) {
throw new Error('超过5M');
}
const fileName = (rawFile.name || 'image').replace(/\.[^.]+$/, '') + '.jpg';
const compressedFile = new File([blob], fileName, { type: 'image/jpeg' });
fileItem.file = compressedFile;
fileItem.size = compressedFile.size;
fileItem.content = await this.blobToDataURL(blob);
const mb1 = (compressedFile.size / 1024 / 1024).toFixed(2);
console.log(`压缩后为:${mb1}M`)
return compressedFile;
},
isCameraSupported() { // 是否支持 getUserMedia(不支持则降级系统选择器)
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) && window.isSecureContext !== false
},
onLicenseSlotClick(type) { // 行驶证三槽位点击:已有图片→预览,无图片→跳拍摄页(不支持相机则降级系统选择器)
this.cameraGuideType = type
const listMap = {
front: 'vehicleLicenseFrontList',
back: 'vehicleLicenseBackList',
car: 'vehicleLicenseCarPhotoList'
}
const item = (this[listMap[type]] || [])[0]
const src = item && (item.url || item.content)
if (src) {
// 已有图片:预览(删除按钮在拦截层之上,可删除后重拍)
ImagePreview({ images: [src], closeable: true })
return
}
// 无图片:拍照
if (!this.isCameraSupported()) {
// 不支持 getUserMedia(如 iOS 微信、非 HTTPS、老 WebView):直接调系统选择器
this.triggerNativeUpload(type)
return
}
this.$router.push({ name: 'cameraGuide', query: { type } })
},
isCameraPermissionError(e) { // 是否为相机权限类错误
return !!(e && (e.name === 'NotAllowedError' || e.name === 'SecurityError' || e.name === 'PermissionDeniedError'))
},
handleCameraFail(e) { // 相机不可用统一处理:权限类弹确认框,其他异常提示后由用户点击触发相册(保证点击手势有效)
if (this.isCameraPermissionError(e)) {
Dialog.confirm({
title: '提示',
message: '相机权限未开启,无法使用拍摄引导。是否重新尝试开启相机?',
confirmButtonText: '重新尝试',
cancelButtonText: '从相册选择',
confirmButtonColor: '#354D93',
}).then(() => {
// 重新走前置探测,探测通过才打开拍摄引导
this.onLicenseSlotClick(this.cameraGuideType)
}).catch(() => {
this.triggerNativeUpload(this.cameraGuideType)
})
} else {
Dialog.alert({
title: '提示',
message: '当前设备无法调用相机拍摄,请从相册选择照片上传。',
confirmButtonText: '从相册选择',
confirmButtonColor: '#354D93',
}).then(() => {
this.triggerNativeUpload(this.cameraGuideType)
})
}
},
triggerNativeUpload(type) { // 主动触发对应 uploader 的原生文件选择
const refMap = { front: 'licenseFrontUploader', back: 'licenseBackUploader', car: 'licenseCarUploader' }
this.$nextTick(() => {
const uploader = this.$refs[refMap[type]]
const input = uploader && uploader.$el && uploader.$el.querySelector('.van-uploader__input')
if (input) input.click()
})
},
onCameraConfirm({ blob, dataURL }) { // 拍摄确认:构造 {file, content} 走现有压缩→上传→OCR 链路
const type = this.cameraGuideType
const file = new File([blob], `vehicle_license_${type}_${Date.now()}.jpg`, { type: 'image/jpeg' })
const fileItem = { file, content: dataURL }
const handlerMap = {
front: 'vehicleLicenseFrontHandler',
back: 'vehicleLicenseBackHandler',
car: 'vehicleLicenseCarHandler'
}
const listMap = {
front: 'vehicleLicenseFrontList',
back: 'vehicleLicenseBackList',
car: 'vehicleLicenseCarPhotoList'
}
// 写回 uploader 的 v-model 列表,使槽位缩略图回显
this[listMap[type]] = [fileItem]
this[handlerMap[type]](fileItem)
},
async vehicleLicenseFrontHandler(file) { // 上传 行驶证首页
console.log('file',file)
try {
const uploadFile = await this.compressFileItem(file);
console.log('uploadFile',uploadFile)
if (uploadFile.size > 5 * 1024 * 1024) {
this.onOversize();
return;
}
const formData = new FormData();
formData.append("file" , uploadFile);
let res = await uploadImage(formData)
this.vehicleLicenseFront = res.data;
await this.vehicleOcrHandler();
} catch (e) {
console.error('upload compress error:', e);
if (e && e.message === '超过5M') {
this.onOversize();
} else {
this.$toast(e && e.message ? e.message : '上传失败,请重试');
}
}
},
async vehicleLicenseBackHandler(file) { // 上传 行驶证副页
try {
this.vehicleLicenseBackOcrFlag = true;
const uploadFile = await this.compressFileItem(file);
if (uploadFile.size > 5 * 1024 * 1024) {
this.onOversize();
return;
}
const formData = new FormData();
formData.append("file" , uploadFile);
let res = await uploadImage(formData);
this.vehicleLicenseBack = res.data;
await this.vehicleBackOcrHandler();
} catch (e) {
console.error('upload compress error:', e);
if (e && e.message === '超过5M') {
this.onOversize();
} else {
this.$toast(e && e.message ? e.message : '上传失败,请重试');
}
}
},
async vehicleLicenseCarHandler(file) { // 上传 行驶证车辆照片
try {
const uploadFile = await this.compressFileItem(file);
if (uploadFile.size > 5 * 1024 * 1024) {
this.onOversize();
return;
}
const formData = new FormData();
formData.append("file" , uploadFile);
let res = await uploadImage(formData);
this.vehicleLicenseCarPhoto = res.data;
} catch (e) {
console.error('upload compress error:', e);
if (e && e.message === '超过5M') {
this.onOversize();
} else {
this.$toast(e && e.message ? e.message : '上传失败,请重试');
}
}
},
async vehicleFrontPhotoHandler(file) { // 上传 车头照
try {
const uploadFile = await this.compressFileItem(file);
if (uploadFile.size > 5 * 1024 * 1024) {
this.onOversize();
return;
}
const formData = new FormData();
formData.append("file" , uploadFile);
let res = await uploadImage(formData);
this.vehicleFrontPhoto = res.data;
await this.ocrCarFrontHandler()
} catch (e) {
console.error('upload compress error:', e);
if (e && e.message === '超过5M') {
this.onOversize();
} else {
this.$toast(e && e.message ? e.message : '上传失败,请重试');
}
}
},
vehicleLicenseFrontDelete() { // 删除 行驶证首页
this.vehicleLicenseFront = '';
},
vehicleLicenseBackDelete() { // 删除 行驶证副页
this.vehicleLicenseBack = '';
this.vehicleLicenseBackOcrFlag = false;
},
vehicleLicenseCarPhotoDelete() { // 删除 行驶证车辆照片
this.vehicleLicenseCarPhoto = '';
},
vehicleFrontPhotoDelete() { // 删除 车头照
this.vehicleFrontPhoto = '';
},
clickPreview(){
console.log(' this.insurancePicturePhoto', this.insurancePicturePhoto)
if(this.insurancePicturePhoto && this.insurancePicturePhoto.indexOf('.pdf') !== -1){
let url=this.insurancePicturePhoto?.split(',')[0]
window.open(url.replace("http://", "https://"))
}
},
async insurancePictureFilesHandler(file) {
const formData = new FormData();
formData.append("file" , file.file);
let res = await uploadImage(formData);
this.insurancePicturePhoto = res.data;
console.log('insurancePictureFilesHandler',this.insurancePictureFiles)
},
async ocrCarFrontHandler() { // 车辆正面 orc 识别
let res = await ocrHandler({
ocrType: 10,
imageUrl: this.vehicleFrontPhoto,
cardSide: 'FRONT'
});
this.vehicleFrontLicensePlate=res.data?.number
this.getVehicleLicense(res.data?.color)
},
async vehicleOcrHandler() { // 行驶证首页 ocr 识别
this.vehicleLicenseInfo.licensePlateNumber = '';
this.vehicleLicenseInfo.vehicleType = '';
this.vehicleLicenseInfo.owner = '';
this.vehicleLicenseInfo.address = '';
this.vehicleLicenseInfo.useNature = '';
this.vehicleLicenseInfo.model = '';
this.vehicleLicenseInfo.vinCode = '';
this.vehicleLicenseInfo.engineNumber = '';
this.vehicleLicenseInfo.registrationDate = '';
this.vehicleLicenseInfo.issueDate = '';
this.vehicleLicenseInfo.issueAuthority = '';
let res = await ocrHandler({
ocrType: 3,
imageUrl: this.vehicleLicenseFront,
cardSide: 'FRONT'
})
if ( res?.data?.frontInfo ) {
let frontInfo = res?.data?.frontInfo;
this.carNum = frontInfo?.plateNo;
this.vehicleLicenseInfo.licensePlateNumber = frontInfo?.plateNo;
this.vehicleLicenseInfo.vehicleType = frontInfo?.vehicleType;
this.vehicleLicenseInfo.owner = frontInfo?.owner;
// 行驶证所有人识别成功后,预判车辆属性(命中服务商信息则锁定为自有车辆)
this.preJudgeVirtualVehicle();
this.vehicleLicenseInfo.address = frontInfo?.address;
this.vehicleLicenseInfo.useNature = frontInfo?.useCharacter;
this.vehicleLicenseInfo.model = frontInfo?.model;
this.vehicleLicenseInfo.vinCode = String(frontInfo?.vin || '').toUpperCase().replace(/O/g, '0');
this.vehicleLicenseInfo.engineNumber = frontInfo?.engineNo;
this.vehicleLicenseInfo.registrationDate = frontInfo?.registerDate;
this.vehicleLicenseInfo.issueDate = frontInfo?.issueDate;
this.vehicleLicenseInfo.issueAuthority = frontInfo?.seal;
this.setDefault();
this.checkDisabledItems();
}
},
checkDisabledItems() {
let _arr = [];
this.selectedOption.map(item => {
let _tempArr = this.vehicleTypes.filter(_item => _item.disabled && _item.value == item);
_arr = [..._arr, ..._tempArr]
})
if (_arr.length > 0) {
this.$toast(`车辆类别数据不合法:${_arr.map(item => item.name).join('、')}`)
return false
}
return true
},
// 车辆属性预判:行驶证所有人与车辆归属服务商的名称/法人/关联人员比对,命中自有则锁定选择器
async preJudgeVirtualVehicle() {
this.virtualVehicleLocked = false;
this.virtualVehicleMatchRole = '';
const owner = (this.vehicleLicenseInfo.owner || '').trim();
if (!owner) {
return;
}
try {
const res = await preJudgeVirtualVehicleApi({
owner,
supplierId: this.supplierId || undefined // 车辆归属服务商ID,自录场景为空由后端取当前用户服务商
});
if (res?.data?.locked) {
this.virtualVehicleLocked = true;
this.virtualVehicleMatchRole = res.data.matchRole || '';
this.virtualVehicle = 1;
}
} catch (e) {
// 预判失败不影响保存,选择器保持可编辑
}
},
async vehicleBackOcrHandler() { // 行驶证副页 ocr 识别
this.vehicleLicenseInfo.recordNumber = '';
this.vehicleLicenseInfo.passengerCapacity = '';
this.vehicleLicenseInfo.totalWeight = '';
this.vehicleLicenseInfo.curbWeight = '';
this.vehicleLicenseInfo.permittedWeight = '';
this.vehicleLicenseInfo.overallDimension = '';
this.vehicleLicenseInfo.tractionWeight = '';
this.vehicleLicenseInfo.backPlateNo = ''; // 行驶证副页 车牌号,仅用来判断 ocr 识别是否成功
this.vehicleLicenseInfo.energySign = '';
this.vehicleLicenseInfo.marks = ''
let res = await ocrHandler({
ocrType: 3,
imageUrl: this.vehicleLicenseBack,
cardSide: 'BACK'
})
if(res?.data?.backInfo) {
let backInfo = res?.data?.backInfo;
this.vehicleLicenseInfo.recordNumber = backInfo?.fileNo;
this.vehicleLicenseInfo.passengerCapacity = backInfo?.allowNum;
this.vehicleLicenseInfo.totalWeight = backInfo?.totalMass;
this.vehicleLicenseInfo.curbWeight = backInfo?.curbWeight;
this.vehicleLicenseInfo.permittedWeight = backInfo?.loadQuality;
this.vehicleLicenseInfo.overallDimension = backInfo?.externalSize;
this.vehicleLicenseInfo.tractionWeight = backInfo?.TotalQuasiMass;
this.vehicleLicenseInfo.backPlateNo = backInfo?.plateNo;
this.vehicleLicenseInfo.energySign = backInfo?.fuelType;
this.vehicleLicenseInfo.marks = backInfo?.marks
}
console.log('this.vehicle', this.vehicleLicenseInfo)
},
loadAll(i){
// console.log(i)
this.show = !this.show
const refName='checkboxGroup' + i;
const element1 = this.$refs[refName]; // 通过动态属性名访问引用this.$refs.checkboxGroup.style.display = 'block';
const refName1='serviceline' + i;
const element = this.$refs[refName1];
if(this.show){
element1[0].style.display = 'block'
element[0].src= require('@/assets/arr_bot.png')
}else {
element1[0].style.display = 'none'
element[0].src= require('@/assets/arr_right.png')
}
},
async getTypeList() {
let result=await vehicleTypeList();
this.typeList=[]
if(result.code === 200){
for (const key in result.data){
// console.log("keys",result.data[key])
this.typeList.push({name:result.data[key],disabled:false,value:(key+1)})
// this.typeList.push(result.data[key])
}
}
},
async getSupplierServiceTree(){
let res = await supplierServiceTree({ supplierId: this.supplierId });
this.supplierServiceList=res.data
this.oldSupplierServiceList=this.supplierServiceList
},
async vehicleInfo(){
let res= await getInfoById({
vehicleId:this.id
})
let result=res.data;
console.log("result",result)
this.id=result.vehicleId
this.approvalForm.vehicleId=this.id
this.carNum=result.plateNumber
this.isJoin=result.hasPolymerization.code
this.selectedOption=result.vehicleType?.split(',').map((item)=>{
return Number(item);
});
this.auditStatus = result.auditStatus;
this.virtualVehicle = result.virtualVehicle;
// 车辆属性溯源:编辑场景初始化匹配角色,已是自有车辆且命中角色时锁定选择器
this.virtualVehicleMatchRole = result.virtualVehicleMatchRole || '';
this.virtualVehicleLocked = !!(result.virtualVehicleMatchRole && result.virtualVehicle === 1);
this.$refs.tree.setCheckedKeys(result.serviceIds)
this.vehicleStatus = result.vehicleStatus
this.vehicleLicenseFront = result.vehicleLicenseFront;
this.vehicleLicenseBack = result.vehicleLicenseBack;
this.vehicleFrontPhoto = result.vehicleFrontPhoto;
this.insurancePicturePhoto = result.insurancePicturePhoto;
this.vehicleLicenseInfo = result.vehicleLicenseInfo;
this.vehicleLicenseCarPhoto = result.vehicleLicenseCarPhoto;
this.vehicleLicense = result.vehicleLicense;
this.vehicleFrontLicensePlate = result.vehicleFrontLicensePlate;
this.hasLiabilityInsurance = result.hasLiabilityInsurance
this.insuranceCorp = result.insuranceCorp;
this.liabilityInsuranceAudit = result.liabilityInsuranceAudit
if(this.insurancePicturePhoto) {
this.insurancePictureFiles = [{url: this.insurancePicturePhoto?.split(',')[0]}]
}
console.log('this.insurancePictureFiles',this.insurancePictureFiles)
if( this.vehicleLicenseFront ) {
this.vehicleLicenseFrontList = [{ url : this.vehicleLicenseFront }];
}
if(this.vehicleLicenseBack) {
this.vehicleLicenseBackList = [{ url : this.vehicleLicenseBack }];
}
if(this.vehicleFrontPhoto) {
this.vehicleFrontPhotoList = [{ url : this.vehicleFrontPhoto }];
}
if(this.vehicleLicenseCarPhoto) {
this.vehicleLicenseCarPhotoList = [{ url : this.vehicleLicenseCarPhoto }];
}
if(this.vehicleLicenseCarPhoto) {
this.vehicleLicenseCarPhotoList = [{ url : this.vehicleLicenseCarPhoto }];
}
if( result.liabilityInsuranceEndTime && result.liabilityInsuranceStartTime ) {
this.dateVal = formatDate1(result.liabilityInsuranceStartTime) + ' - ' + formatDate1(result.liabilityInsuranceEndTime)
}
this.originData={
vehicleType:result.vehicleType,
vehicleStatus:result.vehicleStatus,
virtualVehicle:result.virtualVehicle,
serviceIds:result.serviceIds,
hasLiabilityInsurance:result.hasLiabilityInsurance,
insuranceCorp:result.insuranceCorp,
liabilityInsuranceStartTime:result.liabilityInsuranceStartTime,
liabilityInsuranceEndTime:result.liabilityInsuranceEndTime,
insurancePicturePhoto:result.insurancePicturePhoto
}
},
isChange(e){
this.hasLiabilityInsurance=e
if (e === 0) {
Dialog.confirm({
title: '提醒',
message: '有救援职业责任险车辆会提升案件聚合能力和接单能力。',
confirmButtonText: '我已知晓', // 确认按钮文字,默认为“确认”
showCancelButton: false // 是否显示取消按钮,默认为 true。如果不需要取消按钮,可以设置为 false。
})
.then(() => { /* 确认后的操作 */ })
.catch(() => { /* 取消后的操作 */ });
}
},
isVehicleChange(e) {
this.vehicleStatus=e
},
/* async submitAuditHandle(){//提交审核
let urls=[]
this.insurancePictureFiles?.forEach(item => urls.push(item.url))
let time =this.dateVal ? this.formatDateTimeRange(this.dateVal) : ''
// let time1=time.endTime?.split(' ')
let newFormValue={
vehicleStatus:this.vehicleStatus,
virtualVehicle:this.virtualVehicle,
serviceIds:this.$refs.tree.getCheckedKeys(true) || [],
vehicleType:this.selectedOption?.join(',') || '',
hasLiabilityInsurance:this.hasLiabilityInsurance,
insuranceCorp:this.insuranceCorp,
insurancePicturePhoto:urls?.join(','),
// liabilityInsuranceStartTime:time.startTime ? time.startTime : '',
liabilityInsuranceEndTime:time.endTime ? time.endTime : '',
}
console.log('newFormValue',newFormValue)
let oldFormValue={...this.originData,}
console.log('oldFormValue',oldFormValue)
this.compareObjects(newFormValue,oldFormValue);
if( this.changedFields && this.changedFields.length>0){
const arr1Set = new Set(this.changedFields);
const change1=['vehicleStatus','virtualVehicle','vehicleType','serviceIds']
const change2=['hasLiabilityInsurance','insurancePicturePhoto','insuranceCorp','liabilityInsuranceStartTime','liabilityInsuranceEndTime']
const serviceChangeItems = change1.filter(item => arr1Set.has(item));
console.log('serviceChangeItems',serviceChangeItems)
if(serviceChangeItems?.length>0){
this.vehicleInfoChange=true
this.approvalForm.serviceChange={}
change1.forEach(key => {
this.approvalForm.serviceChange[key] = this[key];
if(key=='serviceIds'){
this.approvalForm.serviceChange[key]=this.$refs.tree.getCheckedKeys(true)
}
if(key=='vehicleType'){
this.approvalForm.serviceChange[key]=this.selectedOption?.join(',') || ''
}
});
}else {
this.vehicleInfoChange=false
this.approvalForm.serviceChange=null
}
const insuranceChangeItems = change2.filter(item => arr1Set.has(item));
console.log('insuranceChangeItems',insuranceChangeItems)
if(insuranceChangeItems?.length>0){
this.insuranceChange=true
this.approvalForm.insuranceChange={}
change2.forEach(key => {
this.approvalForm.insuranceChange[key] =this[key];
let time =this.dateVal ? this.formatDateTimeRange(this.dateVal) : ''
if(key=='liabilityInsuranceStartTime' && time && time.startTime){
this.approvalForm.insuranceChange[key]=time.startTime
}
if(key=='liabilityInsuranceEndTime' && time && time.endTime){
// let time1=time.endTime?.split(' ')
this.approvalForm.insuranceChange[key]= time.endTime
}
if(key=='insurancePicturePhoto' && this.insurancePictureFiles?.length>0){
let urls=[]
this.insurancePictureFiles?.forEach(item => urls.push(item.url))
this.approvalForm.insuranceChange[key]=urls?.join(',')
}
});
}else {
this.insuranceChange=false
this.approvalForm.insuranceChange=null
}
}
if((this.vehicleInfoChange || this.insuranceChange) && this.id){
// this.approvalDialogShow=true
this.submitApprovalHandle()
console.log('11111',this.approvalForm.serviceChange)
console.log('2222',this.approvalForm.insuranceChange)
}else {
this.approvalDialogShow=false
await this.submitBtn();
}
},*/
async submitApprovalHandle(){//提交审批-走接口
if(this.vehicleInfoChange){
if(!(this.selectedOption.length > 0)){
this.$toast('车辆类别不能为空')
return
}
let res = this.checkDisabledItems();
if(!res) {
return false
}
if( !this.virtualVehicle ) {
this.$toast('车辆属性不能为空')
return
}
if( !this.vehicleStatus ) {
this.$toast('车辆状态不能为空')
return
}
/* if(!this.approvalForm.serviceRemark){
this.$toast('车辆和服务备注不能为空')
return
}*/
}
if(this.insuranceChange){
if(this.hasLiabilityInsurance === null || this.hasLiabilityInsurance === undefined || this.hasLiabilityInsurance === '') { // 有职业责任险,就需要有保单照片
this.$toast('救援职业责任险不能为空')
return
}
if(!this.insurancePicturePhoto && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('保单照片不能为空')
return
}
if(this.selectedOption?.length==1 && this.selectedOption.includes(1)){//选择小修车时需清除原本的拖车服务类型
let data=this.oldSupplierServiceList.filter(item => item.name ==='拖车服务')
this.serviceIds = this.serviceIds.filter(item => !data[0].children.some(obj => obj.id === item));
}
if(!this.insuranceCorp && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('承保保司不能为空')
return
}
if(!this.dateVal && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('保单有效期不能为空')
return
}
/* if(!this.approvalForm.insuranceRemark){
this.$toast('保单备注不能为空')
return
}*/
}
try {
this.loading = true;
await saveSupplierApproval({...this.approvalForm})
} finally {
this.loading = false;
}
this.$toast('提交审批成功')
this.closeApproval()
setTimeout(()=>{
this.$router.back();
},2000)
},
compareObjects(newObj, oldObj) {
this.changedFields=[]
for (let key in newObj) {
if( Array.isArray(newObj[key])){
if(newObj[key]?.length != oldObj[key]?.length){
this.changedFields.push(key);
}else {
// console.log('newObj[key]',newObj[key])
if(newObj[key][0]?.id){
let flag=newObj[key].every((item, index) => item.id == oldObj[key][index]?.id);
// console.log('flag',flag)
if(!flag){
this.changedFields.push(key);
}
}else{
let flag=newObj[key].every((value, index) => value == oldObj[key][index]);
if(!flag){
this.changedFields.push(key);
}
}
}
}else{
let isEqual = (
(newObj[key] == null && oldObj[key] == 0) ||
(newObj[key] == 0 && oldObj[key] == null) ||
(newObj[key] == oldObj[key])
);
if (!isEqual) {
this.changedFields.push(key);
}
}
}
},
closeApproval(){
this.approvalForm.serviceRemark=''
this.approvalForm.insuranceRemark=''
this.approvalForm.vehicleOtherRemark=''
this.approvalDialogShow=false
this.vehicleInfoChange=false
this.insuranceChange=false
},
back() {
this.$router.back();
/*if( this.supplierId ) {
this.$router.back();
} else {
this.$router.push({
name:'vehicleManage',
params:{
id: this.id
}
})
}*/
},
validatePlateNumber(value) {
if (!value) {
return { valid: false, message: '请输入车牌号' }
}
const provinceList = ['京','津','冀','晋','蒙','辽','吉','黑','沪','苏','浙','皖','闽','赣','鲁','豫','鄂','湘','粤','桂','琼','渝','川','贵','云','藏','陕','甘','青','宁','新']
const firstChar = value.charAt(0)
if (!provinceList.includes(firstChar)) {
return { valid: false, message: '车牌号首位必须是省级简称(京/沪/津/渝/冀/晋/辽/吉/黑/苏/浙/皖/闽/赣/鲁/豫/鄂/湘/粤/琼/川/贵/云/陕/甘/青/蒙/桂/藏/宁/新)' }
}
if (!/^[\u4e00-\u9fa5][A-Z0-9]+$/.test(value)) {
return { valid: false, message: '车牌号只能包含汉字、字母和数字' }
}
if (![7, 8].includes(value.length)) {
return { valid: false, message: `车牌号必须为 7 位或 8 位(当前 ${value.length} 位)` }
}
return { valid: true, message: '' }
},
validateVinNo(value) {
const normalizedValue = String(value || '').toUpperCase()
if (!normalizedValue) {
return { valid: false, message: '车架号识别错误,请重新拍照上传。' }
}
if (!/^[A-NP-Z0-9]{17}$/.test(normalizedValue)) {
if (normalizedValue.includes('O')) {
return { valid: false, message: '车架号识别错误,请重新拍照上传。' }
}
if (!/^[A-NP-Z0-9]+$/.test(normalizedValue)) {
return { valid: false, message: '车架号识别错误,请重新拍照上传。' }
}
return { valid: false, message: `车架号识别错误,请重新拍照上传。` }
}
return { valid: true, message: '' }
},
async submitBtn(){
const plateResult = this.validatePlateNumber(this.carNum)
if (!plateResult.valid) {
Dialog.alert({ message: plateResult.message })
return
}
const vinCode = String(this.vehicleLicenseInfo.vinCode || '')
const vinResult = this.validateVinNo(vinCode)
if (!vinResult.valid) {
Dialog.alert({ message: vinResult.message })
return
}
this.vehicleLicenseInfo.vinCode = vinCode
if( !this.vehicleLicenseFront ) {
this.$toast('行驶证主页照片不能为空')
return
}
if( !this.vehicleLicenseBack ) {
this.$toast('行驶证副页照片不能为空')
return
}
if( !this.vehicleLicenseCarPhoto ) {
this.$toast('行驶证车辆照片页不能为空')
return
}
if( !this.vehicleFrontPhoto ) {
this.$toast('车头照片不能为空')
return
}
if( !this.vehicleLicenseInfo.licensePlateNumber ) {
this.$toast('行驶证主页识别失败')
return
}
if(this.vehicleLicenseBackOcrFlag && !this.vehicleLicenseInfo.backPlateNo ) {
this.$toast('行驶证副页识别失败')
return
}
if(!(this.selectedOption.length > 0)){
this.$toast('车辆类别不能为空')
return
}
let res = this.checkDisabledItems();
if(!res) {
return false
}
if( !this.virtualVehicle ) {
this.$toast('车辆属性不能为空')
return
}
if( !this.vehicleStatus ) {
this.$toast('车辆状态不能为空')
return
}
if(this.hasLiabilityInsurance === null || this.hasLiabilityInsurance === undefined || this.hasLiabilityInsurance === '') { // 有职业责任险,就需要有保单照片
this.$toast('救援职业责任险不能为空')
return
}
if(!this.insurancePicturePhoto && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('保单照片不能为空')
return
}
if(this.selectedOption?.length==1 && this.selectedOption.includes(1)){//选择小修车时需清除原本的拖车服务类型
let data=this.oldSupplierServiceList.filter(item => item.name ==='拖车服务')
this.serviceIds = this.serviceIds.filter(item => !data[0].children.some(obj => obj.id === item));
}
if(!this.insuranceCorp && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('承保保司不能为空')
return
}
if(!this.dateVal && this.hasLiabilityInsurance == 1) { // 有职业责任险,就需要有保单照片
this.$toast('保单有效期不能为空')
return
}
if( this.hasLiabilityInsurance == 1){
if(this.permissonList.includes('hasInsuranceAudit') && !this.liabilityInsuranceAmount && this.liabilityInsuranceAmount!=0){
this.$toast('保费不能为空')
return
}
if(this.permissonList.includes('hasInsuranceAudit') && !this.liabilityInsuranceQuota){
this.$toast('保额不能为空')
return
}
/* if(!this.insuranceCode){
this.$toast('保单号(救援)不能为空')
return
}*/
}
let timeObj;
if(this.dateVal) {
timeObj = this.formatDateTimeRange(this.dateVal)
}
this.serviceIds = this.$refs.tree.getCheckedKeys(true)
// console.log('1122',this.vehicleStatus)
// return
await saveVehicle({
vehicleId:this.id ? this.id : '',
supplierId: this.supplierId || undefined,
plateNumber:this.carNum ? this.carNum :'',
vehicleType:this.selectedOption?.length>0 ? this.selectedOption.join(',') : '',
// hasPolymerization:this.isJoin,
serviceIds:this.serviceIds ? this.serviceIds : [],
vehicleLicenseFront: this.vehicleLicenseFront,
vehicleLicenseBack: this.vehicleLicenseBack,
vehicleFrontPhoto: this.vehicleFrontPhoto,
vehicleLicenseInfo: this.vehicleLicenseInfo,
vehicleLicenseCarPhoto: this.vehicleLicenseCarPhoto,
vehicleLicense: this.vehicleLicense,
vehicleFrontLicensePlate:this.vehicleFrontLicensePlate,
hasLiabilityInsurance: this.hasLiabilityInsurance,
insurancePicturePhoto: this.hasLiabilityInsurance == 1 ? this.insurancePicturePhoto : '',
insuranceCorp: this.hasLiabilityInsurance == 1 ? this.insuranceCorp : '',
liabilityInsuranceStartTime: this.hasLiabilityInsurance == 1 ? (timeObj?.startTime || '' ) : '',
liabilityInsuranceEndTime: this.hasLiabilityInsurance == 1 ? (timeObj?.endTime || '') : '',
virtualVehicle: this.virtualVehicle,
canSubmitApproval:true,
vehicleStatus:this.vehicleStatus,
liabilityInsuranceAmount:this.liabilityInsuranceAmount,
liabilityInsuranceQuota:this.liabilityInsuranceQuota,
insuranceCode:this.insuranceCode,
insuranceCodeZd:this.insuranceCodeZd,
})
if(this.id){
this.$toast('修改成功')
}else{
this.$toast('添加成功')
}
setTimeout(()=>{
this.$router.back();
},2000)
}
},
components:{
CellGroup,
TwoCommonBtn
}
}
</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";
/* 行驶证三槽位透明点击拦截层 */
.uploadSlotWrap{
position: relative;
display: inline-block;
.slotClickMask{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
}
/* 已上传图片的删除按钮置于拦截层之上,保证可删除重传 */
::v-deep .van-uploader__preview-delete{
z-index: 11;
}
}
.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;
::v-deep .van-nav-bar--fixed {
z-index: 9999 !important;
}
}
.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;
}
/* 车辆属性-匹配角色溯源 */
.virtualVehicleField {
display: flex;
align-items: center;
justify-content: flex-end;
.matchRoleArrow {
margin-left: 6px;
cursor: pointer;
color: #909399;
user-select: none;
display: inline-block;
transition: transform .2s;
&.expanded {
transform: rotate(180deg);
color: #354D93;
}
}
}
.matchRolePanel {
margin: 0 25px 6px 0;
padding: 4px 10px;
font-size: 12px;
color: #606266;
background: #f4f4f5;
border-radius: 4px;
line-height: 1.6;
text-align: right;
}
.pop_wrap {
width: 340px;
box-sizing: border-box;
padding: 10px 15px;
position: relative;
.pop_title {
font-size: 14px;
font-weight: 500;
}
.pop_content {
width: 304px;
font-size: 14px;
color: #4C5361;
margin-left: 6px;
line-height: 25px;
.iptWrap{
margin: 10px 0;
}
}
.tip_button_wrap {
display: flex;
justify-content: flex-end;
.close {
border: 1px solid #ccc;
color: black;
background-color: #FFFFFF;
}
.continue {
padding: 6px 10px;
border-radius: 4px;
}
div:last-child{
background-color: #3266be;
color: #FFFFFF;
margin-left: 8px;
margin-right: 4px;
}
}
}
.loading {
position: relative;
}
.loading::after{
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border: 2px solid #000;
border-top-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.vanIpt{
width: 80%;
display: flex;
align-items: center;
}
.vanIpt1{
width: 72%;
}
</style>