1、完善发货模板

2、完善运输计划
3、完善运单管理
This commit is contained in:
2026-08-05 15:52:24 +08:00
parent e683c2cc00
commit 433c611195
72 changed files with 7556 additions and 772 deletions

View File

@@ -92,25 +92,23 @@
</span>
</template>
<template #menu="{ row }">
<el-button
<el-link
type="primary"
text
v-if="hasPermission('driver_view')"
@click="openDriver(row, true)"
>
查看
</el-button>
<el-button type="primary" text v-if="hasPermission('driver_edit')" @click="openDriver(row)">
</el-link>
<el-link type="primary" v-if="hasPermission('driver_edit')" @click="openDriver(row)">
修改
</el-button>
<el-button
</el-link>
<el-link
type="primary"
text
v-if="hasPermission('driver_status')"
@click="handleStatus(row)"
>
{{ row.status === 1 ? '停用' : '启用' }}
</el-button>
</el-link>
</template>
</avue-crud>
@@ -186,10 +184,15 @@
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="住址" prop="addressRegion">
<el-input
v-model="driverForm.addressRegion"
maxlength="100"
placeholder="省 / 市 / 区"
<el-cascader
ref="addressRegionCascader"
v-model="driverForm.addressRegionPath"
:options="regionOptions"
:props="regionCascaderProps"
:placeholder="driverForm.addressRegion || '请选择省市区'"
clearable
filterable
@change="handleAddressRegionChange"
/>
</el-form-item>
</el-col>
@@ -496,6 +499,7 @@ import {
recognitionTransportCertificates,
} from '@/api/transportCapacity/driver';
import { getDeptTree } from '@/api/system/dept';
import { getLazyTree } from '@/api/base/region';
import { exportBlob } from '@/api/common';
import { getToken } from '@/utils/auth';
import { getUploadHeaders } from '@/utils/upload';
@@ -510,6 +514,7 @@ const emptyForm = () => ({
gender: '',
nation: '',
education: '',
addressRegionPath: [],
addressRegion: '',
address: '',
postList: ['司机'],
@@ -644,6 +649,7 @@ export default {
],
relationOptions: ['父母', '配偶', '子女', '兄弟姐妹', '朋友', '同事'],
organizationOptions: [],
regionOptions: [],
formRules: {
driverName: [{ required: true, message: '请输入司机姓名', trigger: 'blur' }],
idCardNo: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
@@ -709,9 +715,18 @@ export default {
}
return this.driverForm.id ? '修改司机' : '新增司机';
},
regionCascaderProps() {
return {
value: 'id',
label: 'title',
children: 'children',
emitPath: true,
};
},
},
created() {
this.initDeptTree();
this.initRegionOptions();
},
methods: {
hasPermission(code) {
@@ -738,6 +753,117 @@ export default {
});
return result;
},
initRegionOptions() {
getLazyTree().then(res => {
const regions = this.buildRegionTree(res.data.data || []);
this.regionOptions = this.extractChinaRegionOptions(regions);
this.syncAddressRegionPath();
});
},
buildRegionTree(regions = []) {
const flatRegions = [];
const collectRegions = list => {
(list || []).forEach(item => {
flatRegions.push({
...item,
children: undefined,
});
if (item.children?.length) collectRegions(item.children);
});
};
collectRegions(regions);
const nodeMap = new Map();
flatRegions.forEach(item => {
const id = item.id === undefined || item.id === null ? item.value : item.id;
if (id === undefined || id === null) return;
nodeMap.set(String(id), {
...item,
id: String(id),
parentId:
item.parentId === undefined || item.parentId === null ? '' : String(item.parentId),
children: [],
});
});
const rootNodes = [];
nodeMap.forEach(node => {
const parent = nodeMap.get(node.parentId);
if (parent && parent !== node) {
parent.children.push(node);
} else {
rootNodes.push(node);
}
});
return rootNodes.map(item => this.normalizeRegionTreeNode(item));
},
normalizeRegionTreeNode(region) {
const children = (region.children || []).map(item => this.normalizeRegionTreeNode(item));
return {
...region,
children: children.length ? children : undefined,
leaf: !children.length,
};
},
isChinaRegion(region = {}) {
const title = String(region.title || region.name || '').trim();
return title === '中国' || title === '中华人民共和国';
},
extractChinaRegionOptions(regions) {
const china = (regions || []).find(item => this.isChinaRegion(item));
if (china?.children?.length) return china.children;
if (regions.length === 1 && regions[0].children?.length) return regions[0].children;
return regions;
},
findRegionOption(options, value) {
for (const item of options || []) {
if (String(item.id) === String(value)) return item;
const child = this.findRegionOption(item.children, value);
if (child) return child;
}
return null;
},
findRegionPathByName(options = [], addressRegion = '', parents = []) {
const target = String(addressRegion || '').replace(/\s+/g, '');
if (!target) return [];
for (const item of options || []) {
const nextParents = [...parents, item];
const nextName = nextParents.map(region => region.title || region.name || '').join('');
if (nextName === target) {
return nextParents.map(region => region.id);
}
const childPath = this.findRegionPathByName(item.children, target, nextParents);
if (childPath.length) return childPath;
}
return [];
},
getAddressRegionLabels(value) {
const nodes = this.$refs.addressRegionCascader?.getCheckedNodes?.() || [];
if (nodes.length && nodes[0].pathLabels?.length) {
return nodes[0].pathLabels;
}
return (value || [])
.map(code => this.findRegionOption(this.regionOptions, code)?.title)
.filter(Boolean);
},
handleAddressRegionChange(value) {
if (!value || !value.length) {
this.driverForm.addressRegion = '';
this.$refs.driverForm?.validateField('addressRegion');
return;
}
this.$nextTick(() => {
this.driverForm.addressRegion = this.getAddressRegionLabels(value).join('');
this.$refs.driverForm?.validateField('addressRegion');
});
},
syncAddressRegionPath() {
if (!this.driverForm.addressRegion || this.driverForm.addressRegionPath?.length) return;
const path = this.findRegionPathByName(this.regionOptions, this.driverForm.addressRegion);
if (path.length) {
this.driverForm.addressRegionPath = path;
}
},
validateDrivingLicenseEndDate(rule, value, callback) {
if (!this.driverForm.drivingLicenseStartDate) {
callback(new Error('请选择驾驶证有效期起'));
@@ -768,8 +894,12 @@ export default {
this.driverForm = {
...emptyForm(),
...detail,
addressRegionPath: Array.isArray(detail.addressRegionPath)
? detail.addressRegionPath
: [],
postList: detail.posts ? detail.posts.split(',').filter(Boolean) : [],
};
this.syncAddressRegionPath();
this.driverBox = true;
});
},
@@ -817,11 +947,8 @@ export default {
});
},
recognizeDrivingLicense() {
const objectKeys = [
this.drivingLicenseUploads.drivingLicenseFront || this.driverForm.drivingLicenseFront,
this.drivingLicenseUploads.drivingLicenseBack || this.driverForm.drivingLicenseBack,
].filter(Boolean);
if (!objectKeys.length) {
const certificates = this.buildDrivingLicenseRecognitionCertificates();
if (!certificates.length) {
this.$message.warning('驾驶证图片上传成功,未获取到图片地址,无法自动识别');
return;
}
@@ -830,7 +957,7 @@ export default {
text: '驾驶证识别中',
background: 'rgba(255, 255, 255, 0.7)',
});
recognitionTransportCertificates(objectKeys)
recognitionTransportCertificates(certificates)
.then(res => {
const data = res.data.data || {};
this.applyDrivingLicenseRecognition(data);
@@ -843,8 +970,20 @@ export default {
loading.close();
});
},
buildDrivingLicenseRecognitionCertificates() {
return ['drivingLicenseFront', 'drivingLicenseBack']
.map(prop => {
const objectKey = this.drivingLicenseUploads[prop] || this.driverForm[prop];
if (!objectKey) return null;
return {
driverLicenseUrl: objectKey,
};
})
.filter(Boolean);
},
applyDrivingLicenseRecognition(data = {}) {
const driverName = data.driverLicenseName || data.name || this.getOcrValue(data, ['name', '姓名']);
const driverName =
data.driverLicenseName || data.name || this.getOcrValue(data, ['name', '姓名']);
const idCardNo = String(
data.driverLicenseIdNo ||
data.idNo ||
@@ -1021,6 +1160,7 @@ export default {
qualificationNo: this.driverForm.qualificationNo || this.driverForm.idCardNo,
posts: this.driverForm.postList.join(','),
};
delete row.addressRegionPath;
this.submitLoading = true;
submit(row)
.then(() => {
@@ -1183,18 +1323,6 @@ export default {
flex-shrink: 0;
}
:deep(.avue-crud__search .avue-form__menu),
:deep(.avue-crud__search .avue-form__menu--right) {
flex: 0 0 auto;
width: auto;
max-width: none;
}
:deep(.avue-crud__search .avue-form__menu--right) {
margin-left: 12px;
justify-content: flex-end;
}
:deep(.el-table th .cell),
:deep(.el-table td .cell) {
white-space: nowrap;
@@ -1217,6 +1345,7 @@ export default {
:deep(.el-date-editor.el-input),
:deep(.el-date-editor.el-input__wrapper),
:deep(.el-cascader),
:deep(.el-select),
:deep(.el-input) {
width: 100%;