1200 lines
37 KiB
Vue
1200 lines
37 KiB
Vue
<template>
|
||
<basic-container class="transport-vehicle-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"
|
||
plain
|
||
v-if="hasPermission('transport_vehicle_add')"
|
||
@click="openVehicle()"
|
||
>新增
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-download"
|
||
plain
|
||
v-if="hasPermission('transport_vehicle_export')"
|
||
@click="handleExport"
|
||
>导出
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
icon="el-icon-delete"
|
||
plain
|
||
v-if="hasPermission('transport_vehicle_delete')"
|
||
@click="handleDelete"
|
||
>批量删除
|
||
</el-button>
|
||
</template>
|
||
<template #menu-right>
|
||
<el-radio-group
|
||
v-model="query.expireStatus"
|
||
class="vehicle-stat"
|
||
@change="handleExpireChange"
|
||
>
|
||
<el-radio-button label="">全部({{ expiryStat.total }})</el-radio-button>
|
||
<el-radio-button label="within30">30天内到期({{ expiryStat.within30 }})</el-radio-button>
|
||
<el-radio-button label="expired">已到期({{ expiryStat.expired }})</el-radio-button>
|
||
</el-radio-group>
|
||
</template>
|
||
<template #plateNo="{ row }">
|
||
<el-button type="primary" link @click="openVehicle(row, true)">
|
||
{{ row.plateNo }}
|
||
</el-button>
|
||
</template>
|
||
<template #status="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||
{{ row.status === 1 ? '启用' : '停用' }}
|
||
</el-tag>
|
||
</template>
|
||
<template #drivingLicenseEndDate="{ row }">
|
||
<span :class="getDateClass(row, 'drivingLicenseEndDate', 'drivingLicenseLongTerm')">
|
||
{{ formatEndDate(row.drivingLicenseEndDate, row.drivingLicenseLongTerm) }}
|
||
</span>
|
||
</template>
|
||
<template #roadTransportCertEndDate="{ row }">
|
||
<span :class="getDateClass(row, 'roadTransportCertEndDate', 'roadTransportCertLongTerm')">
|
||
{{ formatEndDate(row.roadTransportCertEndDate, row.roadTransportCertLongTerm) }}
|
||
</span>
|
||
</template>
|
||
<template #annualReviewEndDate="{ row }">
|
||
<span :class="getDateClass(row, 'annualReviewEndDate', 'annualReviewLongTerm')">
|
||
{{ formatEndDate(row.annualReviewEndDate, row.annualReviewLongTerm) }}
|
||
</span>
|
||
</template>
|
||
<template #menu="{ row }">
|
||
<el-link
|
||
type="primary"
|
||
v-if="hasPermission('transport_vehicle_view')"
|
||
@click="openVehicle(row, true)"
|
||
>
|
||
查看
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="hasPermission('transport_vehicle_edit')"
|
||
@click="openVehicle(row)"
|
||
>
|
||
修改
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="hasPermission('transport_vehicle_status')"
|
||
@click="handleStatus(row)"
|
||
>
|
||
{{ row.status === 1 ? '停用' : '启用' }}
|
||
</el-link>
|
||
</template>
|
||
</avue-crud>
|
||
|
||
<el-dialog
|
||
:title="dialogTitle"
|
||
append-to-body
|
||
v-model="vehicleBox"
|
||
width="92%"
|
||
top="4vh"
|
||
class="vehicle-dialog"
|
||
@closed="resetVehicle"
|
||
>
|
||
<el-form
|
||
ref="vehicleForm"
|
||
:model="vehicleForm"
|
||
:rules="formRules"
|
||
label-position="right"
|
||
:disabled="readonly"
|
||
class="vehicle-form"
|
||
>
|
||
<div class="section-title">基础车辆信息</div>
|
||
<el-row :gutter="18">
|
||
<el-col :span="24">
|
||
<el-form-item label="所有组织" prop="organizationName">
|
||
<el-select
|
||
v-model="vehicleForm.organizationName"
|
||
filterable
|
||
clearable
|
||
placeholder="请选择"
|
||
>
|
||
<el-option
|
||
v-for="item in organizationOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="10">
|
||
<el-form-item label="车牌号" prop="plateNo">
|
||
<div class="plate-group">
|
||
<el-select
|
||
v-model="vehicleForm.plateProvince"
|
||
placeholder="请选择"
|
||
filterable
|
||
@change="syncPlateNo(false)"
|
||
>
|
||
<el-option
|
||
v-for="item in plateProvinceOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
<el-input
|
||
v-model="vehicleForm.plateNoBody"
|
||
maxlength="7"
|
||
show-word-limit
|
||
@input="handlePlateBodyInput"
|
||
/>
|
||
<el-select
|
||
v-model="vehicleForm.plateColor"
|
||
placeholder="请选择"
|
||
@change="syncPlateNo(false)"
|
||
>
|
||
<el-option
|
||
v-for="item in plateColorOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="车辆类型" prop="vehicleType">
|
||
<el-select
|
||
v-model="vehicleForm.vehicleType"
|
||
filterable
|
||
allow-create
|
||
default-first-option
|
||
>
|
||
<el-option
|
||
v-for="item in vehicleTypeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="外廓尺寸(毫米)">
|
||
<div class="dimension-group">
|
||
<div class="dimension-group__item">
|
||
<span class="dimension-group__label">长</span>
|
||
<el-input-number
|
||
v-model="vehicleForm.outerLength"
|
||
:min="0"
|
||
:precision="0"
|
||
controls-position="right"
|
||
/>
|
||
</div>
|
||
<div class="dimension-group__item">
|
||
<span class="dimension-group__label">宽</span>
|
||
<el-input-number
|
||
v-model="vehicleForm.outerWidth"
|
||
:min="0"
|
||
:precision="0"
|
||
controls-position="right"
|
||
/>
|
||
</div>
|
||
<div class="dimension-group__item">
|
||
<span class="dimension-group__label">高</span>
|
||
<el-input-number
|
||
v-model="vehicleForm.outerHeight"
|
||
:min="0"
|
||
:precision="0"
|
||
controls-position="right"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="8">
|
||
<el-form-item label="核定载质量(KG)" prop="approvedLoadKg">
|
||
<el-input-number
|
||
v-model="vehicleForm.approvedLoadKg"
|
||
:min="0"
|
||
:precision="0"
|
||
controls-position="right"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="准牵引总质量(KG)" prop="tractionMassKg">
|
||
<el-input-number
|
||
v-model="vehicleForm.tractionMassKg"
|
||
:min="0"
|
||
:precision="0"
|
||
controls-position="right"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="业务关系" prop="businessRelation">
|
||
<el-select v-model="vehicleForm.businessRelation">
|
||
<el-option
|
||
v-for="item in businessRelationOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="8">
|
||
<el-form-item label="能源类型" prop="energyType">
|
||
<el-select
|
||
v-model="vehicleForm.energyType"
|
||
filterable
|
||
allow-create
|
||
default-first-option
|
||
>
|
||
<el-option
|
||
v-for="item in energyTypeOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="强制报废日期" prop="compulsoryScrapDate">
|
||
<div class="date-with-flag">
|
||
<el-date-picker
|
||
v-model="vehicleForm.compulsoryScrapDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="vehicleForm.compulsoryScrapLongTerm === 1"
|
||
/>
|
||
<el-checkbox
|
||
v-model="vehicleForm.compulsoryScrapLongTerm"
|
||
:true-label="1"
|
||
:false-label="0"
|
||
>
|
||
长期有效
|
||
</el-checkbox>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="海关备案号" prop="customsRecordNo">
|
||
<el-input v-model="vehicleForm.customsRecordNo" maxlength="50" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<div class="section-title">车辆资质图片</div>
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="行驶证档案编号" prop="drivingLicenseNo">
|
||
<el-input v-model="vehicleForm.drivingLicenseNo" maxlength="50" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="行驶证有效期" prop="drivingLicenseEndDate">
|
||
<div class="date-with-flag">
|
||
<el-date-picker
|
||
v-model="vehicleForm.drivingLicenseEndDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
<el-checkbox
|
||
v-model="vehicleForm.drivingLicenseLongTerm"
|
||
:true-label="1"
|
||
:false-label="0"
|
||
>
|
||
长期有效
|
||
</el-checkbox>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="10">
|
||
<el-form-item label="道路运输证号" prop="roadTransportCertNo">
|
||
<el-input v-model="vehicleForm.roadTransportCertNo" maxlength="50" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="8">
|
||
<el-form-item label="道路运输证有效期" prop="roadTransportCertEndDate">
|
||
<div class="date-with-flag">
|
||
<el-date-picker
|
||
v-model="vehicleForm.roadTransportCertEndDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
<el-checkbox
|
||
v-model="vehicleForm.roadTransportCertLongTerm"
|
||
:true-label="1"
|
||
:false-label="0"
|
||
>
|
||
长期有效
|
||
</el-checkbox>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="道路运输年审有效期" prop="annualReviewEndDate">
|
||
<div class="date-with-flag">
|
||
<el-date-picker
|
||
v-model="vehicleForm.annualReviewEndDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="vehicleForm.annualReviewLongTerm === 1"
|
||
/>
|
||
<el-checkbox
|
||
v-model="vehicleForm.annualReviewLongTerm"
|
||
:true-label="1"
|
||
:false-label="0"
|
||
>
|
||
长期有效
|
||
</el-checkbox>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="8">
|
||
<el-form-item label="机动车登记编号" prop="registrationNo">
|
||
<el-input v-model="vehicleForm.registrationNo" maxlength="50" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="机动车登记日期" prop="registrationDate">
|
||
<el-date-picker
|
||
v-model="vehicleForm.registrationDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="8">
|
||
<image-upload-field
|
||
label="行驶证主页正面"
|
||
prop="drivingLicenseImage"
|
||
:value="vehicleForm.drivingLicenseImage"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
class-prefix="vehicle"
|
||
@success="
|
||
(url, file) =>
|
||
handleVehicleCertificateUploadSuccess('drivingLicenseImage', url, file)
|
||
"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<image-upload-field
|
||
label="行驶证主页反面"
|
||
prop="drivingLicenseMainBack"
|
||
:value="vehicleForm.drivingLicenseMainBack"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
class-prefix="vehicle"
|
||
@success="
|
||
(url, file) =>
|
||
handleVehicleCertificateUploadSuccess('drivingLicenseMainBack', url, file)
|
||
"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<image-upload-field
|
||
label="行驶证副页正面"
|
||
prop="drivingLicenseViceFront"
|
||
:value="vehicleForm.drivingLicenseViceFront"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
class-prefix="vehicle"
|
||
@success="
|
||
(url, file) =>
|
||
handleVehicleCertificateUploadSuccess('drivingLicenseViceFront', url, file)
|
||
"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="18">
|
||
<el-col :span="8">
|
||
<image-upload-field
|
||
label="行驶证副页反面"
|
||
prop="drivingLicenseViceBack"
|
||
:value="vehicleForm.drivingLicenseViceBack"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
class-prefix="vehicle"
|
||
@success="
|
||
(url, file) =>
|
||
handleVehicleCertificateUploadSuccess('drivingLicenseViceBack', url, file)
|
||
"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<image-upload-field
|
||
label="道路运输证图片"
|
||
prop="roadTransportCertImage"
|
||
:value="vehicleForm.roadTransportCertImage"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
class-prefix="vehicle"
|
||
@success="url => setImage('roadTransportCertImage', url)"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<image-upload-field
|
||
label="机动车登记本"
|
||
prop="registrationImage"
|
||
:value="vehicleForm.registrationImage"
|
||
:readonly="readonly"
|
||
:headers="uploadHeaders"
|
||
large
|
||
class-prefix="vehicle"
|
||
@success="
|
||
(url, file) =>
|
||
handleVehicleCertificateUploadSuccess('registrationImage', url, file)
|
||
"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="vehicleBox = false">取消</el-button>
|
||
<el-button type="primary" v-if="!readonly" :loading="submitLoading" @click="handleSubmit">
|
||
确定
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</basic-container>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapGetters } from 'vuex';
|
||
import NProgress from 'nprogress';
|
||
import { ElLoading } from 'element-plus';
|
||
import { option } from '@/option/transportCapacity/transport-vehicle';
|
||
import {
|
||
getList,
|
||
getDetail,
|
||
submit,
|
||
remove,
|
||
changeStatus,
|
||
getExpiryStat,
|
||
recognitionTransportCertificates,
|
||
} from '@/api/transportCapacity/transport-vehicle';
|
||
import { getDeptTree } from '@/api/system/dept';
|
||
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 emptyForm = () => ({
|
||
id: '',
|
||
organizationName: '',
|
||
plateProvince: '',
|
||
plateNoBody: '',
|
||
plateNo: '',
|
||
plateColor: '',
|
||
vehicleType: '',
|
||
outerLength: undefined,
|
||
outerWidth: undefined,
|
||
outerHeight: undefined,
|
||
approvedLoadKg: undefined,
|
||
tractionMassKg: undefined,
|
||
businessRelation: '自有',
|
||
energyType: '',
|
||
compulsoryScrapDate: '',
|
||
compulsoryScrapLongTerm: 0,
|
||
customsRecordNo: '',
|
||
drivingLicenseNo: '',
|
||
drivingLicenseStartDate: '',
|
||
drivingLicenseEndDate: '',
|
||
drivingLicenseLongTerm: 0,
|
||
drivingLicenseImage: '',
|
||
drivingLicenseMainBack: '',
|
||
drivingLicenseViceFront: '',
|
||
drivingLicenseViceBack: '',
|
||
roadTransportCertNo: '',
|
||
roadTransportCertStartDate: '',
|
||
roadTransportCertEndDate: '',
|
||
roadTransportCertLongTerm: 0,
|
||
roadTransportCertImage: '',
|
||
annualReviewEndDate: '',
|
||
annualReviewLongTerm: 0,
|
||
registrationNo: '',
|
||
registrationDate: '',
|
||
registrationImage: '',
|
||
status: 1,
|
||
});
|
||
|
||
const plateProvinceOptions = [
|
||
'京',
|
||
'津',
|
||
'沪',
|
||
'渝',
|
||
'冀',
|
||
'豫',
|
||
'云',
|
||
'辽',
|
||
'黑',
|
||
'湘',
|
||
'皖',
|
||
'鲁',
|
||
'新',
|
||
'苏',
|
||
'浙',
|
||
'赣',
|
||
'鄂',
|
||
'桂',
|
||
'甘',
|
||
'晋',
|
||
'蒙',
|
||
'陕',
|
||
'吉',
|
||
'闽',
|
||
'贵',
|
||
'粤',
|
||
'青',
|
||
'藏',
|
||
'川',
|
||
'宁',
|
||
'琼',
|
||
'港',
|
||
'澳',
|
||
].map(item => ({ label: item, value: item }));
|
||
|
||
const plateColorOptions = ['黄牌', '蓝牌', '绿牌'].map(item => ({ label: item, value: item }));
|
||
|
||
export default {
|
||
components: {
|
||
ImageUploadField,
|
||
},
|
||
data() {
|
||
return {
|
||
option,
|
||
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,
|
||
},
|
||
vehicleBox: false,
|
||
readonly: false,
|
||
vehicleForm: emptyForm(),
|
||
vehicleCertificateUploads: {},
|
||
uploadHeaders: getUploadHeaders(),
|
||
organizationOptions: [],
|
||
plateProvinceOptions,
|
||
plateColorOptions,
|
||
vehicleTypeOptions: [
|
||
{ label: '高栏车', value: '高栏' },
|
||
{ label: '低栏车', value: '低栏' },
|
||
{ label: '平板车', value: '平板' },
|
||
{ label: '厢式车', value: '厢式' },
|
||
{ label: '拖挂车', value: '拖挂' },
|
||
{ label: '半挂车', value: '半挂' },
|
||
],
|
||
businessRelationOptions: ['自有', '管理挂靠', '业务合作'],
|
||
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
|
||
formRules: {
|
||
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||
plateNo: [{ validator: this.validatePlateNo, trigger: 'change' }],
|
||
vehicleType: [{ required: true, message: '请选择车辆类型', trigger: 'change' }],
|
||
approvedLoadKg: [{ required: true, message: '请输入核定载质量', trigger: 'blur' }],
|
||
businessRelation: [{ required: true, message: '请选择业务关系', trigger: 'change' }],
|
||
energyType: [{ required: true, message: '请选择能源类型', trigger: 'change' }],
|
||
drivingLicenseNo: [{ required: true, message: '请输入行驶证档案编号', trigger: 'blur' }],
|
||
drivingLicenseEndDate: [{ required: true, message: '请选择行驶证有效期', trigger: 'change' }],
|
||
roadTransportCertNo: [{ required: true, message: '请输入道路运输证号', trigger: 'blur' }],
|
||
roadTransportCertEndDate: [
|
||
{ required: true, message: '请选择道路运输证有效期', trigger: 'change' },
|
||
],
|
||
annualReviewEndDate: [{ validator: this.validateAnnualReviewEndDate, trigger: 'change' }],
|
||
registrationNo: [{ required: true, message: '请输入机动车登记编号', trigger: 'blur' }],
|
||
},
|
||
};
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission', 'userInfo']),
|
||
isAdmin() {
|
||
const authority = this.userInfo.authority || '';
|
||
return authority.includes('admin');
|
||
},
|
||
permissionList() {
|
||
return {
|
||
addBtn: this.hasPermission('transport_vehicle_add'),
|
||
viewBtn: this.hasPermission('transport_vehicle_view'),
|
||
delBtn: this.hasPermission('transport_vehicle_delete'),
|
||
editBtn: this.hasPermission('transport_vehicle_edit'),
|
||
};
|
||
},
|
||
dialogTitle() {
|
||
if (this.readonly) {
|
||
return '查看车辆';
|
||
}
|
||
return this.vehicleForm.id ? '修改车辆' : '新增车辆';
|
||
},
|
||
},
|
||
created() {
|
||
this.initDeptTree();
|
||
},
|
||
methods: {
|
||
hasPermission(code) {
|
||
return this.isAdmin || this.permission?.[code] === true;
|
||
},
|
||
initDeptTree() {
|
||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||
this.organizationOptions = this.flattenDeptOptions(res.data.data || []);
|
||
const column = this.findColumn(this.option.column, 'organizationName');
|
||
column.dicData = this.organizationOptions;
|
||
});
|
||
},
|
||
flattenDeptOptions(tree = [], level = 0) {
|
||
const result = [];
|
||
tree.forEach(item => {
|
||
const label = item.title || item.deptName || item.name;
|
||
result.push({
|
||
label: `${' '.repeat(level)}${label}`,
|
||
value: label,
|
||
});
|
||
if (item.children && item.children.length) {
|
||
result.push(...this.flattenDeptOptions(item.children, level + 1));
|
||
}
|
||
});
|
||
return result;
|
||
},
|
||
validatePlateNo(rule, value, callback) {
|
||
const plateProvince = String(this.vehicleForm.plateProvince || '').trim();
|
||
const plateNoBody = String(this.vehicleForm.plateNoBody || '').trim().toUpperCase();
|
||
const plateColor = String(this.vehicleForm.plateColor || '').trim();
|
||
if (!plateProvince || !plateNoBody || !plateColor) {
|
||
callback(new Error('请完整填写车牌号'));
|
||
return;
|
||
}
|
||
if (!/^[A-Z][A-Z0-9挂学警港澳]{5,6}$/.test(plateNoBody)) {
|
||
callback(new Error('请输入标准车牌号'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
validateAnnualReviewEndDate(rule, value, callback) {
|
||
if (this.vehicleForm.annualReviewLongTerm !== 1 && !value) {
|
||
callback(new Error('请选择道路运输年审有效期'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
openVehicle(row, readonly = false) {
|
||
this.readonly = readonly;
|
||
if (!row?.id) {
|
||
this.vehicleForm = emptyForm();
|
||
this.syncPlateNo(false);
|
||
this.vehicleBox = true;
|
||
return;
|
||
}
|
||
getDetail(row.id).then(res => {
|
||
this.vehicleForm = {
|
||
...emptyForm(),
|
||
...(res.data.data || {}),
|
||
};
|
||
this.splitPlateNo();
|
||
this.syncPlateNo(false);
|
||
this.vehicleBox = true;
|
||
});
|
||
},
|
||
resetVehicle() {
|
||
this.vehicleForm = emptyForm();
|
||
this.vehicleCertificateUploads = {};
|
||
this.readonly = false;
|
||
this.submitLoading = false;
|
||
this.$refs.vehicleForm?.clearValidate();
|
||
},
|
||
setImage(prop, url) {
|
||
this.vehicleForm[prop] = url;
|
||
this.$refs.vehicleForm?.validateField(prop);
|
||
},
|
||
handleVehicleCertificateUploadSuccess(prop, url, file = {}) {
|
||
this.setImage(prop, url);
|
||
this.vehicleCertificateUploads[prop] = this.getUploadRecognitionKey(file, url);
|
||
this.recognizeVehicleCertificates();
|
||
},
|
||
recognizeVehicleCertificates() {
|
||
const objectKeys = [
|
||
this.vehicleCertificateUploads.drivingLicenseImage || this.vehicleForm.drivingLicenseImage,
|
||
this.vehicleCertificateUploads.drivingLicenseMainBack ||
|
||
this.vehicleForm.drivingLicenseMainBack,
|
||
this.vehicleCertificateUploads.drivingLicenseViceFront ||
|
||
this.vehicleForm.drivingLicenseViceFront,
|
||
this.vehicleCertificateUploads.drivingLicenseViceBack ||
|
||
this.vehicleForm.drivingLicenseViceBack,
|
||
this.vehicleCertificateUploads.registrationImage || this.vehicleForm.registrationImage,
|
||
].filter(Boolean);
|
||
if (!objectKeys.length) {
|
||
this.$message.warning('车辆资质图片上传成功,未获取到图片地址,无法自动识别');
|
||
return;
|
||
}
|
||
const loading = ElLoading.service({
|
||
lock: true,
|
||
text: '车辆资质识别中',
|
||
background: 'rgba(255, 255, 255, 0.7)',
|
||
});
|
||
recognitionTransportCertificates(objectKeys)
|
||
.then(res => {
|
||
this.applyVehicleCertificateRecognition(res.data.data || {});
|
||
this.$message.success('车辆资质识别完成');
|
||
})
|
||
.catch(() => {
|
||
this.$message.warning('车辆资质图片上传成功,自动识别失败,请手动填写车辆资质信息');
|
||
})
|
||
.finally(() => {
|
||
loading.close();
|
||
});
|
||
},
|
||
applyVehicleCertificateRecognition(data = {}) {
|
||
const plateNo = data.drivingPlateNo || data.vehicleLicensePlateNo || '';
|
||
const vehicleType = data.vehicleLicenseVehicleType || data.vehicleType || '';
|
||
const drivingLicenseNo =
|
||
data.vehicleLicenseFileNo || data.drivingLicenseNo || data.fileNo || '';
|
||
const drivingLicenseEndDate =
|
||
data.vehicleLicenseInspectionValidDate ||
|
||
data.drivingLicenseEndDate ||
|
||
data.vehicleLicenseValidDateEnd ||
|
||
'';
|
||
const registrationDate =
|
||
data.vehicleLicenseRegisterDate || data.registrationDate || data.dateRegister || '';
|
||
const registrationNo = data.registrationNo || data.vehicleLicenseNo || '';
|
||
if (plateNo) {
|
||
this.vehicleForm.plateNo = plateNo;
|
||
this.splitPlateNo();
|
||
this.syncPlateNo(false);
|
||
}
|
||
if (vehicleType) {
|
||
this.vehicleForm.vehicleType = this.normalizeVehicleType(vehicleType);
|
||
}
|
||
if (drivingLicenseNo) {
|
||
this.vehicleForm.drivingLicenseNo = drivingLicenseNo;
|
||
}
|
||
if (drivingLicenseEndDate) {
|
||
this.vehicleForm.drivingLicenseEndDate = this.normalizeDate(drivingLicenseEndDate);
|
||
this.vehicleForm.drivingLicenseLongTerm = 0;
|
||
}
|
||
if (registrationDate) {
|
||
this.vehicleForm.registrationDate = this.normalizeDate(registrationDate);
|
||
}
|
||
if (registrationNo) {
|
||
this.vehicleForm.registrationNo = registrationNo;
|
||
}
|
||
this.$nextTick(() => {
|
||
[
|
||
'plateNo',
|
||
'vehicleType',
|
||
'drivingLicenseNo',
|
||
'drivingLicenseEndDate',
|
||
'registrationNo',
|
||
].forEach(prop => {
|
||
this.$refs.vehicleForm?.validateField(prop);
|
||
});
|
||
});
|
||
},
|
||
getUploadRecognitionKey(file = {}, url = '') {
|
||
return (
|
||
file.objectKey ||
|
||
file.key ||
|
||
file.fileName ||
|
||
file.name ||
|
||
file.link ||
|
||
file.url ||
|
||
file.domain ||
|
||
url ||
|
||
''
|
||
);
|
||
},
|
||
normalizeDate(value = '') {
|
||
const dateValue = String(value || '').trim();
|
||
const match = dateValue.match(/^(\d{4})[-/.年](\d{1,2})[-/.月](\d{1,2})日?$/);
|
||
if (!match) return dateValue;
|
||
const [, year, month, day] = match;
|
||
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||
},
|
||
normalizeVehicleType(value = '') {
|
||
const vehicleType = String(value || '').trim();
|
||
const option = this.vehicleTypeOptions.find(
|
||
item => item.value === vehicleType || item.label === vehicleType
|
||
);
|
||
return option ? option.value : vehicleType;
|
||
},
|
||
handlePlateBodyInput(value) {
|
||
this.vehicleForm.plateNoBody = String(value || '').toUpperCase();
|
||
this.syncPlateNo();
|
||
},
|
||
syncPlateNo(validate = true) {
|
||
const plateProvince = String(this.vehicleForm.plateProvince || '').trim();
|
||
const plateNoBody = String(this.vehicleForm.plateNoBody || '').trim().toUpperCase();
|
||
const plateColor = String(this.vehicleForm.plateColor || '').trim();
|
||
this.vehicleForm.plateNoBody = plateNoBody;
|
||
this.vehicleForm.plateNo = plateProvince && plateNoBody ? `${plateProvince}${plateNoBody}` : '';
|
||
this.vehicleForm.plateColor = plateColor;
|
||
if (validate) {
|
||
this.$refs.vehicleForm?.validateField('plateNo');
|
||
}
|
||
},
|
||
splitPlateNo() {
|
||
const plateNo = String(this.vehicleForm.plateNo || '').trim();
|
||
if (!plateNo) {
|
||
this.vehicleForm.plateProvince = '';
|
||
this.vehicleForm.plateNoBody = '';
|
||
return;
|
||
}
|
||
this.vehicleForm.plateProvince = plateNo.slice(0, 1);
|
||
this.vehicleForm.plateNoBody = plateNo.slice(1).toUpperCase();
|
||
},
|
||
handleSubmit() {
|
||
this.syncPlateNo();
|
||
this.$refs.vehicleForm.validate(valid => {
|
||
if (!valid) {
|
||
return;
|
||
}
|
||
this.submitLoading = true;
|
||
submit(this.vehicleForm)
|
||
.then(() => {
|
||
this.$message.success('操作成功');
|
||
this.vehicleBox = 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() {
|
||
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) {
|
||
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 (includeExpire && this.query.expireStatus) {
|
||
query.expireStatus = this.query.expireStatus;
|
||
}
|
||
return query;
|
||
},
|
||
handleExport() {
|
||
this.$confirm('是否导出车辆数据?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
NProgress.start();
|
||
exportBlob(
|
||
'/blade-transport/transport-vehicle/export-transport-vehicle',
|
||
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 || '-';
|
||
},
|
||
getDateClass(row, dateProp, longTermProp) {
|
||
if (row[longTermProp] === 1 || !row[dateProp]) {
|
||
return '';
|
||
}
|
||
const today = this.$dayjs();
|
||
const endDate = this.$dayjs(row[dateProp]);
|
||
if (endDate.isBefore(today, 'day')) {
|
||
return 'date-danger';
|
||
}
|
||
if (endDate.diff(today, 'day') <= 30) {
|
||
return 'date-warning';
|
||
}
|
||
return '';
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.transport-vehicle-page {
|
||
.vehicle-stat {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
:deep(.el-table th .cell),
|
||
:deep(.el-table td .cell) {
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.vehicle-form {
|
||
.section-title {
|
||
margin: 18px 0 14px;
|
||
padding-left: 10px;
|
||
border-left: 3px solid #e74b5f;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
line-height: 18px;
|
||
}
|
||
|
||
.section-title:first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
:deep(.el-date-editor.el-input),
|
||
:deep(.el-date-editor.el-input__wrapper),
|
||
:deep(.el-select),
|
||
:deep(.el-input),
|
||
:deep(.el-input-number) {
|
||
width: 100%;
|
||
}
|
||
|
||
.plate-group {
|
||
display: grid;
|
||
grid-template-columns: 112px minmax(0, 1fr) 112px;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.dimension-group {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.dimension-group__item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.dimension-group__label {
|
||
flex-shrink: 0;
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.date-with-flag {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.date-with-flag :deep(.el-date-editor) {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.date-with-flag :deep(.el-checkbox) {
|
||
flex-shrink: 0;
|
||
margin-right: 0;
|
||
}
|
||
|
||
.date-with-flag :deep(.el-checkbox__label) {
|
||
padding-left: 4px;
|
||
}
|
||
|
||
.date-range {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
:deep(.el-form-item__label) {
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
:deep(.vehicle-uploader) {
|
||
display: block;
|
||
width: 100%;
|
||
}
|
||
|
||
:deep(.vehicle-uploader .el-upload),
|
||
:deep(.vehicle-uploader.el-upload) {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 170px;
|
||
border: 1px dashed #c0c4cc;
|
||
background: #fafafa;
|
||
overflow: hidden;
|
||
}
|
||
|
||
:deep(.vehicle-uploader--large .el-upload),
|
||
:deep(.vehicle-uploader--large.el-upload) {
|
||
height: 260px;
|
||
}
|
||
|
||
:deep(.vehicle-uploader__image) {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
display: block;
|
||
}
|
||
|
||
:deep(.vehicle-uploader__empty) {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #606266;
|
||
}
|
||
|
||
.date-warning {
|
||
color: #f56c6c;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.date-danger {
|
||
color: #d40000;
|
||
font-weight: 600;
|
||
}
|
||
</style>
|