Files
tms-erp-web/src/views/transportCapacity/vehicle.vue
b2894lxlx 433c611195 1、完善发货模板
2、完善运输计划
3、完善运单管理
2026-08-05 15:52:24 +08:00

1200 lines
37 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
<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>