2684624979
# Conflicts: # src/views/business/contract-manage.vue # src/views/business/temporary-credit-limit.vue # src/views/transportCapacity/vehicle.vue
1943 lines
65 KiB
Vue
1943 lines
65 KiB
Vue
<template>
|
||
<basic-container class="driver-page">
|
||
<avue-crud
|
||
:option="option"
|
||
:table-loading="loading"
|
||
:data="data"
|
||
v-model:page="page"
|
||
v-model="form"
|
||
ref="crud"
|
||
:permission="permissionList"
|
||
@search-change="searchChange"
|
||
@search-reset="searchReset"
|
||
@selection-change="selectionChange"
|
||
@current-change="currentChange"
|
||
@size-change="sizeChange"
|
||
@refresh-change="refreshChange"
|
||
@on-load="onLoad"
|
||
>
|
||
<template #menu-left>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-plus"
|
||
v-if="hasPermission('driver_add')"
|
||
@click="openDriver()"
|
||
>新增
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-download"
|
||
plain
|
||
v-if="hasPermission('driver_export')"
|
||
@click="handleExport"
|
||
>导出
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
icon="el-icon-delete"
|
||
plain
|
||
v-if="hasPermission('driver_delete')"
|
||
@click="handleDelete"
|
||
>批量删除
|
||
</el-button>
|
||
</template>
|
||
<template #header>
|
||
<div class="driver-page__expiry-tags">
|
||
<el-check-tag
|
||
v-for="item in expiryTagOptions"
|
||
:key="item.value"
|
||
:checked="query.expireStatus === item.value"
|
||
@change="handleExpireChange(item.value)"
|
||
>
|
||
{{ item.label }}({{ expiryStat[item.statKey] || 0 }})
|
||
</el-check-tag>
|
||
</div>
|
||
</template>
|
||
<template #driverName="{ row }">
|
||
<el-link type="primary" @click="openDriver(row, true)">{{ row.driverName }}</el-link>
|
||
</template>
|
||
<template #idCardNo="{ row }">
|
||
<span>{{ row.idCardNo || '-' }}</span>
|
||
</template>
|
||
<template #organizationName="{ row }">
|
||
{{ String(row.organizationName || '').replace(/^[\s\u3000]+/, '') }}
|
||
</template>
|
||
<template #status="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
|
||
{{ row.status === 1 ? '启用' : '停用' }}
|
||
</el-tag>
|
||
</template>
|
||
<template #drivingLicenseEndDate="{ row }">
|
||
<span
|
||
:class="{
|
||
'driver-page__date-expiring': isDateExpiring(
|
||
row,
|
||
'drivingLicenseEndDate',
|
||
'drivingLicenseLongTerm'
|
||
),
|
||
}"
|
||
>
|
||
{{ formatEndDate(row.drivingLicenseEndDate, row.drivingLicenseLongTerm) }}
|
||
</span>
|
||
</template>
|
||
<template #qualificationEndDate="{ row }">
|
||
<span
|
||
:class="{
|
||
'driver-page__date-expiring': isDateExpiring(
|
||
row,
|
||
'qualificationEndDate',
|
||
'qualificationLongTerm'
|
||
),
|
||
}"
|
||
>
|
||
{{ formatEndDate(row.qualificationEndDate, row.qualificationLongTerm) }}
|
||
</span>
|
||
</template>
|
||
<template #menu="{ row }">
|
||
<el-link type="primary" v-if="hasPermission('driver_view')" @click="openDriver(row, true)">
|
||
查看
|
||
</el-link>
|
||
<el-link type="primary" v-if="hasPermission('driver_edit')" @click="openDriver(row)">
|
||
修改
|
||
</el-link>
|
||
<el-link type="primary" v-if="hasPermission('driver_status')" @click="handleStatus(row)">
|
||
{{ row.status === 1 ? '停用' : '启用' }}
|
||
</el-link>
|
||
</template>
|
||
</avue-crud>
|
||
|
||
<el-dialog
|
||
:title="dialogTitle"
|
||
append-to-body
|
||
v-model="driverBox"
|
||
width="90%"
|
||
top="4vh"
|
||
class="driver-dialog"
|
||
@closed="resetDriver"
|
||
>
|
||
<el-form
|
||
ref="driverForm"
|
||
:model="driverForm"
|
||
:rules="formRules"
|
||
label-position="right"
|
||
label-width="calc(4em + 24px)"
|
||
:disabled="readonly"
|
||
class="driver-form archive-form"
|
||
>
|
||
<section-card title="基础身份信息">
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="司机姓名" prop="driverName">
|
||
<el-input
|
||
v-model="driverForm.driverName"
|
||
maxlength="10"
|
||
show-word-limit
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="身份证号" prop="idCardNo">
|
||
<el-input
|
||
v-model="driverForm.idCardNo"
|
||
maxlength="18"
|
||
show-word-limit
|
||
placeholder="请输入"
|
||
@input="handleIdCardInput"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="出生年月" prop="birthday">
|
||
<el-date-picker
|
||
v-model="driverForm.birthday"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="请选择"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="性别" prop="gender">
|
||
<el-select
|
||
v-model="driverForm.gender"
|
||
clearable
|
||
placeholder="请选择"
|
||
:validate-event="submitAttempted"
|
||
>
|
||
<el-option label="男" value="男" />
|
||
<el-option label="女" value="女" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="民族" prop="nation">
|
||
<el-select v-model="driverForm.nation" clearable filterable placeholder="请选择">
|
||
<el-option
|
||
v-for="item in nationOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="学历" prop="education">
|
||
<el-select
|
||
v-model="driverForm.education"
|
||
clearable
|
||
filterable
|
||
placeholder="请选择"
|
||
>
|
||
<el-option
|
||
v-for="item in educationOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="住址" prop="addressRegion">
|
||
<el-cascader
|
||
ref="addressRegionCascader"
|
||
v-model="driverForm.addressRegionPath"
|
||
:options="regionOptions"
|
||
:props="regionCascaderProps"
|
||
:placeholder="driverForm.addressRegion || '请选择'"
|
||
clearable
|
||
filterable
|
||
@change="handleAddressRegionChange"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="详细地址" prop="address">
|
||
<el-input
|
||
v-model="driverForm.address"
|
||
maxlength="200"
|
||
show-word-limit
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="驾驶车辆" prop="drivingVehicle">
|
||
<el-input
|
||
v-model="driverForm.drivingVehicle"
|
||
maxlength="30"
|
||
clearable
|
||
readonly
|
||
placeholder="请选择车辆"
|
||
:disabled="readonly"
|
||
@clear="driverForm.drivingVehicle = ''"
|
||
@click="!readonly && openVehicleSelector()"
|
||
>
|
||
<template #append>
|
||
<el-tooltip content="选择车辆" placement="top">
|
||
<el-button
|
||
:icon="List"
|
||
:disabled="readonly"
|
||
aria-label="选择车辆"
|
||
@click="openVehicleSelector"
|
||
/>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="岗位" prop="postList">
|
||
<el-checkbox-group v-model="driverForm.postList">
|
||
<el-checkbox label="司机" />
|
||
<el-checkbox label="押运员" />
|
||
<el-checkbox label="装卸管理员" />
|
||
</el-checkbox-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<image-upload-field
|
||
label="身份证正面照"
|
||
prop="idCardFront"
|
||
:value="driverForm.idCardFront"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
@success="url => handleIdCardUploadSuccess('idCardFront', url)"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<image-upload-field
|
||
label="身份证反面照"
|
||
prop="idCardBack"
|
||
:value="driverForm.idCardBack"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
@success="url => handleIdCardUploadSuccess('idCardBack', url)"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<image-upload-field
|
||
label="大头照"
|
||
prop="headPhoto"
|
||
:value="driverForm.headPhoto"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
@success="url => setImage('headPhoto', url)"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
</section-card>
|
||
|
||
<section-card title="驾驶证信息">
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="准驾车型" prop="drivingType">
|
||
<el-select
|
||
v-model="driverForm.drivingType"
|
||
clearable
|
||
filterable
|
||
allow-create
|
||
default-first-option
|
||
placeholder="请选择"
|
||
:validate-event="submitAttempted"
|
||
>
|
||
<el-option
|
||
v-for="item in drivingTypeOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="档案编号" prop="drivingLicenseNo">
|
||
<el-input
|
||
v-model="driverForm.drivingLicenseNo"
|
||
maxlength="50"
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="有效期" prop="drivingLicenseEndDate">
|
||
<div class="date-range">
|
||
<el-date-picker
|
||
v-model="driverForm.drivingLicenseStartDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="起"
|
||
:disabled="driverForm.drivingLicenseLongTerm === 1"
|
||
@change="validateFormField('drivingLicenseEndDate')"
|
||
/>
|
||
<el-date-picker
|
||
v-model="driverForm.drivingLicenseEndDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="止"
|
||
:disabled="driverForm.drivingLicenseLongTerm === 1"
|
||
@change="validateFormField('drivingLicenseEndDate')"
|
||
/>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="长期有效" prop="drivingLicenseLongTerm">
|
||
<el-checkbox
|
||
v-model="driverForm.drivingLicenseLongTerm"
|
||
:true-label="1"
|
||
:false-label="0"
|
||
@change="validateFormField('drivingLicenseEndDate')"
|
||
>
|
||
是
|
||
</el-checkbox>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<image-upload-field
|
||
label="驾驶证主页"
|
||
prop="drivingLicenseFront"
|
||
:value="driverForm.drivingLicenseFront"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
@success="
|
||
(url, file) => handleDrivingLicenseUploadSuccess('drivingLicenseFront', url, file)
|
||
"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<image-upload-field
|
||
label="驾驶证副页"
|
||
prop="drivingLicenseBack"
|
||
:value="driverForm.drivingLicenseBack"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
@success="
|
||
(url, file) => handleDrivingLicenseUploadSuccess('drivingLicenseBack', url, file)
|
||
"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
</section-card>
|
||
|
||
<section-card title="从业资格证信息">
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="从业资格认证类型" prop="qualificationType">
|
||
<el-select
|
||
v-model="driverForm.qualificationType"
|
||
clearable
|
||
filterable
|
||
placeholder="请选择"
|
||
:validate-event="submitAttempted"
|
||
>
|
||
<el-option
|
||
v-for="item in qualificationTypeOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="资格证号" prop="qualificationNo">
|
||
<el-input
|
||
v-model="driverForm.qualificationNo"
|
||
maxlength="50"
|
||
placeholder="默认身份证号"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="有效期" prop="qualificationEndDate">
|
||
<div class="date-range">
|
||
<el-date-picker
|
||
v-model="driverForm.qualificationStartDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="起"
|
||
:disabled="driverForm.qualificationLongTerm === 1"
|
||
@change="validateFormField('qualificationEndDate')"
|
||
/>
|
||
<el-date-picker
|
||
v-model="driverForm.qualificationEndDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="止"
|
||
:disabled="driverForm.qualificationLongTerm === 1"
|
||
@change="validateFormField('qualificationEndDate')"
|
||
/>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="长期有效" prop="qualificationLongTerm">
|
||
<el-checkbox
|
||
v-model="driverForm.qualificationLongTerm"
|
||
:true-label="1"
|
||
:false-label="0"
|
||
@change="validateFormField('qualificationEndDate')"
|
||
>
|
||
是
|
||
</el-checkbox>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<image-upload-field
|
||
label="封面页"
|
||
prop="qualificationFront"
|
||
:value="driverForm.qualificationFront"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
@success="url => handleQualificationUploadSuccess('qualificationFront', url)"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<image-upload-field
|
||
label="内容页"
|
||
prop="qualificationBack"
|
||
:value="driverForm.qualificationBack"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
@success="url => handleQualificationUploadSuccess('qualificationBack', url)"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
</section-card>
|
||
|
||
<section-card title="司机联系信息">
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="司机类型" prop="driverType">
|
||
<el-select v-model="driverForm.driverType" filterable placeholder="请选择">
|
||
<el-option label="自有" value="自有" />
|
||
<el-option label="外协" value="外协" />
|
||
<el-option label="承运管理员" value="承运管理员" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="手机号" prop="mobile">
|
||
<el-input
|
||
v-model="driverForm.mobile"
|
||
maxlength="20"
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="紧急联系人姓名" prop="emergencyContactName">
|
||
<el-input
|
||
v-model="driverForm.emergencyContactName"
|
||
maxlength="20"
|
||
placeholder="请输入"
|
||
@input="driverForm.emergencyContactName = removeDigits($event)"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="紧急联系人手机号" prop="emergencyContactMobile">
|
||
<el-input
|
||
v-model="driverForm.emergencyContactMobile"
|
||
maxlength="20"
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="与联系人关系" prop="contactRelation">
|
||
<el-select
|
||
v-model="driverForm.contactRelation"
|
||
clearable
|
||
filterable
|
||
allow-create
|
||
default-first-option
|
||
placeholder="请选择"
|
||
>
|
||
<el-option
|
||
v-for="item in relationOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="所属组织" prop="organizationName">
|
||
<el-cascader
|
||
v-model="organizationCascaderValue"
|
||
:options="organizationTreeOptions"
|
||
:props="organizationCascaderProps"
|
||
filterable
|
||
clearable
|
||
placeholder="请选择"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</section-card>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="driverBox = false">取消</el-button>
|
||
<el-button type="primary" v-if="!readonly" :loading="submitLoading" @click="handleSubmit">
|
||
确定
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
v-model="vehicleSelectorVisible"
|
||
title="选择车辆"
|
||
append-to-body
|
||
width="92%"
|
||
top="4vh"
|
||
class="vehicle-selector-dialog"
|
||
@closed="resetVehicleSelector"
|
||
>
|
||
<avue-crud
|
||
v-if="vehicleSelectorVisible"
|
||
ref="vehicleSelectorCrud"
|
||
v-model="vehicleSelectorForm"
|
||
v-model:page="vehicleSelectorPage"
|
||
:option="vehicleSelectorOption"
|
||
:table-loading="vehicleSelectorLoading"
|
||
:data="vehicleSelectorData"
|
||
@search-change="vehicleSelectorSearchChange"
|
||
@search-reset="vehicleSelectorSearchReset"
|
||
@current-change="vehicleSelectorCurrentChange"
|
||
@size-change="vehicleSelectorSizeChange"
|
||
@refresh-change="vehicleSelectorRefreshChange"
|
||
@on-load="loadVehicleSelector"
|
||
>
|
||
<template #plateNo="{ row }">
|
||
<span>{{ row.plateNo || '-' }}</span>
|
||
</template>
|
||
<template #organizationName="{ row }">
|
||
{{ String(row.organizationName || '').replace(/^[\s\u3000]+/, '') }}
|
||
</template>
|
||
<template #boundDriver="{ row }">
|
||
<span>{{ row.boundDriver || '-' }}</span>
|
||
</template>
|
||
<template #certificationStatus="{ row }">
|
||
<el-tag :type="getVehicleCertificationStatus(row).type" class="status-text">
|
||
{{ getVehicleCertificationStatus(row).label }}
|
||
</el-tag>
|
||
</template>
|
||
<template #status="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
|
||
{{ row.status === 1 ? '启用' : '停用' }}
|
||
</el-tag>
|
||
</template>
|
||
<template #drivingLicenseEndDate="{ row }">
|
||
{{ formatEndDate(row.drivingLicenseEndDate, row.drivingLicenseLongTerm) }}
|
||
</template>
|
||
<template #roadTransportCertEndDate="{ row }">
|
||
{{ formatEndDate(row.roadTransportCertEndDate, row.roadTransportCertLongTerm) }}
|
||
</template>
|
||
<template #annualReviewEndDate="{ row }">
|
||
{{ formatEndDate(row.annualReviewEndDate, row.annualReviewLongTerm) }}
|
||
</template>
|
||
<template #menu="{ row }">
|
||
<el-link type="primary" @click="selectDrivingVehicle(row)">选择</el-link>
|
||
</template>
|
||
</avue-crud>
|
||
</el-dialog>
|
||
</basic-container>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapGetters } from 'vuex';
|
||
import NProgress from 'nprogress';
|
||
import { ElLoading } from 'element-plus';
|
||
import { List } from '@element-plus/icons-vue';
|
||
import { option } from '@/option/transportCapacity/driver';
|
||
import { option as transportVehicleOption } from '@/option/transportCapacity/transport-vehicle';
|
||
import {
|
||
getList,
|
||
getDetail,
|
||
submit,
|
||
remove,
|
||
changeStatus,
|
||
getExpiryStat,
|
||
recognitionIDCard,
|
||
recognitionTransportCertificates,
|
||
recognizeBaiduOcr,
|
||
} from '@/api/transportCapacity/driver';
|
||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||
import { getDeptTree } from '@/api/system/dept';
|
||
import organizationSearch from '@/mixins/organization-search';
|
||
import { getDictionary } from '@/api/system/dictbiz';
|
||
import { getLazyTree } from '@/api/base/region';
|
||
import { exportBlob } from '@/api/common';
|
||
import { getToken } from '@/utils/auth';
|
||
import { getUploadHeaders } from '@/utils/upload';
|
||
import { downloadXls } from '@/utils/util';
|
||
import ImageUploadField from '@/components/image-upload-field/main.vue';
|
||
|
||
const vehicleSelectorOption = {
|
||
...transportVehicleOption,
|
||
height: 430,
|
||
selection: false,
|
||
addBtn: false,
|
||
editBtn: false,
|
||
delBtn: false,
|
||
viewBtn: false,
|
||
columnBtn: false,
|
||
menuWidth: 90,
|
||
menuFixed: 'right',
|
||
column: transportVehicleOption.column.map(column => ({
|
||
...column,
|
||
dicData: Array.isArray(column.dicData)
|
||
? column.dicData.map(item => ({ ...item }))
|
||
: column.dicData,
|
||
})),
|
||
};
|
||
|
||
const emptyForm = () => ({
|
||
id: '',
|
||
driverName: '',
|
||
idCardNo: '',
|
||
birthday: '',
|
||
gender: '',
|
||
nation: '',
|
||
education: '',
|
||
addressRegionPath: [],
|
||
addressRegion: '',
|
||
address: '',
|
||
drivingVehicle: '',
|
||
postList: ['司机'],
|
||
posts: '',
|
||
idCardFront: '',
|
||
idCardBack: '',
|
||
headPhoto: '',
|
||
drivingType: '',
|
||
drivingLicenseNo: '',
|
||
drivingLicenseStartDate: '',
|
||
drivingLicenseEndDate: '',
|
||
drivingLicenseLongTerm: 0,
|
||
drivingLicenseFront: '',
|
||
drivingLicenseBack: '',
|
||
qualificationType: '',
|
||
qualificationNo: '',
|
||
qualificationStartDate: '',
|
||
qualificationEndDate: '',
|
||
qualificationLongTerm: 0,
|
||
qualificationFront: '',
|
||
qualificationBack: '',
|
||
driverType: '自有',
|
||
mobile: '',
|
||
contactRelation: '',
|
||
organizationName: '',
|
||
emergencyContactName: '',
|
||
emergencyContactMobile: '',
|
||
status: 1,
|
||
});
|
||
|
||
export default {
|
||
components: {
|
||
ImageUploadField,
|
||
},
|
||
mixins: [organizationSearch],
|
||
data() {
|
||
return {
|
||
option,
|
||
List,
|
||
form: {},
|
||
query: {
|
||
expireStatus: '',
|
||
},
|
||
searchForm: {},
|
||
loading: true,
|
||
submitLoading: false,
|
||
data: [],
|
||
ids: '',
|
||
selectionList: [],
|
||
page: {
|
||
pageSize: 10,
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
expiryStat: {
|
||
total: 0,
|
||
within30: 0,
|
||
expired: 0,
|
||
},
|
||
driverBox: false,
|
||
vehicleSelectorVisible: false,
|
||
vehicleSelectorLoading: false,
|
||
vehicleSelectorOption,
|
||
vehicleSelectorForm: {},
|
||
vehicleSelectorData: [],
|
||
vehicleSelectorSearchForm: {},
|
||
vehicleSelectorPage: {
|
||
pageSize: 10,
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
readonly: false,
|
||
submitAttempted: false,
|
||
driverForm: emptyForm(),
|
||
drivingLicenseUploads: {},
|
||
uploadHeaders: getUploadHeaders(),
|
||
nationOptions: [
|
||
'汉族',
|
||
'蒙古族',
|
||
'回族',
|
||
'藏族',
|
||
'维吾尔族',
|
||
'苗族',
|
||
'彝族',
|
||
'壮族',
|
||
'布依族',
|
||
'朝鲜族',
|
||
'满族',
|
||
'侗族',
|
||
'瑶族',
|
||
'白族',
|
||
'土家族',
|
||
'哈尼族',
|
||
'哈萨克族',
|
||
'傣族',
|
||
'黎族',
|
||
'傈僳族',
|
||
'佤族',
|
||
'畲族',
|
||
'高山族',
|
||
'拉祜族',
|
||
'水族',
|
||
'东乡族',
|
||
'纳西族',
|
||
'景颇族',
|
||
'柯尔克孜族',
|
||
'土族',
|
||
'达斡尔族',
|
||
'仫佬族',
|
||
'羌族',
|
||
'布朗族',
|
||
'撒拉族',
|
||
'毛南族',
|
||
'仡佬族',
|
||
'锡伯族',
|
||
'阿昌族',
|
||
'普米族',
|
||
'塔吉克族',
|
||
'怒族',
|
||
'乌孜别克族',
|
||
'俄罗斯族',
|
||
'鄂温克族',
|
||
'德昂族',
|
||
'保安族',
|
||
'裕固族',
|
||
'京族',
|
||
'塔塔尔族',
|
||
'独龙族',
|
||
'鄂伦春族',
|
||
'赫哲族',
|
||
'门巴族',
|
||
'珞巴族',
|
||
'基诺族',
|
||
],
|
||
educationOptions: ['初中', '高中', '中专', '大专', '本科', '硕士及以上'],
|
||
drivingTypeOptions: ['A1', 'A2', 'A3', 'B1', 'B2', 'C1', 'C2'],
|
||
qualificationTypeOptions: [],
|
||
relationOptions: ['父母', '配偶', '子女', '兄弟姐妹', '朋友', '同事', '其他'],
|
||
organizationOptions: [],
|
||
regionOptions: [],
|
||
formRules: {
|
||
driverName: [{ required: true, message: '请输入司机姓名', trigger: 'blur' }],
|
||
idCardNo: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
|
||
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
|
||
postList: [
|
||
{
|
||
type: 'array',
|
||
required: true,
|
||
min: 1,
|
||
message: '请至少选择一个岗位',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
drivingType: [{ required: true, message: '请选择准驾车型', trigger: 'change' }],
|
||
drivingLicenseNo: [{ required: true, message: '请输入档案编号', trigger: 'blur' }],
|
||
drivingLicenseEndDate: [
|
||
{ required: true, validator: this.validateDrivingLicenseEndDate, trigger: 'change' },
|
||
],
|
||
qualificationType: [
|
||
{ required: true, message: '请选择从业资格认证类型', trigger: 'change' },
|
||
],
|
||
qualificationNo: [{ required: true, message: '请输入资格证号', trigger: 'blur' }],
|
||
qualificationEndDate: [
|
||
{ required: true, validator: this.validateQualificationEndDate, trigger: 'change' },
|
||
],
|
||
driverType: [{ required: true, message: '请选择司机类型', trigger: 'change' }],
|
||
mobile: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||
emergencyContactName: [
|
||
{ required: true, message: '请输入紧急联系人姓名', trigger: 'blur' },
|
||
{ validator: this.validateEmergencyContactName, trigger: 'blur' },
|
||
],
|
||
emergencyContactMobile: [
|
||
{ required: true, message: '请输入紧急联系人手机号', trigger: 'blur' },
|
||
],
|
||
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||
idCardFront: [{ required: true, message: '请上传身份证正面照', trigger: 'change' }],
|
||
idCardBack: [{ required: true, message: '请上传身份证反面照', trigger: 'change' }],
|
||
drivingLicenseFront: [{ required: true, message: '请上传驾驶证主页', trigger: 'change' }],
|
||
drivingLicenseBack: [{ required: true, message: '请上传驾驶证副页', trigger: 'change' }],
|
||
},
|
||
};
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission', 'userInfo']),
|
||
// 所属组织级联:表单存组织名称,级联控件用 id 路径,两者在此双向转换
|
||
organizationCascaderValue: {
|
||
get() {
|
||
return this.resolveOrgPath(this.driverForm.organizationName);
|
||
},
|
||
set(path) {
|
||
this.driverForm.organizationName = this.resolveOrgName(path);
|
||
},
|
||
},
|
||
isAdmin() {
|
||
const authority = this.userInfo.authority || '';
|
||
return authority.includes('admin');
|
||
},
|
||
permissionList() {
|
||
return {
|
||
addBtn: this.hasPermission('driver_add'),
|
||
viewBtn: this.hasPermission('driver_view'),
|
||
delBtn: this.hasPermission('driver_delete'),
|
||
editBtn: this.hasPermission('driver_edit'),
|
||
};
|
||
},
|
||
dialogTitle() {
|
||
if (this.readonly) {
|
||
return '查看司机';
|
||
}
|
||
return this.driverForm.id ? '修改司机' : '新增司机';
|
||
},
|
||
regionCascaderProps() {
|
||
return {
|
||
value: 'id',
|
||
label: 'title',
|
||
children: 'children',
|
||
emitPath: true,
|
||
};
|
||
},
|
||
expiryTagOptions() {
|
||
return [
|
||
{ label: '全部', value: '', statKey: 'total' },
|
||
{ label: '30天内到期', value: 'within30', statKey: 'within30' },
|
||
{ label: '已到期', value: 'expired', statKey: 'expired' },
|
||
];
|
||
},
|
||
},
|
||
created() {
|
||
this.initDeptTree();
|
||
this.loadOrganizationOptions();
|
||
this.initRegionOptions();
|
||
this.initQualificationTypeOptions();
|
||
},
|
||
methods: {
|
||
hasPermission(code) {
|
||
return this.isAdmin || this.permission?.[code] === true;
|
||
},
|
||
initDeptTree() {
|
||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||
this.organizationOptions = this.formatDeptOptions(res.data.data || []);
|
||
const column = this.findColumn(this.option.column, 'organizationName');
|
||
column.dicData = this.organizationOptions;
|
||
const vehicleColumn = this.findColumn(
|
||
this.vehicleSelectorOption.column,
|
||
'organizationName'
|
||
);
|
||
vehicleColumn.dicData = this.organizationOptions;
|
||
if (this.driverBox && !this.driverForm.id && !this.driverForm.organizationName) {
|
||
this.driverForm.organizationName = this.getCurrentOrganizationName();
|
||
}
|
||
});
|
||
},
|
||
initQualificationTypeOptions() {
|
||
getDictionary({ code: 'type_of_cert' }).then(res => {
|
||
this.qualificationTypeOptions = (res.data.data || [])
|
||
.map(item => item.dictValue)
|
||
.filter(Boolean);
|
||
const matchedType = this.matchQualificationType(this.driverForm.qualificationType);
|
||
if (matchedType) {
|
||
this.driverForm.qualificationType = matchedType;
|
||
}
|
||
});
|
||
},
|
||
formatDeptOptions(tree = [], level = 0) {
|
||
const result = [];
|
||
tree.forEach(item => {
|
||
const rawLabel = item.title || item.deptName || item.name;
|
||
result.push({
|
||
label: `${' '.repeat(level)}${rawLabel}`,
|
||
value: rawLabel,
|
||
rawLabel,
|
||
id: item.id || item.value,
|
||
});
|
||
if (item.children && item.children.length) {
|
||
result.push(...this.formatDeptOptions(item.children, level + 1));
|
||
}
|
||
});
|
||
return result;
|
||
},
|
||
getCurrentOrganizationName() {
|
||
const currentDeptId = this.userInfo.deptId || this.userInfo.dept_id;
|
||
const currentDept = this.organizationOptions.find(
|
||
item => String(item.id) === String(currentDeptId)
|
||
);
|
||
return currentDept?.rawLabel || this.userInfo.deptName || this.userInfo.dept_name || '';
|
||
},
|
||
initRegionOptions() {
|
||
getLazyTree().then(res => {
|
||
const regions = this.buildRegionTree(res.data.data || []);
|
||
this.regionOptions = this.extractChinaRegionOptions(regions);
|
||
this.syncAddressRegionPath();
|
||
});
|
||
},
|
||
buildRegionTree(regions = []) {
|
||
const flatRegions = [];
|
||
const collectRegions = list => {
|
||
(list || []).forEach(item => {
|
||
flatRegions.push({
|
||
...item,
|
||
children: undefined,
|
||
});
|
||
if (item.children?.length) collectRegions(item.children);
|
||
});
|
||
};
|
||
collectRegions(regions);
|
||
|
||
const nodeMap = new Map();
|
||
flatRegions.forEach(item => {
|
||
const id = item.id === undefined || item.id === null ? item.value : item.id;
|
||
if (id === undefined || id === null) return;
|
||
nodeMap.set(String(id), {
|
||
...item,
|
||
id: String(id),
|
||
parentId:
|
||
item.parentId === undefined || item.parentId === null ? '' : String(item.parentId),
|
||
children: [],
|
||
});
|
||
});
|
||
|
||
const rootNodes = [];
|
||
nodeMap.forEach(node => {
|
||
const parent = nodeMap.get(node.parentId);
|
||
if (parent && parent !== node) {
|
||
parent.children.push(node);
|
||
} else {
|
||
rootNodes.push(node);
|
||
}
|
||
});
|
||
return rootNodes.map(item => this.normalizeRegionTreeNode(item));
|
||
},
|
||
normalizeRegionTreeNode(region) {
|
||
const children = (region.children || []).map(item => this.normalizeRegionTreeNode(item));
|
||
return {
|
||
...region,
|
||
children: children.length ? children : undefined,
|
||
leaf: !children.length,
|
||
};
|
||
},
|
||
isChinaRegion(region = {}) {
|
||
const title = String(region.title || region.name || '').trim();
|
||
return title === '中国' || title === '中华人民共和国';
|
||
},
|
||
extractChinaRegionOptions(regions) {
|
||
const china = (regions || []).find(item => this.isChinaRegion(item));
|
||
if (china?.children?.length) return china.children;
|
||
if (regions.length === 1 && regions[0].children?.length) return regions[0].children;
|
||
return regions;
|
||
},
|
||
findRegionOption(options, value) {
|
||
for (const item of options || []) {
|
||
if (String(item.id) === String(value)) return item;
|
||
const child = this.findRegionOption(item.children, value);
|
||
if (child) return child;
|
||
}
|
||
return null;
|
||
},
|
||
findRegionPathByName(options = [], addressRegion = '', parents = []) {
|
||
const target = String(addressRegion || '').replace(/\s+/g, '');
|
||
if (!target) return [];
|
||
for (const item of options || []) {
|
||
const nextParents = [...parents, item];
|
||
const nextName = nextParents.map(region => region.title || region.name || '').join('');
|
||
if (nextName === target) {
|
||
return nextParents.map(region => region.id);
|
||
}
|
||
const childPath = this.findRegionPathByName(item.children, target, nextParents);
|
||
if (childPath.length) return childPath;
|
||
}
|
||
return [];
|
||
},
|
||
getAddressRegionLabels(value) {
|
||
const nodes = this.$refs.addressRegionCascader?.getCheckedNodes?.() || [];
|
||
if (nodes.length && nodes[0].pathLabels?.length) {
|
||
return nodes[0].pathLabels;
|
||
}
|
||
return (value || [])
|
||
.map(code => this.findRegionOption(this.regionOptions, code)?.title)
|
||
.filter(Boolean);
|
||
},
|
||
handleAddressRegionChange(value) {
|
||
if (!value || !value.length) {
|
||
this.driverForm.addressRegion = '';
|
||
this.$refs.driverForm?.validateField('addressRegion');
|
||
return;
|
||
}
|
||
this.$nextTick(() => {
|
||
this.driverForm.addressRegion = this.getAddressRegionLabels(value).join('');
|
||
this.$refs.driverForm?.validateField('addressRegion');
|
||
});
|
||
},
|
||
syncAddressRegionPath() {
|
||
if (!this.driverForm.addressRegion || this.driverForm.addressRegionPath?.length) return;
|
||
const path = this.findRegionPathByName(this.regionOptions, this.driverForm.addressRegion);
|
||
if (path.length) {
|
||
this.driverForm.addressRegionPath = path;
|
||
}
|
||
},
|
||
validateDrivingLicenseEndDate(rule, value, callback) {
|
||
// 勾选长期有效:起止均已禁用,不再校验
|
||
if (this.driverForm.drivingLicenseLongTerm === 1) {
|
||
callback();
|
||
return;
|
||
}
|
||
if (!this.driverForm.drivingLicenseStartDate) {
|
||
callback(new Error('请选择驾驶证有效期起'));
|
||
return;
|
||
}
|
||
if (!value) {
|
||
callback(new Error('请选择驾驶证有效期止'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
validateQualificationEndDate(rule, value, callback) {
|
||
// 勾选长期有效:起止均已禁用,不再校验
|
||
if (this.driverForm.qualificationLongTerm === 1) {
|
||
callback();
|
||
return;
|
||
}
|
||
if (!this.driverForm.qualificationStartDate) {
|
||
callback(new Error('请选择从业资格证有效期起'));
|
||
return;
|
||
}
|
||
if (!value) {
|
||
callback(new Error('请选择从业资格证有效期止'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
validateEmergencyContactName(rule, value, callback) {
|
||
if (/\d/.test(value || '')) {
|
||
callback(new Error('紧急联系人姓名不能包含数字'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
removeDigits(value = '') {
|
||
return String(value).replace(/\d/g, '');
|
||
},
|
||
handleIdCardInput(value) {
|
||
const idCardNo = String(value || '').toUpperCase();
|
||
this.driverForm.idCardNo = idCardNo;
|
||
const idCardInfo = this.parseIdCardInfo(idCardNo);
|
||
if (idCardInfo.birthday) {
|
||
this.driverForm.birthday = idCardInfo.birthday;
|
||
} else {
|
||
this.driverForm.birthday = '';
|
||
}
|
||
if (idCardInfo.gender) {
|
||
this.driverForm.gender = idCardInfo.gender;
|
||
} else {
|
||
this.driverForm.gender = '';
|
||
}
|
||
if (this.submitAttempted) {
|
||
this.$nextTick(() => {
|
||
this.$refs.driverForm?.validateField(['idCardNo', 'birthday', 'gender']);
|
||
});
|
||
}
|
||
},
|
||
openDriver(row, readonly = false) {
|
||
this.readonly = readonly;
|
||
this.submitAttempted = false;
|
||
if (!row?.id) {
|
||
this.driverForm = emptyForm();
|
||
this.driverForm.organizationName = this.getCurrentOrganizationName();
|
||
this.driverBox = true;
|
||
return;
|
||
}
|
||
getDetail(row.id).then(res => {
|
||
const detail = res.data.data || {};
|
||
this.driverForm = {
|
||
...emptyForm(),
|
||
...detail,
|
||
addressRegionPath: Array.isArray(detail.addressRegionPath)
|
||
? detail.addressRegionPath
|
||
: [],
|
||
postList: detail.posts ? detail.posts.split(',').filter(Boolean) : [],
|
||
};
|
||
this.syncAddressRegionPath();
|
||
this.driverBox = true;
|
||
});
|
||
},
|
||
resetDriver() {
|
||
this.driverForm = emptyForm();
|
||
this.drivingLicenseUploads = {};
|
||
this.readonly = false;
|
||
this.submitAttempted = false;
|
||
this.submitLoading = false;
|
||
this.$refs.driverForm?.clearValidate();
|
||
},
|
||
openVehicleSelector() {
|
||
if (this.readonly) return;
|
||
this.vehicleSelectorPage.currentPage = 1;
|
||
this.vehicleSelectorVisible = true;
|
||
},
|
||
resetVehicleSelector() {
|
||
this.vehicleSelectorForm = {};
|
||
this.vehicleSelectorData = [];
|
||
this.vehicleSelectorSearchForm = {};
|
||
this.vehicleSelectorPage = {
|
||
pageSize: 10,
|
||
currentPage: 1,
|
||
total: 0,
|
||
};
|
||
},
|
||
normalizeVehicleSelectorQuery(params = {}) {
|
||
const query = {
|
||
...this.vehicleSelectorSearchForm,
|
||
...params,
|
||
};
|
||
if (Array.isArray(query.organizationName)) {
|
||
query.organizationName = query.organizationName[query.organizationName.length - 1] || '';
|
||
}
|
||
return query;
|
||
},
|
||
loadVehicleSelector(page = this.vehicleSelectorPage, params = {}) {
|
||
this.vehicleSelectorLoading = true;
|
||
getVehicleList(page.currentPage, page.pageSize, this.normalizeVehicleSelectorQuery(params))
|
||
.then(res => {
|
||
const data = res.data.data || {};
|
||
this.vehicleSelectorData = data.records || [];
|
||
this.vehicleSelectorPage.total = data.total || 0;
|
||
})
|
||
.finally(() => {
|
||
this.vehicleSelectorLoading = false;
|
||
});
|
||
},
|
||
vehicleSelectorSearchChange(params, done) {
|
||
this.vehicleSelectorSearchForm = params;
|
||
this.vehicleSelectorPage.currentPage = 1;
|
||
this.loadVehicleSelector(this.vehicleSelectorPage, params);
|
||
done();
|
||
},
|
||
vehicleSelectorSearchReset() {
|
||
this.vehicleSelectorSearchForm = {};
|
||
this.vehicleSelectorPage.currentPage = 1;
|
||
this.loadVehicleSelector();
|
||
},
|
||
vehicleSelectorCurrentChange(currentPage) {
|
||
this.vehicleSelectorPage.currentPage = currentPage;
|
||
},
|
||
vehicleSelectorSizeChange(pageSize) {
|
||
this.vehicleSelectorPage.pageSize = pageSize;
|
||
},
|
||
vehicleSelectorRefreshChange() {
|
||
this.loadVehicleSelector();
|
||
},
|
||
selectDrivingVehicle(row) {
|
||
this.driverForm.drivingVehicle = row.plateNo || '';
|
||
this.vehicleSelectorVisible = false;
|
||
},
|
||
getVehicleCertificationStatus(row) {
|
||
const statusMap = {
|
||
0: { label: '认证中', type: 'warning' },
|
||
1: { label: '已认证', type: 'success' },
|
||
2: { label: '已驳回', type: 'danger' },
|
||
};
|
||
return statusMap[row.certificationStatus] || { label: '-', type: 'info' };
|
||
},
|
||
setImage(prop, url) {
|
||
this.driverForm[prop] = url;
|
||
this.$refs.driverForm?.validateField(prop);
|
||
},
|
||
handleIdCardUploadSuccess(prop, url) {
|
||
this.setImage(prop, url);
|
||
if (prop !== 'idCardFront') {
|
||
return;
|
||
}
|
||
this.recognizeBaiduOcr(url, 'id_card', 'front', '身份证', data => {
|
||
this.applyIdCardRecognition(data);
|
||
});
|
||
},
|
||
handleDrivingLicenseUploadSuccess(prop, url) {
|
||
this.setImage(prop, url);
|
||
this.drivingLicenseUploads[prop] = url;
|
||
this.recognizeBaiduOcr(
|
||
url,
|
||
'driving_license',
|
||
prop === 'drivingLicenseBack' ? 'back' : 'front',
|
||
'驾驶证',
|
||
data => {
|
||
this.applyDrivingLicenseRecognition(data);
|
||
}
|
||
);
|
||
},
|
||
handleQualificationUploadSuccess(prop, url) {
|
||
this.setImage(prop, url);
|
||
this.recognizeBaiduOcr(url, 'general', '', '从业资格证', data => {
|
||
this.applyQualificationRecognition(data);
|
||
});
|
||
},
|
||
recognizeBaiduOcr(url, type, side, documentName, applyRecognition) {
|
||
if (!url) {
|
||
this.$message.warning(`${documentName}图片上传成功,未获取到图片地址,无法自动识别`);
|
||
return;
|
||
}
|
||
const loading = ElLoading.service({
|
||
lock: true,
|
||
text: `${documentName}识别中`,
|
||
background: 'rgba(255, 255, 255, 0.7)',
|
||
});
|
||
recognizeBaiduOcr(url, type, side)
|
||
.then(res => {
|
||
applyRecognition(res.data.data?.result || {});
|
||
this.$message.success(`${documentName}识别完成`);
|
||
})
|
||
.catch(() => {
|
||
this.$message.warning(`${documentName}图片上传成功,自动识别失败,请手动填写相关信息`);
|
||
})
|
||
.finally(() => {
|
||
loading.close();
|
||
});
|
||
},
|
||
recognizeIdCard(url = '') {
|
||
if (!url) {
|
||
this.$message.warning('身份证图片上传成功,未获取到图片地址,无法自动识别');
|
||
return;
|
||
}
|
||
const loading = ElLoading.service({
|
||
lock: true,
|
||
text: '身份证识别中',
|
||
background: 'rgba(255, 255, 255, 0.7)',
|
||
});
|
||
recognitionIDCard(url)
|
||
.then(res => {
|
||
const data = res.data.data || {};
|
||
this.applyIdCardRecognition(data);
|
||
this.$message.success('身份证识别完成');
|
||
})
|
||
.catch(() => {
|
||
this.$message.warning('身份证图片上传成功,自动识别失败,请手动填写身份信息');
|
||
})
|
||
.finally(() => {
|
||
loading.close();
|
||
});
|
||
},
|
||
recognizeDrivingLicense() {
|
||
const certificates = this.buildDrivingLicenseRecognitionCertificates();
|
||
if (!certificates.length) {
|
||
this.$message.warning('驾驶证图片上传成功,未获取到图片地址,无法自动识别');
|
||
return;
|
||
}
|
||
const loading = ElLoading.service({
|
||
lock: true,
|
||
text: '驾驶证识别中',
|
||
background: 'rgba(255, 255, 255, 0.7)',
|
||
});
|
||
recognitionTransportCertificates(certificates)
|
||
.then(res => {
|
||
const data = res.data.data || {};
|
||
this.applyDrivingLicenseRecognition(data);
|
||
this.$message.success('驾驶证识别完成');
|
||
})
|
||
.catch(() => {
|
||
this.$message.warning('驾驶证图片上传成功,自动识别失败,请手动填写驾驶证信息');
|
||
})
|
||
.finally(() => {
|
||
loading.close();
|
||
});
|
||
},
|
||
buildDrivingLicenseRecognitionCertificates() {
|
||
return ['drivingLicenseFront', 'drivingLicenseBack']
|
||
.map(prop => {
|
||
const url = this.drivingLicenseUploads[prop] || this.driverForm[prop];
|
||
if (!url) return null;
|
||
return {
|
||
driverLicenseUrl: url,
|
||
};
|
||
})
|
||
.filter(Boolean);
|
||
},
|
||
applyDrivingLicenseRecognition(data = {}) {
|
||
const driverName =
|
||
data.driverLicenseName || data.name || this.getOcrValue(data, ['name', '姓名']);
|
||
const idCardNo = String(
|
||
data.driverLicenseIdNo ||
|
||
data.idNo ||
|
||
data.idCardNo ||
|
||
this.getOcrValue(data, ['ID_no', 'idNo', 'idCardNo', '证号'])
|
||
).toUpperCase();
|
||
const drivingType =
|
||
data.driverLicenseType ||
|
||
data.drivingType ||
|
||
data.driverClass ||
|
||
this.getOcrValue(data, ['class', '准驾车型']);
|
||
const drivingLicenseNo =
|
||
data.driverLicenseFileNo ||
|
||
data.drivingLicenseNo ||
|
||
data.fileNo ||
|
||
this.getOcrValue(data, ['file_no', '档案编号']);
|
||
const validDateStart =
|
||
data.driverLicenseValidDateStart ||
|
||
data.drivingLicenseStartDate ||
|
||
this.getOcrValue(data, ['date_vaild_start', '有效期限(起始时间)', '有效起始日期']);
|
||
const validDateEnd =
|
||
data.driverLicenseValidDateEnd ||
|
||
data.drivingLicenseEndDate ||
|
||
this.getOcrValue(data, ['date_vaild_end', '有效期限(终止时间)', '有效期限(终⽌时间)', '有效截止日期']);
|
||
if (driverName && !this.driverForm.driverName) {
|
||
this.driverForm.driverName = driverName;
|
||
}
|
||
if (idCardNo && !this.driverForm.idCardNo) {
|
||
this.driverForm.idCardNo = idCardNo;
|
||
}
|
||
if (idCardNo && !this.driverForm.qualificationNo) {
|
||
this.driverForm.qualificationNo = idCardNo;
|
||
}
|
||
if (drivingType) {
|
||
this.driverForm.drivingType = drivingType;
|
||
}
|
||
if (drivingLicenseNo) {
|
||
this.driverForm.drivingLicenseNo = drivingLicenseNo;
|
||
}
|
||
if (validDateStart) {
|
||
this.driverForm.drivingLicenseStartDate = this.normalizeBirthday(validDateStart);
|
||
}
|
||
if (validDateEnd) {
|
||
this.driverForm.drivingLicenseEndDate = this.normalizeBirthday(validDateEnd);
|
||
this.driverForm.drivingLicenseLongTerm = 0;
|
||
}
|
||
this.applyDrivingLicenseValidity(data);
|
||
this.$nextTick(() => {
|
||
[
|
||
'driverName',
|
||
'idCardNo',
|
||
'drivingType',
|
||
'drivingLicenseNo',
|
||
'drivingLicenseEndDate',
|
||
'qualificationNo',
|
||
].forEach(prop => {
|
||
this.$refs.driverForm?.validateField(prop);
|
||
});
|
||
});
|
||
},
|
||
applyDrivingLicenseValidity(data = {}) {
|
||
const validity = this.getOcrValue(data, ['有效期限', '有效期']);
|
||
if (!validity) return;
|
||
const dateList = validity.match(/\d{4}[-/.年]\d{1,2}[-/.月]\d{1,2}日?/g) || [];
|
||
if (!this.driverForm.drivingLicenseStartDate && dateList[0]) {
|
||
this.driverForm.drivingLicenseStartDate = this.normalizeBirthday(dateList[0]);
|
||
}
|
||
if (/(长期|永久)/.test(validity)) {
|
||
this.driverForm.drivingLicenseLongTerm = 1;
|
||
this.driverForm.drivingLicenseEndDate = '';
|
||
} else if (!this.driverForm.drivingLicenseEndDate && dateList[1]) {
|
||
this.driverForm.drivingLicenseEndDate = this.normalizeBirthday(dateList[1]);
|
||
this.driverForm.drivingLicenseLongTerm = 0;
|
||
}
|
||
},
|
||
applyIdCardRecognition(data = {}) {
|
||
const driverName = data.name || data.driverName || this.getOcrValue(data, ['name', '姓名']);
|
||
const idCardNo = String(
|
||
data.idNo ||
|
||
data.idCardNo ||
|
||
data.idCard ||
|
||
this.getOcrValue(data, ['idNo', 'idCardNo', 'number', '公民身份号码', '身份证号'])
|
||
).toUpperCase();
|
||
const ocrGender = data.gender || this.getOcrValue(data, ['gender', '性别']);
|
||
const ocrNation =
|
||
data.nation ||
|
||
data.ethnicGroup ||
|
||
this.getOcrValue(data, ['ethnic_group', 'nation', '民族']);
|
||
const ocrAddress = data.address || this.getOcrValue(data, ['address', '住址', '地址']);
|
||
if (driverName) {
|
||
this.driverForm.driverName = driverName;
|
||
}
|
||
if (ocrGender) {
|
||
this.driverForm.gender = ocrGender;
|
||
}
|
||
if (ocrNation) {
|
||
this.driverForm.nation = this.normalizeNation(ocrNation);
|
||
}
|
||
if (ocrAddress) {
|
||
this.driverForm.address = String(ocrAddress).trim();
|
||
}
|
||
if (idCardNo) {
|
||
this.driverForm.idCardNo = idCardNo;
|
||
if (!this.driverForm.qualificationNo) {
|
||
this.driverForm.qualificationNo = idCardNo;
|
||
}
|
||
const idCardInfo = this.parseIdCardInfo(idCardNo);
|
||
if (idCardInfo.birthday) {
|
||
this.driverForm.birthday = idCardInfo.birthday;
|
||
}
|
||
if (!this.driverForm.gender && idCardInfo.gender) {
|
||
this.driverForm.gender = idCardInfo.gender;
|
||
}
|
||
}
|
||
this.$nextTick(() => {
|
||
[
|
||
'driverName',
|
||
'idCardNo',
|
||
'birthday',
|
||
'gender',
|
||
'nation',
|
||
'qualificationNo',
|
||
'address',
|
||
].forEach(prop => {
|
||
this.$refs.driverForm?.validateField(prop);
|
||
});
|
||
});
|
||
},
|
||
getOcrValue(data = {}, keys = []) {
|
||
const keyInfos = data.keyInfos || data.keyInfo || data.ocrKeyInfos || [];
|
||
const list = Array.isArray(keyInfos) ? keyInfos : [];
|
||
const item = list.find(info => {
|
||
const key = String(info.key || '').toLowerCase();
|
||
const description = String(info.description || '');
|
||
return keys.some(value => key === String(value).toLowerCase() || description === value);
|
||
});
|
||
if (item) return item.value || '';
|
||
const wordsResult = data.words_result || data.wordsResult || {};
|
||
const normalizedKeys = keys.map(key => String(key).toLowerCase());
|
||
if (Array.isArray(wordsResult)) {
|
||
const wordItem = wordsResult.find(info =>
|
||
normalizedKeys.includes(String(info.key || info.name || '').toLowerCase())
|
||
);
|
||
return wordItem?.words || wordItem?.value || '';
|
||
}
|
||
const wordEntry = Object.entries(wordsResult).find(([key]) =>
|
||
normalizedKeys.includes(String(key).toLowerCase())
|
||
);
|
||
if (!wordEntry) return '';
|
||
const value = wordEntry[1];
|
||
return typeof value === 'object' ? value.words || value.value || '' : value || '';
|
||
},
|
||
applyQualificationRecognition(data = {}) {
|
||
const qualificationType = this.getQualificationType(data);
|
||
const qualificationNo = this.getQualificationNo(data);
|
||
const matchedType = this.matchQualificationType(qualificationType);
|
||
if (matchedType) {
|
||
this.driverForm.qualificationType = matchedType;
|
||
} else if (qualificationType && !this.qualificationTypeOptions.length) {
|
||
this.driverForm.qualificationType = qualificationType;
|
||
}
|
||
if (qualificationNo && !this.driverForm.qualificationNo) {
|
||
this.driverForm.qualificationNo = String(qualificationNo).replace(/\s/g, '');
|
||
}
|
||
const words = this.getBaiduOcrWords(data).join(' ');
|
||
const dateList = words.match(/\d{4}[-/.年]\d{1,2}[-/.月]\d{1,2}日?/g) || [];
|
||
if (/(长期|永久有效)/.test(words)) {
|
||
this.driverForm.qualificationLongTerm = 1;
|
||
this.driverForm.qualificationEndDate = '';
|
||
} else if (dateList.length) {
|
||
this.driverForm.qualificationEndDate = this.normalizeBirthday(dateList[dateList.length - 1]);
|
||
this.driverForm.qualificationLongTerm = 0;
|
||
}
|
||
this.$nextTick(() => {
|
||
['qualificationType', 'qualificationNo', 'qualificationEndDate'].forEach(prop => {
|
||
this.$refs.driverForm?.validateField(prop);
|
||
});
|
||
});
|
||
},
|
||
getQualificationType(data = {}) {
|
||
const fieldValue = this.getOcrValue(data, ['从业资格类别', '从业资格类型']);
|
||
if (fieldValue) return String(fieldValue).trim();
|
||
const words = this.getBaiduOcrWords(data);
|
||
const labelIndex = words.findIndex(word => /从业资格类别|从业资格类型/.test(word));
|
||
if (labelIndex < 0) return '';
|
||
const firstValue = words[labelIndex].replace(/^.*?(?:从业资格类别|从业资格类型)\s*[::]?/, '').trim();
|
||
const values = firstValue ? [firstValue] : [];
|
||
for (let index = labelIndex + 1; index < words.length; index += 1) {
|
||
const word = String(words[index] || '').trim();
|
||
if (!word || /^(有效起始日期|有效期限|核发机关|继续教育信息|诚信考核信息)/.test(word)) {
|
||
break;
|
||
}
|
||
values.push(word);
|
||
}
|
||
return values.join('').replace(/[,,;;]+$/, '').trim();
|
||
},
|
||
matchQualificationType(value = '') {
|
||
const text = String(value || '').replace(/\s/g, '');
|
||
if (!text) return '';
|
||
const options = this.qualificationTypeOptions || [];
|
||
const normalizedOptions = options.map(option => ({
|
||
value: option,
|
||
text: String(option).replace(/\s/g, ''),
|
||
}));
|
||
const exact = normalizedOptions.find(option => option.text === text);
|
||
if (exact) return exact.value;
|
||
const candidates = text.split(/[,,;;]/).filter(Boolean);
|
||
const matched = normalizedOptions.find(option =>
|
||
candidates.some(candidate => candidate.includes(option.text) || option.text.includes(candidate))
|
||
);
|
||
return matched?.value || '';
|
||
},
|
||
getQualificationNo(data = {}) {
|
||
const fieldValue = this.getOcrValue(data, ['从业资格证号', '资格证号', '证书编号']);
|
||
if (fieldValue) return fieldValue;
|
||
const words = this.getBaiduOcrWords(data);
|
||
const labelIndex = words.findIndex(word => /^(?:(?:从业)?资格证号?|证书编号)[::]?$/.test(word));
|
||
if (labelIndex >= 0 && words[labelIndex + 1]) {
|
||
return words[labelIndex + 1];
|
||
}
|
||
const certificateLine = words.find(word => /(?:从业)?资格证号?|证书编号/.test(word));
|
||
const match = certificateLine?.match(/(?:(?:从业)?资格证号?|证书编号)\s*[::]?\s*([A-Z0-9-]{6,})/i);
|
||
return match?.[1] || '';
|
||
},
|
||
getBaiduOcrWords(data = {}) {
|
||
const wordsResult = data.words_result || data.wordsResult || {};
|
||
if (Array.isArray(wordsResult)) {
|
||
return wordsResult.map(item => item.words || item.value || '').filter(Boolean);
|
||
}
|
||
return Object.values(wordsResult)
|
||
.map(item => (typeof item === 'object' ? item.words || item.value || '' : item || ''))
|
||
.filter(Boolean);
|
||
},
|
||
normalizeNation(value = '') {
|
||
const nation = String(value || '').trim();
|
||
if (!nation) return '';
|
||
if (this.nationOptions.includes(nation)) return nation;
|
||
const fullNation = `${nation}族`;
|
||
return this.nationOptions.includes(fullNation) ? fullNation : nation;
|
||
},
|
||
normalizeBirthday(value = '') {
|
||
const birthday = String(value || '').trim();
|
||
const compactMatch = birthday.match(/^(\d{4})(\d{2})(\d{2})$/);
|
||
if (compactMatch) {
|
||
const [, year, month, day] = compactMatch;
|
||
return `${year}-${month}-${day}`;
|
||
}
|
||
const match = birthday.match(/^(\d{4})[-/.年](\d{1,2})[-/.月](\d{1,2})日?$/);
|
||
if (!match) return birthday;
|
||
const [, year, month, day] = match;
|
||
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||
},
|
||
parseIdCardInfo(idCardNo = '') {
|
||
if (!/^\d{17}[\dX]$/.test(idCardNo)) {
|
||
return {};
|
||
}
|
||
const birthday = `${idCardNo.slice(6, 10)}-${idCardNo.slice(10, 12)}-${idCardNo.slice(
|
||
12,
|
||
14
|
||
)}`;
|
||
const date = new Date(`${birthday}T00:00:00`);
|
||
const validDate =
|
||
!Number.isNaN(date.getTime()) &&
|
||
date.getFullYear() === Number(idCardNo.slice(6, 10)) &&
|
||
date.getMonth() + 1 === Number(idCardNo.slice(10, 12)) &&
|
||
date.getDate() === Number(idCardNo.slice(12, 14));
|
||
return {
|
||
birthday: validDate ? birthday : '',
|
||
gender: Number(idCardNo.slice(16, 17)) % 2 === 1 ? '男' : '女',
|
||
};
|
||
},
|
||
validateFormField(prop) {
|
||
this.$refs.driverForm?.validateField(prop);
|
||
},
|
||
handleSubmit() {
|
||
this.submitAttempted = true;
|
||
this.$refs.driverForm.validate(valid => {
|
||
if (!valid) {
|
||
return;
|
||
}
|
||
const row = {
|
||
...this.driverForm,
|
||
birthday: this.normalizeBirthday(this.driverForm.birthday),
|
||
drivingLicenseStartDate: this.normalizeBirthday(this.driverForm.drivingLicenseStartDate),
|
||
drivingLicenseEndDate: this.normalizeBirthday(this.driverForm.drivingLicenseEndDate),
|
||
qualificationStartDate: this.normalizeBirthday(this.driverForm.qualificationStartDate),
|
||
qualificationEndDate: this.normalizeBirthday(this.driverForm.qualificationEndDate),
|
||
qualificationNo: this.driverForm.qualificationNo || this.driverForm.idCardNo,
|
||
posts: this.driverForm.postList.join(','),
|
||
};
|
||
delete row.addressRegionPath;
|
||
this.submitLoading = true;
|
||
submit(row)
|
||
.then(() => {
|
||
this.$message.success('操作成功');
|
||
this.driverBox = false;
|
||
this.onLoad(this.page);
|
||
})
|
||
.finally(() => {
|
||
this.submitLoading = false;
|
||
});
|
||
});
|
||
},
|
||
handleStatus(row) {
|
||
const status = row.status === 1 ? 2 : 1;
|
||
const text = status === 1 ? '启用' : '停用';
|
||
this.$confirm(`是否${text}该司机?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
changeStatus(row.id, status).then(() => {
|
||
this.$message.success('操作成功');
|
||
this.onLoad(this.page);
|
||
});
|
||
});
|
||
},
|
||
handleDelete() {
|
||
if (this.selectionList.length === 0) {
|
||
this.$message.warning('请选择至少一条数据');
|
||
return;
|
||
}
|
||
this.$confirm('是否删除选中的司机数据?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
remove(this.ids).then(() => {
|
||
this.$message.success('操作成功');
|
||
this.onLoad(this.page);
|
||
});
|
||
});
|
||
},
|
||
handleExpireChange(expireStatus) {
|
||
this.query.expireStatus = expireStatus;
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page);
|
||
},
|
||
refreshStat() {
|
||
getExpiryStat(this.buildQuery(false)).then(res => {
|
||
this.expiryStat = {
|
||
total: res.data.data?.total || 0,
|
||
within30: res.data.data?.within30 || 0,
|
||
expired: res.data.data?.expired || 0,
|
||
};
|
||
});
|
||
},
|
||
searchReset() {
|
||
this.searchForm = {};
|
||
this.query = {
|
||
expireStatus: this.query.expireStatus,
|
||
};
|
||
this.onLoad(this.page);
|
||
},
|
||
searchChange(params, done) {
|
||
// 所属组织级联返回的是 id 路径,转成组织名称传给后端
|
||
this.normalizeOrganizationSearch(params);
|
||
this.searchForm = params;
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page, params);
|
||
done();
|
||
},
|
||
selectionChange(list) {
|
||
this.selectionList = list;
|
||
this.ids = list.map(item => item.id).join(',');
|
||
},
|
||
selectionClear() {
|
||
this.selectionList = [];
|
||
this.ids = '';
|
||
this.$refs.crud?.toggleSelection();
|
||
},
|
||
currentChange(currentPage) {
|
||
this.page.currentPage = currentPage;
|
||
},
|
||
sizeChange(pageSize) {
|
||
this.page.pageSize = pageSize;
|
||
},
|
||
refreshChange() {
|
||
this.onLoad(this.page, this.searchForm);
|
||
},
|
||
onLoad(page, params = {}) {
|
||
this.loading = true;
|
||
getList(page.currentPage, page.pageSize, this.buildQuery(true, params))
|
||
.then(res => {
|
||
const data = res.data.data;
|
||
this.page.total = data.total;
|
||
this.data = data.records;
|
||
this.selectionClear();
|
||
this.refreshStat();
|
||
})
|
||
.finally(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
buildQuery(includeExpire = true, params = {}) {
|
||
const query = {
|
||
...params,
|
||
...this.searchForm,
|
||
};
|
||
if (Array.isArray(query.organizationName)) {
|
||
query.organizationName = query.organizationName[query.organizationName.length - 1] || '';
|
||
}
|
||
if (includeExpire && this.query.expireStatus) {
|
||
query.expireStatus = this.query.expireStatus;
|
||
}
|
||
return query;
|
||
},
|
||
handleExport() {
|
||
this.$confirm('是否导出司机数据?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
NProgress.start();
|
||
exportBlob('/blade-transport/driver/export-driver', this.buildExportParams(), {
|
||
feedback: true,
|
||
})
|
||
.then(res => {
|
||
downloadXls(res.data, `司机管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||
})
|
||
.finally(() => {
|
||
NProgress.done();
|
||
});
|
||
});
|
||
},
|
||
buildExportParams() {
|
||
return {
|
||
...this.buildQuery(true),
|
||
ids: this.ids,
|
||
[this.website.tokenHeader]: getToken(),
|
||
};
|
||
},
|
||
formatEndDate(date, longTerm) {
|
||
if (longTerm === 1) {
|
||
return '长期有效';
|
||
}
|
||
return date || '-';
|
||
},
|
||
isDateExpiring(row, dateProp, longTermProp) {
|
||
if (row[longTermProp] === 1 || !row[dateProp]) {
|
||
return false;
|
||
}
|
||
const today = this.$dayjs();
|
||
const endDate = this.$dayjs(row[dateProp]);
|
||
return endDate.isValid() && endDate.diff(today, 'day') <= 30;
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.driver-page {
|
||
&__expiry-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 8px;
|
||
margin-bottom: 16px;
|
||
|
||
:deep(.el-check-tag) {
|
||
margin: 0;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
:deep(.el-table th .cell),
|
||
:deep(.el-table td .cell) {
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.driver-form {
|
||
:deep(.el-form-item__label) {
|
||
flex: 0 0 calc(4em + 24px) !important;
|
||
width: calc(4em + 24px) !important;
|
||
height: auto;
|
||
white-space: normal !important;
|
||
word-break: break-all;
|
||
overflow-wrap: anywhere;
|
||
line-height: 18px;
|
||
}
|
||
|
||
:deep(.el-form-item) {
|
||
align-items: center;
|
||
}
|
||
|
||
:deep(.el-form-item.is-required .el-form-item__label) {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
:deep(.el-form-item.is-required .el-form-item__label::before) {
|
||
display: inline-block;
|
||
height: 18px;
|
||
line-height: 18px;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
:deep(.el-date-editor.el-input),
|
||
:deep(.el-date-editor.el-input__wrapper),
|
||
:deep(.el-cascader),
|
||
:deep(.el-select),
|
||
:deep(.el-input) {
|
||
width: 100%;
|
||
}
|
||
|
||
.date-range {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
.date-range :deep(.el-date-editor) {
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
}
|
||
|
||
// 大尺寸证件照:按身份证 1.586:1 比例缩放(约真实尺寸 75%,240×151),左对齐显示
|
||
:deep(.driver-uploader) {
|
||
display: block;
|
||
}
|
||
|
||
:deep(.driver-uploader .el-upload),
|
||
:deep(.driver-uploader.el-upload) {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 170px;
|
||
border: 1px dashed #c0c4cc;
|
||
background: #fafafa;
|
||
overflow: hidden;
|
||
}
|
||
|
||
// 大尺寸模式:跟随列宽自适应(最大 240px,约 1.586:1 证件比例),
|
||
// 保证不同分辨率 / 系统缩放下都与上方输入框左右对齐
|
||
:deep(.driver-uploader--large) {
|
||
display: block;
|
||
width: 100%;
|
||
max-width: 240px;
|
||
flex: none;
|
||
margin: 0;
|
||
}
|
||
|
||
:deep(.driver-uploader--large .el-upload),
|
||
:deep(.driver-uploader--large.el-upload) {
|
||
width: 100%;
|
||
height: 151px;
|
||
}
|
||
|
||
:deep(.driver-uploader__image) {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
display: block;
|
||
}
|
||
|
||
:deep(.driver-uploader__empty) {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #606266;
|
||
}
|
||
|
||
// 弹窗内可滚动(弹窗 body 灰底已由全局 .el-dialog__body 规则生效)
|
||
.driver-dialog {
|
||
:deep(.el-dialog__body) {
|
||
max-height: 72vh;
|
||
overflow: auto;
|
||
}
|
||
}
|
||
|
||
.driver-page__date-expiring {
|
||
color: #d40000;
|
||
font-weight: 600;
|
||
}
|
||
.el-form-item {
|
||
margin-bottom: 14px !important;
|
||
}
|
||
</style>
|