1、车船模块fix bug
2、运力模块fix bug 3、新增设备台账 4、其他bug 修复 5、调整组织、人员模块
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<el-dialog :model-value="modelValue" title="船舶综合台账" width="96%" top="3vh" append-to-body destroy-on-close @update:model-value="$emit('update:modelValue', $event)">
|
||||
<div v-loading="detailLoading" class="ship-ledger">
|
||||
<section v-for="section in sections" :key="section.title" class="ship-ledger__section">
|
||||
<div class="dialog-section-title">{{ section.title }}</div>
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item v-for="item in section.items" :key="item.label" :label="item.label">
|
||||
{{ display(item) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</section>
|
||||
<section class="ship-ledger__section">
|
||||
<div class="dialog-section-title">证书附件</div>
|
||||
<div class="ship-ledger__images">
|
||||
<div v-for="item in imageFields" :key="item.prop" class="ship-ledger__image-item">
|
||||
<span>{{ item.label }}</span>
|
||||
<el-image v-if="shipDetail[item.prop]" :src="shipDetail[item.prop]" :preview-src-list="imageUrls" preview-teleported fit="cover" />
|
||||
<el-empty v-else :image-size="36" description="暂无附件" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<el-tabs v-model="activeTab" @tab-change="loadRecords">
|
||||
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.label" :name="tab.name" />
|
||||
</el-tabs>
|
||||
<el-table v-loading="tableLoading" :data="records" border stripe>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column v-for="column in activeColumns" :key="column.prop" :prop="column.prop" :label="column.label" :min-width="column.width || 140" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<div class="ship-ledger__pagination"><el-pagination v-model:current-page="page.current" v-model:page-size="page.size" :page-sizes="[10, 20, 50]" :total="page.total" layout="total, sizes, prev, pager, next" @size-change="handlePageChange" @current-change="loadRecords" /></div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDetail } from '@/api/transportCapacity/transport-ship';
|
||||
import { getList as insurance } from '@/api/vehicle/insurance-record';
|
||||
import { getList as violation } from '@/api/vehicle/violation-record';
|
||||
import { getList as maintenance } from '@/api/vehicle/maintenance-record';
|
||||
import { getList as maintenancePlan } from '@/api/vehicle/maintenance-plan';
|
||||
import { getList as accident } from '@/api/vehicle/accident-record';
|
||||
import { getList as annualInspection } from '@/api/vehicle/annual-inspection-record';
|
||||
import { getList as change } from '@/api/vehicle/transport-change-record';
|
||||
import { getList as oilElectric } from '@/api/vehicle/oil-electric-record';
|
||||
import { getList as otherExpense } from '@/api/vehicle/other-expense-record';
|
||||
|
||||
const columns = (...items) => items.map(([label, prop]) => ({ label, prop }));
|
||||
const tabConfigs = [
|
||||
['insurance', '保险记录', insurance, columns(['船号', 'vehicleNo'], ['保险类型', 'insuranceType'], ['开始日期', 'startDate'], ['结束日期', 'endDate'], ['保费', 'premium'], ['保单号', 'policyNo'])],
|
||||
['violation', '违章记录', violation, columns(['船号', 'vehicleNo'], ['驾驶人/船长', 'driverName'], ['类型', 'violationType'], ['事项', 'violationItem'], ['违章时间', 'violationTime'], ['罚款金额', 'fineAmount'])],
|
||||
['maintenance', '维修记录', maintenance, columns(['船号', 'vehicleNo'], ['维修人', 'maintainer'], ['维修时间', 'maintenanceTime'], ['维修位置', 'location'], ['费用', 'cost'])],
|
||||
['maintenancePlan', '保养记录', maintenancePlan, columns(['船号', 'vehicleNo'], ['保养人', 'maintainer'], ['保养时间', 'maintenanceTime'], ['保养项目', 'maintenanceItem'], ['费用', 'cost'])],
|
||||
['accident', '事故记录', accident, columns(['船号', 'vehicleNo'], ['事故时间', 'accidentTime'], ['事故地点', 'accidentLocation'], ['事故类型', 'accidentType'], ['损失金额', 'lossAmount'])],
|
||||
['annualInspection', '年检记录', annualInspection, columns(['船号', 'vehicleNo'], ['年检日期', 'inspectionDate'], ['年检有效期至', 'inspectionEndDate'], ['年检机构', 'inspectionOrganization'], ['费用', 'cost'])],
|
||||
['change', '变更记录', change, columns(['船号', 'vehicleNo'], ['变更类型', 'changeType'], ['变更时间', 'changeTime'], ['变更前内容', 'beforeChange'], ['变更后内容', 'afterChange'])],
|
||||
['equipment', '设备台账', null, columns(['设备编码', 'equipmentCode'], ['设备名称', 'equipmentName'], ['设备类型', 'equipmentType'], ['安装日期', 'installDate'], ['状态', 'status'])],
|
||||
['oilElectric', '油电台账', oilElectric, columns(['船号', 'vehicleNo'], ['加油/充电日期', 'recordDate'], ['类型', 'energyType'], ['数量', 'quantity'], ['金额', 'amount'])],
|
||||
['otherExpense', '其他费用记录', otherExpense, columns(['船号', 'vehicleNo'], ['费用日期', 'expenseDate'], ['费用类型', 'expenseType'], ['金额', 'amount'], ['备注', 'remark'])],
|
||||
].map(([name, label, request, cols]) => ({ name, label, request, columns: cols }));
|
||||
|
||||
export default {
|
||||
props: { modelValue: Boolean, ship: { type: Object, default: () => ({}) } }, emits: ['update:modelValue'],
|
||||
data() { return { detailLoading: false, tableLoading: false, shipDetail: {}, activeTab: 'insurance', records: [], page: { current: 1, size: 10, total: 0 } }; },
|
||||
computed: {
|
||||
tabs() { return tabConfigs; }, activeConfig() { return tabConfigs.find(item => item.name === this.activeTab) || tabConfigs[0]; }, activeColumns() { return this.activeConfig.columns; },
|
||||
imageFields() { return [['船舶所有权证书', 'ownershipCertImage'], ['内河船舶安全与环保证书', 'safetyCertImage'], ['船舶国籍证书', 'nationalityCertImage'], ['最低安全配员证书', 'safeManningCertImage'], ['船舶营业运输证', 'businessTransportCertImage'], ['光船租赁登记证书', 'leaseContractImage']].map(([label, prop]) => ({ label, prop })); },
|
||||
imageUrls() { return this.imageFields.map(item => this.shipDetail[item.prop]).filter(Boolean); },
|
||||
sections() { const s = this.shipDetail; return [
|
||||
{ title: '船舶基本信息', items: [['船舶所属组织', 'organizationName'], ['船名', 'shipName'], ['安放龙骨日期/建造完工日期', 'constructionDate'], ['总长(m)', 'totalLength'], ['船宽(m)', 'shipWidth'], ['型深(m)', 'moldedDepth'], ['最大船高(m)', 'maxShipHeight'], ['空载吃水(t)', 'lightDraft'], ['满载吃水(t)', 'fullLoadDraft'], ['航区', 'navigationArea']] },
|
||||
{ title: '船舶证书信息', items: [['登记号码', 'ownershipRegistrationNo'], ['初次登记号码', 'initialRegistrationNo'], ['船舶所有人', 'shipOwner'], ['取得所有权日期', 'ownershipAcquisitionDate'], ['船舶识别号', 'shipIdentifierNo'], ['总吨', 'grossTonnage'], ['净吨', 'netTonnage'], ['船检登记号', 'shipInspectionNo'], ['船舶类型', 'shipType'], ['国籍证书有效期', 'nationalityCertDate'], ['最低安全配员证书有效期', 'safeManningCertDate'], ['起租日期', 'leaseStartDate'], ['终止日期', 'leaseEndDate'], ['船舶承租人', 'shipLessee'], ['营业运输证证书编号', 'businessTransportCertNo'], ['营业运输证发证日期', 'businessTransportCertIssueDate'], ['营业运输证有效期至', 'businessTransportCertEndDate'], ['船舶经营人', 'shipOperator']] },
|
||||
].map(section => ({ ...section, items: section.items.map(([label, prop]) => ({ label, prop, value: prop === 'constructionDate' ? `${s.keelLayingDate || '-'} / ${s.buildCompletionDate || '-'}` : prop === 'nationalityCertDate' ? `${s.nationalityCertStartDate || '-'} 至 ${s.nationalityCertEndDate || '-'}` : prop === 'safeManningCertDate' ? `${s.safeManningCertStartDate || '-'} 至 ${s.safeManningCertEndDate || '-'}` : s[prop] })) })); },
|
||||
},
|
||||
watch: { modelValue(value) { if (value) this.initialize(); } },
|
||||
methods: {
|
||||
display(item) { return item.value === null || item.value === undefined || item.value === '' ? '-' : item.value; },
|
||||
async initialize() { this.activeTab = 'insurance'; this.page.current = 1; this.shipDetail = { ...this.ship }; if (this.ship.id) { this.detailLoading = true; try { const res = await getDetail(this.ship.id); this.shipDetail = { ...this.ship, ...(res.data.data || {}) }; } finally { this.detailLoading = false; } } this.loadRecords(); },
|
||||
async loadRecords() { const { request } = this.activeConfig; if (!request) { this.records = []; this.page.total = 0; return; } this.tableLoading = true; try { const res = await request(this.page.current, this.page.size, { vehicleNo: this.shipDetail.shipName || this.ship.shipName, vehicleType: '船舶' }); const data = res.data.data || {}; this.records = data.records || []; this.page.total = data.total || 0; } finally { this.tableLoading = false; } },
|
||||
handlePageChange() { this.page.current = 1; this.loadRecords(); },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ship-ledger__section { margin-bottom: 16px; }
|
||||
.ship-ledger__images { display: flex; gap: 16px; overflow-x: auto; padding: 12px 0; }
|
||||
.ship-ledger__image-item { width: 150px; flex: 0 0 150px; text-align: center; color: #606266; }
|
||||
.ship-ledger__image-item > span { display: block; margin-bottom: 8px; }
|
||||
.ship-ledger__image-item :deep(.el-image) { width: 150px; height: 110px; border: 1px solid #eff1f7; }
|
||||
.ship-ledger__image-item :deep(.el-empty) { height: 110px; border: 1px solid #eff1f7; }
|
||||
.ship-ledger__pagination { display: flex; justify-content: flex-end; margin-top: 16px; }
|
||||
</style>
|
||||
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
title="车辆综合台账"
|
||||
width="96%"
|
||||
top="3vh"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
>
|
||||
<div v-loading="detailLoading" class="vehicle-ledger">
|
||||
<section class="vehicle-ledger__overview">
|
||||
<div class="vehicle-ledger__title">
|
||||
<span>{{ vehicleDetail.plateNo || vehicle.plateNo }}</span>
|
||||
<el-tag :type="plateColorType">{{ vehicleDetail.plateColor || '-' }}</el-tag>
|
||||
<el-tag type="primary">{{ vehicleDetail.businessRelation || '-' }}</el-tag>
|
||||
</div>
|
||||
<el-descriptions :column="4" border>
|
||||
<el-descriptions-item label="所属组织">{{ vehicleDetail.organizationName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="车辆类型">{{ vehicleDetail.vehicleType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="能源类型">{{ vehicleDetail.energyType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="外廓尺寸(mm)">{{ dimensions }}</el-descriptions-item>
|
||||
<el-descriptions-item label="核定载质量(KG)">{{ vehicleDetail.approvedLoadKg ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="准牵引总质量(KG)">{{ vehicleDetail.tractionMassKg ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="行驶证档案编号">{{ vehicleDetail.drivingLicenseNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="道路运输证号">{{ vehicleDetail.roadTransportCertNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="行驶证有效期">{{ vehicleDetail.drivingLicenseEndDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="道路运输证有效期">{{ vehicleDetail.roadTransportCertEndDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="道路运输证年审有效期">{{ vehicleDetail.annualReviewEndDate || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="vehicle-ledger__images">
|
||||
<div v-for="item in imageFields" :key="item.prop" class="vehicle-ledger__image-item">
|
||||
<span>{{ item.label }}</span>
|
||||
<el-image
|
||||
v-if="vehicleDetail[item.prop]"
|
||||
:src="vehicleDetail[item.prop]"
|
||||
:preview-src-list="imageUrls"
|
||||
preview-teleported
|
||||
fit="cover"
|
||||
/>
|
||||
<el-empty v-else :image-size="36" description="暂无图片" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-tabs v-model="activeTab" class="vehicle-ledger__tabs" @tab-change="loadRecords">
|
||||
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.label" :name="tab.name" />
|
||||
</el-tabs>
|
||||
<el-table v-loading="tableLoading" :data="records" border stripe>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in activeColumns"
|
||||
:key="column.prop"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
:min-width="column.width || 140"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
<div class="vehicle-ledger__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="page.current"
|
||||
v-model:page-size="page.size"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="page.total"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="handlePageChange"
|
||||
@current-change="loadRecords"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDetail } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getInsuranceList } from '@/api/vehicle/insurance-record';
|
||||
import { getList as getViolationList } from '@/api/vehicle/violation-record';
|
||||
import { getList as getMaintenanceList } from '@/api/vehicle/maintenance-record';
|
||||
import { getList as getMaintenancePlanList } from '@/api/vehicle/maintenance-plan';
|
||||
import { getList as getTireList } from '@/api/vehicle/tire-replacement-record';
|
||||
import { getList as getAccidentList } from '@/api/vehicle/accident-record';
|
||||
import { getList as getAnnualInspectionList } from '@/api/vehicle/annual-inspection-record';
|
||||
import { getList as getMileageList } from '@/api/vehicle/mileage-record';
|
||||
import { getList as getChangeList } from '@/api/vehicle/transport-change-record';
|
||||
import { getList as getOilElectricList } from '@/api/vehicle/oil-electric-record';
|
||||
import { getList as getEtcList } from '@/api/vehicle/etc-record';
|
||||
import { getList as getOtherExpenseList } from '@/api/vehicle/other-expense-record';
|
||||
|
||||
const cols = (...items) => items.map(([label, prop, width]) => ({ label, prop, width }));
|
||||
const tabConfigs = [
|
||||
['insurance', '保险记录', getInsuranceList, cols(['车牌号', 'vehicleNo'], ['保险类型', 'insuranceType'], ['开始日期', 'startDate'], ['结束日期', 'endDate'], ['保费', 'premium'], ['保单号', 'policyNo'])],
|
||||
['violation', '违章记录', getViolationList, cols(['车牌号', 'vehicleNo'], ['驾驶人', 'driverName'], ['类型', 'violationType'], ['事项', 'violationItem'], ['违章时间', 'violationTime'], ['罚款金额', 'fineAmount'])],
|
||||
['maintenance', '维修记录', getMaintenanceList, cols(['车牌号', 'vehicleNo'], ['维修人', 'maintainer'], ['维修时间', 'maintenanceTime'], ['维修位置', 'location'], ['费用', 'cost'], ['维修单位', 'company'])],
|
||||
['maintenancePlan', '保养记录', getMaintenancePlanList, cols(['车牌号', 'vehicleNo'], ['保养人', 'maintainer'], ['保养时间', 'maintenanceTime'], ['保养项目', 'maintenanceItem'], ['费用', 'cost'], ['下次保养时间', 'nextMaintenanceTime'])],
|
||||
['tire', '换胎记录', getTireList, cols(['车牌号', 'vehicleNo'], ['更换时间', 'replacementTime'], ['轮胎品牌', 'tireBrand'], ['轮胎规格', 'tireSpec'], ['费用', 'cost'])],
|
||||
['accident', '事故记录', getAccidentList, cols(['车牌号', 'vehicleNo'], ['事故时间', 'accidentTime'], ['事故地点', 'accidentLocation'], ['事故类型', 'accidentType'], ['损失金额', 'lossAmount'])],
|
||||
['annualInspection', '年检记录', getAnnualInspectionList, cols(['车牌号', 'vehicleNo'], ['年检日期', 'inspectionDate'], ['年检有效期至', 'inspectionEndDate'], ['年检机构', 'inspectionOrganization'], ['费用', 'cost'])],
|
||||
['mileage', '里程记录', getMileageList, cols(['车牌号', 'vehicleNo'], ['记录时间', 'recordTime'], ['里程数', 'mileage'], ['里程单位', 'mileageUnit'], ['备注', 'remark'])],
|
||||
['change', '变更记录', getChangeList, cols(['车牌号', 'vehicleNo'], ['变更类型', 'changeType'], ['变更时间', 'changeTime'], ['变更前内容', 'beforeChange'], ['变更后内容', 'afterChange'])],
|
||||
['equipment', '设备台账', null, cols(['设备编码', 'equipmentCode'], ['设备名称', 'equipmentName'], ['设备类型', 'equipmentType'], ['安装日期', 'installDate'], ['状态', 'status'])],
|
||||
['oilElectric', '油电台账', getOilElectricList, cols(['车牌号', 'vehicleNo'], ['加油/充电日期', 'recordDate'], ['类型', 'energyType'], ['数量', 'quantity'], ['金额', 'amount'])],
|
||||
['etc', 'ETC记录', getEtcList, cols(['车牌号', 'vehicleNo'], ['交易日期', 'transactionDate'], ['交易金额', 'amount'], ['交易地点', 'location'], ['ETC卡号', 'etcCardNo'])],
|
||||
['otherExpense', '其他费用记录', getOtherExpenseList, cols(['车牌号', 'vehicleNo'], ['费用日期', 'expenseDate'], ['费用类型', 'expenseType'], ['金额', 'amount'], ['备注', 'remark'])],
|
||||
].map(([name, label, request, columns]) => ({ name, label, request, columns }));
|
||||
|
||||
export default {
|
||||
props: { modelValue: Boolean, vehicle: { type: Object, default: () => ({}) } },
|
||||
emits: ['update:modelValue'],
|
||||
data() {
|
||||
return { detailLoading: false, tableLoading: false, vehicleDetail: {}, activeTab: 'insurance', records: [], page: { current: 1, size: 10, total: 0 } };
|
||||
},
|
||||
computed: {
|
||||
tabs() { return tabConfigs; },
|
||||
activeConfig() { return tabConfigs.find(item => item.name === this.activeTab) || tabConfigs[0]; },
|
||||
activeColumns() { return this.activeConfig.columns; },
|
||||
dimensions() { const v = this.vehicleDetail; return [v.outerLength, v.outerWidth, v.outerHeight].filter(item => item !== null && item !== undefined && item !== '').join('×') || '-'; },
|
||||
plateColorType() { return { 黄牌: 'warning', 蓝牌: 'primary', 绿牌: 'success' }[this.vehicleDetail.plateColor] || 'info'; },
|
||||
imageFields() { return [['行驶证主页面', 'drivingLicenseImage'], ['行驶证主页反页', 'drivingLicenseMainBack'], ['行驶证副页正页', 'drivingLicenseViceFront'], ['行驶证副页反页', 'drivingLicenseViceBack'], ['道路运输证', 'roadTransportCertImage']].map(([label, prop]) => ({ label, prop })); },
|
||||
imageUrls() { return this.imageFields.map(item => this.vehicleDetail[item.prop]).filter(Boolean); },
|
||||
},
|
||||
watch: { modelValue(value) { if (value) this.initialize(); } },
|
||||
methods: {
|
||||
async initialize() { this.activeTab = 'insurance'; this.page.current = 1; this.vehicleDetail = { ...this.vehicle }; if (this.vehicle.id) { this.detailLoading = true; try { const res = await getDetail(this.vehicle.id); this.vehicleDetail = { ...this.vehicle, ...(res.data.data || {}) }; } finally { this.detailLoading = false; } } this.loadRecords(); },
|
||||
async loadRecords() { const { request } = this.activeConfig; if (!request) { this.records = []; this.page.total = 0; return; } this.tableLoading = true; try { const res = await request(this.page.current, this.page.size, { vehicleNo: this.vehicleDetail.plateNo || this.vehicle.plateNo, vehicleType: '车辆' }); const data = res.data.data || {}; this.records = data.records || []; this.page.total = data.total || 0; } finally { this.tableLoading = false; } },
|
||||
handlePageChange() { this.page.current = 1; this.loadRecords(); },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vehicle-ledger__overview { padding: 16px; border: 1px solid #eff1f7; }
|
||||
.vehicle-ledger__title { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; font-size: 26px; font-weight: 600; }
|
||||
.vehicle-ledger__images { display: flex; gap: 16px; margin-top: 16px; overflow-x: auto; }
|
||||
.vehicle-ledger__image-item { width: 150px; flex: 0 0 150px; color: #606266; text-align: center; }
|
||||
.vehicle-ledger__image-item > span { display: block; margin-bottom: 8px; }
|
||||
.vehicle-ledger__image-item :deep(.el-image) { width: 150px; height: 110px; border: 1px solid #eff1f7; }
|
||||
.vehicle-ledger__image-item :deep(.el-empty) { height: 110px; border: 1px solid #eff1f7; }
|
||||
.vehicle-ledger__tabs { margin-top: 16px; }
|
||||
.vehicle-ledger__pagination { display: flex; justify-content: flex-end; margin-top: 16px; }
|
||||
</style>
|
||||
@@ -41,23 +41,23 @@
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu-right>
|
||||
<el-radio-group
|
||||
v-model="query.expireStatus"
|
||||
class="driver-stat"
|
||||
@change="handleExpireChange"
|
||||
>
|
||||
<el-radio-button label="">全部({{ expiryStat.total }})</el-radio-button>
|
||||
<el-radio-button label="within30"
|
||||
>30天内到期({{ expiryStat.within30 }})</el-radio-button
|
||||
<template #expireStatus-search>
|
||||
<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)"
|
||||
>
|
||||
<el-radio-button label="expired">已到期({{ expiryStat.expired }})</el-radio-button>
|
||||
</el-radio-group>
|
||||
{{ item.label }}({{ expiryStat[item.statKey] || 0 }})
|
||||
</el-check-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #driverName="{ row }">
|
||||
<el-button type="primary" link @click="openDriver(row, true)">
|
||||
{{ row.driverName }}
|
||||
</el-button>
|
||||
<div class="driver-page__identity">
|
||||
<el-link type="primary" @click="openDriver(row, true)">{{ row.driverName }}</el-link>
|
||||
<span>{{ row.idCardNo || '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
@@ -124,7 +124,7 @@
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="司机姓名" prop="driverName">
|
||||
<el-input v-model="driverForm.driverName" maxlength="20" show-word-limit />
|
||||
<el-input v-model="driverForm.driverName" maxlength="10" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -345,8 +345,6 @@
|
||||
v-model="driverForm.qualificationType"
|
||||
clearable
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
>
|
||||
<el-option
|
||||
v-for="item in qualificationTypeOptions"
|
||||
@@ -418,32 +416,35 @@
|
||||
|
||||
<section-card title="司机联系信息">
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="司机类型" prop="driverType">
|
||||
<el-select v-model="driverForm.driverType" filterable>
|
||||
<el-option label="自有" value="自有" />
|
||||
<el-option label="外协" value="外协" />
|
||||
<el-option label="承运管理员" value="承运管理员" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="driverForm.mobile" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="紧急联系人姓名" prop="emergencyContactName">
|
||||
<el-input v-model="driverForm.emergencyContactName" maxlength="20" />
|
||||
<el-input
|
||||
v-model="driverForm.emergencyContactName"
|
||||
maxlength="20"
|
||||
@input="driverForm.emergencyContactName = removeDigits($event)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="紧急联系人手机号" prop="emergencyContactMobile">
|
||||
<el-input v-model="driverForm.emergencyContactMobile" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="与联系人关系" prop="contactRelation">
|
||||
<el-select
|
||||
v-model="driverForm.contactRelation"
|
||||
@@ -461,7 +462,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属组织" prop="organizationName">
|
||||
<el-select v-model="driverForm.organizationName" clearable filterable>
|
||||
<el-option
|
||||
@@ -502,6 +503,7 @@ import {
|
||||
recognitionTransportCertificates,
|
||||
} from '@/api/transportCapacity/driver';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { getLazyTree } from '@/api/base/region';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { getToken } from '@/utils/auth';
|
||||
@@ -639,18 +641,8 @@ export default {
|
||||
],
|
||||
educationOptions: ['初中', '高中', '中专', '大专', '本科', '硕士及以上'],
|
||||
drivingTypeOptions: ['A1', 'A2', 'A3', 'B1', 'B2', 'C1', 'C2'],
|
||||
qualificationTypeOptions: [
|
||||
'放射品押运员',
|
||||
'道路运输从业资格证',
|
||||
'危险货物运输从业资格证',
|
||||
'A1',
|
||||
'A2',
|
||||
'B1',
|
||||
'B2',
|
||||
'C1',
|
||||
'C2',
|
||||
],
|
||||
relationOptions: ['父母', '配偶', '子女', '兄弟姐妹', '朋友', '同事'],
|
||||
qualificationTypeOptions: [],
|
||||
relationOptions: ['父母', '配偶', '子女', '兄弟姐妹', '朋友', '同事', '其他'],
|
||||
organizationOptions: [],
|
||||
regionOptions: [],
|
||||
formRules: {
|
||||
@@ -686,6 +678,7 @@ export default {
|
||||
mobile: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||
emergencyContactName: [
|
||||
{ required: true, message: '请输入紧急联系人姓名', trigger: 'blur' },
|
||||
{ validator: this.validateEmergencyContactName, trigger: 'blur' },
|
||||
],
|
||||
emergencyContactMobile: [
|
||||
{ required: true, message: '请输入紧急联系人手机号', trigger: 'blur' },
|
||||
@@ -726,10 +719,18 @@ export default {
|
||||
emitPath: true,
|
||||
};
|
||||
},
|
||||
expiryTagOptions() {
|
||||
return [
|
||||
{ label: '全部', value: '', statKey: 'total' },
|
||||
{ label: '30天内到期', value: 'within30', statKey: 'within30' },
|
||||
{ label: '已到期', value: 'expired', statKey: 'expired' },
|
||||
];
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
this.initRegionOptions();
|
||||
this.initQualificationTypeOptions();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
@@ -740,6 +741,16 @@ export default {
|
||||
this.organizationOptions = this.formatDeptOptions(res.data.data || []);
|
||||
const column = this.findColumn(this.option.column, 'organizationName');
|
||||
column.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);
|
||||
});
|
||||
},
|
||||
formatDeptOptions(tree = [], level = 0) {
|
||||
@@ -749,6 +760,8 @@ export default {
|
||||
result.push({
|
||||
label: `${' '.repeat(level)}${label}`,
|
||||
value: label,
|
||||
rawLabel: label,
|
||||
id: item.id || item.value,
|
||||
});
|
||||
if (item.children && item.children.length) {
|
||||
result.push(...this.formatDeptOptions(item.children, level + 1));
|
||||
@@ -756,6 +769,13 @@ export default {
|
||||
});
|
||||
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 || []);
|
||||
@@ -885,10 +905,21 @@ export default {
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateEmergencyContactName(rule, value, callback) {
|
||||
if (/\d/.test(value || '')) {
|
||||
callback(new Error('紧急联系人姓名不能包含数字'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
removeDigits(value = '') {
|
||||
return String(value).replace(/\d/g, '');
|
||||
},
|
||||
openDriver(row, readonly = false) {
|
||||
this.readonly = readonly;
|
||||
if (!row?.id) {
|
||||
this.driverForm = emptyForm();
|
||||
this.driverForm.organizationName = this.getCurrentOrganizationName();
|
||||
this.driverBox = true;
|
||||
return;
|
||||
}
|
||||
@@ -921,9 +952,9 @@ export default {
|
||||
this.setImage(prop, url);
|
||||
this.recognizeIdCard(url);
|
||||
},
|
||||
handleDrivingLicenseUploadSuccess(prop, url, file = {}) {
|
||||
handleDrivingLicenseUploadSuccess(prop, url) {
|
||||
this.setImage(prop, url);
|
||||
this.drivingLicenseUploads[prop] = this.getUploadRecognitionKey(file, url);
|
||||
this.drivingLicenseUploads[prop] = url;
|
||||
this.recognizeDrivingLicense();
|
||||
},
|
||||
recognizeIdCard(url = '') {
|
||||
@@ -976,10 +1007,10 @@ export default {
|
||||
buildDrivingLicenseRecognitionCertificates() {
|
||||
return ['drivingLicenseFront', 'drivingLicenseBack']
|
||||
.map(prop => {
|
||||
const objectKey = this.drivingLicenseUploads[prop] || this.driverForm[prop];
|
||||
if (!objectKey) return null;
|
||||
const url = this.drivingLicenseUploads[prop] || this.driverForm[prop];
|
||||
if (!url) return null;
|
||||
return {
|
||||
driverLicenseUrl: objectKey,
|
||||
driverLicenseUrl: url,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
@@ -1046,19 +1077,6 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
getUploadRecognitionKey(file = {}, url = '') {
|
||||
return (
|
||||
file.objectKey ||
|
||||
file.key ||
|
||||
file.fileName ||
|
||||
file.name ||
|
||||
file.link ||
|
||||
file.url ||
|
||||
file.domain ||
|
||||
url ||
|
||||
''
|
||||
);
|
||||
},
|
||||
applyIdCardRecognition(data = {}) {
|
||||
const driverName = data.name || data.driverName || this.getOcrValue(data, ['name', '姓名']);
|
||||
const idCardNo = String(
|
||||
@@ -1206,7 +1224,8 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
handleExpireChange() {
|
||||
handleExpireChange(expireStatus) {
|
||||
this.query.expireStatus = expireStatus;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
@@ -1322,8 +1341,24 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.driver-page {
|
||||
.driver-stat {
|
||||
flex-shrink: 0;
|
||||
&__identity {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__expiry-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-check-tag) {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table th .cell),
|
||||
@@ -1409,9 +1444,8 @@ export default {
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.driver-page {
|
||||
.driver-stat {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
&__expiry-tags {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,14 +44,17 @@
|
||||
批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu-right>
|
||||
<el-radio-group v-model="query.expireStatus" class="ship-stat" @change="handleExpireChange">
|
||||
<el-radio-button label="">全部({{ expiryStat.total }})</el-radio-button>
|
||||
<el-radio-button label="within30"
|
||||
>30天内到期({{ expiryStat.within30 }})</el-radio-button
|
||||
<template #expireStatus-search>
|
||||
<div class="transport-ship-page__expiry-tags">
|
||||
<el-check-tag
|
||||
v-for="item in expiryTagOptions"
|
||||
:key="item.value"
|
||||
:checked="query.expireStatus === item.value"
|
||||
@change="handleExpireChange(item.value)"
|
||||
>
|
||||
<el-radio-button label="expired">已到期({{ expiryStat.expired }})</el-radio-button>
|
||||
</el-radio-group>
|
||||
{{ item.label }}({{ expiryStat[item.statKey] || 0 }})
|
||||
</el-check-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #shipName="{ row }">
|
||||
<el-button type="primary" link @click="openShip(row, true)">
|
||||
@@ -69,14 +72,22 @@
|
||||
</span>
|
||||
</template>
|
||||
<template #safeManningCertEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'safeManningCertEndDate', 'safeManningCertLongTerm')">
|
||||
<span
|
||||
:class="
|
||||
getCertificateExpiryClass(row, 'safeManningCertEndDate', 'safeManningCertLongTerm')
|
||||
"
|
||||
>
|
||||
{{ formatEndDate(row.safeManningCertEndDate, row.safeManningCertLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #businessTransportCertEndDate="{ row }">
|
||||
<span
|
||||
:class="
|
||||
getDateClass(row, 'businessTransportCertEndDate', 'businessTransportCertLongTerm')
|
||||
getCertificateExpiryClass(
|
||||
row,
|
||||
'businessTransportCertEndDate',
|
||||
'businessTransportCertLongTerm'
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ formatEndDate(row.businessTransportCertEndDate, row.businessTransportCertLongTerm) }}
|
||||
@@ -94,7 +105,7 @@
|
||||
<el-link
|
||||
type="primary"
|
||||
v-if="hasPermission('transport_ship_view')"
|
||||
@click="openShip(row, true)"
|
||||
@click="openShipLedger(row)"
|
||||
>
|
||||
综合台账
|
||||
</el-link>
|
||||
@@ -108,6 +119,8 @@
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<ship-ledger v-model="shipLedgerBox" :ship="ledgerShip" />
|
||||
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
append-to-body
|
||||
@@ -146,7 +159,7 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="船名" prop="shipName">
|
||||
<el-input v-model="shipForm.shipName" maxlength="50" show-word-limit />
|
||||
<el-input v-model="shipForm.shipName" maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -172,64 +185,64 @@
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="总长" prop="totalLength">
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="shipForm.totalLength"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
@input="normalizeNonNegativeDecimal('totalLength')"
|
||||
>
|
||||
<template #suffix>m</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="船宽" prop="shipWidth">
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="shipForm.shipWidth"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
@input="normalizeNonNegativeDecimal('shipWidth')"
|
||||
>
|
||||
<template #suffix>m</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="型深" prop="moldedDepth">
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="shipForm.moldedDepth"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
@input="normalizeNonNegativeDecimal('moldedDepth')"
|
||||
>
|
||||
<template #suffix>m</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最大船高" prop="maxShipHeight">
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="shipForm.maxShipHeight"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
@input="normalizeNonNegativeDecimal('maxShipHeight')"
|
||||
>
|
||||
<template #suffix>m</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="空载吃水" prop="lightDraft">
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="shipForm.lightDraft"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
@input="normalizeNonNegativeDecimal('lightDraft')"
|
||||
>
|
||||
<template #suffix>t</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="满载吃水" prop="fullLoadDraft">
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="shipForm.fullLoadDraft"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
@input="normalizeNonNegativeDecimal('fullLoadDraft')"
|
||||
>
|
||||
<template #suffix>t</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -349,7 +362,12 @@
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="船舶类型" prop="shipType">
|
||||
<el-input v-model="shipForm.shipType" maxlength="50" />
|
||||
<el-select v-model="shipForm.shipType" placeholder="请选择船舶类型" clearable>
|
||||
<el-option label="散货船" value="散货船" />
|
||||
<el-option label="集装箱船" value="集装箱船" />
|
||||
<el-option label="杂货船" value="杂货船" />
|
||||
<el-option label="油船" value="油船" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -577,6 +595,7 @@ import { getToken } from '@/utils/auth';
|
||||
import { getUploadHeaders } from '@/utils/upload';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import ImageUploadField from '@/components/image-upload-field/main.vue';
|
||||
import ShipLedger from './components/ship-ledger.vue';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: '',
|
||||
@@ -626,7 +645,7 @@ const emptyForm = () => ({
|
||||
});
|
||||
|
||||
export default {
|
||||
components: { ImageUploadField, InfoFilled },
|
||||
components: { ImageUploadField, InfoFilled, ShipLedger },
|
||||
data() {
|
||||
return {
|
||||
option,
|
||||
@@ -641,13 +660,18 @@ export default {
|
||||
page: { pageSize: 10, currentPage: 1, total: 0 },
|
||||
expiryStat: { total: 0, within30: 0, expired: 0 },
|
||||
shipBox: false,
|
||||
shipLedgerBox: false,
|
||||
ledgerShip: {},
|
||||
readonly: false,
|
||||
shipForm: emptyForm(),
|
||||
uploadHeaders: getUploadHeaders(),
|
||||
organizationOptions: [],
|
||||
formRules: {
|
||||
organizationName: [{ required: true, message: '请选择船舶所属组织', trigger: 'change' }],
|
||||
shipName: [{ required: true, message: '请输入船名', trigger: 'blur' }],
|
||||
shipName: [
|
||||
{ required: true, message: '请输入船名', trigger: 'blur' },
|
||||
{ max: 20, message: '船名不能超过20个字符', trigger: 'blur' },
|
||||
],
|
||||
keelLayingDate: [{ validator: this.validateKeelCompletionDate, trigger: 'change' }],
|
||||
shipIdentifierNo: [{ required: true, message: '请输入船舶识别号', trigger: 'blur' }],
|
||||
totalLength: [{ validator: this.validateNonNegativeField, trigger: 'change' }],
|
||||
@@ -663,7 +687,7 @@ export default {
|
||||
grossTonnage: [{ required: true, message: '请输入总吨', trigger: 'change' }],
|
||||
netTonnage: [{ required: true, message: '请输入净吨', trigger: 'change' }],
|
||||
shipInspectionNo: [{ required: true, message: '请输入船检登记号', trigger: 'blur' }],
|
||||
shipType: [{ required: true, message: '请输入船舶类型', trigger: 'blur' }],
|
||||
shipType: [{ required: true, message: '请选择船舶类型', trigger: 'change' }],
|
||||
nationalityCertStartDate: [
|
||||
{ validator: this.validateNationalityCertDate, trigger: 'change' },
|
||||
],
|
||||
@@ -704,6 +728,13 @@ export default {
|
||||
if (this.readonly) return '船舶综合台账';
|
||||
return this.shipForm.id ? '修改船舶' : '新增船舶';
|
||||
},
|
||||
expiryTagOptions() {
|
||||
return [
|
||||
{ label: '全部', value: '', statKey: 'total' },
|
||||
{ label: '30天内到期', value: 'within30', statKey: 'within30' },
|
||||
{ label: '已到期', value: 'expired', statKey: 'expired' },
|
||||
];
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
@@ -717,6 +748,9 @@ export default {
|
||||
this.organizationOptions = this.flattenDeptOptions(res.data.data || []);
|
||||
const column = this.findColumn(this.option.column, 'organizationName');
|
||||
column.dicData = this.organizationOptions;
|
||||
if (this.shipBox && !this.shipForm.id && !this.shipForm.organizationName) {
|
||||
this.shipForm.organizationName = this.getCurrentOrganizationName();
|
||||
}
|
||||
});
|
||||
},
|
||||
flattenDeptOptions(tree = [], level = 0) {
|
||||
@@ -726,6 +760,8 @@ export default {
|
||||
result.push({
|
||||
label: `${' '.repeat(level)}${label}`,
|
||||
value: label,
|
||||
rawLabel: label,
|
||||
id: item.id || item.value,
|
||||
});
|
||||
if (item.children && item.children.length) {
|
||||
result.push(...this.flattenDeptOptions(item.children, level + 1));
|
||||
@@ -733,9 +769,22 @@ export default {
|
||||
});
|
||||
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 || '';
|
||||
},
|
||||
validateField(prop) {
|
||||
this.$refs.shipForm?.validateField(prop);
|
||||
},
|
||||
normalizeNonNegativeDecimal(prop) {
|
||||
const value = String(this.shipForm[prop] ?? '').replace(/[^\d.]/g, '');
|
||||
const [integerPart = '', ...decimalParts] = value.split('.');
|
||||
const decimalPart = decimalParts.join('').slice(0, 2);
|
||||
this.shipForm[prop] = decimalParts.length ? `${integerPart}.${decimalPart}` : integerPart;
|
||||
},
|
||||
validateNonNegativeField(rule, value, callback) {
|
||||
if (value !== undefined && value !== null && value < 0) {
|
||||
callback(new Error('数值不能小于0'));
|
||||
@@ -818,6 +867,7 @@ export default {
|
||||
this.readonly = readonly;
|
||||
if (!row?.id) {
|
||||
this.shipForm = emptyForm();
|
||||
this.shipForm.organizationName = this.getCurrentOrganizationName();
|
||||
this.shipBox = true;
|
||||
return;
|
||||
}
|
||||
@@ -826,6 +876,10 @@ export default {
|
||||
this.shipBox = true;
|
||||
});
|
||||
},
|
||||
openShipLedger(row) {
|
||||
this.ledgerShip = { ...row };
|
||||
this.shipLedgerBox = true;
|
||||
},
|
||||
resetShip() {
|
||||
this.shipForm = emptyForm();
|
||||
this.readonly = false;
|
||||
@@ -881,7 +935,8 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
handleExpireChange() {
|
||||
handleExpireChange(expireStatus) {
|
||||
this.query.expireStatus = expireStatus;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
@@ -977,14 +1032,28 @@ export default {
|
||||
if (endDate.diff(today, 'day') <= 30) return 'date-warning';
|
||||
return '';
|
||||
},
|
||||
getCertificateExpiryClass(row, dateProp, longTermProp) {
|
||||
if (row[longTermProp] === 1 || !row[dateProp]) return '';
|
||||
const endDate = this.$dayjs(row[dateProp]);
|
||||
return endDate.isValid() && endDate.diff(this.$dayjs(), 'day') <= 30 ? 'date-danger' : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.transport-ship-page {
|
||||
.ship-stat {
|
||||
flex-shrink: 0;
|
||||
&__expiry-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-check-tag) {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table th .cell),
|
||||
|
||||
@@ -41,18 +41,17 @@
|
||||
>批量删除
|
||||
</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
|
||||
<template #expireStatus-search>
|
||||
<div class="transport-vehicle-page__expiry-tags">
|
||||
<el-check-tag
|
||||
v-for="item in expiryTagOptions"
|
||||
:key="item.value"
|
||||
:checked="query.expireStatus === item.value"
|
||||
@change="handleExpireChange(item.value)"
|
||||
>
|
||||
<el-radio-button label="expired">已到期({{ expiryStat.expired }})</el-radio-button>
|
||||
</el-radio-group>
|
||||
{{ item.label }}({{ expiryStat[item.statKey] || 0 }})
|
||||
</el-check-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #plateNo="{ row }">
|
||||
<el-button type="primary" link @click="openVehicle(row, true)">
|
||||
@@ -64,6 +63,14 @@
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #boundDriver>
|
||||
<span>-</span>
|
||||
</template>
|
||||
<template #certificationStatus="{ row }">
|
||||
<el-tag :type="getCertificationStatus(row).type">
|
||||
{{ getCertificationStatus(row).label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #drivingLicenseEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'drivingLicenseEndDate', 'drivingLicenseLongTerm')">
|
||||
{{ formatEndDate(row.drivingLicenseEndDate, row.drivingLicenseLongTerm) }}
|
||||
@@ -94,6 +101,10 @@
|
||||
>
|
||||
修改
|
||||
</el-link>
|
||||
<el-link type="primary" @click="openVehicleLedger(row)">综合台账</el-link>
|
||||
<el-link type="primary" v-if="row.certificationStatus === 0" @click="openCertificationAudit(row)">
|
||||
审核
|
||||
</el-link>
|
||||
<el-link
|
||||
type="primary"
|
||||
v-if="hasPermission('transport_vehicle_status')"
|
||||
@@ -104,6 +115,26 @@
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<vehicle-ledger v-model="vehicleLedgerBox" :vehicle="ledgerVehicle" />
|
||||
|
||||
<el-dialog v-model="certificationAuditBox" title="车辆认证审核" width="92%" top="4vh">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item v-for="item in auditDetailItems" :key="item.label" :label="item.label">
|
||||
{{ formatAuditValue(item) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-form v-if="showRejectReason" class="certification-audit__reason" label-position="right" label-width="auto">
|
||||
<el-form-item label="驳回原因" required>
|
||||
<el-input v-model="certificationRejectReason" type="textarea" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="certificationAuditBox = false">取消</el-button>
|
||||
<el-button type="danger" @click="handleCertificationReject">认证驳回</el-button>
|
||||
<el-button type="primary" @click="handleCertificationApprove">认证通过</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
append-to-body
|
||||
@@ -201,29 +232,26 @@
|
||||
<div class="dimension-group">
|
||||
<div class="dimension-group__item">
|
||||
<span class="dimension-group__label">长</span>
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="vehicleForm.outerLength"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
inputmode="numeric"
|
||||
@input="vehicleForm.outerLength = digitsOnly($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="dimension-group__item">
|
||||
<span class="dimension-group__label">宽</span>
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="vehicleForm.outerWidth"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
inputmode="numeric"
|
||||
@input="vehicleForm.outerWidth = digitsOnly($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="dimension-group__item">
|
||||
<span class="dimension-group__label">高</span>
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="vehicleForm.outerHeight"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
inputmode="numeric"
|
||||
@input="vehicleForm.outerHeight = digitsOnly($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,21 +261,19 @@
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="核定载质量(KG)" prop="approvedLoadKg">
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="vehicleForm.approvedLoadKg"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
inputmode="numeric"
|
||||
@input="vehicleForm.approvedLoadKg = digitsOnly($event)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="准牵引总质量(KG)" prop="tractionMassKg">
|
||||
<el-input-number
|
||||
<el-input
|
||||
v-model="vehicleForm.tractionMassKg"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
inputmode="numeric"
|
||||
@input="vehicleForm.tractionMassKg = digitsOnly($event)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -509,6 +535,7 @@ import {
|
||||
submit,
|
||||
remove,
|
||||
changeStatus,
|
||||
auditCertification,
|
||||
getExpiryStat,
|
||||
recognitionTransportCertificates,
|
||||
} from '@/api/transportCapacity/transport-vehicle';
|
||||
@@ -518,6 +545,7 @@ import { getToken } from '@/utils/auth';
|
||||
import { getUploadHeaders } from '@/utils/upload';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import ImageUploadField from '@/components/image-upload-field/main.vue';
|
||||
import VehicleLedger from './components/vehicle-ledger.vue';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: '',
|
||||
@@ -599,6 +627,7 @@ const plateColorOptions = ['黄牌', '蓝牌', '绿牌'].map(item => ({ label: i
|
||||
export default {
|
||||
components: {
|
||||
ImageUploadField,
|
||||
VehicleLedger,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -607,7 +636,9 @@ export default {
|
||||
query: {
|
||||
expireStatus: '',
|
||||
},
|
||||
searchForm: {},
|
||||
searchForm: {
|
||||
businessRelation: '',
|
||||
},
|
||||
loading: true,
|
||||
submitLoading: false,
|
||||
data: [],
|
||||
@@ -624,6 +655,12 @@ export default {
|
||||
expired: 0,
|
||||
},
|
||||
vehicleBox: false,
|
||||
vehicleLedgerBox: false,
|
||||
ledgerVehicle: {},
|
||||
certificationAuditBox: false,
|
||||
certificationAuditForm: {},
|
||||
certificationRejectReason: '',
|
||||
showRejectReason: false,
|
||||
readonly: false,
|
||||
vehicleForm: emptyForm(),
|
||||
vehicleCertificateUploads: {},
|
||||
@@ -643,7 +680,10 @@ export default {
|
||||
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
|
||||
formRules: {
|
||||
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||||
plateNo: [{ validator: this.validatePlateNo, trigger: 'change' }],
|
||||
plateNo: [
|
||||
{ required: true, message: '请输入车牌号', trigger: 'change' },
|
||||
{ validator: this.validatePlateNo, trigger: 'change' },
|
||||
],
|
||||
vehicleType: [{ required: true, message: '请选择车辆类型', trigger: 'change' }],
|
||||
approvedLoadKg: [{ required: true, message: '请输入核定载质量', trigger: 'blur' }],
|
||||
businessRelation: [{ required: true, message: '请选择业务关系', trigger: 'change' }],
|
||||
@@ -681,6 +721,25 @@ export default {
|
||||
}
|
||||
return this.vehicleForm.id ? '修改车辆' : '新增车辆';
|
||||
},
|
||||
auditDetailItems() {
|
||||
return [
|
||||
['所属组织', 'organizationName'], ['车牌号', 'plateNo'], ['车牌颜色', 'plateColor'],
|
||||
['车辆类型', 'vehicleType'], ['外廓长度(mm)', 'outerLength'], ['外廓宽度(mm)', 'outerWidth'],
|
||||
['外廓高度(mm)', 'outerHeight'], ['核定载质量(KG)', 'approvedLoadKg'], ['准牵引总质量(KG)', 'tractionMassKg'],
|
||||
['业务关系', 'businessRelation'], ['能源类型', 'energyType'], ['强制报废日期', 'compulsoryScrapDate'],
|
||||
['海关备案号', 'customsRecordNo'], ['行驶证档案编号', 'drivingLicenseNo'], ['行驶证有效期起', 'drivingLicenseStartDate'],
|
||||
['行驶证有效期止', 'drivingLicenseEndDate'], ['道路运输证号', 'roadTransportCertNo'], ['道路运输证有效期起', 'roadTransportCertStartDate'],
|
||||
['道路运输证有效期止', 'roadTransportCertEndDate'], ['道路运输年审有效期', 'annualReviewEndDate'],
|
||||
['机动车登记编号', 'registrationNo'], ['机动车登记日期', 'registrationDate'], ['备注', 'remark'],
|
||||
].map(([label, prop]) => ({ label, prop }));
|
||||
},
|
||||
expiryTagOptions() {
|
||||
return [
|
||||
{ label: '全部', value: '', statKey: 'total' },
|
||||
{ label: '30天内到期', value: 'within30', statKey: 'within30' },
|
||||
{ label: '已到期', value: 'expired', statKey: 'expired' },
|
||||
];
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
@@ -694,6 +753,9 @@ export default {
|
||||
this.organizationOptions = this.flattenDeptOptions(res.data.data || []);
|
||||
const column = this.findColumn(this.option.column, 'organizationName');
|
||||
column.dicData = this.organizationOptions;
|
||||
if (this.vehicleBox && !this.vehicleForm.id && !this.vehicleForm.organizationName) {
|
||||
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
|
||||
}
|
||||
});
|
||||
},
|
||||
flattenDeptOptions(tree = [], level = 0) {
|
||||
@@ -703,6 +765,8 @@ export default {
|
||||
result.push({
|
||||
label: `${' '.repeat(level)}${label}`,
|
||||
value: label,
|
||||
rawLabel: label,
|
||||
id: item.id || item.value,
|
||||
});
|
||||
if (item.children && item.children.length) {
|
||||
result.push(...this.flattenDeptOptions(item.children, level + 1));
|
||||
@@ -710,6 +774,16 @@ export default {
|
||||
});
|
||||
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 || '';
|
||||
},
|
||||
digitsOnly(value = '') {
|
||||
return String(value).replace(/\D/g, '');
|
||||
},
|
||||
validatePlateNo(rule, value, callback) {
|
||||
const plateProvince = String(this.vehicleForm.plateProvince || '').trim();
|
||||
const plateNoBody = String(this.vehicleForm.plateNoBody || '')
|
||||
@@ -737,6 +811,7 @@ export default {
|
||||
this.readonly = readonly;
|
||||
if (!row?.id) {
|
||||
this.vehicleForm = emptyForm();
|
||||
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
|
||||
this.syncPlateNo(false);
|
||||
this.vehicleBox = true;
|
||||
return;
|
||||
@@ -751,6 +826,38 @@ export default {
|
||||
this.vehicleBox = true;
|
||||
});
|
||||
},
|
||||
openVehicleLedger(row) {
|
||||
this.ledgerVehicle = { ...row };
|
||||
this.vehicleLedgerBox = true;
|
||||
},
|
||||
openCertificationAudit(row) {
|
||||
getDetail(row.id).then(res => {
|
||||
this.certificationAuditForm = { ...(res.data.data || {}) };
|
||||
this.certificationRejectReason = '';
|
||||
this.showRejectReason = false;
|
||||
this.certificationAuditBox = true;
|
||||
});
|
||||
},
|
||||
formatAuditValue(item) {
|
||||
const value = this.certificationAuditForm[item.prop];
|
||||
return value === undefined || value === null || value === '' ? '-' : value;
|
||||
},
|
||||
handleCertificationApprove() {
|
||||
auditCertification(this.certificationAuditForm.id, 1).then(() => {
|
||||
this.$message.success('认证已通过');
|
||||
this.certificationAuditBox = false;
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
},
|
||||
handleCertificationReject() {
|
||||
if (!this.showRejectReason) { this.showRejectReason = true; return; }
|
||||
if (!this.certificationRejectReason.trim()) { this.$message.warning('请输入认证驳回原因'); return; }
|
||||
auditCertification(this.certificationAuditForm.id, 2, this.certificationRejectReason).then(() => {
|
||||
this.$message.success('认证已驳回');
|
||||
this.certificationAuditBox = false;
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
},
|
||||
resetVehicle() {
|
||||
this.vehicleForm = emptyForm();
|
||||
this.vehicleCertificateUploads = {};
|
||||
@@ -762,9 +869,9 @@ export default {
|
||||
this.vehicleForm[prop] = url;
|
||||
this.$refs.vehicleForm?.validateField(prop);
|
||||
},
|
||||
handleVehicleCertificateUploadSuccess(prop, url, file = {}) {
|
||||
handleVehicleCertificateUploadSuccess(prop, url) {
|
||||
this.setImage(prop, url);
|
||||
this.vehicleCertificateUploads[prop] = this.getUploadRecognitionKey(file, url);
|
||||
this.vehicleCertificateUploads[prop] = url;
|
||||
this.recognizeVehicleCertificates();
|
||||
},
|
||||
recognizeVehicleCertificates() {
|
||||
@@ -845,19 +952,6 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
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})日?$/);
|
||||
@@ -948,7 +1042,8 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
handleExpireChange() {
|
||||
handleExpireChange(expireStatus) {
|
||||
this.query.expireStatus = expireStatus;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
@@ -962,7 +1057,9 @@ export default {
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.searchForm = {};
|
||||
this.searchForm = {
|
||||
businessRelation: '',
|
||||
};
|
||||
this.query = {
|
||||
expireStatus: this.query.expireStatus,
|
||||
};
|
||||
@@ -1063,14 +1160,44 @@ export default {
|
||||
}
|
||||
return '';
|
||||
},
|
||||
getCertificationStatus(row) {
|
||||
if (row.certificationStatus === 0) return { label: '认证中', type: 'warning' };
|
||||
if (row.certificationStatus === 1) return { label: '已认证', type: 'success' };
|
||||
if (row.certificationStatus === 2) return { label: '已驳回', type: 'danger' };
|
||||
const today = this.$dayjs().startOf('day');
|
||||
const certificateDates = [
|
||||
[row.drivingLicenseEndDate, row.drivingLicenseLongTerm],
|
||||
[row.roadTransportCertEndDate, row.roadTransportCertLongTerm],
|
||||
];
|
||||
if (certificateDates.some(([date, longTerm]) => longTerm !== 1 && !date)) {
|
||||
return { label: '未认证', type: 'info' };
|
||||
}
|
||||
if (
|
||||
certificateDates.some(
|
||||
([date, longTerm]) => longTerm !== 1 && this.$dayjs(date).isBefore(today, 'day')
|
||||
)
|
||||
) {
|
||||
return { label: '已过期', type: 'danger' };
|
||||
}
|
||||
return { label: '已认证', type: 'success' };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.transport-vehicle-page {
|
||||
.vehicle-stat {
|
||||
flex-shrink: 0;
|
||||
&__expiry-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-check-tag) {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table th .cell),
|
||||
@@ -1193,7 +1320,7 @@ export default {
|
||||
}
|
||||
|
||||
.date-warning {
|
||||
color: #f56c6c;
|
||||
color: #d40000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user