This commit is contained in:
2026-07-27 17:19:36 +08:00
parent e50d1b8fda
commit 1c7532e0de
132 changed files with 7066 additions and 1241 deletions

View File

@@ -72,7 +72,7 @@
v-for="(item, index) in apiPathList"
:key="index"
:gutter="8"
style="margin-bottom: 8px"
style="margin-bottom: 16px"
>
<el-col :span="22">
<el-input
@@ -106,7 +106,7 @@
v-for="(item, index) in extParamsList"
:key="index"
:gutter="8"
style="margin-bottom: 8px"
style="margin-bottom: 16px"
>
<el-col :span="8">
<el-input v-model="item.key" placeholder="参数名" :disabled="type === 'view'" />
@@ -330,7 +330,10 @@ export default {
menuWidth: 350,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
@@ -493,7 +496,10 @@ export default {
tip: false,
header: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -157,13 +157,16 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
selection: true,
labelWidth: 120,
searchLabelWidth: 120,
labelWidth: 'auto',
searchLabelWidth: 160,
menuWidth: 250,
dialogWidth: 900,
dialogClickModal: false,
@@ -263,7 +266,10 @@ export default {
tip: false,
simplePage: true,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
tree: true,
border: true,
index: true,
@@ -324,12 +330,15 @@ export default {
optionScope: {
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
selection: true,
labelWidth: 120,
labelWidth: 'auto',
menuWidth: 250,
dialogWidth: 900,
dialogClickModal: false,

View File

@@ -158,13 +158,16 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
selection: true,
labelWidth: 120,
searchLabelWidth: 120,
labelWidth: 'auto',
searchLabelWidth: 160,
menuWidth: 250,
dialogWidth: 900,
dialogClickModal: false,
@@ -300,7 +303,10 @@ export default {
tip: false,
simplePage: true,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
tree: true,
border: true,
index: true,
@@ -363,12 +369,15 @@ export default {
optionScope: {
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
selection: true,
labelWidth: 120,
labelWidth: 'auto',
menuWidth: 250,
dialogWidth: 900,
dialogClickModal: false,

View File

@@ -83,7 +83,7 @@
v-for="(item, index) in limitPatternList"
:key="index"
:gutter="8"
style="margin-bottom: 8px"
style="margin-bottom: 16px"
>
<el-col :span="22">
<el-input
@@ -208,9 +208,12 @@ export default {
dialogWidth: 700,
tip: false,
searchShow: true,
searchMenuSpan: 6,
labelWidth: 110,
searchLabelWidth: 90,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
labelWidth: 'auto',
searchLabelWidth: 160,
border: true,
index: true,
viewBtn: true,
@@ -360,7 +363,10 @@ export default {
tip: false,
header: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -242,14 +242,17 @@ export default {
tip: false,
simplePage: true,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
tree: true,
border: true,
index: true,
selection: true,
viewBtn: true,
labelWidth: 120,
searchLabelWidth: 120,
labelWidth: 'auto',
searchLabelWidth: 160,
menuWidth: 350,
dialogWidth: 600,
dialogClickModal: false,

View File

@@ -79,15 +79,45 @@
{{ row.status === 1 ? '停用' : '启用' }}
</el-button>
</template>
<template #districtCode-form>
<el-select
v-model="form.districtCode"
clearable
filterable
:loading="districtLoading"
:disabled="!form.cityCode"
:placeholder="form.cityCode ? '请选择区县' : '请先选择城市'"
style="width: 100%"
@change="handleDistrictChange"
>
<el-option
v-for="item in districtOptions"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</template>
<template #longitude-form>
<el-input
v-model="form.longitude"
clearable
placeholder="请输入经度"
@input="handleCoordinateInput('longitude', $event)"
/>
</template>
<template #latitude-form>
<el-input
v-model="form.latitude"
clearable
placeholder="请输入纬度"
@input="handleCoordinateInput('latitude', $event)"
/>
</template>
</avue-crud>
<el-dialog title="空港机场主数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form
:option="excelOption"
v-model="excelForm"
:upload-before="uploadBefore"
:upload-after="uploadAfter"
>
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -104,8 +134,10 @@ import { getLazyTree } from '@/api/base/region';
import { exportBlob } from '@/api/common';
import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken } from '@/utils/auth';
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@@ -128,19 +160,17 @@ export default {
}
};
const validateLongitude = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback();
} else if (Number(value) < -180 || Number(value) > 180) {
callback(new Error('经度范围为 -180 到 180'));
const message = getCoordinateValidationMessage(value, 'longitude');
if (message) {
callback(new Error(message));
} else {
callback();
}
};
const validateLatitude = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback();
} else if (Number(value) < -90 || Number(value) > 90) {
callback(new Error('纬度范围为 -90 到 90'));
const message = getCoordinateValidationMessage(value, 'latitude');
if (message) {
callback(new Error(message));
} else {
callback();
}
@@ -154,6 +184,9 @@ export default {
excelForm: {},
provinceOptions: [],
cityOptions: [],
districtOptions: [],
districtLoading: false,
regionInitializing: false,
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
@@ -165,11 +198,15 @@ export default {
height: 'auto',
calcHeight: 32,
dialogWidth: 980,
labelPosition: 'right',
tip: false,
searchShow: true,
labelWidth: 120,
searchLabelWidth: 120,
searchMenuSpan: 6,
labelWidth: 'auto',
searchLabelWidth: 160,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
@@ -185,8 +222,8 @@ export default {
label: '编码',
prop: 'code',
search: true,
addDisabled: true,
editDisabled: true,
addDisplay: false,
editDisplay: false,
placeholder: '系统自动生成',
searchPlaceholder: '请输入编码',
},
@@ -271,14 +308,35 @@ export default {
editDisplay: false,
},
{
label: '行政区划',
prop: 'regionName',
search: true,
label: '所属区县',
prop: 'districtCode',
type: 'select',
formslot: true,
hide: true,
props: {
label: 'title',
value: 'id',
},
dicData: [],
filterable: true,
rules: [{ required: true, message: '请选择区县', trigger: 'change' }],
change: ({ value }) => this.handleDistrictChange(value),
},
{
label: '所属区县',
prop: 'districtName',
minWidth: 140,
addDisplay: false,
editDisplay: false,
},
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 160,
overHidden: false,
maxlength: 128,
showWordLimit: true,
rules: [{ max: 128, message: '行政区划不能超过128字', trigger: 'blur' }],
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{
label: '详细地址',
@@ -295,20 +353,24 @@ export default {
{
label: '经度',
prop: 'longitude',
type: 'number',
precision: 6,
formslot: true,
minWidth: 130,
overHidden: false,
rules: [{ validator: validateLongitude, trigger: 'blur' }],
rules: [
{ required: true, message: '请输入经度', trigger: 'blur' },
{ validator: validateLongitude, trigger: 'blur' },
],
},
{
label: '纬度',
prop: 'latitude',
type: 'number',
precision: 6,
formslot: true,
minWidth: 130,
overHidden: false,
rules: [{ validator: validateLatitude, trigger: 'blur' }],
rules: [
{ required: true, message: '请输入纬度', trigger: 'blur' },
{ validator: validateLatitude, trigger: 'blur' },
],
},
{
label: '数据来源',
@@ -317,6 +379,8 @@ export default {
search: true,
minWidth: 120,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '初始导入', value: '初始导入' },
@@ -333,6 +397,8 @@ export default {
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
@@ -383,6 +449,7 @@ export default {
],
},
excelOption: {
labelPosition: 'right',
submitBtn: false,
emptyBtn: false,
column: [
@@ -419,7 +486,9 @@ export default {
},
isAdmin() {
const authority = this.userInfo && this.userInfo.authority;
return Array.isArray(authority) ? authority.includes('admin') : String(authority || '').includes('admin');
return Array.isArray(authority)
? authority.includes('admin')
: String(authority || '').includes('admin');
},
ids() {
let ids = [];
@@ -441,7 +510,7 @@ export default {
},
loadProvinceOptions() {
getLazyTree(DEFAULT_COUNTRY_CODE).then(res => {
this.provinceOptions = res.data.data;
this.provinceOptions = (res.data.data || []).map(this.normalizeRegionOption);
this.findColumn('provinceCode').dicData = this.provinceOptions;
});
},
@@ -452,10 +521,60 @@ export default {
return Promise.resolve();
}
return getLazyTree(provinceCode).then(res => {
this.cityOptions = res.data.data;
this.cityOptions = (res.data.data || []).map(this.normalizeRegionOption);
this.findColumn('cityCode').dicData = this.cityOptions;
});
},
loadDistrictOptions(cityCode) {
if (!cityCode) {
this.districtOptions = [];
this.findColumn('districtCode').dicData = [];
return Promise.resolve();
}
this.districtLoading = true;
return getLazyTree(cityCode)
.then(res => {
this.districtOptions = (res.data.data || []).map(this.normalizeRegionOption);
this.findColumn('districtCode').dicData = this.districtOptions;
})
.finally(() => {
this.districtLoading = false;
});
},
normalizeRegionOption(region) {
return {
...region,
id: region.id === undefined || region.id === null ? region.id : String(region.id),
};
},
normalizeRegionForm(row) {
return {
...row,
provinceCode:
row.provinceCode === undefined || row.provinceCode === null
? row.provinceCode
: String(row.provinceCode),
cityCode:
row.cityCode === undefined || row.cityCode === null ? row.cityCode : String(row.cityCode),
districtCode:
row.districtCode === undefined || row.districtCode === null
? row.districtCode
: String(row.districtCode),
};
},
ensureDistrictOption(row) {
if (!row.districtCode || !row.districtName) return;
const exists = this.districtOptions.some(item => item.id === row.districtCode);
if (exists) return;
this.districtOptions = [
...this.districtOptions,
{
id: row.districtCode,
title: row.districtName,
},
];
this.findColumn('districtCode').dicData = this.districtOptions;
},
handleIataChange(value) {
this.form.iataCode = String(value || '')
.toUpperCase()
@@ -472,22 +591,67 @@ export default {
handleProvinceChange(value) {
const province = this.provinceOptions.find(item => item.id === value);
this.form.provinceName = province ? province.title : '';
if (this.regionInitializing) {
this.loadCityOptions(value);
return;
}
this.form.cityCode = undefined;
this.form.cityName = '';
this.form.districtCode = undefined;
this.form.districtName = '';
this.form.regionCode = '';
this.form.regionName = '';
this.loadCityOptions(value);
this.loadDistrictOptions();
},
handleCityChange(value) {
const city = this.cityOptions.find(item => item.id === value);
this.form.cityName = city ? city.title : '';
if (this.regionInitializing) {
this.loadDistrictOptions(value);
return;
}
this.form.districtCode = undefined;
this.form.districtName = '';
this.form.regionCode = '';
this.form.regionName = '';
this.loadDistrictOptions(value);
},
handleDistrictChange(value) {
const district = this.districtOptions.find(item => item.id === value);
this.form.districtName = district ? district.title : '';
this.form.regionCode = value || '';
this.form.regionName = district ? district.title : '';
},
handleCoordinateInput(prop, value) {
this.form[prop] = normalizeCoordinateInput(value);
},
normalizeRow(row) {
row.code = row.iataCode ? `JC-${row.iataCode}` : row.code;
row.regionCode = String(row.regionCode || '').trim();
row.regionName = String(row.regionName || '').trim();
row.districtCode = String(row.districtCode || '').trim();
row.districtName = String(row.districtName || '').trim();
row.detailAddress = String(row.detailAddress || '').trim();
row.longitude = normalizeCoordinateInput(row.longitude);
row.latitude = normalizeCoordinateInput(row.latitude);
if (!row.dataSource) row.dataSource = '手动录入';
if (!row.status) row.status = 1;
return row;
},
validateCoordinateFields(row) {
const longitudeMessage = getCoordinateValidationMessage(row.longitude, 'longitude', true);
if (longitudeMessage) {
this.$message.warning(longitudeMessage);
return false;
}
const latitudeMessage = getCoordinateValidationMessage(row.latitude, 'latitude', true);
if (latitudeMessage) {
this.$message.warning(latitudeMessage);
return false;
}
return true;
},
validateUnique(row) {
const rowId = String(row.id || '');
const hasDuplicate = (params, prop) => {
@@ -519,6 +683,10 @@ export default {
},
rowSave(row, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateCoordinateFields(submitRow)) {
loading();
return;
}
this.validateUnique(submitRow)
.then(() => submit(submitRow))
.then(() => {
@@ -530,6 +698,10 @@ export default {
},
rowUpdate(row, index, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateCoordinateFields(submitRow)) {
loading();
return;
}
this.validateUnique(submitRow)
.then(() => submit(submitRow))
.then(() => {
@@ -584,16 +756,30 @@ export default {
},
beforeOpen(done, type) {
if (['edit', 'view'].includes(type)) {
this.regionInitializing = true;
getDetail(this.form.id).then(res => {
this.form = res.data.data;
this.loadCityOptions(this.form.provinceCode).then(() => done());
const detail = this.normalizeRegionForm(res.data.data || {});
this.loadCityOptions(detail.provinceCode)
.then(() => this.loadDistrictOptions(detail.cityCode))
.then(() => {
this.ensureDistrictOption(detail);
this.form = detail;
done();
this.$nextTick(() => {
window.setTimeout(() => {
this.regionInitializing = false;
}, 0);
});
});
});
return;
}
this.form.code = '';
this.form.dataSource = '手动录入';
this.form.status = 1;
this.loadCityOptions(this.form.provinceCode).then(() => done());
this.loadCityOptions(this.form.provinceCode)
.then(() => this.loadDistrictOptions(this.form.cityCode))
.then(() => done());
},
searchReset() {
this.query = {};
@@ -635,23 +821,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadBefore(file, done) {
const fileName = file && file.name ? file.name.toLowerCase() : '';
if (!/\.(xls|xlsx)$/.test(fileName)) {
this.$message.error('请上传 .xls,.xlsx 标准格式文件');
return false;
}
if (typeof done === 'function') {
done();
}
return true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '空港机场主数据');
},
handleExport() {
this.$confirm('是否导出空港机场主数据?', '提示', {

View File

@@ -0,0 +1,705 @@
<template>
<basic-container class="cargo-type-page">
<avue-crud
:option="option"
:table-loading="loading"
:data="data"
v-model:page="page"
v-model="form"
ref="crud"
:permission="permissionList"
:before-open="beforeOpen"
@row-save="rowSave"
@row-update="rowUpdate"
@row-del="rowDel"
@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-upload"
plain
v-if="permission.cargo_type_import"
@click="handleImport"
>批量导入
</el-button>
<el-button
type="primary"
icon="el-icon-download"
plain
v-if="permission.cargo_type_template"
@click="handleTemplate"
>下载模板
</el-button>
<el-button
type="primary"
icon="el-icon-download"
plain
v-if="permission.cargo_type_export"
@click="handleExport"
>批量导出
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
plain
v-if="permission.cargo_type_delete"
@click="handleDelete"
>批量删除
</el-button>
</template>
<template #parentCargoName="{ row }">
{{ row.typeLevel === 1 ? '/' : row.parentCargoName || '/' }}
</template>
<template #parentCargoCode="{ row }">
{{ row.typeLevel === 1 ? '/' : row.parentCargoCode || '/' }}
</template>
<template #createUserName="{ row }">
{{ row.createUserName || row.createUser || '' }}
</template>
<template #parentId-form>
<el-select
v-model="form.parentId"
filterable
remote
clearable
:remote-method="loadParentOptions"
:loading="parentLoading"
:disabled="form.typeLevel !== 2"
placeholder="请输入名称或编码搜索"
style="width: 100%"
@change="handleParentChange"
>
<el-option
v-for="item in parentOptions"
:key="item.id"
:label="`${item.cargoName}${item.cargoCode}`"
:value="item.id"
/>
</el-select>
</template>
<template #menu="{ row, index }">
<el-button
type="primary"
text
icon="el-icon-view"
v-if="permission.cargo_type_view"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-button>
<el-button
type="primary"
text
icon="el-icon-edit"
v-if="permission.cargo_type_edit"
@click="$refs.crud.rowEdit(row, index)"
>
编辑
</el-button>
<el-button
type="primary"
text
icon="el-icon-delete"
v-if="permission.cargo_type_delete"
@click="rowDel(row)"
>
删除
</el-button>
</template>
</avue-crud>
<empty-pagination
:page="page"
@size-change="sizeChange"
@current-change="currentChange"
@load="onLoad(page, query)"
/>
<el-dialog title="货物类型导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
</el-button>
</template>
</avue-form>
</el-dialog>
</basic-container>
</template>
<script>
import {
getDetail,
getList,
getNextCode,
getParentOptions,
remove,
submit,
} from '@/api/base/cargo-type';
import { exportBlob } from '@/api/common';
import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
const typeLevelDic = [
{ label: '一级货物类型', value: 1 },
{ label: '二级货物类型', value: 2 },
];
export default {
data() {
const validateCargoName = (rule, value, callback) => {
const cargoName = String(value || '').trim();
if (!cargoName) {
callback(new Error('请输入货物类型名称'));
} else if (cargoName.length > 50) {
callback(new Error('货物类型名称不能超过50字符'));
} else {
callback();
}
};
const validateCargoCode = (rule, value, callback) => {
const cargoCode = String(value || '').trim();
const pattern = this.form.typeLevel === 1 ? /^\d{2}$/ : /^\d{4}$/;
if (!pattern.test(cargoCode)) {
callback(new Error('货物类型编码格式不正确'));
} else if (
this.form.typeLevel === 2 &&
this.form.parentCargoCode &&
!cargoCode.startsWith(this.form.parentCargoCode)
) {
callback(new Error('二级编码前2位必须与上级货物类型编码一致'));
} else {
callback();
}
};
const validateRemark = (rule, value, callback) => {
if (String(value || '').length > 200) {
callback(new Error('备注不能超过200字'));
} else {
callback();
}
};
return {
form: {},
query: {},
loading: true,
data: [],
excelBox: false,
excelForm: {},
parentOptions: [],
parentLoading: false,
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
currentPage: 1,
total: 0,
},
selectionList: [],
option: {
height: 'auto',
calcHeight: 32,
dialogWidth: 760,
labelPosition: 'right',
labelWidth: 'auto',
tip: false,
searchBtnText: '查询',
emptyBtnText: '重置',
saveBtnText: '提交',
updateBtnText: '提交',
searchShow: true,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
indexWidth: 70,
viewBtn: false,
delBtn: false,
editBtn: false,
selection: true,
dialogClickModal: false,
menuWidth: 220,
column: [
{
label: '类型',
prop: 'typeLevel',
type: 'radio',
dataType: 'number',
dicData: typeLevelDic,
search: true,
value: 1,
span: 24,
minWidth: 120,
rules: [{ required: true, message: '请选择货物类型级别', trigger: 'change' }],
change: ({ value }) => this.handleTypeLevelChange(value),
},
{
label: '上级货物类型',
prop: 'parentId',
formslot: true,
hide: true,
display: false,
span: 24,
rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }],
},
{
label: '上级货物类型',
prop: 'parentCargoName',
slot: true,
addDisplay: false,
editDisplay: false,
display: false,
minWidth: 160,
},
{
label: '上级货物类型编码',
prop: 'parentCargoCode',
slot: true,
readonly: true,
addDisplay: false,
editDisplay: false,
display: false,
minWidth: 160,
rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }],
},
{
label: '货物类型',
prop: 'cargoName',
minWidth: 150,
maxlength: 50,
showWordLimit: true,
rules: [{ validator: validateCargoName, trigger: 'blur' }],
},
{
label: '货物类型编码',
prop: 'cargoCode',
minWidth: 140,
search: true,
maxlength: 4,
rules: [{ validator: validateCargoCode, trigger: 'blur' }],
change: ({ value }) => this.handleCargoCodeChange(value),
},
{
label: '创建人',
prop: 'createUserName',
slot: true,
addDisplay: false,
editDisplay: false,
display: false,
minWidth: 120,
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 4,
span: 24,
minWidth: 180,
maxlength: 200,
showWordLimit: true,
overHidden: true,
rules: [{ validator: validateRemark, trigger: 'blur' }],
},
{
label: '更新时间',
prop: 'updateTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
addDisplay: false,
editDisplay: false,
display: false,
minWidth: 160,
},
{
label: '创建时间',
prop: 'createTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
addDisplay: false,
editDisplay: false,
display: false,
minWidth: 160,
},
],
},
excelOption: {
submitBtn: false,
emptyBtn: false,
column: [
{
label: '模板上传',
prop: 'excelFile',
type: 'upload',
drag: true,
loadText: '模板上传中,请稍等',
span: 24,
propsHttp: {
res: 'data',
},
headers: {
'Blade-Auth': `bearer ${getToken()}`,
},
tip: '请上传 .xls,.xlsx 标准格式文件',
action: '/blade-system/cargo-type/import-cargo-type',
},
{
label: '模板下载',
prop: 'excelTemplate',
formslot: true,
span: 24,
},
],
},
};
},
computed: {
...mapGetters(['permission']),
permissionList() {
return {
addBtn: this.validData(this.permission.cargo_type_add, false),
};
},
ids() {
return this.selectionList.map(ele => ele.id).join(',');
},
},
methods: {
findCargoColumn(prop) {
return this.option.column.find(item => item.prop === prop) || {};
},
syncColumnDisplay() {
const isChild = this.form.typeLevel === 2;
this.findCargoColumn('parentId').display = isChild;
this.findCargoColumn('parentCargoCode').display = isChild;
this.findCargoColumn('parentCargoCode').addDisplay = isChild;
this.findCargoColumn('parentCargoCode').editDisplay = isChild;
},
handleTypeLevelChange(value) {
this.form.typeLevel = value;
if (value === 1) {
this.form.parentId = undefined;
this.form.parentCargoName = '';
this.form.parentCargoCode = '';
this.form.cargoCode = String(this.form.cargoCode || '')
.replace(/\D/g, '')
.slice(0, 2);
} else {
this.form.cargoCode = String(this.form.cargoCode || '')
.replace(/\D/g, '')
.slice(0, 4);
}
this.syncColumnDisplay();
},
handleCargoCodeChange(value) {
const maxLength = this.form.typeLevel === 1 ? 2 : 4;
this.form.cargoCode = String(value || '')
.replace(/\D/g, '')
.slice(0, maxLength);
},
loadParentOptions(keyword = '') {
this.parentLoading = true;
return getParentOptions(keyword)
.then(res => {
this.parentOptions = res.data.data || [];
})
.finally(() => {
this.parentLoading = false;
});
},
ensureParentOption(row) {
if (!row.parentId || !row.parentCargoName) return;
const exists = this.parentOptions.some(item => String(item.id) === String(row.parentId));
if (exists) return;
this.parentOptions = [
...this.parentOptions,
{
id: row.parentId,
cargoName: row.parentCargoName,
cargoCode: row.parentCargoCode,
},
];
},
handleParentChange(value) {
const parent = this.parentOptions.find(item => String(item.id) === String(value));
this.form.parentCargoName = parent ? parent.cargoName : '';
this.form.parentCargoCode = parent ? parent.cargoCode : '';
if (!parent) {
this.form.cargoCode = '';
return;
}
getNextCode(parent.cargoCode).then(res => {
this.form.cargoCode = res.data.data;
});
},
normalizeRow(row) {
row.typeLevel = Number(row.typeLevel || 1);
row.cargoName = String(row.cargoName || '').trim();
row.cargoCode = String(row.cargoCode || '').trim();
row.remark = String(row.remark || '').trim();
if (row.typeLevel === 1) {
row.parentId = undefined;
row.parentCargoName = '';
row.parentCargoCode = '';
}
return row;
},
validateRow(row) {
if (![1, 2].includes(row.typeLevel)) {
this.$message.warning('请选择货物类型级别');
return false;
}
if (row.typeLevel === 2 && !row.parentId) {
this.$message.warning('请选择上级货物类型');
return false;
}
if (!row.cargoName) {
this.$message.warning('请输入货物类型名称');
return false;
}
if (row.cargoName.length > 50) {
this.$message.warning('货物类型名称不能超过50字符');
return false;
}
const pattern = row.typeLevel === 1 ? /^\d{2}$/ : /^\d{4}$/;
if (!pattern.test(row.cargoCode)) {
this.$message.warning('货物类型编码格式不正确');
return false;
}
if (row.typeLevel === 2 && !row.cargoCode.startsWith(row.parentCargoCode)) {
this.$message.warning('二级编码前2位必须与上级货物类型编码一致');
return false;
}
if (row.remark.length > 200) {
this.$message.warning('备注不能超过200字');
return false;
}
return true;
},
rowSave(row, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateRow(submitRow)) {
loading();
return;
}
submit(submitRow).then(
() => {
this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' });
done();
},
error => {
window.console.log(error);
loading();
}
);
},
rowUpdate(row, index, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateRow(submitRow)) {
loading();
return;
}
submit(submitRow).then(
() => {
this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' });
done();
},
error => {
window.console.log(error);
loading();
}
);
},
rowDel(row) {
this.$confirm('确定要删除该货物类型吗?删除后不可恢复。', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => remove(row.id))
.then(() => {
this.afterDelete(1);
this.$message({ type: 'success', message: '操作成功!' });
});
},
handleDelete() {
if (this.selectionList.length === 0) {
this.$message.warning('请选择至少一条数据');
return;
}
this.$confirm('确定删除所选数据,删除后不可恢复?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => remove(this.ids))
.then(() => {
this.afterDelete(this.selectionList.length);
this.$message({ type: 'success', message: '操作成功!' });
this.$refs.crud.toggleSelection();
});
},
afterDelete(deleteCount) {
if (this.page.currentPage > 1 && this.data.length <= deleteCount) {
this.page.currentPage -= 1;
}
this.onLoad(this.page, this.query);
},
beforeOpen(done, type) {
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
this.form = res.data.data || {};
this.ensureParentOption(this.form);
this.syncColumnDisplay();
done();
});
return;
}
this.form = {
typeLevel: 1,
cargoName: '',
cargoCode: '',
parentId: undefined,
parentCargoName: '',
parentCargoCode: '',
remark: '',
};
this.syncColumnDisplay();
done();
},
searchReset() {
this.query = {};
this.page.currentPage = 1;
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
selectionChange(list) {
this.selectionList = list;
},
selectionClear() {
this.selectionList = [];
this.$refs.crud.toggleSelection();
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
refreshChange() {
this.onLoad(this.page, this.query);
},
onLoad(page, params = {}) {
this.loading = true;
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
.then(res => {
const data = res.data.data;
this.page.total = data.total;
this.data = data.records || [];
this.selectionClear();
})
.finally(() => {
this.loading = false;
});
},
handleImport() {
openImportDialog(this, '货物类型');
},
buildExportParams() {
const params = { ...this.query };
if (this.ids) {
params.ids = this.ids;
}
return params;
},
handleExport() {
this.$confirm('是否导出货物类型数据?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-system/cargo-type/export-cargo-type', this.buildExportParams())
.then(res => {
downloadXls(res.data, `货物类型${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})
.finally(() => {
NProgress.done();
});
});
},
handleTemplate() {
NProgress.start();
exportBlob('/blade-system/cargo-type/export-template', {})
.then(res => {
downloadXls(res.data, '货物类型导入模板.xlsx');
})
.finally(() => {
NProgress.done();
});
},
},
};
</script>
<style lang="scss" scoped>
.cargo-type-page {
:deep(.avue-crud__header) {
margin-top: 12px;
background: transparent;
}
:deep(.el-card) {
box-shadow: none;
}
:deep(.avue-crud__search) {
padding: 12px 12px 4px;
margin-bottom: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
:deep(.avue-crud__search .el-form-item__label) {
min-width: 160px;
white-space: nowrap;
}
:deep(.el-table) {
--el-table-border-color: #eff1f7;
--el-table-row-hover-bg-color: #f5f7fa;
}
:deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
background: #fafafa;
}
:deep(.el-table__body tr:nth-child(even) > td.el-table-fixed-column--left),
:deep(.el-table__body tr:nth-child(even) > td.el-table-fixed-column--right) {
background: #fafafa;
}
}
</style>

View File

@@ -133,6 +133,26 @@
</el-button>
</div>
</template>
<template #longitude-form>
<el-input
v-model="form.longitude"
clearable
placeholder="请输入经度"
:disabled="dialogReadonly || form.addressType !== '常规地址'"
@input="handleCoordinateInput('longitude', $event)"
/>
</template>
<template #latitude-form>
<el-input
v-model="form.latitude"
clearable
placeholder="请输入纬度"
:disabled="dialogReadonly || form.addressType !== '常规地址'"
@input="handleCoordinateInput('latitude', $event)"
/>
</template>
</avue-crud>
<el-dialog
@@ -179,6 +199,7 @@ import { createOption } from '@/option/base/common-address';
import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util';
import { getToken } from '@/utils/auth';
import { isCoordinateInput, normalizeCoordinateInput } from '@/utils/coordinate';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@@ -190,6 +211,8 @@ export default {
const validateLongitude = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback(new Error('经度不能为空'));
} else if (!isCoordinateInput(value)) {
callback(new Error('经度最多保留6位小数'));
} else if (Number(value) < -180 || Number(value) > 180) {
callback(new Error('经度范围为 -180 到 180'));
} else {
@@ -199,6 +222,8 @@ export default {
const validateLatitude = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback(new Error('纬度不能为空'));
} else if (!isCoordinateInput(value)) {
callback(new Error('纬度最多保留6位小数'));
} else if (Number(value) < -90 || Number(value) > 90) {
callback(new Error('纬度范围为 -90 到 90'));
} else {
@@ -319,6 +344,9 @@ export default {
this.form.longitude = source.longitude;
this.form.latitude = source.latitude;
},
handleCoordinateInput(prop, value) {
this.form[prop] = normalizeCoordinateInput(value);
},
clearTypeRelatedFields(siteCode = '') {
this.form.sourceId = undefined;
this.form.siteCode = siteCode;
@@ -356,6 +384,8 @@ export default {
detailAddress: String(row.detailAddress || '').trim(),
regionCode: String(row.regionCode || '').trim(),
regionName: String(row.regionName || '').trim(),
longitude: normalizeCoordinateInput(row.longitude),
latitude: normalizeCoordinateInput(row.latitude),
contactName: String(row.contactName || '').trim(),
contactPhone: String(row.contactPhone || '').trim(),
remark: String(row.remark || '').trim(),
@@ -400,6 +430,10 @@ export default {
return false;
}
if (row.longitude !== undefined && row.longitude !== null && row.longitude !== '') {
if (!isCoordinateInput(row.longitude)) {
this.$message.warning('经度最多保留6位小数');
return false;
}
if (Number(row.longitude) < -180 || Number(row.longitude) > 180) {
this.$message.warning('经度范围为 -180 到 180');
return false;
@@ -407,6 +441,10 @@ export default {
row.longitude = Number(row.longitude).toFixed(6);
}
if (row.latitude !== undefined && row.latitude !== null && row.latitude !== '') {
if (!isCoordinateInput(row.latitude)) {
this.$message.warning('纬度最多保留6位小数');
return false;
}
if (Number(row.latitude) < -90 || Number(row.latitude) > 90) {
this.$message.warning('纬度范围为 -90 到 90');
return false;

View File

@@ -90,7 +90,7 @@
</avue-crud>
<el-dialog title="币种汇率导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -106,6 +106,8 @@ import { changeStatus, getDetail, getList, submit } from '@/api/base/currency';
import { exportBlob } from '@/api/common';
import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken } from '@/utils/auth';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@@ -131,7 +133,7 @@ export default {
};
const validateExchangeRate = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback(new Error('请输入汇率'));
callback();
} else if (Number(value) <= 0) {
callback(new Error('汇率必须大于0'));
} else if (!/^\d+(\.\d{1,6})?$/.test(String(value))) {
@@ -157,10 +159,13 @@ export default {
height: 'auto',
calcHeight: 32,
dialogWidth: 860,
labelPosition: 'top',
labelPosition: 'right',
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
@@ -195,7 +200,6 @@ export default {
label: '汇率',
prop: 'exchangeRate',
type: 'number',
search: true,
minWidth: 120,
precision: 6,
rules: [{ validator: validateExchangeRate, trigger: 'blur' }],
@@ -216,7 +220,6 @@ export default {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
searchRange: true,
search: true,
hide: true,
addDisplay: false,
editDisplay: false,
@@ -239,7 +242,6 @@ export default {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
searchRange: true,
search: true,
hide: true,
addDisplay: false,
editDisplay: false,
@@ -278,6 +280,14 @@ export default {
showWordLimit: true,
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
},
{
label: '更新人',
prop: 'updateUserName',
formatter: formatUpdateUserName,
addDisplay: false,
editDisplay: false,
minWidth: 120,
},
{
label: '更新时间',
prop: 'updateTime',
@@ -296,7 +306,6 @@ export default {
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
searchRange: true,
search: true,
hide: true,
addDisplay: false,
editDisplay: false,
@@ -344,7 +353,10 @@ export default {
},
methods: {
handleCodeChange(value) {
this.form.code = String(value || '').toUpperCase().replace(/[^A-Z]/g, '').slice(0, 3);
this.form.code = String(value || '')
.toUpperCase()
.replace(/[^A-Z]/g, '')
.slice(0, 3);
const matched = this.data.find(item => item.code === this.form.code);
if (matched) {
this.form.name = matched.name;
@@ -375,6 +387,7 @@ export default {
normalizeRow(row) {
const values = { ...row };
values.code = String(values.code || '').toUpperCase();
values.exchangeRate = values.exchangeRate === '' ? null : values.exchangeRate;
values.dataSource = values.dataSource || '手工录入';
values.status = values.status || 1;
return values;
@@ -384,11 +397,21 @@ export default {
this.$message.warning('备注不能超过 200 字');
return false;
}
if (Number(row.exchangeRate) <= 0) {
if (
row.exchangeRate !== undefined &&
row.exchangeRate !== null &&
row.exchangeRate !== '' &&
Number(row.exchangeRate) <= 0
) {
this.$message.warning('汇率必须大于0');
return false;
}
if (!/^\d+(\.\d{1,6})?$/.test(String(row.exchangeRate))) {
if (
row.exchangeRate !== undefined &&
row.exchangeRate !== null &&
row.exchangeRate !== '' &&
!/^\d+(\.\d{1,6})?$/.test(String(row.exchangeRate))
) {
this.$message.warning('汇率最多保留6位小数');
return false;
}
@@ -519,12 +542,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '币种汇率');
},
handleExport() {
this.$confirm('是否导出币种汇率数据?', '提示', {
@@ -550,7 +568,9 @@ export default {
};
},
handleTemplate() {
exportBlob(`/blade-system/currency/export-template?${this.website.tokenHeader}=${getToken()}`).then(res => {
exportBlob(
`/blade-system/currency/export-template?${this.website.tokenHeader}=${getToken()}`
).then(res => {
downloadXls(res.data, '币种汇率模板.xlsx');
});
},

View File

@@ -93,7 +93,10 @@ export default {
saveBtnText: '提交',
updateBtnText: '提交',
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',

View File

@@ -80,9 +80,13 @@ export default {
height: 'auto',
calcHeight: 32,
dialogWidth: 680,
labelPosition: 'right',
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
@@ -94,15 +98,29 @@ export default {
menuWidth: 160,
column: [
{
label: '中文名称',
label: '费用类型',
prop: 'feeCategory',
type: 'select',
search: true,
filterable: true,
dicUrl: '/blade-system/dict/dictionary?code=fee_category',
props: {
label: 'dictValue',
value: 'dictKey',
},
minWidth: 120,
rules: [{ required: true, message: '请选择费用类型', trigger: 'change' }],
},
{
label: '费用项',
prop: 'name',
minWidth: 140,
search: true,
maxlength: 50,
rules: [{ required: true, message: '请输入中文名称', trigger: 'blur' }],
rules: [{ required: true, message: '请输入费用项', trigger: 'blur' }],
},
{
label: '英文名称',
label: '费用项代码',
prop: 'englishName',
minWidth: 160,
search: true,
@@ -119,6 +137,8 @@ export default {
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
addDisplay: false,
editDisplay: false,
value: 1,
},
{
@@ -164,6 +184,7 @@ export default {
},
methods: {
normalizeRow(row) {
row.feeCategory = String(row.feeCategory || '').trim();
row.name = String(row.name || '').trim();
row.englishName = String(row.englishName || '').trim();
if (!row.status) row.status = 1;

View File

@@ -60,6 +60,9 @@
<template #parentCode="{ row }">
<span>{{ row.category === '港口' ? '-' : row.parentCode }}</span>
</template>
<template #name-header>
<span>港口/码头</span>
</template>
<template #status="{ row }">
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
{{ row.status === 1 ? '启用' : '停用' }}
@@ -71,7 +74,7 @@
clearable
filterable
:disabled="isParentRegionLocked"
placeholder="请选择国家"
:placeholder="isParentRegionLocked ? '自动带出' : '请选择国家'"
style="width: 100%"
@change="handleCountryChange"
>
@@ -83,6 +86,32 @@
/>
</el-select>
</template>
<template #category-form>
<el-radio-group v-model="form.category" @change="handleCategoryChange">
<el-radio label="港口">港口</el-radio>
<el-radio label="码头">码头</el-radio>
</el-radio-group>
</template>
<template #code-form>
<el-input
v-if="form.category !== '码头'"
v-model="form.code"
clearable
maxlength="5"
placeholder="请输入5位大写字母"
@input="handleCodeChange"
@blur="handleCodeChange(form.code)"
/>
<div v-else>
<el-input
v-model="form.terminalCode"
clearable
maxlength="24"
placeholder="请输入码头标识"
@input="handleTerminalCodeChange"
/>
</div>
</template>
<template #menu="{ row, index }">
<el-button
type="primary"
@@ -103,27 +132,72 @@
{{ row.status === 1 ? '停用' : '启用' }}
</el-button>
</template>
<template #regionCode-form>
<el-cascader
:key="regionCascaderKey"
v-model="form.regionCode"
:props="regionProps"
<template #cityCode-form>
<el-select
v-model="form.cityCode"
clearable
filterable
:disabled="isRegionDisabled"
:loading="cityLoading"
:disabled="isCityDisabled"
:placeholder="isParentRegionLocked ? '自动带出' : '请先选择国家'"
style="width: 100%"
@change="handleRegionChange"
@change="handleCityChange"
>
<el-option
v-for="item in cityOptions"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</template>
<template #districtCode-form>
<el-select
v-model="form.districtCode"
clearable
filterable
:loading="districtLoading"
:disabled="isDistrictDisabled"
:placeholder="
isParentRegionLocked ? '自动带出' : form.cityCode ? '请选择区县' : '请先选择城市'
"
style="width: 100%"
@change="handleDistrictChange"
>
<el-option
v-for="item in districtOptions"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</template>
<template #longitude-form>
<el-input
v-model="form.longitude"
clearable
placeholder="请输入经度"
@input="handleCoordinateInput('longitude', $event)"
/>
</template>
<template #latitude-form>
<el-input
v-model="form.latitude"
clearable
placeholder="请输入纬度"
@input="handleCoordinateInput('latitude', $event)"
/>
</template>
</avue-crud>
<empty-pagination
:page="page"
@size-change="sizeChange"
@current-change="currentChange"
@load="onLoad(page, query)"
/>
<el-dialog title="港口码头主数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form
:option="excelOption"
v-model="excelForm"
:upload-before="uploadBefore"
:upload-after="uploadAfter"
>
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -147,8 +221,10 @@ import { getLazyTree } from '@/api/base/region';
import { exportBlob } from '@/api/common';
import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken } from '@/utils/auth';
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@@ -179,19 +255,17 @@ export default {
callback();
};
const validateLongitude = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback();
} else if (Number(value) < -180 || Number(value) > 180) {
callback(new Error('经度范围为 -180 到 180'));
const message = getCoordinateValidationMessage(value, 'longitude');
if (message) {
callback(new Error(message));
} else {
callback();
}
};
const validateLatitude = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback();
} else if (Number(value) < -90 || Number(value) > 90) {
callback(new Error('纬度范围为 -90 到 90'));
const message = getCoordinateValidationMessage(value, 'latitude');
if (message) {
callback(new Error(message));
} else {
callback();
}
@@ -205,17 +279,12 @@ export default {
excelForm: {},
portOptions: [],
countryOptions: [],
regionCascaderKey: 0,
regionNodeMap: {},
regionNameMap: {},
regionProps: {
lazy: true,
lazyLoad: this.loadRegionNode,
label: 'title',
value: 'id',
checkStrictly: false,
emitPath: true,
},
cityOptions: [],
cityLoading: false,
cityOptionCache: {},
districtOptions: [],
districtLoading: false,
districtOptionCache: {},
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
@@ -227,10 +296,15 @@ export default {
height: 'auto',
calcHeight: 32,
dialogWidth: 980,
labelPosition: 'right',
labelWidth: 'auto',
tip: false,
searchShow: true,
searchLabelWidth: 120,
searchMenuSpan: 6,
searchLabelWidth: 160,
searchIcon: true,
searchIndex: 8,
searchMenuSpan: 24,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
@@ -242,31 +316,13 @@ export default {
dialogClickModal: false,
menuWidth: 160,
column: [
{
label: '编码',
prop: 'code',
search: true,
width: 140,
placeholder: '港口如 CNSHG码头如 CNSHG-CT1',
rules: [
{ required: true, message: '请输入编码', trigger: 'blur' },
{ validator: validateCode, trigger: 'blur' },
],
change: ({ value }) => this.handleCodeChange(value),
blur: ({ value }) => this.handleCodeChange(value),
},
{
label: '港口/码头名称',
prop: 'name',
minWidth: 140,
search: true,
rules: [{ required: true, message: '请输入港口/码头名称', trigger: 'blur' }],
},
{
label: '类型',
prop: 'category',
type: 'select',
search: true,
formslot: true,
span: 24,
dicData: [
{ label: '全部', value: '' },
{ label: '港口', value: '港口' },
@@ -285,6 +341,8 @@ export default {
},
dicData: [],
hide: true,
span: 12,
placeholder: '请选择',
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
change: ({ value }) => this.handleParentChange(value),
},
@@ -301,50 +359,147 @@ export default {
prop: 'parentCode',
minWidth: 120,
slot: true,
disabled: true,
span: 12,
addDisplay: false,
editDisplay: false,
placeholder: '自动带出',
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
},
{
label: '港口/码头名称',
prop: 'name',
minWidth: 140,
search: true,
span: 12,
placeholder: '请输入',
rules: [{ required: true, message: '请输入港口/码头名称', trigger: 'blur' }],
},
{
label: '编码',
prop: 'code',
search: true,
formslot: true,
width: 140,
span: 12,
placeholder: '请输入编码',
rules: [
{ required: true, message: '请输入编码', trigger: 'blur' },
{ validator: validateCode, trigger: 'blur' },
],
change: ({ value }) => this.handleCodeChange(value),
blur: ({ value }) => this.handleCodeChange(value),
},
{
label: '国家',
prop: 'country',
type: 'select',
search: true,
formslot: true,
filterable: true,
dicData: [],
span: 12,
minWidth: 100,
minWidth: 140,
overHidden: false,
placeholder: '自动带出',
searchPlaceholder: '请选择 国家',
change: ({ value }) => this.handleCountryNameChange(value),
rules: [{ required: true, message: '请选择国家', trigger: 'change' }],
},
{
label: '城市',
prop: 'regionCode',
prop: 'cityCode',
formslot: true,
hide: true,
span: 12,
placeholder: '自动带出',
rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }],
},
{
label: '城市',
prop: 'city',
type: 'select',
search: true,
filterable: true,
dicData: [],
addDisplay: false,
editDisplay: false,
minWidth: 100,
change: ({ value }) => this.handleCityNameChange(value),
},
{
label: '区县',
prop: 'districtCode',
type: 'select',
formslot: true,
hide: true,
props: {
label: 'title',
value: 'id',
},
dicData: [],
span: 12,
placeholder: '选择城市后再选择区县',
rules: [{ required: true, message: '请选择区县', trigger: 'change' }],
},
{
label: '区县',
prop: 'districtName',
type: 'select',
search: true,
filterable: true,
dicData: [],
minWidth: 100,
addDisplay: false,
editDisplay: false,
},
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 140,
span: 12,
placeholder: '选择区县后自动填写',
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{
label: '详细地址',
prop: 'detailAddress',
type: 'textarea',
minRows: 3,
span: 24,
minWidth: 220,
overHidden: false,
placeholder: '请输入',
maxlength: 255,
showWordLimit: true,
rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }],
},
{
label: '经度',
prop: 'longitude',
type: 'number',
precision: 6,
formslot: true,
minWidth: 130,
span: 12,
placeholder: '请输入',
overHidden: false,
rules: [{ validator: validateLongitude, trigger: 'blur' }],
rules: [
{ required: true, message: '请输入经度', trigger: 'blur' },
{ validator: validateLongitude, trigger: 'blur' },
],
},
{
label: '纬度',
prop: 'latitude',
type: 'number',
precision: 6,
formslot: true,
minWidth: 130,
span: 12,
placeholder: '请输入',
overHidden: false,
rules: [{ validator: validateLatitude, trigger: 'blur' }],
rules: [
{ required: true, message: '请输入纬度', trigger: 'blur' },
{ validator: validateLatitude, trigger: 'blur' },
],
},
{
label: '数据来源',
@@ -353,6 +508,8 @@ export default {
search: true,
minWidth: 120,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '初始化导入', value: '初始化导入' },
@@ -369,6 +526,8 @@ export default {
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
@@ -418,6 +577,7 @@ export default {
],
},
excelOption: {
labelPosition: 'right',
submitBtn: false,
emptyBtn: false,
column: [
@@ -459,11 +619,14 @@ export default {
: String(authority || '').includes('admin');
},
isParentRegionLocked() {
return this.form.category === '码头' && !!this.form.parentId;
return this.form.category === '码头';
},
isRegionDisabled() {
isCityDisabled() {
return this.isParentRegionLocked || !this.form.countryCode;
},
isDistrictDisabled() {
return this.isParentRegionLocked || !this.form.cityCode;
},
ids() {
let ids = [];
this.selectionList.forEach(ele => {
@@ -487,36 +650,52 @@ export default {
return;
}
if (this.form.category === '码头') {
this.form.code = code.replace(/[^A-Z0-9-]/g, '');
const terminalCode = this.getTerminalCodePart(code).replace(/[^A-Z0-9]/g, '');
if (this.form.parentCode) {
this.handleTerminalCodeChange(terminalCode);
} else {
this.form.terminalCode = terminalCode;
this.form.code = code.replace(/[^A-Z0-9-]/g, '');
}
}
},
loadRegionNode(node, resolve) {
const parentCode = node.level === 0 ? this.form.countryCode : node.value;
if (!parentCode) {
resolve([]);
return;
handleTerminalCodeChange(value) {
const terminalCode = String(value || '')
.toUpperCase()
.replace(/[^A-Z0-9]/g, '');
this.form.terminalCode = terminalCode;
this.form.code =
this.form.parentCode && terminalCode ? `${this.form.parentCode}-${terminalCode}` : '';
},
getTerminalCodePart(code) {
const value = String(code || '').toUpperCase();
if (this.form.parentCode && value.startsWith(`${this.form.parentCode}-`)) {
return value.slice(this.form.parentCode.length + 1);
}
getLazyTree(parentCode).then(res => {
const regionList = res.data.data || [];
const nodes = regionList.map(region => {
this.regionNodeMap[region.id] = region;
this.regionNameMap[region.title] = region.id;
return {
id: region.id,
title: region.title,
leaf: node.level >= 1,
};
});
resolve(nodes);
});
return value.split('-').slice(1).join('-');
},
syncTerminalCode() {
if (this.form.category !== '码头') return;
this.form.terminalCode = this.getTerminalCodePart(this.form.code);
this.handleTerminalCodeChange(this.form.terminalCode);
},
setColumnDisplay(prop, display) {
const column = this.findColumn(this.option.column, prop);
if (!column) return;
column.display = display;
column.addDisplay = display;
column.editDisplay = display;
column.viewDisplay = display;
},
loadCountryOptions() {
return getLazyTree('0').then(res => {
this.countryOptions = res.data.data || [];
this.countryOptions.forEach(country => {
this.regionNodeMap[country.id] = country;
this.regionNameMap[country.title] = country.id;
});
this.countryOptions = (res.data.data || []).map(this.normalizeRegionOption);
const countryColumn = this.findColumn(this.option.column, 'country');
countryColumn.dicData = this.countryOptions.map(country => ({
label: country.title,
value: country.title,
id: country.id,
}));
});
},
loadPortOptions() {
@@ -525,22 +704,39 @@ export default {
this.findColumn(this.option.column, 'parentId').dicData = this.portOptions;
});
},
handleCategoryChange(value) {
clearParentPortFields() {
this.form.parentId = undefined;
this.form.parentCode = '';
this.form.parentName = '';
this.form.countryCode = '';
this.form.country = '';
this.clearCityValue();
},
handleCategoryChange(value, reset = true) {
const parentColumn = this.findColumn(this.option.column, 'parentId');
parentColumn.rules[0].required = value === '码头';
this.setColumnDisplay('parentId', value === '码头');
if (value === '港口') {
this.form.parentId = undefined;
this.form.parentCode = '';
this.form.parentName = '';
this.form.terminalCode = '';
this.form.code = reset ? '' : String(this.form.code || '').split('-')[0];
} else if (reset) {
this.clearParentPortFields();
}
this.handleCodeChange(this.form.code);
},
handleCountryChange(value) {
const country = this.countryOptions.find(item => item.id === value);
this.form.country = country ? country.title : '';
this.form.regionCode = [];
this.form.city = '';
this.regionCascaderKey += 1;
this.clearCityValue();
this.loadCityOptions(value);
},
handleCountryNameChange(value) {
const country = this.countryOptions.find(item => item.title === value);
this.setDistrictOptions([]);
this.loadCityOptions(country ? country.id : '');
},
syncCountryName(countryCode) {
const country = this.countryOptions.find(item => item.id === countryCode);
@@ -548,37 +744,177 @@ export default {
this.form.country = country.title;
}
},
normalizeRegionOption(region) {
return {
...region,
id: region.id === undefined || region.id === null ? region.id : String(region.id),
parentCode:
region.parentCode === undefined || region.parentCode === null
? region.parentCode
: String(region.parentCode),
};
},
setCityOptions(cityOptions) {
this.cityOptions = cityOptions;
const cityCodeColumn = this.findColumn(this.option.column, 'cityCode');
if (cityCodeColumn) {
cityCodeColumn.dicData = cityOptions;
}
const cityColumn = this.findColumn(this.option.column, 'city');
if (cityColumn) {
cityColumn.dicData = cityOptions.map(city => ({
label: city.title,
value: city.title,
id: city.id,
}));
}
},
clearCityValue() {
this.form.cityCode = undefined;
this.form.city = '';
this.clearDistrictValue();
},
clearDistrictValue() {
this.form.districtCode = undefined;
this.form.districtName = '';
this.form.regionCode = '';
},
loadCityOptions(countryCode) {
if (!countryCode) {
this.setCityOptions([]);
return Promise.resolve([]);
}
if (this.cityOptionCache[countryCode]) {
this.setCityOptions(this.cityOptionCache[countryCode]);
return Promise.resolve(this.cityOptionCache[countryCode]);
}
this.cityLoading = true;
return getLazyTree(countryCode)
.then(res => {
const firstLevelList = (res.data.data || []).map(this.normalizeRegionOption);
return Promise.all(
firstLevelList.map(parent => {
return getLazyTree(parent.id).then(childRes => {
const childList = (childRes.data.data || []).map(this.normalizeRegionOption);
return childList.map(child => ({
...child,
parentCode: child.parentCode || parent.id,
}));
});
})
).then(cityGroups => {
const cityList = cityGroups.flat();
const options = cityList.length > 0 ? cityList : firstLevelList;
this.cityOptionCache[countryCode] = options;
this.setCityOptions(options);
return options;
});
})
.finally(() => {
this.cityLoading = false;
});
},
resolveCityCode(cityName, regionCode) {
const city = this.cityOptions.find(item => item.title === cityName || item.id === regionCode);
return city ? city.id : '';
},
setDistrictOptions(districtOptions) {
this.districtOptions = districtOptions;
const districtCodeColumn = this.findColumn(this.option.column, 'districtCode');
if (districtCodeColumn) {
districtCodeColumn.dicData = districtOptions;
}
const districtNameColumn = this.findColumn(this.option.column, 'districtName');
if (districtNameColumn) {
districtNameColumn.dicData = districtOptions.map(district => ({
label: district.title,
value: district.title,
id: district.id,
}));
}
},
loadDistrictOptions(cityCode) {
if (!cityCode) {
this.setDistrictOptions([]);
return Promise.resolve([]);
}
if (this.districtOptionCache[cityCode]) {
this.setDistrictOptions(this.districtOptionCache[cityCode]);
return Promise.resolve(this.districtOptionCache[cityCode]);
}
this.districtLoading = true;
return getLazyTree(cityCode)
.then(res => {
const options = (res.data.data || []).map(this.normalizeRegionOption);
this.districtOptionCache[cityCode] = options;
this.setDistrictOptions(options);
return options;
})
.finally(() => {
this.districtLoading = false;
});
},
resolveDistrictCode(districtName, regionCode) {
const district = this.districtOptions.find(
item => item.id === regionCode || item.title === districtName
);
return district ? district.id : regionCode || '';
},
handleParentChange(value) {
const parent = this.portOptions.find(item => item.id === value);
if (!parent) return;
const terminalCode = String(this.form.code || '')
.split('-')
.slice(1)
.join('-');
if (!parent) {
this.clearParentPortFields();
this.syncTerminalCode();
return;
}
const terminalCode =
this.form.terminalCode ||
String(this.form.code || '')
.split('-')
.slice(1)
.join('-');
this.form.parentCode = parent.code;
this.form.parentName = parent.name;
if (this.form.category === '码头' && terminalCode) {
this.form.code = `${parent.code}-${terminalCode}`;
}
this.form.terminalCode = terminalCode;
this.handleTerminalCodeChange(terminalCode);
this.form.country = parent.country;
this.form.city = parent.city;
this.form.districtCode = parent.districtCode;
this.form.districtName = parent.districtName;
this.form.regionCode = parent.regionCode;
this.resolveCountryCode(parent.country)
.then(countryCode => {
this.form.countryCode = countryCode;
this.syncCountryName(countryCode);
this.regionCascaderKey += 1;
return this.resolveRegionCode(parent.country, parent.city);
return this.loadCityOptions(countryCode);
})
.then(regionCode => {
this.form.regionCode = regionCode;
.then(() => {
this.form.cityCode = this.resolveCityCode(parent.city, parent.regionCode);
return this.loadDistrictOptions(this.form.cityCode);
})
.then(() => {
this.form.districtCode = this.resolveDistrictCode(parent.districtName, parent.regionCode);
this.handleDistrictChange(this.form.districtCode);
});
},
handleRegionChange(value) {
const regionCode = Array.isArray(value) ? value : [];
const provinceCode = regionCode[0];
const cityCode = regionCode[1];
const city = this.regionNodeMap[cityCode];
handleCityChange(value) {
const city = this.cityOptions.find(item => item.id === value);
this.form.city = city ? city.title : '';
this.clearDistrictValue();
this.loadDistrictOptions(value);
},
handleCityNameChange(value) {
const city = this.cityOptions.find(item => item.title === value);
this.setDistrictOptions([]);
this.loadDistrictOptions(city ? city.id : '');
},
handleDistrictChange(value) {
const district = this.districtOptions.find(item => item.id === value);
this.form.districtName = district ? district.title : '';
this.form.regionCode = value || '';
},
handleCoordinateInput(prop, value) {
this.form[prop] = normalizeCoordinateInput(value);
},
resolveCountryCode(country) {
if (!country) {
@@ -597,55 +933,26 @@ export default {
return option ? option.id : DEFAULT_COUNTRY_CODE;
});
},
resolveRegionCode(country, city) {
if (!country || !city) {
return Promise.resolve([]);
}
const countryCode = this.form.countryCode || this.regionNameMap[country];
if (!countryCode) {
return Promise.resolve([]);
}
return this.loadRegionList(countryCode).then(provinceList => {
const province = provinceList.find(item => item.title === country || item.title === city);
if (province) {
return this.loadRegionList(province.id).then(cityList => {
const cityNode = cityList.find(item => item.title === city);
return cityNode ? [province.id, cityNode.id] : [province.id];
});
}
return provinceList.reduce((promise, item) => {
return promise.then(result => {
if (result.length > 0) {
return result;
}
return this.loadRegionList(item.id).then(cityList => {
const cityNode = cityList.find(child => child.title === city);
return cityNode ? [item.id, cityNode.id] : [];
});
});
}, Promise.resolve([]));
});
},
loadRegionList(parentCode) {
return getLazyTree(parentCode).then(res => {
const regionList = res.data.data || [];
regionList.forEach(region => {
this.regionNodeMap[region.id] = region;
this.regionNameMap[region.title] = region.id;
});
return regionList;
});
},
normalizeRow(row) {
row.code = String(row.code || '').toUpperCase();
if (row.category === '港口') {
row.parentId = undefined;
row.parentCode = undefined;
row.parentName = undefined;
const submitRow = { ...row };
submitRow.code = String(submitRow.code || '').toUpperCase();
submitRow.regionCode = String(submitRow.regionCode || '').trim();
submitRow.districtCode = String(submitRow.districtCode || '').trim();
submitRow.districtName = String(submitRow.districtName || '').trim();
submitRow.detailAddress = String(submitRow.detailAddress || '').trim();
submitRow.longitude = normalizeCoordinateInput(submitRow.longitude);
submitRow.latitude = normalizeCoordinateInput(submitRow.latitude);
if (submitRow.category === '港口') {
submitRow.parentId = undefined;
submitRow.parentCode = undefined;
submitRow.parentName = undefined;
}
if (!row.dataSource) row.dataSource = '手工导入';
if (!row.status) row.status = 1;
return row;
if (!submitRow.dataSource) submitRow.dataSource = '手工导入';
if (!submitRow.status) submitRow.status = 1;
delete submitRow.terminalCode;
delete submitRow.cityCode;
delete submitRow.countryCode;
return submitRow;
},
validateRequiredFields(row) {
const isBlank = value => value === undefined || value === null || String(value).trim() === '';
@@ -665,8 +972,26 @@ export default {
this.$message.warning('请选择上级港口');
return false;
}
if (!Array.isArray(row.regionCode) || row.regionCode.length < 2) {
this.$message.warning('请选择所属城市');
if (isBlank(row.regionCode)) {
this.$message.warning('请选择区县');
return false;
}
if (isBlank(row.longitude)) {
this.$message.warning('请输入经度');
return false;
}
const longitudeMessage = getCoordinateValidationMessage(row.longitude, 'longitude');
if (longitudeMessage) {
this.$message.warning(longitudeMessage);
return false;
}
if (isBlank(row.latitude)) {
this.$message.warning('请输入纬度');
return false;
}
const latitudeMessage = getCoordinateValidationMessage(row.latitude, 'latitude');
if (latitudeMessage) {
this.$message.warning(latitudeMessage);
return false;
}
return true;
@@ -770,37 +1095,57 @@ export default {
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
this.form = res.data.data;
this.form.terminalCode = this.getTerminalCodePart(this.form.code);
this.resolveCountryCode(this.form.country)
.then(countryCode => {
this.form.countryCode = countryCode;
this.syncCountryName(countryCode);
this.regionCascaderKey += 1;
return this.resolveRegionCode(this.form.country, this.form.city);
return this.loadCityOptions(countryCode);
})
.then(regionCode => {
this.form.regionCode = regionCode;
this.handleCategoryChange(this.form.category);
.then(() => {
this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode);
return this.loadDistrictOptions(this.form.cityCode);
})
.then(() => {
this.form.districtCode = this.resolveDistrictCode(
this.form.districtName,
this.form.regionCode
);
if (!this.form.regionCode) {
this.handleDistrictChange(this.form.districtCode);
}
this.handleCategoryChange(this.form.category, false);
this.syncTerminalCode();
done();
});
});
return;
} else {
this.resolveCountryCode(this.form.country)
.then(countryCode => {
this.form.countryCode = countryCode;
this.syncCountryName(countryCode);
this.regionCascaderKey += 1;
return this.resolveRegionCode(this.form.country, this.form.city);
})
.then(regionCode => {
this.form.regionCode = regionCode;
done();
});
return;
}
this.resolveCountryCode(this.form.country)
.then(countryCode => {
this.form.countryCode = countryCode;
this.syncCountryName(countryCode);
return this.loadCityOptions(countryCode);
})
.then(() => {
this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode);
return this.loadDistrictOptions(this.form.cityCode);
})
.then(() => {
this.form.districtCode = this.resolveDistrictCode(
this.form.districtName,
this.form.regionCode
);
if (!this.form.regionCode) {
this.handleDistrictChange(this.form.districtCode);
}
done();
});
},
searchReset() {
this.query = {};
this.setCityOptions([]);
this.setDistrictOptions([]);
this.onLoad(this.page);
},
searchChange(params, done) {
@@ -839,24 +1184,9 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadBefore(file, done) {
const fileName = file && file.name ? file.name.toLowerCase() : '';
if (!/\.(xls|xlsx)$/.test(fileName)) {
this.$message.error('请上传 .xls,.xlsx 标准格式文件');
return false;
}
if (typeof done === 'function') {
done();
}
return true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
this.loadPortOptions();
done();
openImportDialog(this, '港口码头主数据', () => {
this.loadPortOptions();
});
},
handleExport() {
this.$confirm('是否导出港口码头主数据?', '提示', {
@@ -894,13 +1224,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.port-terminal-page {
:deep(.avue-crud__header),
:deep(.el-table th .cell),
:deep(.el-table td .cell) {
white-space: nowrap;
}
}
</style>

View File

@@ -79,15 +79,45 @@
{{ row.status === 1 ? '停用' : '启用' }}
</el-button>
</template>
<template #districtCode-form>
<el-select
v-model="form.districtCode"
clearable
filterable
:loading="districtLoading"
:disabled="!form.cityCode"
:placeholder="form.cityCode ? '请选择区县' : '请先选择城市'"
style="width: 100%"
@change="handleDistrictChange"
>
<el-option
v-for="item in districtOptions"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</template>
<template #longitude-form>
<el-input
v-model="form.longitude"
clearable
placeholder="请输入经度"
@input="handleCoordinateInput('longitude', $event)"
/>
</template>
<template #latitude-form>
<el-input
v-model="form.latitude"
clearable
placeholder="请输入纬度"
@input="handleCoordinateInput('latitude', $event)"
/>
</template>
</avue-crud>
<el-dialog title="铁路车站主数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form
:option="excelOption"
v-model="excelForm"
:upload-before="uploadBefore"
:upload-after="uploadAfter"
>
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -104,8 +134,10 @@ import { getLazyTree } from '@/api/base/region';
import { exportBlob } from '@/api/common';
import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken } from '@/utils/auth';
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@@ -128,19 +160,17 @@ export default {
}
};
const validateLongitude = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback();
} else if (Number(value) < -180 || Number(value) > 180) {
callback(new Error('经度范围为 -180 到 180'));
const message = getCoordinateValidationMessage(value, 'longitude');
if (message) {
callback(new Error(message));
} else {
callback();
}
};
const validateLatitude = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback();
} else if (Number(value) < -90 || Number(value) > 90) {
callback(new Error('纬度范围为 -90 到 90'));
const message = getCoordinateValidationMessage(value, 'latitude');
if (message) {
callback(new Error(message));
} else {
callback();
}
@@ -154,6 +184,8 @@ export default {
excelForm: {},
provinceOptions: [],
cityOptions: [],
districtOptions: [],
districtLoading: false,
regionInitializing: false,
page: {
pageSize: 10,
@@ -166,9 +198,14 @@ export default {
height: 'auto',
calcHeight: 32,
dialogWidth: 980,
labelPosition: 'right',
labelWidth: 'auto',
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
@@ -186,8 +223,8 @@ export default {
minWidth: 120,
overHidden: false,
search: true,
addDisabled: true,
editDisabled: true,
addDisplay: false,
editDisplay: false,
placeholder: '系统自动生成',
searchPlaceholder: '请输入编码',
},
@@ -271,14 +308,36 @@ export default {
editDisplay: false,
},
{
label: '行政区划',
prop: 'regionName',
search: true,
label: '所属区县',
prop: 'districtCode',
type: 'select',
formslot: true,
hide: true,
props: {
label: 'title',
value: 'id',
},
dicData: [],
filterable: true,
span: 12,
rules: [{ required: true, message: '请选择所属区县', trigger: 'change' }],
change: ({ value }) => this.handleDistrictChange(value),
},
{
label: '所属区县',
prop: 'districtName',
minWidth: 140,
addDisplay: false,
editDisplay: false,
},
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 160,
overHidden: false,
maxlength: 128,
showWordLimit: true,
rules: [{ max: 128, message: '行政区划不能超过128字', trigger: 'blur' }],
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{
label: '详细地址',
@@ -295,20 +354,24 @@ export default {
{
label: '经度',
prop: 'longitude',
type: 'number',
precision: 6,
formslot: true,
minWidth: 130,
overHidden: false,
rules: [{ validator: validateLongitude, trigger: 'blur' }],
rules: [
{ required: true, message: '请输入经度', trigger: 'blur' },
{ validator: validateLongitude, trigger: 'blur' },
],
},
{
label: '纬度',
prop: 'latitude',
type: 'number',
precision: 6,
formslot: true,
minWidth: 130,
overHidden: false,
rules: [{ validator: validateLatitude, trigger: 'blur' }],
rules: [
{ required: true, message: '请输入纬度', trigger: 'blur' },
{ validator: validateLatitude, trigger: 'blur' },
],
},
{
label: '数据来源',
@@ -317,6 +380,8 @@ export default {
search: true,
minWidth: 120,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '初始化导入', value: '初始化导入' },
@@ -333,6 +398,8 @@ export default {
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
@@ -383,6 +450,7 @@ export default {
],
},
excelOption: {
labelPosition: 'right',
submitBtn: false,
emptyBtn: false,
column: [
@@ -419,7 +487,9 @@ export default {
},
isAdmin() {
const authority = this.userInfo && this.userInfo.authority;
return Array.isArray(authority) ? authority.includes('admin') : String(authority || '').includes('admin');
return Array.isArray(authority)
? authority.includes('admin')
: String(authority || '').includes('admin');
},
ids() {
let ids = [];
@@ -456,6 +526,22 @@ export default {
this.findColumn('cityCode').dicData = this.cityOptions;
});
},
loadDistrictOptions(cityCode) {
if (!cityCode) {
this.districtOptions = [];
this.findColumn('districtCode').dicData = [];
return Promise.resolve();
}
this.districtLoading = true;
return getLazyTree(cityCode)
.then(res => {
this.districtOptions = (res.data.data || []).map(this.normalizeRegionOption);
this.findColumn('districtCode').dicData = this.districtOptions;
})
.finally(() => {
this.districtLoading = false;
});
},
normalizeRegionOption(region) {
return {
...region,
@@ -471,6 +557,10 @@ export default {
: String(row.provinceCode),
cityCode:
row.cityCode === undefined || row.cityCode === null ? row.cityCode : String(row.cityCode),
districtCode:
row.districtCode === undefined || row.districtCode === null
? row.districtCode
: String(row.districtCode),
};
},
ensureCityOption(row) {
@@ -486,6 +576,19 @@ export default {
];
this.findColumn('cityCode').dicData = this.cityOptions;
},
ensureDistrictOption(row) {
if (!row.districtCode || !row.districtName) return;
const exists = this.districtOptions.some(item => item.id === row.districtCode);
if (exists) return;
this.districtOptions = [
...this.districtOptions,
{
id: row.districtCode,
title: row.districtName,
},
];
this.findColumn('districtCode').dicData = this.districtOptions;
},
handleTmisChange(value) {
this.form.tmisCode = String(value || '')
.replace(/\D/g, '')
@@ -507,20 +610,61 @@ export default {
}
this.form.cityCode = undefined;
this.form.cityName = '';
this.form.districtCode = undefined;
this.form.districtName = '';
this.form.regionCode = '';
this.form.regionName = '';
this.loadCityOptions(value);
this.loadDistrictOptions();
},
handleCityChange(value) {
const city = this.cityOptions.find(item => item.id === value);
this.form.cityName = city ? city.title : '';
if (this.regionInitializing) {
this.loadDistrictOptions(value).then(() => this.ensureDistrictOption(this.form));
return;
}
this.form.districtCode = undefined;
this.form.districtName = '';
this.form.regionCode = '';
this.form.regionName = '';
this.loadDistrictOptions(value);
},
handleDistrictChange(value) {
const district = this.districtOptions.find(item => item.id === value);
this.form.districtName = district ? district.title : '';
this.form.regionCode = value || '';
this.form.regionName = district ? district.title : '';
},
handleCoordinateInput(prop, value) {
this.form[prop] = normalizeCoordinateInput(value);
},
normalizeRow(row) {
row.code = row.tmisCode ? `TL${row.tmisCode}` : row.code;
row.regionCode = String(row.regionCode || '').trim();
row.regionName = String(row.regionName || '').trim();
row.districtCode = String(row.districtCode || '').trim();
row.districtName = String(row.districtName || '').trim();
row.detailAddress = String(row.detailAddress || '').trim();
row.longitude = normalizeCoordinateInput(row.longitude);
row.latitude = normalizeCoordinateInput(row.latitude);
if (!row.dataSource) row.dataSource = '手动';
if (!row.status) row.status = 1;
return row;
},
validateCoordinateFields(row) {
const longitudeMessage = getCoordinateValidationMessage(row.longitude, 'longitude', true);
if (longitudeMessage) {
this.$message.warning(longitudeMessage);
return false;
}
const latitudeMessage = getCoordinateValidationMessage(row.latitude, 'latitude', true);
if (latitudeMessage) {
this.$message.warning(latitudeMessage);
return false;
}
return true;
},
validateUnique(row) {
const rowId = String(row.id || '');
const hasDuplicate = (params, prop) => {
@@ -552,6 +696,10 @@ export default {
},
rowSave(row, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateCoordinateFields(submitRow)) {
loading();
return;
}
this.validateUnique(submitRow)
.then(() => submit(submitRow))
.then(() => {
@@ -563,6 +711,10 @@ export default {
},
rowUpdate(row, index, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateCoordinateFields(submitRow)) {
loading();
return;
}
this.validateUnique(submitRow)
.then(() => submit(submitRow))
.then(() => {
@@ -620,23 +772,30 @@ export default {
this.regionInitializing = true;
getDetail(this.form.id).then(res => {
const detail = this.normalizeRegionForm(res.data.data || {});
this.loadCityOptions(detail.provinceCode).then(() => {
this.ensureCityOption(detail);
this.form = detail;
done();
this.$nextTick(() => {
window.setTimeout(() => {
this.regionInitializing = false;
}, 0);
this.loadCityOptions(detail.provinceCode)
.then(() => {
this.ensureCityOption(detail);
return this.loadDistrictOptions(detail.cityCode);
})
.then(() => {
this.ensureDistrictOption(detail);
this.form = detail;
done();
this.$nextTick(() => {
window.setTimeout(() => {
this.regionInitializing = false;
}, 0);
});
});
});
});
return;
}
this.form.code = '';
this.form.dataSource = '手动';
this.form.status = 1;
this.loadCityOptions(this.form.provinceCode).then(() => done());
this.loadCityOptions(this.form.provinceCode)
.then(() => this.loadDistrictOptions(this.form.cityCode))
.then(() => done());
},
searchReset() {
this.query = {};
@@ -678,23 +837,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadBefore(file, done) {
const fileName = file && file.name ? file.name.toLowerCase() : '';
if (!/\.(xls|xlsx)$/.test(fileName)) {
this.$message.error('请上传 .xls,.xlsx 标准格式文件');
return false;
}
if (typeof done === 'function') {
done();
}
return true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '铁路车站主数据');
},
handleExport() {
this.$confirm('是否导出铁路车站主数据?', '提示', {

View File

@@ -1,6 +1,6 @@
<template>
<el-row>
<el-col :span="9">
<el-row class="region-page">
<el-col class="region-page__tree-panel" :span="6">
<div class="box">
<el-scrollbar>
<basic-container>
@@ -9,7 +9,7 @@
</el-scrollbar>
</div>
</el-col>
<el-col :span="15">
<el-col class="region-page__form-panel" :span="18">
<basic-container>
<el-button-group>
<el-button
@@ -55,17 +55,34 @@
@click="handleSync"
>同步
</el-button>
<!-- <el-button-->
<!-- v-if="permission.region_debug"-->
<!-- type="primary"-->
<!-- icon="el-icon-video-play"-->
<!-- @click="handleDebug"-->
<!-- >调试-->
<!-- </el-button>-->
<!-- <el-button-->
<!-- v-if="permission.region_debug"-->
<!-- type="primary"-->
<!-- icon="el-icon-video-play"-->
<!-- @click="handleDebug"-->
<!-- >调试-->
<!-- </el-button>-->
</el-button-group>
</basic-container>
<basic-container>
<avue-form ref="form" :option="regionOption" v-model="regionForm" @submit="handleSubmit">
<template #parentName="{}">
<el-select
class="region-parent-select"
v-model="regionForm.parentCode"
filterable
:disabled="isCountryRegion"
placeholder="请选择父区划"
@change="handleParentChange"
>
<el-option
v-for="item in parentOptions"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</template>
<template #code="{}">
<el-input
v-if="isCountryRegion"
@@ -83,7 +100,7 @@
</template>
</avue-form>
<el-dialog title="行政区划数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -105,6 +122,7 @@ import { exportBlob } from '@/api/common';
import { mapGetters } from 'vuex';
import { validatenull } from '@/utils/validate';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@@ -118,6 +136,7 @@ export default {
treeCode: '',
treeParentCode: '',
treeData: [],
parentOptions: [],
syncLoading: false,
treeOption: {
nodeKey: 'id',
@@ -137,7 +156,7 @@ export default {
},
addBtn: false,
menu: false,
size: 'small',
size: 'default',
props: {
labelText: '标题',
label: 'title',
@@ -147,13 +166,13 @@ export default {
},
regionForm: {},
regionOption: {
labelWidth: 100,
labelWidth: 'auto',
column: [
{
label: '父区划编号',
prop: 'parentCode',
span: 24,
disabled: true,
display: false,
rules: [
{
required: true,
@@ -166,7 +185,7 @@ export default {
label: '父区划名称',
prop: 'parentName',
span: 24,
disabled: true,
formslot: true,
},
{
label: '区划编号',
@@ -295,7 +314,7 @@ export default {
debugBox: false,
debugForm: {},
debugOption: {
labelWidth: 50,
labelWidth: 'auto',
submitBtn: false,
emptyBtn: false,
column: [
@@ -345,24 +364,25 @@ export default {
if (this.isCountryRegion) {
return;
}
const parentCode = this.isProvinceRegion ? '' : this.regionForm.parentCode;
this.regionForm.code = parentCode + this.regionForm.subCode;
this.syncRegionCode();
},
'excelForm.isCovered'() {
if (this.excelForm.isCovered !== '') {
const column = this.findColumn(this.excelOption.column, 'excelFile');
column.action = `/blade-system/region/import-region?isCovered=${this.excelForm.isCovered}`;
}
this.syncImportAction();
},
},
computed: {
...mapGetters(['permission']),
isCountryRegion() {
return Number(this.regionForm.regionLevel) === 0 || this.regionForm.parentCode === this.topCode;
return (
Number(this.regionForm.regionLevel) === 0 || this.regionForm.parentCode === this.topCode
);
},
isProvinceRegion() {
return Number(this.regionForm.regionLevel) === 1;
},
isEditRegion() {
return !validatenull(this.regionForm.originalCode);
},
permissionList() {
return {
addBtn: this.validData(this.permission.region_add, false),
@@ -392,28 +412,30 @@ export default {
});
},
nodeClick(data) {
const column = this.findColumn(this.regionOption.column, 'parentCode');
column.disabled = true;
this.setRegionColumnDisplay('code', true);
this.treeCode = data.id;
this.treeParentCode = data.parentId;
getDetail(this.treeCode).then(res => {
this.regionForm = res.data.data;
this.regionForm.originalCode = this.regionForm.code;
this.regionForm.subCode =
this.regionForm.parentCode === this.topCode
? this.regionForm.code
: this.regionForm.code.replace(this.regionForm.parentCode, '');
this.loadParentOptions(this.regionForm.parentCode, this.regionForm.parentName);
});
},
addCountry() {
const column = this.findColumn(this.regionOption.column, 'parentCode');
column.disabled = true;
this.setRegionColumnDisplay('code', true);
this.treeCode = '';
this.treeParentCode = this.topCode;
this.parentOptions = this.getRootParentOptions();
this.regionForm = {
parentCode: this.topCode,
parentName: '根节点',
code: '',
subCode: '',
originalCode: '',
name: '',
regionLevel: 0,
sort: 1,
@@ -424,13 +446,84 @@ export default {
this.$message.warning('请先选择一项区划');
return;
}
this.regionForm.parentCode = this.regionForm.code;
this.regionForm.parentName = this.regionForm.name;
const parentCode = this.regionForm.code;
const parentName = this.regionForm.name;
const parentGroupCode = this.regionForm.parentCode || this.topCode;
this.setRegionColumnDisplay('code', true);
this.regionForm.parentCode = parentCode;
this.regionForm.parentName = parentName;
this.regionForm.code = '';
this.regionForm.subCode = '';
this.regionForm.originalCode = '';
this.regionForm.name = '';
this.regionForm.regionLevel =
this.regionForm.regionLevel === 5 ? 5 : this.regionForm.regionLevel + 1;
this.loadParentOptionsByGroup(parentGroupCode, parentCode, parentName);
},
setRegionColumnDisplay(prop, display) {
const column = this.findColumn(this.regionOption.column, prop);
if (column) {
column.display = display;
}
},
handleParentChange(value) {
const parent = this.parentOptions.find(item => item.code === value);
this.regionForm.parentName = parent ? parent.name : '';
this.syncRegionCode();
},
syncRegionCode() {
if (this.isCountryRegion) {
return;
}
const parentCode = this.isProvinceRegion ? '' : this.regionForm.parentCode || '';
this.regionForm.code = parentCode + (this.regionForm.subCode || '');
},
getRootParentOptions() {
return [
{
code: this.topCode,
name: '根节点',
},
];
},
normalizeParentOption(region) {
return {
code: String(region.code || region.id || ''),
name: region.name || region.title || '',
};
},
ensureSelectedParent(options, selectedCode, selectedName) {
if (validatenull(selectedCode) || options.some(item => item.code === selectedCode)) {
return options;
}
return [
...options,
{
code: String(selectedCode),
name: selectedName || selectedCode,
},
];
},
loadParentOptions(selectedCode, selectedName) {
if (validatenull(selectedCode) || selectedCode === this.topCode) {
this.parentOptions = this.getRootParentOptions();
return;
}
getDetail(selectedCode).then(res => {
const parent = res.data.data || {};
const groupCode = parent.parentCode || this.topCode;
this.loadParentOptionsByGroup(groupCode, selectedCode, selectedName);
});
},
loadParentOptionsByGroup(groupCode, selectedCode, selectedName) {
if (validatenull(groupCode)) {
this.parentOptions = this.ensureSelectedParent([], selectedCode, selectedName);
return;
}
getLazyTree(groupCode).then(res => {
const options = res.data.data.map(this.normalizeParentOption).filter(item => item.code);
this.parentOptions = this.ensureSelectedParent(options, selectedCode, selectedName);
});
},
handleSubmit(form, done) {
if (Number(form.regionLevel) === 0) {
@@ -481,18 +574,27 @@ export default {
this.$refs.form.resetForm();
});
},
uploadAfter(res, done, loading, column) {
window.console.log(column);
this.excelBox = false;
this.initTree();
done();
handleImport() {
this.syncImportAction();
openImportDialog(this, '行政区划', () => {
this.initTree();
});
},
syncImportAction() {
const column = this.findColumn(this.excelOption.column, 'excelFile');
if (!column) {
return;
}
const covered = this.excelForm.isCovered;
if (covered === '' || covered === undefined || covered === null) {
column.action = '/blade-system/region/import-region';
} else {
column.action = `/blade-system/region/import-region?isCovered=${covered}`;
}
},
handleDebug() {
this.debugBox = true;
},
handleImport() {
this.excelBox = true;
},
handleSync() {
this.$confirm('确定同步最新行政区划数据?', {
confirmButtonText: '确定',
@@ -553,4 +655,46 @@ export default {
.box .el-scrollbar__wrap {
overflow: scroll;
}
.region-parent-select {
width: 100%;
}
.region-page .region-page__tree-panel {
flex: 0 0 17.5%;
max-width: 17.5%;
}
.region-page .region-page__form-panel {
flex: 0 0 82.5%;
max-width: 82.5%;
}
.region-page__tree-panel .avue-tree__filter {
padding: 0 8px 8px;
}
.region-page__tree-panel .avue-tree__filter .el-input,
.region-page__tree-panel .avue-tree__filter .el-input__wrapper {
width: 100%;
min-height: 32px;
}
.region-page__tree-panel .el-tree.el-tree--highlight-current {
padding: 8px;
box-sizing: border-box;
}
.region-page__form-panel .avue-form__group.avue-form__group--flex {
padding: 12px;
box-sizing: border-box;
}
@media (max-width: 768px) {
.region-page .region-page__tree-panel,
.region-page .region-page__form-panel {
flex: 0 0 100%;
max-width: 100%;
}
}
</style>

View File

@@ -89,8 +89,9 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchIndex: 3,
searchMenuSpan: 24,
searchMenuPosition: 'right',
searchIndex: 8,
searchIcon: true,
border: true,
index: true,
@@ -99,7 +100,7 @@ export default {
addBtn: false,
delBtn: false,
menuWidth: 120,
labelWidth: 120,
labelWidth: 'auto',
dialogType: 'drawer',
column: [
{

View File

@@ -59,7 +59,10 @@ const option = reactive({
dialogWidth: 950,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -60,7 +60,10 @@ export default {
dialogWidth: 950,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -24,7 +24,7 @@ export default {
file: {},
},
option: {
labelWidth: 120,
labelWidth: 'auto',
menuBtn: false,
column: [
{

View File

@@ -22,7 +22,7 @@
</template>
</avue-crud>
<el-dialog title="流程删除" append-to-body v-model="followBox" width="20%">
<el-form :model="form" ref="form" label-width="80px">
<el-form :model="form" ref="form" label-width="auto">
<el-form-item label="删除理由">
<el-input v-model="deleteReason" placeholder="请输入删除理由" />
</el-form-item>
@@ -62,7 +62,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -88,7 +88,7 @@
</template>
</el-dialog>
<el-dialog title="流程变更" append-to-body v-model="stateBox" width="20%">
<el-form :model="form" ref="form" label-width="80px">
<el-form :model="form" ref="form" label-width="auto">
<el-form-item label="流程状态">
<el-select v-model="flowState" placeholder="请选择" value="">
<el-option
@@ -150,7 +150,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -207,7 +207,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -41,7 +41,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -41,7 +41,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -41,7 +41,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -69,7 +69,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -108,7 +108,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -86,14 +86,17 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
selection: true,
grid: true,
menuWidth: 350,
labelWidth: 100,
labelWidth: 'auto',
dialogWidth: 880,
dialogClickModal: false,
column: [
@@ -103,7 +106,7 @@ export default {
value: 1,
span: 24,
width: 120,
searchLabelWidth: 50,
searchLabelWidth: 160,
row: true,
dicUrl: '/blade-system/dict/dictionary?code=oss',
props: {

View File

@@ -86,14 +86,17 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
selection: true,
grid: true,
menuWidth: 350,
labelWidth: 100,
labelWidth: 'auto',
dialogWidth: 880,
dialogClickModal: false,
column: [
@@ -103,7 +106,7 @@ export default {
value: 1,
span: 24,
width: 100,
searchLabelWidth: 50,
searchLabelWidth: 160,
row: true,
dicUrl: '/blade-system/dict/dictionary?code=sms',
props: {

View File

@@ -67,7 +67,7 @@
<!-- 手动锁定用户 -->
<el-dialog title="锁定用户" append-to-body v-model="lockUserBox" width="500px">
<el-form :model="lockUserForm" label-width="100px" :rules="lockUserRules" ref="lockUserFormRef">
<el-form :model="lockUserForm" label-width="auto" :rules="lockUserRules" ref="lockUserFormRef">
<el-form-item label="用户账号">
<el-input :model-value="lockUserForm.account" disabled />
</el-form-item>
@@ -108,7 +108,7 @@
<!-- 解锁原因确认 -->
<el-dialog title="解锁确认" append-to-body v-model="unlockReasonBox" width="450px">
<el-form :model="unlockReasonForm" label-width="80px">
<el-form :model="unlockReasonForm" label-width="auto">
<el-form-item label="解锁原因">
<el-input
v-model="unlockReasonForm.unlockReason"

View File

@@ -55,7 +55,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -73,7 +73,10 @@ export default {
tip: false,
simplePage: true,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
tree: true,
border: true,
index: true,

View File

@@ -79,7 +79,10 @@ export default {
tip: false,
simplePage: true,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
dialogWidth: '60%',
tree: true,
border: true,
@@ -137,13 +140,6 @@ export default {
type: 'icon',
slot: true,
iconList: iconList,
rules: [
{
required: true,
message: '请输入菜单图标',
trigger: 'click',
},
],
},
{
label: '菜单编号',

View File

@@ -136,7 +136,7 @@
:before-close="beforeClose"
append-to-body
>
<el-form :disabled="view" ref="form" :model="form" label-width="80px">
<el-form :disabled="view" ref="form" :model="form" label-width="auto">
<!-- 表单字段 -->
<el-form-item label="参数名:" prop="paramName">
<el-input v-model="form.paramName" placeholder="请输入参数名" />

View File

@@ -59,7 +59,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -231,7 +231,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -55,7 +55,10 @@ export default {
dialogWidth: 900,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
@@ -84,7 +87,7 @@ export default {
value: 1,
span: 24,
width: 120,
searchLabelWidth: 100,
searchLabelWidth: 160,
row: true,
gridRow: true,
dicUrl: '/blade-system/dict/dictionary?code=datasource_category',

View File

@@ -90,7 +90,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -165,7 +165,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,

View File

@@ -275,7 +275,7 @@
ref="passwordFormRef"
:model="passwordForm"
:rules="passwordRules"
label-width="90px"
label-width="auto"
>
<el-form-item label="登录账号">
<el-input v-model="passwordForm.account" disabled />

View File

@@ -4,7 +4,7 @@
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<!-- 个人信息 Tab -->
<el-tab-pane label="个人信息" name="info">
<el-form ref="infoForm" :model="infoForm" label-width="100px">
<el-form ref="infoForm" :model="infoForm" label-width="auto">
<el-form-item label="头像">
<div class="avatar-upload-container">
<div class="avatar-wrapper" v-if="infoForm.avatar">
@@ -74,7 +74,7 @@
<!-- 修改密码 Tab -->
<el-tab-pane label="修改密码" name="password">
<el-form ref="passwordForm" :model="passwordForm" label-width="100px">
<el-form ref="passwordForm" :model="passwordForm" label-width="auto">
<el-form-item label="原密码">
<el-input
v-model="passwordForm.oldPassword"

View File

@@ -57,7 +57,10 @@ export default {
dialogWidth: 900,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
@@ -86,7 +89,7 @@ export default {
value: 1,
span: 24,
width: 120,
searchLabelWidth: 100,
searchLabelWidth: 160,
row: true,
gridRow: true,
display: false,

View File

@@ -114,7 +114,7 @@
ref="driverForm"
:model="driverForm"
:rules="formRules"
label-position="top"
label-position="right"
:disabled="readonly"
class="driver-form"
>
@@ -156,13 +156,22 @@
<el-col :span="4">
<el-form-item label="学历" prop="education">
<el-select v-model="driverForm.education" clearable filterable>
<el-option v-for="item in educationOptions" :key="item" :label="item" :value="item" />
<el-option
v-for="item in educationOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="住址" prop="addressRegion">
<el-input v-model="driverForm.addressRegion" maxlength="100" placeholder="省 / 市 / 区" />
<el-input
v-model="driverForm.addressRegion"
maxlength="100"
placeholder="省 / 市 / 区"
/>
</el-form-item>
</el-col>
<el-col :span="8">
@@ -223,8 +232,19 @@
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="准驾车型" prop="drivingType">
<el-select v-model="driverForm.drivingType" clearable filterable allow-create default-first-option>
<el-option v-for="item in drivingTypeOptions" :key="item" :label="item" :value="item" />
<el-select
v-model="driverForm.drivingType"
clearable
filterable
allow-create
default-first-option
>
<el-option
v-for="item in drivingTypeOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
@@ -254,7 +274,11 @@
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="drivingLicenseLongTerm">
<el-checkbox v-model="driverForm.drivingLicenseLongTerm" :true-label="1" :false-label="0">
<el-checkbox
v-model="driverForm.drivingLicenseLongTerm"
:true-label="1"
:false-label="0"
>
</el-checkbox>
</el-form-item>
@@ -287,7 +311,13 @@
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="从业资格证类型" prop="qualificationType">
<el-select v-model="driverForm.qualificationType" clearable filterable allow-create default-first-option>
<el-select
v-model="driverForm.qualificationType"
clearable
filterable
allow-create
default-first-option
>
<el-option label="道路运输从业资格证" value="道路运输从业资格证" />
<el-option label="危险货物运输从业资格证" value="危险货物运输从业资格证" />
</el-select>
@@ -295,7 +325,11 @@
</el-col>
<el-col :span="6">
<el-form-item label="资格证号" prop="qualificationNo">
<el-input v-model="driverForm.qualificationNo" maxlength="50" placeholder="默认身份证号" />
<el-input
v-model="driverForm.qualificationNo"
maxlength="50"
placeholder="默认身份证号"
/>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -310,7 +344,11 @@
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="qualificationLongTerm">
<el-checkbox v-model="driverForm.qualificationLongTerm" :true-label="1" :false-label="0">
<el-checkbox
v-model="driverForm.qualificationLongTerm"
:true-label="1"
:false-label="0"
>
</el-checkbox>
</el-form-item>
@@ -366,19 +404,42 @@
</el-col>
<el-col :span="6">
<el-form-item label="与联系人关系" prop="contactRelation">
<el-select v-model="driverForm.contactRelation" clearable filterable allow-create default-first-option>
<el-option v-for="item in relationOptions" :key="item" :label="item" :value="item" />
<el-select
v-model="driverForm.contactRelation"
clearable
filterable
allow-create
default-first-option
>
<el-option
v-for="item in relationOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="所属组织" prop="organizationName">
<el-input v-model="driverForm.organizationName" maxlength="50" />
<el-cascader
v-model="driverForm.organizationName"
:options="deptCascaderOptions"
:props="deptCascaderProps"
clearable
filterable
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input v-model="driverForm.remark" type="textarea" maxlength="200" show-word-limit />
<el-input
v-model="driverForm.remark"
type="textarea"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
@@ -397,7 +458,15 @@
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
import { option } from '@/option/transportCapacity/driver';
import { getList, getDetail, submit, remove, changeStatus, getExpiryStat } from '@/api/transportCapacity/driver';
import {
getList,
getDetail,
submit,
remove,
changeStatus,
getExpiryStat,
} from '@/api/transportCapacity/driver';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { getToken } from '@/utils/auth';
import { downloadXls } from '@/utils/util';
@@ -528,16 +597,30 @@ export default {
educationOptions: ['初中', '高中', '中专', '大专', '本科', '硕士及以上'],
drivingTypeOptions: ['A1', 'A2', 'A3', 'B1', 'B2', 'C1', 'C2'],
relationOptions: ['父母', '配偶', '子女', '兄弟姐妹', '朋友', '同事'],
deptCascaderOptions: [],
deptCascaderProps: {
label: 'label',
value: 'value',
children: 'children',
checkStrictly: true,
emitPath: false,
},
formRules: {
driverName: [{ required: true, message: '请输入司机姓名', trigger: 'blur' }],
idCardNo: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
drivingType: [{ required: true, message: '请选择准驾车型', trigger: 'change' }],
drivingLicenseEndDate: [{ validator: this.validateDrivingLicenseEndDate, trigger: 'change' }],
drivingLicenseEndDate: [
{ validator: this.validateDrivingLicenseEndDate, trigger: 'change' },
],
driverType: [{ required: true, message: '请选择司机类型', trigger: 'change' }],
mobile: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
emergencyContactName: [{ required: true, message: '请输入紧急联系人姓名', trigger: 'blur' }],
emergencyContactMobile: [{ required: true, message: '请输入紧急联系人手机号', trigger: 'blur' }],
organizationName: [{ required: true, message: '请输入所属组织', trigger: 'blur' }],
emergencyContactName: [
{ required: true, message: '请输入紧急联系人姓名', trigger: 'blur' },
],
emergencyContactMobile: [
{ required: true, message: '请输入紧急联系人手机号', trigger: 'blur' },
],
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
idCardFront: [{ required: true, message: '请上传身份证正面照', trigger: 'change' }],
idCardBack: [{ required: true, message: '请上传身份证反面照', trigger: 'change' }],
drivingLicenseFront: [{ required: true, message: '请上传驾驶证主页', trigger: 'change' }],
@@ -567,10 +650,33 @@ export default {
return this.driverForm.id ? '修改司机' : '新增司机';
},
},
created() {
this.initDeptTree();
},
methods: {
hasPermission(code) {
return this.isAdmin || this.permission?.[code] === true;
},
initDeptTree() {
getDeptTree(this.userInfo.tenantId).then(res => {
this.deptCascaderOptions = this.formatDeptCascaderOptions(res.data.data || []);
const column = this.findColumn(this.option.column, 'organizationName');
column.dicData = this.deptCascaderOptions;
});
},
formatDeptCascaderOptions(tree = []) {
return tree.map(item => {
const label = item.title || item.deptName || item.name;
const option = {
label,
value: label,
};
if (item.children && item.children.length) {
option.children = this.formatDeptCascaderOptions(item.children);
}
return option;
});
},
validateDrivingLicenseEndDate(rule, value, callback) {
if (this.driverForm.drivingLicenseLongTerm !== 1 && !value) {
callback(new Error('请选择驾驶证有效期止'));
@@ -720,6 +826,9 @@ export default {
...params,
...this.searchForm,
};
if (Array.isArray(query.organizationName)) {
query.organizationName = query.organizationName[query.organizationName.length - 1] || '';
}
if (includeExpire && this.query.expireStatus) {
query.expireStatus = this.query.expireStatus;
}

View File

@@ -2,13 +2,31 @@
<basic-container class="transport-ship-page">
<div class="ship-toolbar">
<div class="ship-toolbar__actions">
<el-button type="primary" icon="el-icon-plus" plain v-if="hasPermission('transport_ship_add')" @click="openShip()">
<el-button
type="primary"
icon="el-icon-plus"
plain
v-if="hasPermission('transport_ship_add')"
@click="openShip()"
>
新增
</el-button>
<el-button type="primary" icon="el-icon-download" plain v-if="hasPermission('transport_ship_export')" @click="handleExport">
<el-button
type="primary"
icon="el-icon-download"
plain
v-if="hasPermission('transport_ship_export')"
@click="handleExport"
>
导出
</el-button>
<el-button type="danger" icon="el-icon-delete" plain v-if="hasPermission('transport_ship_delete')" @click="handleDelete">
<el-button
type="danger"
icon="el-icon-delete"
plain
v-if="hasPermission('transport_ship_delete')"
@click="handleDelete"
>
批量删除
</el-button>
</div>
@@ -56,7 +74,11 @@
</span>
</template>
<template #businessTransportCertEndDate="{ row }">
<span :class="getDateClass(row, 'businessTransportCertEndDate', 'businessTransportCertLongTerm')">
<span
:class="
getDateClass(row, 'businessTransportCertEndDate', 'businessTransportCertLongTerm')
"
>
{{ formatEndDate(row.businessTransportCertEndDate, row.businessTransportCertLongTerm) }}
</span>
</template>
@@ -66,10 +88,22 @@
</span>
</template>
<template #menu="{ row }">
<el-button type="primary" text icon="el-icon-edit" v-if="hasPermission('transport_ship_edit')" @click="openShip(row)">
<el-button
type="primary"
text
icon="el-icon-edit"
v-if="hasPermission('transport_ship_edit')"
@click="openShip(row)"
>
修改
</el-button>
<el-button type="primary" text icon="el-icon-view" v-if="hasPermission('transport_ship_view')" @click="openShip(row, true)">
<el-button
type="primary"
text
icon="el-icon-view"
v-if="hasPermission('transport_ship_view')"
@click="openShip(row, true)"
>
综合台账
</el-button>
<el-button
@@ -84,8 +118,22 @@
</template>
</avue-crud>
<el-dialog :title="dialogTitle" append-to-body v-model="shipBox" width="92%" top="4vh" @closed="resetShip">
<el-form ref="shipForm" :model="shipForm" :rules="formRules" label-position="top" :disabled="readonly" class="ship-form">
<el-dialog
:title="dialogTitle"
append-to-body
v-model="shipBox"
width="92%"
top="4vh"
@closed="resetShip"
>
<el-form
ref="shipForm"
:model="shipForm"
:rules="formRules"
label-position="right"
:disabled="readonly"
class="ship-form"
>
<div class="section-title">基础船舶信息</div>
<el-row :gutter="18">
<el-col :span="6">
@@ -95,7 +143,14 @@
</el-col>
<el-col :span="6">
<el-form-item label="船舶识别号" prop="shipIdentifierNo">
<el-input v-model="shipForm.shipIdentifierNo" maxlength="50" show-word-limit @input="shipForm.shipIdentifierNo = String(shipForm.shipIdentifierNo || '').toUpperCase()" />
<el-input
v-model="shipForm.shipIdentifierNo"
maxlength="50"
show-word-limit
@input="
shipForm.shipIdentifierNo = String(shipForm.shipIdentifierNo || '').toUpperCase()
"
/>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -111,7 +166,12 @@
<el-col :span="6">
<el-form-item label="船舶类型" prop="shipType">
<el-select v-model="shipForm.shipType" filterable allow-create default-first-option>
<el-option v-for="item in shipTypeOptions" :key="item" :label="item" :value="item" />
<el-option
v-for="item in shipTypeOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
@@ -134,16 +194,34 @@
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="国籍证有效期至" prop="nationalityCertEndDate">
<el-date-picker v-model="shipForm.nationalityCertEndDate" type="date" value-format="YYYY-MM-DD" :disabled="shipForm.nationalityCertLongTerm === 1" />
<el-date-picker
v-model="shipForm.nationalityCertEndDate"
type="date"
value-format="YYYY-MM-DD"
:disabled="shipForm.nationalityCertLongTerm === 1"
/>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="nationalityCertLongTerm">
<el-checkbox v-model="shipForm.nationalityCertLongTerm" :true-label="1" :false-label="0"></el-checkbox>
<el-checkbox
v-model="shipForm.nationalityCertLongTerm"
:true-label="1"
:false-label="0"
></el-checkbox
>
</el-form-item>
</el-col>
<el-col :span="12">
<image-upload-field label="国籍证图片" prop="nationalityCertImage" :value="shipForm.nationalityCertImage" :readonly="readonly" :headers="uploadHeaders" large @success="url => setImage('nationalityCertImage', url)" />
<image-upload-field
label="国籍证图片"
prop="nationalityCertImage"
:value="shipForm.nationalityCertImage"
:readonly="readonly"
:headers="uploadHeaders"
large
@success="url => setImage('nationalityCertImage', url)"
/>
</el-col>
</el-row>
@@ -151,16 +229,34 @@
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="最低安全配员证书有效期至" prop="safeManningCertEndDate">
<el-date-picker v-model="shipForm.safeManningCertEndDate" type="date" value-format="YYYY-MM-DD" :disabled="shipForm.safeManningCertLongTerm === 1" />
<el-date-picker
v-model="shipForm.safeManningCertEndDate"
type="date"
value-format="YYYY-MM-DD"
:disabled="shipForm.safeManningCertLongTerm === 1"
/>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="safeManningCertLongTerm">
<el-checkbox v-model="shipForm.safeManningCertLongTerm" :true-label="1" :false-label="0"></el-checkbox>
<el-checkbox
v-model="shipForm.safeManningCertLongTerm"
:true-label="1"
:false-label="0"
></el-checkbox
>
</el-form-item>
</el-col>
<el-col :span="12">
<image-upload-field label="最低安全配员证书图片" prop="safeManningCertImage" :value="shipForm.safeManningCertImage" :readonly="readonly" :headers="uploadHeaders" large @success="url => setImage('safeManningCertImage', url)" />
<image-upload-field
label="最低安全配员证书图片"
prop="safeManningCertImage"
:value="shipForm.safeManningCertImage"
:readonly="readonly"
:headers="uploadHeaders"
large
@success="url => setImage('safeManningCertImage', url)"
/>
</el-col>
</el-row>
@@ -168,16 +264,34 @@
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="营业运输证有效期至" prop="businessTransportCertEndDate">
<el-date-picker v-model="shipForm.businessTransportCertEndDate" type="date" value-format="YYYY-MM-DD" :disabled="shipForm.businessTransportCertLongTerm === 1" />
<el-date-picker
v-model="shipForm.businessTransportCertEndDate"
type="date"
value-format="YYYY-MM-DD"
:disabled="shipForm.businessTransportCertLongTerm === 1"
/>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="businessTransportCertLongTerm">
<el-checkbox v-model="shipForm.businessTransportCertLongTerm" :true-label="1" :false-label="0"></el-checkbox>
<el-checkbox
v-model="shipForm.businessTransportCertLongTerm"
:true-label="1"
:false-label="0"
></el-checkbox
>
</el-form-item>
</el-col>
<el-col :span="12">
<image-upload-field label="营业运输证图片" prop="businessTransportCertImage" :value="shipForm.businessTransportCertImage" :readonly="readonly" :headers="uploadHeaders" large @success="url => setImage('businessTransportCertImage', url)" />
<image-upload-field
label="营业运输证图片"
prop="businessTransportCertImage"
:value="shipForm.businessTransportCertImage"
:readonly="readonly"
:headers="uploadHeaders"
large
@success="url => setImage('businessTransportCertImage', url)"
/>
</el-col>
</el-row>
@@ -185,22 +299,39 @@
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="承租有效期至" prop="leaseEndDate">
<el-date-picker v-model="shipForm.leaseEndDate" type="date" value-format="YYYY-MM-DD" :disabled="shipForm.leaseLongTerm === 1" />
<el-date-picker
v-model="shipForm.leaseEndDate"
type="date"
value-format="YYYY-MM-DD"
:disabled="shipForm.leaseLongTerm === 1"
/>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="leaseLongTerm">
<el-checkbox v-model="shipForm.leaseLongTerm" :true-label="1" :false-label="0"></el-checkbox>
<el-checkbox v-model="shipForm.leaseLongTerm" :true-label="1" :false-label="0"
></el-checkbox
>
</el-form-item>
</el-col>
<el-col :span="12">
<image-upload-field label="承租合同图片" prop="leaseContractImage" :value="shipForm.leaseContractImage" :readonly="readonly" :headers="uploadHeaders" large @success="url => setImage('leaseContractImage', url)" />
<image-upload-field
label="承租合同图片"
prop="leaseContractImage"
:value="shipForm.leaseContractImage"
:readonly="readonly"
:headers="uploadHeaders"
large
@success="url => setImage('leaseContractImage', url)"
/>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="shipBox = false">取消</el-button>
<el-button type="primary" v-if="!readonly" :loading="submitLoading" @click="handleSubmit">确定</el-button>
<el-button type="primary" v-if="!readonly" :loading="submitLoading" @click="handleSubmit"
>确定</el-button
>
</template>
</el-dialog>
</basic-container>
@@ -210,7 +341,14 @@
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
import { option } from '@/option/transportCapacity/transport-ship';
import { getList, getDetail, submit, remove, changeStatus, getExpiryStat } from '@/api/transportCapacity/transport-ship';
import {
getList,
getDetail,
submit,
remove,
changeStatus,
getExpiryStat,
} from '@/api/transportCapacity/transport-ship';
import { exportBlob } from '@/api/common';
import { getToken } from '@/utils/auth';
import { downloadXls } from '@/utils/util';
@@ -313,9 +451,15 @@ export default {
shipIdentifierNo: [{ required: true, message: '请输入船舶识别号', trigger: 'blur' }],
organizationName: [{ required: true, message: '请输入所属组织', trigger: 'blur' }],
shipType: [{ required: true, message: '请选择船舶类型', trigger: 'change' }],
nationalityCertEndDate: [{ validator: this.validateNationalityCertEndDate, trigger: 'change' }],
safeManningCertEndDate: [{ validator: this.validateSafeManningCertEndDate, trigger: 'change' }],
businessTransportCertEndDate: [{ validator: this.validateBusinessTransportCertEndDate, trigger: 'change' }],
nationalityCertEndDate: [
{ validator: this.validateNationalityCertEndDate, trigger: 'change' },
],
safeManningCertEndDate: [
{ validator: this.validateSafeManningCertEndDate, trigger: 'change' },
],
businessTransportCertEndDate: [
{ validator: this.validateBusinessTransportCertEndDate, trigger: 'change' },
],
remark: [{ max: 200, message: '备注最多200字', trigger: 'blur' }],
},
};
@@ -344,15 +488,18 @@ export default {
return this.isAdmin || this.permission?.[code] === true;
},
validateNationalityCertEndDate(rule, value, callback) {
if (this.shipForm.nationalityCertLongTerm !== 1 && !value) return callback(new Error('请选择国籍证有效期至'));
if (this.shipForm.nationalityCertLongTerm !== 1 && !value)
return callback(new Error('请选择国籍证有效期至'));
callback();
},
validateSafeManningCertEndDate(rule, value, callback) {
if (this.shipForm.safeManningCertLongTerm !== 1 && !value) return callback(new Error('请选择最低安全配员证书有效期至'));
if (this.shipForm.safeManningCertLongTerm !== 1 && !value)
return callback(new Error('请选择最低安全配员证书有效期至'));
callback();
},
validateBusinessTransportCertEndDate(rule, value, callback) {
if (this.shipForm.businessTransportCertLongTerm !== 1 && !value) return callback(new Error('请选择营业运输证有效期至'));
if (this.shipForm.businessTransportCertLongTerm !== 1 && !value)
return callback(new Error('请选择营业运输证有效期至'));
callback();
},
openShip(row, readonly = false) {
@@ -395,7 +542,11 @@ export default {
handleStatus(row) {
const status = row.status === 1 ? 2 : 1;
const text = status === 1 ? '启用' : '停用';
this.$confirm(`是否${text}该船舶?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
this.$confirm(`是否${text}该船舶?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
changeStatus(row.id, status).then(() => {
this.$message.success('操作成功');
this.onLoad(this.page);
@@ -407,7 +558,11 @@ export default {
this.$message.warning('请选择至少一条数据');
return;
}
this.$confirm('是否删除选中的船舶数据?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
this.$confirm('是否删除选中的船舶数据?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
remove(this.ids).then(() => {
this.$message.success('操作成功');
this.onLoad(this.page);
@@ -476,9 +631,16 @@ export default {
return query;
},
handleExport() {
this.$confirm('是否导出船舶数据?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
this.$confirm('是否导出船舶数据?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/transport-ship/export-transport-ship', this.buildExportParams())
exportBlob(
'/blade-transport/transport-ship/export-transport-ship',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `船舶管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})

View File

@@ -27,7 +27,11 @@
>批量删除
</el-button>
</div>
<el-radio-group v-model="query.expireStatus" class="vehicle-stat" @change="handleExpireChange">
<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>
@@ -119,7 +123,7 @@
ref="vehicleForm"
:model="vehicleForm"
:rules="formRules"
label-position="top"
label-position="right"
:disabled="readonly"
class="vehicle-form"
>
@@ -142,33 +146,68 @@
</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" :label="item" :value="item" />
<el-select
v-model="vehicleForm.vehicleType"
filterable
allow-create
default-first-option
>
<el-option
v-for="item in vehicleTypeOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<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-option
v-for="item in businessRelationOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<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
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="6">
<el-form-item label="核定载质量(KG)" prop="approvedLoadKg">
<el-input-number v-model="vehicleForm.approvedLoadKg" :min="0" :precision="0" controls-position="right" />
<el-input-number
v-model="vehicleForm.approvedLoadKg"
:min="0"
:precision="0"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="准牵引总质量(KG)" prop="tractionMassKg">
<el-input-number v-model="vehicleForm.tractionMassKg" :min="0" :precision="0" controls-position="right" />
<el-input-number
v-model="vehicleForm.tractionMassKg"
:min="0"
:precision="0"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -181,17 +220,32 @@
</el-col>
<el-col :span="6">
<el-form-item label="外廓长度(mm)" prop="outerLength">
<el-input-number v-model="vehicleForm.outerLength" :min="0" :precision="0" controls-position="right" />
<el-input-number
v-model="vehicleForm.outerLength"
:min="0"
:precision="0"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="外廓宽度(mm)" prop="outerWidth">
<el-input-number v-model="vehicleForm.outerWidth" :min="0" :precision="0" controls-position="right" />
<el-input-number
v-model="vehicleForm.outerWidth"
:min="0"
:precision="0"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="外廓高度(mm)" prop="outerHeight">
<el-input-number v-model="vehicleForm.outerHeight" :min="0" :precision="0" controls-position="right" />
<el-input-number
v-model="vehicleForm.outerHeight"
:min="0"
:precision="0"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -211,7 +265,11 @@
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="compulsoryScrapLongTerm">
<el-checkbox v-model="vehicleForm.compulsoryScrapLongTerm" :true-label="1" :false-label="0">
<el-checkbox
v-model="vehicleForm.compulsoryScrapLongTerm"
:true-label="1"
:false-label="0"
>
</el-checkbox>
</el-form-item>
@@ -228,7 +286,12 @@
<el-col :span="10">
<el-form-item label="行驶证有效期" prop="drivingLicenseEndDate">
<div class="date-range">
<el-date-picker v-model="vehicleForm.drivingLicenseStartDate" type="date" value-format="YYYY-MM-DD" placeholder="起" />
<el-date-picker
v-model="vehicleForm.drivingLicenseStartDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="起"
/>
<el-date-picker
v-model="vehicleForm.drivingLicenseEndDate"
type="date"
@@ -241,7 +304,11 @@
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="drivingLicenseLongTerm">
<el-checkbox v-model="vehicleForm.drivingLicenseLongTerm" :true-label="1" :false-label="0">
<el-checkbox
v-model="vehicleForm.drivingLicenseLongTerm"
:true-label="1"
:false-label="0"
>
</el-checkbox>
</el-form-item>
@@ -269,7 +336,12 @@
<el-col :span="10">
<el-form-item label="道路运输证有效期" prop="roadTransportCertEndDate">
<div class="date-range">
<el-date-picker v-model="vehicleForm.roadTransportCertStartDate" type="date" value-format="YYYY-MM-DD" placeholder="起" />
<el-date-picker
v-model="vehicleForm.roadTransportCertStartDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="起"
/>
<el-date-picker
v-model="vehicleForm.roadTransportCertEndDate"
type="date"
@@ -282,7 +354,11 @@
</el-col>
<el-col :span="4">
<el-form-item label="长期有效" prop="roadTransportCertLongTerm">
<el-checkbox v-model="vehicleForm.roadTransportCertLongTerm" :true-label="1" :false-label="0">
<el-checkbox
v-model="vehicleForm.roadTransportCertLongTerm"
:true-label="1"
:false-label="0"
>
</el-checkbox>
</el-form-item>
@@ -299,7 +375,11 @@
</el-col>
<el-col :span="4">
<el-form-item label="年审长期有效" prop="annualReviewLongTerm">
<el-checkbox v-model="vehicleForm.annualReviewLongTerm" :true-label="1" :false-label="0">
<el-checkbox
v-model="vehicleForm.annualReviewLongTerm"
:true-label="1"
:false-label="0"
>
</el-checkbox>
</el-form-item>
@@ -326,12 +406,21 @@
</el-col>
<el-col :span="6">
<el-form-item label="机动车登记日期" prop="registrationDate">
<el-date-picker v-model="vehicleForm.registrationDate" type="date" value-format="YYYY-MM-DD" />
<el-date-picker
v-model="vehicleForm.registrationDate"
type="date"
value-format="YYYY-MM-DD"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input v-model="vehicleForm.remark" type="textarea" maxlength="200" show-word-limit />
<el-input
v-model="vehicleForm.remark"
type="textarea"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -499,16 +588,24 @@ export default {
organizationName: [{ required: true, message: '请输入所属组织', trigger: 'blur' }],
plateNo: [
{ required: true, message: '请输入车牌号', trigger: 'blur' },
{ pattern: /^[\u4e00-\u9fa5][A-Z][A-Z0-9挂学警港澳]{5,6}$/, message: '请输入标准车牌号', trigger: 'blur' },
{
pattern: /^[\u4e00-\u9fa5][A-Z][A-Z0-9挂学警港澳]{5,6}$/,
message: '请输入标准车牌号',
trigger: 'blur',
},
],
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: [{ validator: this.validateDrivingLicenseEndDate, trigger: 'change' }],
drivingLicenseEndDate: [
{ validator: this.validateDrivingLicenseEndDate, trigger: 'change' },
],
roadTransportCertNo: [{ required: true, message: '请输入道路运输证号', trigger: 'blur' }],
roadTransportCertEndDate: [{ validator: this.validateRoadTransportCertEndDate, trigger: 'change' }],
roadTransportCertEndDate: [
{ validator: this.validateRoadTransportCertEndDate, trigger: 'change' },
],
annualReviewEndDate: [{ validator: this.validateAnnualReviewEndDate, trigger: 'change' }],
registrationNo: [{ required: true, message: '请输入机动车登记编号', trigger: 'blur' }],
remark: [{ max: 200, message: '备注最多200字', trigger: 'blur' }],
@@ -708,7 +805,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/transport-vehicle/export-transport-vehicle', this.buildExportParams())
exportBlob(
'/blade-transport/transport-vehicle/export-transport-vehicle',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `车辆管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})

View File

@@ -65,7 +65,10 @@ export default {
option: {
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
tree: true,
border: true,
index: true,

View File

@@ -110,7 +110,10 @@ export default {
option: {
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
tree: true,
border: true,
index: true,

View File

@@ -114,7 +114,10 @@ export default {
option: {
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
tree: true,
border: true,
index: true,

View File

@@ -15,7 +15,7 @@ export default {
return {
form: {},
option: {
labelWidth: 110,
labelWidth: 'auto',
column: [
{
label: '姓名',

View File

@@ -11,7 +11,7 @@ export default {
return {
form: {},
option: {
labelWidth: 110,
labelWidth: 'auto',
column: [
{
label: '用户名',

View File

@@ -63,8 +63,9 @@
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入车牌号/船号查询选择"
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="accident-record-page__input"
/>
@@ -72,6 +73,24 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
<template #attachments-form>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
</avue-crud>
<empty-pagination
:page="page"
@@ -80,7 +99,7 @@
@load="onLoad(page, query)"
/>
<el-dialog title="事故记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -98,6 +117,7 @@ import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/accident-record';
@@ -112,6 +132,7 @@ export default {
loading: true,
excelBox: false,
excelForm: {},
isDetailLoading: false,
option,
excelOption,
page: {
@@ -121,6 +142,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -141,6 +163,9 @@ export default {
editBtn: this.hasPermission('accident_record_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
const ids = [];
this.selectionList.forEach(ele => {
@@ -151,8 +176,8 @@ export default {
},
watch: {
'form.vehicleType': {
handler(value) {
this.updateVehicleTypeDisplays(value || '车辆');
handler(value, oldValue) {
this.updateVehicleTypeDisplays(value || '车辆', oldValue);
},
immediate: true,
},
@@ -167,10 +192,13 @@ export default {
column.dicData = res.data.data;
});
},
updateVehicleTypeDisplays(vehicleType) {
updateVehicleTypeDisplays(vehicleType, oldValue) {
const vehicleTypeColumn = this.findColumn(this.option.column, 'vehicleType');
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
vehicleNoColumn.placeholder =
vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
vehicleTypeColumn.disabled = Boolean(this.form.id);
if (!this.form.id && this.form.vehicleNo) {
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue && this.form.vehicleNo) {
this.form.vehicleNo = '';
}
},
@@ -227,7 +255,10 @@ export default {
const values = { ...row };
values.vehicleType = values.vehicleType || '车辆';
if (values.vehicleNo) {
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
values.vehicleNo =
values.vehicleType === '船舶'
? values.vehicleNo.trim()
: values.vehicleNo.trim().toUpperCase();
}
values.attachments = this.stringifyAttachments(values.attachments);
return values;
@@ -329,6 +360,7 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = {
vehicleType: '车辆',
@@ -336,12 +368,18 @@ export default {
this.updateVehicleTypeDisplays('车辆');
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
this.form = detail;
this.updateVehicleTypeDisplays(detail.vehicleType);
});
this.isDetailLoading = true;
getDetail(this.form.id)
.then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
detail.vehicleType = detail.vehicleType || '车辆';
this.form = detail;
this.updateVehicleTypeDisplays(detail.vehicleType);
})
.finally(() => {
this.isDetailLoading = false;
});
}
done();
},
@@ -385,12 +423,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '事故记录');
},
handleExport() {
this.$confirm('是否导出事故记录数据?', '提示', {
@@ -399,7 +432,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/accident-record/export-accident-record', this.buildExportParams())
exportBlob(
'/blade-transport/accident-record/export-accident-record',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `事故记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})

View File

@@ -63,6 +63,7 @@
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入车牌号/船号查询选择"
value-key="value"
@@ -72,6 +73,24 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
<template #attachments-form>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
</avue-crud>
<empty-pagination
:page="page"
@@ -80,7 +99,7 @@
@load="onLoad(page, query)"
/>
<el-dialog title="年检记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -98,6 +117,7 @@ import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/annual-inspection-record';
@@ -121,6 +141,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -332,6 +353,7 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = {
vehicleType: '车辆',
@@ -389,12 +411,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '年检记录');
},
handleExport() {
this.$confirm('是否导出年检记录数据?', '提示', {
@@ -424,7 +441,9 @@ export default {
},
handleTemplate() {
exportBlob(
`/blade-transport/annual-inspection-record/export-template?${this.website.tokenHeader}=${getToken()}`
`/blade-transport/annual-inspection-record/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '年检记录模板.xlsx');
});

View File

@@ -17,14 +17,14 @@
@on-load="onLoad"
>
<template #menu-left>
<el-button
type="primary"
icon="el-icon-plus"
plain
v-if="hasPermission('credit_score_quantification_add')"
@click="openConfig()"
>新增
</el-button>
<el-button
type="primary"
icon="el-icon-plus"
plain
v-if="hasPermission('credit_score_quantification_add')"
@click="openConfig()"
>新增
</el-button>
<el-button
type="primary"
icon="el-icon-upload"
@@ -49,9 +49,9 @@
@click="handleExport"
>批量导出
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
<el-button
type="danger"
icon="el-icon-delete"
plain
v-if="hasPermission('credit_score_quantification_delete')"
@click="handleDelete"
@@ -109,7 +109,7 @@
</avue-crud>
<el-dialog title="评分量化表数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -127,8 +127,14 @@
@closed="resetConfig"
>
<div class="score-config">
<div class="section-title">评分量化表</div>
<el-form :model="configForm" label-width="110px" class="base-form" :disabled="readonly">
<div class="section-title dialog-section-title">评分量化表</div>
<el-form
:model="configForm"
label-position="right"
label-width="auto"
class="base-form"
:disabled="readonly"
>
<el-form-item label="评定表名称" required>
<el-input v-model="configForm.name" maxlength="30" show-word-limit />
</el-form-item>
@@ -138,7 +144,7 @@
</el-form>
<div class="section-head">
<div class="section-title">评分表</div>
<div class="section-title dialog-section-title">评分表</div>
</div>
<el-table :data="configForm.categories" border class="config-table">
<el-table-column label="序号" type="index" width="70" align="center" />
@@ -154,13 +160,8 @@
</el-table>
<div class="section-head standard-head">
<div class="section-title">评估标准</div>
<el-button
type="primary"
icon="el-icon-plus"
v-if="!readonly"
@click="openStandard()"
>
<div class="section-title dialog-section-title">评估标准</div>
<el-button type="primary" icon="el-icon-plus" v-if="!readonly" @click="openStandard()">
添加
</el-button>
</div>
@@ -168,7 +169,9 @@
<el-table-column label="序号" type="index" width="70" align="center" />
<el-table-column label="信用等级" prop="creditLevel" width="120" align="center" />
<el-table-column label="得分率" width="160" align="center">
<template #default="{ row }">{{ row.scoreRateLower }}% - {{ row.scoreRateUpper }}%</template>
<template #default="{ row }"
>{{ row.scoreRateLower }}% - {{ row.scoreRateUpper }}%</template
>
</el-table-column>
<el-table-column label="最大资金使用额度(万元)" width="210" align="center">
<template #default="{ row }">
@@ -179,7 +182,9 @@
<el-table-column label="操作" width="160" align="center">
<template #default="{ row, $index }">
<el-button type="primary" text @click="openStandard(row, $index)">编辑</el-button>
<el-button type="danger" text v-if="!readonly" @click="removeStandard($index)">删除</el-button>
<el-button type="danger" text v-if="!readonly" @click="removeStandard($index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
@@ -215,7 +220,9 @@
<el-table-column label="操作" width="160" align="center">
<template #default="{ row, $index }">
<el-button type="primary" text @click="openItem(row, $index)">编辑</el-button>
<el-button type="danger" text v-if="!readonly" @click="removeItem($index)">删除</el-button>
<el-button type="danger" text v-if="!readonly" @click="removeItem($index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
@@ -226,7 +233,7 @@
</el-dialog>
<el-dialog title="评分项目详情" append-to-body v-model="itemBox" width="76%">
<el-form :model="itemForm" label-width="100px" :disabled="readonly">
<el-form :model="itemForm" label-position="right" label-width="auto" :disabled="readonly">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="评分项目" required>
@@ -248,7 +255,12 @@
<el-input v-model="option.optionName" maxlength="100" placeholder="选项描述" />
<el-input-number v-model="option.score" :min="0" :precision="2" placeholder="分值" />
<span class="score-unit"></span>
<el-button type="danger" text v-if="!readonly && itemForm.options.length > 1" @click="removeOption(index)">
<el-button
type="danger"
text
v-if="!readonly && itemForm.options.length > 1"
@click="removeOption(index)"
>
删除
</el-button>
</div>
@@ -263,19 +275,36 @@
</el-dialog>
<el-dialog title="评估标准设置" append-to-body v-model="standardBox" width="76%">
<el-form :model="standardForm" label-width="170px" :disabled="readonly">
<el-form :model="standardForm" label-position="right" label-width="auto" :disabled="readonly">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="信用等级" required>
<el-input v-model="standardForm.creditLevel" maxlength="30" />
<el-select v-model="standardForm.creditLevel" placeholder="请选择信用等级" clearable>
<el-option
v-for="item in creditLevelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="得分率(%)" required>
<div class="range-input">
<el-input-number v-model="standardForm.scoreRateLower" :min="0" :max="100" :precision="0" />
<el-input-number
v-model="standardForm.scoreRateLower"
:min="0"
:max="100"
:precision="0"
/>
<span></span>
<el-input-number v-model="standardForm.scoreRateUpper" :min="0" :max="100" :precision="0" />
<el-input-number
v-model="standardForm.scoreRateUpper"
:min="0"
:max="100"
:precision="0"
/>
</div>
</el-form-item>
</el-col>
@@ -303,7 +332,12 @@
</el-col>
</el-row>
<el-form-item label="标准说明">
<el-input v-model="standardForm.standardDescription" type="textarea" maxlength="500" show-word-limit />
<el-input
v-model="standardForm.standardDescription"
type="textarea"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
@@ -325,6 +359,7 @@ import {
} from '@/api/vehicle/credit-score-quantification';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
@@ -361,13 +396,21 @@ export default {
2: { label: '停用', type: 'info' },
3: { label: '草稿', type: 'warning' },
},
creditLevelOptions: ['A', 'B', 'C', 'D', 'E', 'F'].map(item => ({
label: item,
value: item,
})),
option: {
height: 'auto',
calcHeight: 32,
dialogWidth: 760,
labelPosition: 'right',
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
@@ -421,7 +464,6 @@ export default {
type: 'date',
format: 'YYYY-MM-DD 00:00:00',
valueFormat: 'YYYY-MM-DD 00:00:00',
search: true,
hide: true,
display: false,
},
@@ -431,13 +473,13 @@ export default {
type: 'date',
format: 'YYYY-MM-DD 23:59:59',
valueFormat: 'YYYY-MM-DD 23:59:59',
search: true,
hide: true,
display: false,
},
],
},
excelOption: {
labelPosition: 'right',
submitBtn: false,
emptyBtn: false,
column: [
@@ -452,7 +494,8 @@ export default {
res: 'data',
},
tip: '请上传 .xls,.xlsx 标准格式文件',
action: '/blade-transport/credit-score-quantification/import-credit-score-quantification',
action:
'/blade-transport/credit-score-quantification/import-credit-score-quantification',
},
{
label: '模板下载',
@@ -532,7 +575,8 @@ export default {
...category,
items: (category.items || []).map(item => ({
...item,
options: item.options && item.options.length ? item.options : [{ optionName: '', score: 0 }],
options:
item.options && item.options.length ? item.options : [{ optionName: '', score: 0 }],
})),
};
});
@@ -601,9 +645,26 @@ export default {
this.standardForm = row ? JSON.parse(JSON.stringify(row)) : this.emptyStandard();
this.standardBox = true;
},
hasDuplicateCreditLevel(creditLevel, currentIndex = -1) {
const level = String(creditLevel || '').trim();
return this.configForm.standards.some(
(item, index) => index !== currentIndex && String(item.creditLevel || '').trim() === level
);
},
isValidCreditLevel(creditLevel) {
return this.creditLevelOptions.some(item => item.value === String(creditLevel || '').trim());
},
saveStandard() {
if (!this.standardForm.creditLevel || !this.standardForm.creditLevel.trim()) {
this.$message.warning('请输入信用等级');
if (!this.standardForm.creditLevel) {
this.$message.warning('请选择信用等级');
return;
}
if (!this.isValidCreditLevel(this.standardForm.creditLevel)) {
this.$message.warning('信用等级只能选择A/B/C/D/E/F');
return;
}
if (this.hasDuplicateCreditLevel(this.standardForm.creditLevel, this.currentStandardIndex)) {
this.$message.warning('信用等级不能重复');
return;
}
if (
@@ -624,6 +685,7 @@ export default {
return;
}
const standard = JSON.parse(JSON.stringify(this.standardForm));
standard.creditLevel = String(standard.creditLevel || '').trim();
if (this.currentStandardIndex >= 0) {
this.configForm.standards.splice(this.currentStandardIndex, 1, standard);
} else {
@@ -652,6 +714,17 @@ export default {
}
}
}
const creditLevels = this.configForm.standards
.map(item => String(item.creditLevel || '').trim())
.filter(Boolean);
if (creditLevels.some(level => !this.isValidCreditLevel(level))) {
this.$message.warning('评估标准中的信用等级只能选择A/B/C/D/E/F');
return false;
}
if (new Set(creditLevels).size !== creditLevels.length) {
this.$message.warning('评估标准中的信用等级不能重复');
return false;
}
return true;
},
saveConfig() {
@@ -749,12 +822,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '评分量化表');
},
handleExport() {
this.$confirm('是否导出评分量化表数据?', '提示', {
@@ -784,7 +852,9 @@ export default {
},
handleTemplate() {
exportBlob(
`/blade-transport/credit-score-quantification/export-template?${this.website.tokenHeader}=${getToken()}`
`/blade-transport/credit-score-quantification/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '评分量化表模板.xlsx');
});
@@ -808,12 +878,17 @@ export default {
align-items: center;
justify-content: space-between;
margin: 24px 0 12px;
.section-title {
margin: 0;
}
}
.section-title {
margin: 18px 0 12px;
font-size: 20px;
font-weight: 700;
line-height: 20px;
color: #303133;
}

View File

@@ -151,11 +151,11 @@
ref="archiveForm"
:model="archiveForm"
:rules="formRules"
label-position="top"
label-position="right"
:disabled="readonly"
class="archive-form"
>
<div class="section-title">基本信息</div>
<div class="section-title dialog-section-title">基本信息</div>
<el-alert
type="warning"
:closable="false"
@@ -164,7 +164,7 @@
class="archive-rule-alert"
/>
<el-row :gutter="18">
<el-col :span="6">
<el-col v-if="readonly" :span="6">
<el-form-item label="客商编号" prop="customerCode">
<el-input v-model="archiveForm.customerCode" disabled placeholder="保存后自动生成" />
</el-form-item>
@@ -221,7 +221,11 @@
v-model="archiveForm.unifiedCreditCode"
maxlength="18"
show-word-limit
@input="archiveForm.unifiedCreditCode = String(archiveForm.unifiedCreditCode || '').toUpperCase()"
@input="
archiveForm.unifiedCreditCode = String(
archiveForm.unifiedCreditCode || ''
).toUpperCase()
"
/>
</el-form-item>
</el-col>
@@ -335,16 +339,23 @@
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input v-model="archiveForm.remark" type="textarea" maxlength="500" show-word-limit />
<el-input
v-model="archiveForm.remark"
type="textarea"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="section-head">
<div class="section-title">资质附件</div>
<div class="section-title dialog-section-title">资质附件</div>
<div v-if="!readonly" class="section-actions">
<el-button type="primary" icon="el-icon-upload" @click="addAttachment">OCR上传识别</el-button>
<el-button type="primary" icon="el-icon-upload" @click="addAttachment"
>OCR上传识别</el-button
>
<el-button type="primary" icon="el-icon-plus" @click="addAttachment">添加附件</el-button>
<el-button icon="el-icon-download" @click="downloadAttachments">批量下载</el-button>
</div>
@@ -352,7 +363,7 @@
<el-alert
type="info"
:closable="false"
title="支持 JPG、PNG、PDF单文件大小不超过 10MB正式客商需上传公司营业执照、道路运输证等核心材料。"
title="支持 JPG、PNG、PDF、Word、Excel、PPT,单文件大小不超过 10MB正式客商需上传公司营业执照、道路运输证等核心材料。"
class="archive-rule-alert"
/>
<el-alert
@@ -377,7 +388,16 @@
</el-table-column>
<el-table-column label="文件名称" min-width="180">
<template #default="{ row }">
<el-input v-model="row.name" :disabled="readonly" maxlength="100" />
<vehicle-attachment-upload
v-model="row.files"
:readonly="readonly"
:multiple="false"
accept=".jpg,.jpeg,.png,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
:max-size="10"
button-text="上传文件"
:show-tip="false"
@success="file => handleQualificationUploadSuccess(file, row)"
/>
</template>
</el-table-column>
<el-table-column label="附件描述" min-width="220">
@@ -387,27 +407,24 @@
</el-table-column>
<el-table-column label="文件大小" min-width="120">
<template #default="{ row }">
<el-input v-model="row.size" :disabled="readonly" maxlength="30" />
<span>{{ row.size || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="上传人" min-width="120">
<template #default="{ row }">
<el-input v-model="row.uploadUserName" :disabled="readonly" maxlength="30" />
<span>{{ row.uploadUserName || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="上传时间" min-width="170">
<template #default="{ row }">
<el-date-picker
v-model="row.uploadTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:disabled="readonly"
/>
<span>{{ row.uploadTime || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center" v-if="!readonly">
<template #default="{ $index }">
<el-button type="danger" text @click="qualificationFiles.splice($index, 1)">删除</el-button>
<el-button type="danger" text @click="qualificationFiles.splice($index, 1)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
@@ -437,7 +454,12 @@
<el-table-column label="最终得分" prop="finalScore" width="110" align="center" />
<el-table-column label="额度" min-width="150">
<template #default="{ row }">
<el-input-number v-model="row.applyCreditLimit" :min="0" :precision="2" :disabled="readonly" />
<el-input-number
v-model="row.applyCreditLimit"
:min="0"
:precision="2"
:disabled="readonly"
/>
</template>
</el-table-column>
<el-table-column label="信用等级" min-width="130">
@@ -450,8 +472,15 @@
<el-table-column label="操作" width="210" align="center">
<template #default="{ row, $index }">
<el-button type="primary" text @click="openScore(row)">评分明细</el-button>
<el-button type="success" text v-if="!readonly" @click="finishScore(row)">完成评分</el-button>
<el-button type="danger" text v-if="!readonly" @click="archiveForm.scores.splice($index, 1)">
<el-button type="success" text v-if="!readonly" @click="finishScore(row)"
>完成评分</el-button
>
<el-button
type="danger"
text
v-if="!readonly"
@click="archiveForm.scores.splice($index, 1)"
>
删除
</el-button>
</template>
@@ -500,7 +529,13 @@
</template>
</el-dialog>
<el-dialog title="评分明细" append-to-body v-model="scoreBox" width="88%" class="archive-dialog">
<el-dialog
title="评分明细"
append-to-body
v-model="scoreBox"
width="88%"
class="archive-dialog"
>
<el-table :data="currentScore.details" border class="score-detail-table">
<el-table-column label="序号" type="index" width="70" align="center" />
<el-table-column label="评分类目" prop="categoryName" min-width="130" />
@@ -530,7 +565,12 @@
</el-table-column>
<el-table-column label="复评得分" width="130" align="center">
<template #default="{ row }">
<el-input-number v-model="row.reviewScore" :min="0" :precision="2" :disabled="readonly" />
<el-input-number
v-model="row.reviewScore"
:min="0"
:precision="2"
:disabled="readonly"
/>
</template>
</el-table-column>
<el-table-column label="得分说明" prop="scoreDescription" min-width="220" />
@@ -746,7 +786,10 @@ export default {
searchBtnText: '查询',
emptyBtnText: '重置',
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
@@ -877,12 +920,20 @@ export default {
},
missingQualificationText() {
if (this.archiveForm.accessType !== 'formal') return '';
const types = this.qualificationFiles.map(item => item.type || item.name).filter(Boolean);
const types = this.qualificationFiles
.filter(item => item.url)
.map(item => item.type || item.name)
.filter(Boolean);
const missing = ['公司营业执照', '道路运输证'].filter(
item => !types.some(type => String(type).includes(item))
);
return missing.length ? `缺失核心资质材料:${missing.join('、')}` : '';
},
uploadHeaders() {
return {
[this.website.tokenHeader]: getToken(),
};
},
},
methods: {
hasPermission(code) {
@@ -902,7 +953,14 @@ export default {
};
},
emptyContact() {
return { contactName: '', contactPhone: '', email: '', positionName: '', isDefault: 0, remark: '' };
return {
contactName: '',
contactPhone: '',
email: '',
positionName: '',
isDefault: 0,
remark: '',
};
},
emptyReceiptAccount() {
return {
@@ -976,16 +1034,26 @@ export default {
if (Array.isArray(value)) return value;
try {
const list = JSON.parse(value);
return Array.isArray(list) ? list : [];
return Array.isArray(list)
? list.map(item => ({
...item,
files: item.url ? [item] : [],
}))
: [];
} catch (error) {
return String(value)
.split(',')
.map(item => ({ name: item.trim(), url: item.trim() }))
.filter(item => item.url);
.filter(item => item.url)
.map(item => ({
...item,
files: [item],
}));
}
},
stringifyAttachments(list) {
const files = (list || [])
.filter(item => item.url)
.map(item => ({
type: String(item.type || '').trim(),
name: String(item.name || '').trim(),
@@ -995,7 +1063,7 @@ export default {
uploadTime: String(item.uploadTime || '').trim(),
url: String(item.url || '').trim(),
}))
.filter(item => item.type || item.name || item.url);
.filter(item => item.url);
return files.length ? JSON.stringify(files) : '';
},
addAttachment() {
@@ -1007,8 +1075,25 @@ export default {
uploadUserName: this.userInfo.realName || this.userInfo.userName || '',
uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
url: '',
files: [],
});
},
formatAttachmentSize(size) {
if (!size) return '';
const byteSize = Number(size);
if (!Number.isFinite(byteSize)) return String(size);
const mb = byteSize / 1024 / 1024;
if (mb >= 1) return `${mb.toFixed(2)} MB`;
return `${(byteSize / 1024).toFixed(2)} KB`;
},
handleQualificationUploadSuccess(file, row) {
row.name = file.name || row.name || '资质附件';
row.size = this.formatAttachmentSize(file.size || row.size);
row.uploadUserName = this.userInfo.realName || this.userInfo.userName || '';
row.uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
row.url = file.url || row.url || '';
row.files = [file];
},
downloadAttachments() {
const files = this.qualificationFiles.filter(item => item.url);
if (!files.length) {
@@ -1051,7 +1136,9 @@ export default {
if (row && row.id) {
getDetail(row.id).then(res => {
this.archiveForm = this.normalizeDetail(res.data.data);
this.qualificationFiles = this.parseAttachments(this.archiveForm.qualificationAttachments);
this.qualificationFiles = this.parseAttachments(
this.archiveForm.qualificationAttachments
);
this.archiveBox = true;
});
return;
@@ -1089,7 +1176,9 @@ export default {
? archive.customerType.join(',')
: archive.customerType;
archive.qualificationAttachments = this.stringifyAttachments(this.qualificationFiles);
archive.contacts = (archive.contacts || []).filter(item => item.contactName || item.contactPhone);
archive.contacts = (archive.contacts || []).filter(
item => item.contactName || item.contactPhone
);
archive.receiptAccounts = (archive.receiptAccounts || []).filter(
item => item.accountName || item.bankAccount
);
@@ -1161,7 +1250,9 @@ export default {
}
},
scoreOptionChange(detail, optionValue) {
const option = this.parseScoreOptions(detail.optionsJson).find(item => item.value === optionValue);
const option = this.parseScoreOptions(detail.optionsJson).find(
item => item.value === optionValue
);
const score = Number(option?.score || 0);
detail.selfScore = score;
detail.reviewScore = score;
@@ -1327,7 +1418,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/customer-archive/export-customer-archive', this.buildExportParams())
exportBlob(
'/blade-transport/customer-archive/export-customer-archive',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `客商档案${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})
@@ -1375,6 +1469,10 @@ export default {
align-items: center;
justify-content: space-between;
margin: 18px 0 12px;
.section-title {
margin: 0;
}
}
.section-actions {

View File

@@ -63,6 +63,7 @@
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入车牌号模糊查询选择"
value-key="value"
@@ -72,9 +73,15 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
<template #attachments-form>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
</avue-crud>
<el-dialog title="ETC记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -91,6 +98,7 @@ import { getList as getVehicleList } from '@/api/transportCapacity/transport-veh
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/etc-record';
@@ -114,6 +122,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -308,6 +317,7 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = { dataSource: '手工录入' };
}
@@ -360,12 +370,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, 'ETC记录');
},
handleExport() {
this.$confirm('是否导出ETC记录数据?', '提示', {
@@ -391,7 +396,9 @@ export default {
};
},
handleTemplate() {
exportBlob(`/blade-transport/etc-record/export-template?${this.website.tokenHeader}=${getToken()}`).then(res => {
exportBlob(
`/blade-transport/etc-record/export-template?${this.website.tokenHeader}=${getToken()}`
).then(res => {
downloadXls(res.data, 'ETC记录模板.xlsx');
});
},

View File

@@ -64,19 +64,27 @@
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
clearable
placeholder="输入车牌号/船号模糊查询选择"
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="insurance-record-page__vehicle-input"
/>
</template>
<template #policyFileForm>
<div class="insurance-record-page__upload">
<div class="insurance-record-page__upload-icon">OCR</div>
<div class="insurance-record-page__upload-text">
<strong>上传图片,自动识别填写</strong>
<span>支持JPGPNGPDF上传</span>
<div class="insurance-record-page__upload-info">
<div class="insurance-record-page__upload-icon" aria-hidden="true">
<span class="insurance-record-page__upload-card">
<span></span>
</span>
</div>
<div class="insurance-record-page__upload-text">
<strong>上传图片,自动识别填写</strong>
<span>支持JPGPNGPDF上传</span>
</div>
</div>
<el-upload
v-if="hasPermission('insurance_record_ocr')"
class="insurance-record-page__upload-action"
:action="recognizeUrl"
:data="recognizeData"
:headers="uploadHeaders"
@@ -84,9 +92,40 @@
:on-success="handleRecognizeSuccess"
:on-error="handleRecognizeError"
accept=".jpg,.jpeg,.png,.pdf"
v-if="hasPermission('insurance_record_ocr')"
>
<el-button type="primary" plain>上传文件</el-button>
<el-button class="insurance-record-page__upload-button" type="primary" plain>
上传文件
</el-button>
</el-upload>
</div>
</template>
<template #policyFile-form>
<div class="insurance-record-page__upload">
<div class="insurance-record-page__upload-info">
<div class="insurance-record-page__upload-icon" aria-hidden="true">
<span class="insurance-record-page__upload-card">
<span></span>
</span>
</div>
<div class="insurance-record-page__upload-text">
<strong>上传图片,自动识别填写</strong>
<span>支持JPGPNGPDF上传</span>
</div>
</div>
<el-upload
v-if="hasPermission('insurance_record_ocr')"
class="insurance-record-page__upload-action"
:action="recognizeUrl"
:data="recognizeData"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleRecognizeSuccess"
:on-error="handleRecognizeError"
accept=".jpg,.jpeg,.png,.pdf"
>
<el-button class="insurance-record-page__upload-button" type="primary" plain>
上传文件
</el-button>
</el-upload>
</div>
</template>
@@ -98,7 +137,7 @@
@load="onLoad(page, query)"
/>
<el-dialog title="保险记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -116,6 +155,7 @@ import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/insurance-record';
@@ -130,6 +170,7 @@ export default {
loading: true,
excelBox: false,
excelForm: {},
isDetailLoading: false,
option,
excelOption,
page: {
@@ -159,6 +200,9 @@ export default {
editBtn: this.hasPermission('insurance_record_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
const ids = [];
this.selectionList.forEach(ele => {
@@ -181,10 +225,26 @@ export default {
};
},
},
watch: {
'form.vehicleType': {
handler(value, oldValue) {
this.updateVehicleTypeDisplays(value || '车辆', oldValue);
},
immediate: true,
},
},
methods: {
hasPermission(code) {
return this.isAdmin || this.validData(this.permission[code], false);
},
updateVehicleTypeDisplays(vehicleType, oldValue) {
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
vehicleNoColumn.placeholder =
vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue && this.form.vehicleNo) {
this.form.vehicleNo = '';
}
},
initDeptTree() {
getDeptTree(this.userInfo.tenantId).then(res => {
const column = this.findColumn(this.option.column, 'createDept');
@@ -195,9 +255,7 @@ export default {
const vehicleType = this.form.vehicleType || '车辆';
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
const params =
vehicleType === '船舶'
? { shipIdentifierNo: queryString }
: { plateNo: queryString };
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
request(1, 20, params)
.then(res => {
const records = res.data.data.records || [];
@@ -211,8 +269,12 @@ export default {
},
normalizeRow(row) {
const values = { ...row };
values.vehicleType = values.vehicleType || '车辆';
if (values.vehicleNo) {
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
values.vehicleNo =
values.vehicleType === '船舶'
? values.vehicleNo.trim()
: values.vehicleNo.trim().toUpperCase();
}
if (values.policyNo) {
values.policyNo = values.policyNo.trim();
@@ -305,9 +367,17 @@ export default {
this.form.ocrTemplate = '紫金-机动车交强险';
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
this.form = res.data.data;
});
this.isDetailLoading = true;
getDetail(this.form.id)
.then(res => {
this.form = {
...res.data.data,
vehicleType: res.data.data.vehicleType || '车辆',
};
})
.finally(() => {
this.isDetailLoading = false;
});
}
done();
},
@@ -351,12 +421,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '保险记录');
},
handleExport() {
this.$confirm('是否导出保险记录数据?', '提示', {
@@ -365,7 +430,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/insurance-record/export-insurance-record', this.buildExportParams())
exportBlob(
'/blade-transport/insurance-record/export-insurance-record',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `保险记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})
@@ -383,7 +451,9 @@ export default {
},
handleTemplate() {
exportBlob(
`/blade-transport/insurance-record/export-template?${this.website.tokenHeader}=${getToken()}`
`/blade-transport/insurance-record/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '保险记录模板.xlsx');
});
@@ -417,39 +487,153 @@ export default {
&__vehicle-input {
width: 100%;
}
}
&__upload {
display: flex;
align-items: center;
gap: 18px;
min-height: 88px;
padding: 18px 24px;
border: 1px dashed #8c8c8c;
border-radius: 6px;
}
:global(.insurance-record-page__upload) {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 116px;
padding: 26px 46px 26px 32px;
border: 2px dashed #8b8b8b;
border-radius: 12px;
background: #fff;
}
&__upload-icon {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
color: #606266;
border: 2px solid #8c8c8c;
border-radius: 6px;
font-weight: 600;
}
:global(.insurance-record-page__upload-info) {
display: flex;
align-items: center;
min-width: 0;
}
&__upload-text {
display: flex;
flex: 1;
:global(.insurance-record-page__upload-icon) {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
margin-right: 30px;
color: #707070;
}
:global(.insurance-record-page__upload-icon::before),
:global(.insurance-record-page__upload-icon::after) {
position: absolute;
width: 14px;
height: 14px;
content: '';
}
:global(.insurance-record-page__upload-icon::before) {
top: 0;
left: 0;
border-top: 4px solid currentColor;
border-left: 4px solid currentColor;
border-radius: 4px 0 0;
}
:global(.insurance-record-page__upload-icon::after) {
right: 0;
bottom: 0;
border-right: 4px solid currentColor;
border-bottom: 4px solid currentColor;
border-radius: 0 0 4px;
}
:global(.insurance-record-page__upload-card) {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 28px;
border: 4px solid currentColor;
border-radius: 4px;
}
:global(.insurance-record-page__upload-card::before),
:global(.insurance-record-page__upload-card::after) {
position: absolute;
content: '';
}
:global(.insurance-record-page__upload-card::before) {
top: 7px;
left: 9px;
width: 8px;
height: 8px;
border: 3px solid currentColor;
border-radius: 2px;
}
:global(.insurance-record-page__upload-card::after) {
top: 8px;
right: 7px;
width: 8px;
height: 3px;
background: currentColor;
box-shadow: 0 8px 0 currentColor;
}
:global(.insurance-record-page__upload-card span) {
position: absolute;
right: 7px;
bottom: 5px;
width: 10px;
height: 3px;
background: currentColor;
}
:global(.insurance-record-page__upload-text) {
display: flex;
flex-direction: column;
gap: 8px;
color: #303133;
font-size: 20px;
line-height: 1.25;
}
:global(.insurance-record-page__upload-text strong) {
font-weight: 700;
}
:global(.insurance-record-page__upload-text span) {
color: #303133;
font-weight: 500;
}
:global(.insurance-record-page__upload-action) {
flex: 0 0 auto;
margin-left: 24px;
}
:global(.insurance-record-page__upload-action .el-upload) {
display: block;
}
:global(.insurance-record-page__upload-button) {
min-width: 156px;
height: 58px;
border-radius: 8px;
font-size: 20px;
font-weight: 500;
}
@media (max-width: 768px) {
:global(.insurance-record-page__upload) {
align-items: stretch;
flex-direction: column;
gap: 6px;
color: #303133;
gap: 18px;
padding: 22px;
}
span {
color: #606266;
}
:global(.insurance-record-page__upload-action) {
margin-left: 0;
}
:global(.insurance-record-page__upload-button) {
width: 100%;
}
}
</style>

View File

@@ -59,6 +59,17 @@
{{ row.vehicleNo }}
</el-button>
</template>
<template #vehicleNoForm>
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="maintenance-plan-page__input"
/>
</template>
<template #mileage="{ row }">
<span>{{ formatMileage(row.mileage, row.mileageUnit) }}</span>
</template>
@@ -68,9 +79,27 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
<template #attachments-form>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
</avue-crud>
<el-dialog title="保养记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -83,9 +112,12 @@
<script>
import { getList, getDetail, add, update, remove } from '@/api/vehicle/maintenance-plan';
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
@@ -116,13 +148,20 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
option: {
height: 'auto',
calcHeight: 32,
dialogWidth: 980,
dialogWidth: 960,
labelPosition: 'right',
labelWidth: 'auto',
searchLabelWidth: 160,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
@@ -141,6 +180,8 @@ export default {
],
value: '车辆',
search: true,
span: 24,
change: ({ value }) => this.handleVehicleTypeChange(value || '车辆'),
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
},
{
@@ -149,6 +190,8 @@ export default {
slot: true,
search: true,
minWidth: 130,
span: 12,
placeholder: '输入车牌号/船号查询选择',
rules: [
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
@@ -157,6 +200,8 @@ export default {
{
label: '保养人',
prop: 'maintainer',
span: 12,
placeholder: '请输入',
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
},
{
@@ -166,6 +211,8 @@ export default {
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 170,
span: 12,
placeholder: '请选择',
rules: [{ required: true, message: '请选择保养时间', trigger: 'click' }],
},
{
@@ -175,6 +222,9 @@ export default {
precision: 2,
minWidth: 130,
slot: true,
span: 12,
append: '公里',
placeholder: '请输入',
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
},
{
@@ -187,11 +237,14 @@ export default {
],
value: '公里',
hide: true,
display: false,
},
{
label: '保养项目',
prop: 'maintenanceItem',
minWidth: 160,
span: 12,
placeholder: '请输入',
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
},
{
@@ -199,6 +252,9 @@ export default {
prop: 'cost',
type: 'number',
precision: 2,
append: '元',
span: 12,
placeholder: '请输入',
rules: [
{ required: true, message: '请输入费用', trigger: 'blur' },
{ validator: validateNonNegative, trigger: 'blur' },
@@ -207,12 +263,16 @@ export default {
{
label: '店名',
prop: 'storeName',
span: 12,
placeholder: '请输入',
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
},
{
label: '联系电话',
prop: 'contactPhone',
minWidth: 150,
span: 12,
placeholder: '请输入',
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
},
{
@@ -221,6 +281,7 @@ export default {
minWidth: 180,
overHidden: true,
span: 24,
placeholder: '地图选址',
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
},
{
@@ -230,6 +291,8 @@ export default {
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 170,
span: 12,
placeholder: '请选择',
},
{
label: '下次保养里程/航程',
@@ -238,34 +301,29 @@ export default {
precision: 2,
minWidth: 170,
slot: true,
span: 12,
append: '公里',
placeholder: '请输入',
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
},
{
label: '附件',
prop: 'attachments',
type: 'upload',
listType: 'text',
multiple: true,
accept: '.jpg,.jpeg,.png,.pdf',
propsHttp: {
res: 'data',
url: 'link',
name: 'name',
},
action: '/blade-resource/oss/endpoint/put-file',
span: 24,
minWidth: 100,
slot: true,
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 4,
minRows: 5,
span: 24,
hide: true,
placeholder: '请输入',
rules: [{ max: 500, message: '最多 500 个字符', trigger: 'blur' }],
},
{
label: '附件',
prop: 'attachments',
span: 24,
minWidth: 100,
slot: true,
formslot: true,
},
{
label: '创建时间',
prop: 'createTime',
@@ -341,6 +399,7 @@ export default {
],
},
excelOption: {
labelPosition: 'right',
submitBtn: false,
emptyBtn: false,
column: [
@@ -385,6 +444,9 @@ export default {
editBtn: this.hasPermission('maintenance_plan_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
let ids = [];
this.selectionList.forEach(ele => {
@@ -397,6 +459,41 @@ export default {
hasPermission(code) {
return this.isAdmin || this.validData(this.permission[code], false);
},
handleVehicleTypeChange(vehicleType) {
this.updateVehicleType(vehicleType);
if (!this.form.id && this.form.vehicleNo) {
this.form.vehicleNo = '';
}
},
updateVehicleType(vehicleType) {
const mileageUnit = vehicleType === '船舶' ? '海里' : '公里';
const mileageColumn = this.findColumn(this.option.column, 'mileage');
const nextMaintenanceMileageColumn = this.findColumn(
this.option.column,
'nextMaintenanceMileage'
);
const mileageUnitColumn = this.findColumn(this.option.column, 'mileageUnit');
mileageColumn.append = mileageUnit;
nextMaintenanceMileageColumn.append = mileageUnit;
mileageUnitColumn.value = mileageUnit;
this.form.mileageUnit = mileageUnit;
},
fetchVehicleOptions(queryString, callback) {
const vehicleType = this.form.vehicleType || '车辆';
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
const params =
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
request(1, 20, params)
.then(res => {
const records = res.data.data.records || [];
callback(
records.map(item => ({
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
}))
);
})
.catch(() => callback([]));
},
formatMileage(value, unit) {
if (value === undefined || value === null || value === '') return '';
return `${Math.max(Number(value), 0)} ${unit || '公里'}`;
@@ -409,12 +506,20 @@ export default {
const attachments = JSON.parse(value);
return Array.isArray(attachments) ? `${attachments.length}` : '1 个';
} catch (error) {
return value
.split(',')
.map(item => item.trim())
.filter(Boolean).length + ' 个';
return (
value
.split(',')
.map(item => item.trim())
.filter(Boolean).length + ' 个'
);
}
},
isEmpty(value) {
return value === undefined || value === null || value === '';
},
toNumber(value) {
return this.isEmpty(value) ? null : Number(value);
},
parseAttachments(value) {
if (!value || Array.isArray(value)) return value;
if (typeof value !== 'string') return [];
@@ -440,14 +545,31 @@ export default {
},
normalizeRow(row) {
const values = { ...row };
if (!values.mileageUnit) {
values.mileageUnit = values.vehicleType === '船舶' ? '海里' : '公里';
}
values.vehicleType = values.vehicleType || '车辆';
values.mileageUnit = values.vehicleType === '船舶' ? '海里' : '公里';
values.attachments = this.stringifyAttachments(values.attachments);
return values;
},
validateRow(row) {
const mileage = this.toNumber(row.mileage);
const nextMaintenanceMileage = this.toNumber(row.nextMaintenanceMileage);
if (
mileage !== null &&
nextMaintenanceMileage !== null &&
nextMaintenanceMileage <= mileage
) {
this.$message.warning('下次保养里程/航程应大于本次里程/航程数');
return false;
}
return true;
},
rowSave(row, done, loading) {
add(this.normalizeRow(row)).then(
const values = this.normalizeRow(row);
if (!this.validateRow(values)) {
loading();
return;
}
add(values).then(
() => {
this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' });
@@ -460,7 +582,12 @@ export default {
);
},
rowUpdate(row, index, done, loading) {
update(this.normalizeRow(row)).then(
const values = this.normalizeRow(row);
if (!this.validateRow(values)) {
loading();
return;
}
update(values).then(
() => {
this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' });
@@ -502,11 +629,20 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form.vehicleType = '车辆';
this.form.mileageUnit = '公里';
this.updateVehicleType(this.form.vehicleType);
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
detail.vehicleType = detail.vehicleType || '车辆';
detail.mileageUnit = detail.vehicleType === '船舶' ? '海里' : '公里';
this.form = detail;
this.updateVehicleType(detail.vehicleType);
});
}
done();
@@ -551,12 +687,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '保养记录');
},
handleExport() {
this.$confirm('是否导出保养记录数据?', '提示', {
@@ -565,7 +696,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/maintenance-plan/export-maintenance-plan', this.buildExportParams())
exportBlob(
'/blade-transport/maintenance-plan/export-maintenance-plan',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `保养记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})
@@ -583,7 +717,9 @@ export default {
},
handleTemplate() {
exportBlob(
`/blade-transport/maintenance-plan/export-template?${this.website.tokenHeader}=${getToken()}`
`/blade-transport/maintenance-plan/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '保养记录模板.xlsx');
});
@@ -598,5 +734,9 @@ export default {
:deep(.el-table td .cell) {
white-space: nowrap;
}
&__input {
width: 100%;
}
}
</style>

View File

@@ -59,15 +59,44 @@
{{ row.vehicleNo }}
</el-button>
</template>
<template #vehicleNoForm>
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="maintenance-record-page__input"
/>
</template>
<template #mileage="{ row }">
<span>{{ formatMileage(row.mileage, row.mileageUnit) }}</span>
</template>
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
<template #attachments-form>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
</avue-crud>
<el-dialog title="维修记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -80,9 +109,12 @@
<script>
import { getList, getDetail, add, update, remove } from '@/api/vehicle/maintenance-record';
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
@@ -106,6 +138,7 @@ export default {
loading: true,
excelBox: false,
excelForm: {},
isDetailLoading: false,
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
@@ -113,13 +146,20 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
option: {
height: 'auto',
calcHeight: 32,
dialogWidth: 980,
dialogWidth: 960,
labelPosition: 'right',
labelWidth: 'auto',
searchLabelWidth: 160,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
viewBtn: true,
@@ -138,14 +178,19 @@ export default {
],
value: '车辆',
search: true,
span: 24,
change: ({ value }) => this.updateVehicleType(value || '车辆'),
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
},
{
label: '车牌号/船号',
prop: 'vehicleNo',
slot: true,
formslot: true,
search: true,
minWidth: 130,
span: 12,
placeholder: '输入车牌号/船号查询选择',
rules: [
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
@@ -154,6 +199,8 @@ export default {
{
label: '维修人',
prop: 'maintainer',
span: 12,
placeholder: '请输入',
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
},
{
@@ -163,12 +210,16 @@ export default {
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 170,
span: 12,
placeholder: '日期时间选择器',
rules: [{ required: true, message: '请选择维修时间', trigger: 'click' }],
},
{
label: '维修位置',
prop: 'location',
minWidth: 160,
span: 12,
placeholder: '请输入',
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
},
{
@@ -176,6 +227,8 @@ export default {
prop: 'replacedPart',
minWidth: 160,
overHidden: true,
span: 12,
placeholder: '请输入',
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
},
{
@@ -183,6 +236,9 @@ export default {
prop: 'cost',
type: 'number',
precision: 2,
append: '元',
span: 12,
placeholder: '请输入',
rules: [
{ required: true, message: '请输入费用', trigger: 'blur' },
{ validator: validateNonNegative, trigger: 'blur' },
@@ -192,14 +248,27 @@ export default {
label: '维修单位',
prop: 'company',
minWidth: 180,
span: 12,
placeholder: '请输入',
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
},
{
label: '联系方式',
prop: 'contact',
minWidth: 150,
span: 12,
placeholder: '请输入',
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
},
{
label: '地址',
prop: 'address',
minWidth: 180,
overHidden: true,
span: 24,
placeholder: '地图选址',
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
},
{
label: '出厂时间',
prop: 'factoryTime',
@@ -207,6 +276,8 @@ export default {
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 170,
span: 12,
placeholder: '日期时间选择器',
},
{
label: '里程/航程数',
@@ -214,7 +285,10 @@ export default {
type: 'number',
precision: 2,
minWidth: 130,
append: '公里',
slot: true,
span: 12,
placeholder: '请输入',
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
},
{
@@ -229,38 +303,22 @@ export default {
hide: true,
},
{
label: '地址',
prop: 'address',
minWidth: 180,
overHidden: true,
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 5,
span: 24,
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
hide: true,
placeholder: '请输入',
rules: [{ max: 500, message: '最多 500 个字符', trigger: 'blur' }],
},
{
label: '附件',
prop: 'attachments',
type: 'upload',
listType: 'text',
multiple: true,
accept: '.jpg,.jpeg,.png,.pdf',
propsHttp: {
res: 'data',
url: 'link',
name: 'name',
},
action: '/blade-resource/oss/endpoint/put-file',
span: 24,
minWidth: 100,
slot: true,
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 4,
span: 24,
hide: true,
rules: [{ max: 500, message: '最多 500 个字符', trigger: 'blur' }],
formslot: true,
},
{
label: '创建时间',
@@ -337,6 +395,7 @@ export default {
],
},
excelOption: {
labelPosition: 'right',
submitBtn: false,
emptyBtn: false,
column: [
@@ -381,6 +440,9 @@ export default {
editBtn: this.hasPermission('maintenance_record_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
let ids = [];
this.selectionList.forEach(ele => {
@@ -389,10 +451,50 @@ export default {
return ids.join(',');
},
},
watch: {
'form.vehicleType': {
handler(value, oldValue) {
this.updateVehicleType(value || '车辆', oldValue);
},
immediate: true,
},
},
methods: {
hasPermission(code) {
return this.isAdmin || this.validData(this.permission[code], false);
},
updateVehicleType(vehicleType, oldValue) {
const mileageUnit = vehicleType === '船舶' ? '海里' : '公里';
const vehicleTypeColumn = this.findColumn(this.option.column, 'vehicleType');
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
const mileageColumn = this.findColumn(this.option.column, 'mileage');
const mileageUnitColumn = this.findColumn(this.option.column, 'mileageUnit');
vehicleNoColumn.placeholder =
vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
vehicleTypeColumn.disabled = Boolean(this.form.id);
mileageColumn.append = mileageUnit;
mileageUnitColumn.value = mileageUnit;
this.form.mileageUnit = mileageUnit;
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue && this.form.vehicleNo) {
this.form.vehicleNo = '';
}
},
fetchVehicleOptions(queryString, callback) {
const vehicleType = this.form.vehicleType || '车辆';
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
const params =
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
request(1, 20, params)
.then(res => {
const records = res.data.data.records || [];
callback(
records.map(item => ({
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
}))
);
})
.catch(() => callback([]));
},
formatMileage(value, unit) {
if (value === undefined || value === null || value === '') return '';
return `${Math.max(Number(value), 0)} ${unit || '公里'}`;
@@ -405,10 +507,12 @@ export default {
const attachments = JSON.parse(value);
return Array.isArray(attachments) ? `${attachments.length}` : '1 个';
} catch (error) {
return value
.split(',')
.map(item => item.trim())
.filter(Boolean).length + ' 个';
return (
value
.split(',')
.map(item => item.trim())
.filter(Boolean).length + ' 个'
);
}
},
parseAttachments(value) {
@@ -436,8 +540,13 @@ export default {
},
normalizeRow(row) {
const values = { ...row };
if (!values.mileageUnit) {
values.mileageUnit = values.vehicleType === '船舶' ? '海里' : '公里';
values.vehicleType = values.vehicleType || '车辆';
values.mileageUnit = values.vehicleType === '船舶' ? '海里' : '公里';
if (values.vehicleNo) {
values.vehicleNo =
values.vehicleType === '船舶'
? values.vehicleNo.trim()
: values.vehicleNo.trim().toUpperCase();
}
values.attachments = this.stringifyAttachments(values.attachments);
return values;
@@ -498,12 +607,28 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = {
vehicleType: '车辆',
mileageUnit: '公里',
};
this.updateVehicleType('车辆');
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
this.form = detail;
});
this.isDetailLoading = true;
getDetail(this.form.id)
.then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
detail.vehicleType = detail.vehicleType || '车辆';
detail.mileageUnit = detail.vehicleType === '船舶' ? '海里' : '公里';
this.form = detail;
this.updateVehicleType(detail.vehicleType);
})
.finally(() => {
this.isDetailLoading = false;
});
}
done();
},
@@ -547,12 +672,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '维修记录');
},
handleExport() {
this.$confirm('是否导出维修记录数据?', '提示', {
@@ -561,7 +681,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/maintenance-record/export-maintenance-record', this.buildExportParams())
exportBlob(
'/blade-transport/maintenance-record/export-maintenance-record',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `维修记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})
@@ -579,7 +702,9 @@ export default {
},
handleTemplate() {
exportBlob(
`/blade-transport/maintenance-record/export-template?${this.website.tokenHeader}=${getToken()}`
`/blade-transport/maintenance-record/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '维修记录模板.xlsx');
});
@@ -594,5 +719,9 @@ export default {
:deep(.el-table td .cell) {
white-space: nowrap;
}
&__input {
width: 100%;
}
}
</style>

View File

@@ -59,20 +59,57 @@
{{ row.vehicleNo }}
</el-button>
</template>
<template #previousMonthMileage="{ row }">
<span>{{ formatMileage(row.previousMonthMileage, row.mileageUnit) }}</span>
</template>
<template #currentMonthMileage="{ row }">
<span>{{ formatMileage(row.currentMonthMileage, row.mileageUnit) }}</span>
</template>
<template #monthlyMileage="{ row }">
<span>{{ formatMileage(row.monthlyMileage, row.mileageUnit) }}</span>
</template>
<template #totalMileage="{ row }">
<span>{{ formatMileage(row.totalMileage, row.mileageUnit) }}</span>
</template>
<template #vehicleNoForm>
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="Boolean(form.id)"
clearable
placeholder="输入车牌号模糊查询选择"
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="mileage-record-page__input"
/>
</template>
<template #totalMileageStart-search="{ row }">
<div class="mileage-record-page__range">
<el-input
v-model="row.totalMileageStart"
clearable
placeholder="请输入"
type="number"
class="mileage-record-page__range-input"
/>
<span class="mileage-record-page__range-separator">-</span>
<el-input
v-model="row.totalMileageEnd"
clearable
placeholder="请输入"
type="number"
class="mileage-record-page__range-input"
/>
</div>
</template>
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
<template #attachments-form>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
</avue-crud>
<empty-pagination
:page="page"
@@ -81,7 +118,7 @@
@load="onLoad(page, query)"
/>
<el-dialog title="里程记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -95,9 +132,11 @@
<script>
import { add, getDetail, getList, remove, update } from '@/api/vehicle/mileage-record';
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/mileage-record';
@@ -121,6 +160,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -141,6 +181,9 @@ export default {
editBtn: this.hasPermission('mileage_record_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
const ids = [];
this.selectionList.forEach(ele => {
@@ -149,10 +192,43 @@ export default {
return ids.join(',');
},
},
watch: {
'form.vehicleType': {
handler(value) {
this.updateVehicleTypeDisplays(value || '车辆');
},
immediate: true,
},
},
methods: {
hasPermission(code) {
return this.isAdmin || this.validData(this.permission[code], false);
},
updateVehicleTypeDisplays(vehicleType) {
const isShip = vehicleType === '船舶';
const unit = isShip ? '海里' : '公里';
const vehicleTypeColumn = this.findColumn(this.option.column, 'vehicleType');
const labelMap = {
previousMonthMileage: isShip ? '上月统计航程数' : '上月统计里程数',
currentMonthMileage: isShip ? '本月统计航程数' : '本月统计里程数',
monthlyMileage: isShip ? '本月航行航程数' : '本月行驶里程数',
totalMileage: isShip ? '累计航程数' : '累计行驶里程数',
totalMileageStart: isShip ? '累计航程' : '累计行驶里程',
totalMileageEnd: isShip ? '累计航程数结束值' : '累计行驶里程数结束值',
};
Object.keys(labelMap).forEach(prop => {
const column = this.findColumn(this.option.column, prop);
column.label = labelMap[prop];
column.append = unit;
});
vehicleTypeColumn.disabled = Boolean(this.form.id);
const unitColumn = this.findColumn(this.option.column, 'mileageUnit');
unitColumn.value = unit;
this.form.mileageUnit = unit;
if (!this.form.id && this.form.vehicleNo) {
this.form.vehicleNo = '';
}
},
initDeptTree() {
getDeptTree(this.userInfo.tenantId).then(res => {
const column = this.findColumn(this.option.column, 'createDept');
@@ -160,13 +236,27 @@ export default {
});
},
fetchVehicleOptions(queryString, callback) {
getVehicleList(1, 20, { plateNo: queryString })
const vehicleType = this.form.vehicleType || '车辆';
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
const params =
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
request(1, 20, params)
.then(res => {
const records = res.data.data.records || [];
callback(records.map(item => ({ value: item.plateNo })));
callback(
records.map(item => ({
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
}))
);
})
.catch(() => callback([]));
},
formatMileage(value, unit) {
if (this.isEmpty(value)) {
return '';
}
return `${value} ${unit || '公里'}`;
},
formatAttachments(value) {
if (!value) return '';
if (Array.isArray(value)) return `${value.length}`;
@@ -211,12 +301,21 @@ export default {
},
normalizeRow(row) {
const values = { ...row };
values.vehicleType = values.vehicleType || '车辆';
values.mileageUnit = values.vehicleType === '船舶' ? '海里' : '公里';
if (values.vehicleNo) {
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
values.vehicleNo =
values.vehicleType === '船舶'
? values.vehicleNo.trim()
: values.vehicleNo.trim().toUpperCase();
}
const previousMonthMileage = this.toNumber(values.previousMonthMileage);
const currentMonthMileage = this.toNumber(values.currentMonthMileage);
if (this.isEmpty(values.monthlyMileage) && previousMonthMileage !== null && currentMonthMileage !== null) {
if (
this.isEmpty(values.monthlyMileage) &&
previousMonthMileage !== null &&
currentMonthMileage !== null
) {
values.monthlyMileage = this.toMoney(currentMonthMileage - previousMonthMileage);
}
values.attachments = this.stringifyAttachments(values.attachments);
@@ -237,11 +336,12 @@ export default {
return true;
},
validateRow(row) {
const isShip = row.vehicleType === '船舶';
const mileageFields = [
['previousMonthMileage', '上月统计里程'],
['currentMonthMileage', '本月统计里程'],
['monthlyMileage', '本月行驶里程'],
['totalMileage', '累计行驶里程'],
['previousMonthMileage', isShip ? '上月统计航程数' : '上月统计里程'],
['currentMonthMileage', isShip ? '本月统计航程数' : '本月统计里程'],
['monthlyMileage', isShip ? '本月航行航程数' : '本月行驶里程'],
['totalMileage', isShip ? '累计航程数' : '累计行驶里程'],
];
if (!mileageFields.every(([prop, label]) => this.validateMileage(row[prop], label))) {
return false;
@@ -256,11 +356,21 @@ export default {
monthlyMileage !== null &&
this.toMoney(currentMonthMileage - previousMonthMileage) !== this.toMoney(monthlyMileage)
) {
this.$message.warning('本月行驶里程应等于本月统计里程减去上月统计里程');
this.$message.warning(
isShip
? '本月航行航程数应等于本月统计航程数减去上月统计航程数'
: '本月行驶里程数应等于本月统计里程数减去上月统计里程数'
);
return false;
}
if (totalMileage !== null && currentMonthMileage !== null && totalMileage < currentMonthMileage) {
this.$message.warning('累计行驶里程应大于等于本月统计里程');
if (
totalMileage !== null &&
currentMonthMileage !== null &&
totalMileage < currentMonthMileage
) {
this.$message.warning(
isShip ? '累计航程数应大于等于本月统计航程数' : '累计行驶里程数应大于等于本月统计里程数'
);
return false;
}
return true;
@@ -331,19 +441,32 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
vehicleNoColumn.disabled = type !== 'add';
if (type === 'add') {
this.form.vehicleType = '车辆';
this.form.mileageUnit = '公里';
this.updateVehicleTypeDisplays(this.form.vehicleType);
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
detail.vehicleType = detail.vehicleType || this.form.vehicleType || '车辆';
detail.mileageUnit = detail.vehicleType === '船舶' ? '海里' : '公里';
this.form = detail;
this.updateVehicleTypeDisplays(detail.vehicleType);
});
}
done();
},
searchReset() {
this.query = {};
if (this.$refs.crud?.search) {
this.$refs.crud.search.totalMileageStart = undefined;
this.$refs.crud.search.totalMileageEnd = undefined;
}
this.onLoad(this.page);
},
searchChange(params, done) {
@@ -382,12 +505,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '里程记录');
},
handleExport() {
this.$confirm('是否导出里程记录数据?', '提示', {
@@ -396,7 +514,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/mileage-record/export-mileage-record', this.buildExportParams())
exportBlob(
'/blade-transport/mileage-record/export-mileage-record',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `里程记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})
@@ -413,11 +534,11 @@ export default {
};
},
handleTemplate() {
exportBlob(`/blade-transport/mileage-record/export-template?${this.website.tokenHeader}=${getToken()}`).then(
res => {
downloadXls(res.data, '里程记录模板.xlsx');
}
);
exportBlob(
`/blade-transport/mileage-record/export-template?${this.website.tokenHeader}=${getToken()}`
).then(res => {
downloadXls(res.data, '里程记录模板.xlsx');
});
},
},
};
@@ -433,5 +554,21 @@ export default {
&__input {
width: 100%;
}
&__range {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
&__range-input {
width: 150px;
}
&__range-separator {
color: #606266;
line-height: 32px;
}
}
</style>

View File

@@ -63,8 +63,9 @@
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入车牌号/船号查询选择"
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="oil-electric-record-page__input"
/>
@@ -72,9 +73,15 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
<template #attachments-form>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
</avue-crud>
<el-dialog title="油电记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -92,6 +99,7 @@ import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/oil-electric-record';
@@ -106,6 +114,7 @@ export default {
loading: true,
excelBox: false,
excelForm: {},
isDetailLoading: false,
option,
excelOption,
page: {
@@ -115,6 +124,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -135,6 +145,9 @@ export default {
editBtn: this.hasPermission('oil_electric_record_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
const ids = [];
this.selectionList.forEach(ele => {
@@ -145,8 +158,8 @@ export default {
},
watch: {
'form.vehicleType': {
handler() {
this.updateVehicleType();
handler(value, oldValue) {
this.updateVehicleType(value || '车辆', oldValue);
},
immediate: true,
},
@@ -167,8 +180,11 @@ export default {
column.dicData = res.data.data;
});
},
updateVehicleType() {
if (!this.form.id && this.form.vehicleNo) {
updateVehicleType(vehicleType, oldValue) {
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
vehicleNoColumn.placeholder =
vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue && this.form.vehicleNo) {
this.form.vehicleNo = '';
}
},
@@ -238,7 +254,10 @@ export default {
values.vehicleType = values.vehicleType || '车辆';
values.dataSource = values.dataSource || '手工录入';
if (values.vehicleNo) {
values.vehicleNo = values.vehicleType === '船舶' ? values.vehicleNo.trim() : values.vehicleNo.trim().toUpperCase();
values.vehicleNo =
values.vehicleType === '船舶'
? values.vehicleNo.trim()
: values.vehicleNo.trim().toUpperCase();
}
if (values.feeType === '充电') {
values.oilProduct = undefined;
@@ -341,17 +360,26 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = { vehicleType: '车辆', feeType: '加油', dataSource: '手工录入' };
this.updateVehicleType('车辆');
this.updateFeeType('加油');
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
this.form = detail;
this.updateFeeType(detail.feeType);
});
this.isDetailLoading = true;
getDetail(this.form.id)
.then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
detail.vehicleType = detail.vehicleType || '车辆';
this.form = detail;
this.updateVehicleType(detail.vehicleType);
this.updateFeeType(detail.feeType);
})
.finally(() => {
this.isDetailLoading = false;
});
}
done();
},
@@ -395,12 +423,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '油电记录');
},
handleExport() {
this.$confirm('是否导出油电记录数据?', '提示', {
@@ -409,7 +432,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/oil-electric-record/export-oil-electric-record', this.buildExportParams())
exportBlob(
'/blade-transport/oil-electric-record/export-oil-electric-record',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `油电记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})
@@ -426,11 +452,13 @@ export default {
};
},
handleTemplate() {
exportBlob(`/blade-transport/oil-electric-record/export-template?${this.website.tokenHeader}=${getToken()}`).then(
res => {
downloadXls(res.data, '油电记录模板.xlsx');
}
);
exportBlob(
`/blade-transport/oil-electric-record/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '油电记录模板.xlsx');
});
},
},
};

View File

@@ -63,8 +63,9 @@
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入车牌号/船号查询选择"
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="other-expense-record-page__input"
/>
@@ -72,9 +73,15 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
<template #attachments-form>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
</avue-crud>
<el-dialog title="其他费用记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -92,6 +99,7 @@ import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/other-expense-record';
@@ -106,6 +114,7 @@ export default {
loading: true,
excelBox: false,
excelForm: {},
isDetailLoading: false,
option,
excelOption,
page: {
@@ -115,6 +124,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -135,6 +145,9 @@ export default {
editBtn: this.hasPermission('other_expense_record_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
const ids = [];
this.selectionList.forEach(ele => {
@@ -145,8 +158,8 @@ export default {
},
watch: {
'form.vehicleType': {
handler() {
this.updateVehicleType();
handler(value, oldValue) {
this.updateVehicleType(value || '车辆', oldValue);
},
immediate: true,
},
@@ -161,8 +174,11 @@ export default {
column.dicData = res.data.data;
});
},
updateVehicleType() {
if (!this.form.id && this.form.vehicleNo) {
updateVehicleType(vehicleType, oldValue) {
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
vehicleNoColumn.placeholder =
vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue && this.form.vehicleNo) {
this.form.vehicleNo = '';
}
},
@@ -223,7 +239,10 @@ export default {
values.vehicleType = values.vehicleType || '车辆';
values.dataSource = values.dataSource || '手工录入';
if (values.vehicleNo) {
values.vehicleNo = values.vehicleType === '船舶' ? values.vehicleNo.trim() : values.vehicleNo.trim().toUpperCase();
values.vehicleNo =
values.vehicleType === '船舶'
? values.vehicleNo.trim()
: values.vehicleNo.trim().toUpperCase();
}
values.attachments = this.stringifyAttachments(values.attachments);
return values;
@@ -320,19 +339,28 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = {
vehicleType: '车辆',
expenseDate: this.$dayjs().format('YYYY-MM-DD'),
dataSource: '手工录入',
};
this.updateVehicleType('车辆');
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
this.form = detail;
});
this.isDetailLoading = true;
getDetail(this.form.id)
.then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
detail.vehicleType = detail.vehicleType || '车辆';
this.form = detail;
this.updateVehicleType(detail.vehicleType);
})
.finally(() => {
this.isDetailLoading = false;
});
}
done();
},
@@ -386,12 +414,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '其他费用记录');
},
handleExport() {
this.$confirm('是否导出其他费用记录数据?', '提示', {
@@ -400,9 +423,15 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/other-expense-record/export-other-expense-record', this.buildExportParams())
exportBlob(
'/blade-transport/other-expense-record/export-other-expense-record',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `其他费用记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
downloadXls(
res.data,
`其他费用记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
);
})
.finally(() => {
NProgress.done();
@@ -417,11 +446,13 @@ export default {
};
},
handleTemplate() {
exportBlob(`/blade-transport/other-expense-record/export-template?${this.website.tokenHeader}=${getToken()}`).then(
res => {
downloadXls(res.data, '其他费用记录模板.xlsx');
}
);
exportBlob(
`/blade-transport/other-expense-record/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '其他费用记录模板.xlsx');
});
},
},
};

View File

@@ -63,8 +63,9 @@
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入关键字"
placeholder="输入车牌号查询选择"
value-key="value"
class="tire-replacement-record-page__input"
/>
@@ -72,6 +73,24 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
<template #attachments-form>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
</avue-crud>
<empty-pagination
:page="page"
@@ -80,7 +99,7 @@
@load="onLoad(page, query)"
/>
<el-dialog title="换胎记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -97,6 +116,7 @@ import { getList as getVehicleList } from '@/api/transportCapacity/transport-veh
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/tire-replacement-record';
@@ -120,6 +140,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -289,6 +310,7 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = {};
}
@@ -341,12 +363,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '换胎记录');
},
handleExport() {
this.$confirm('是否导出换胎记录数据?', '提示', {
@@ -376,7 +393,9 @@ export default {
},
handleTemplate() {
exportBlob(
`/blade-transport/tire-replacement-record/export-template?${this.website.tokenHeader}=${getToken()}`
`/blade-transport/tire-replacement-record/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '换胎记录模板.xlsx');
});

View File

@@ -63,8 +63,9 @@
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入车牌号/船号模糊查询选择"
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="transport-change-record-page__input"
/>
@@ -72,9 +73,15 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
<template #attachments-form>
<vehicle-attachment-upload v-model="form.attachments" :readonly="boxType === 'view'" />
</template>
</avue-crud>
<el-dialog title="变更记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -92,6 +99,7 @@ import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/transport-change-record';
@@ -106,6 +114,7 @@ export default {
loading: true,
excelBox: false,
excelForm: {},
isDetailLoading: false,
option,
excelOption,
page: {
@@ -115,6 +124,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -135,6 +145,9 @@ export default {
editBtn: this.hasPermission('transport_change_record_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
const ids = [];
this.selectionList.forEach(ele => {
@@ -145,8 +158,8 @@ export default {
},
watch: {
'form.vehicleType': {
handler(value) {
this.updateVehicleType(value || '车辆');
handler(value, oldValue) {
this.updateVehicleType(value || '车辆', oldValue);
},
immediate: true,
},
@@ -161,8 +174,11 @@ export default {
column.dicData = res.data.data;
});
},
updateVehicleType() {
if (!this.form.id && this.form.vehicleNo) {
updateVehicleType(vehicleType, oldValue) {
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
vehicleNoColumn.placeholder =
vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue && this.form.vehicleNo) {
this.form.vehicleNo = '';
}
},
@@ -219,7 +235,10 @@ export default {
const values = { ...row };
values.vehicleType = values.vehicleType || '车辆';
if (values.vehicleNo) {
values.vehicleNo = values.vehicleType === '船舶' ? values.vehicleNo.trim() : values.vehicleNo.trim().toUpperCase();
values.vehicleNo =
values.vehicleType === '船舶'
? values.vehicleNo.trim()
: values.vehicleNo.trim().toUpperCase();
}
values.attachments = this.stringifyAttachments(values.attachments);
return values;
@@ -301,15 +320,24 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = { vehicleType: '车辆' };
this.updateVehicleType('车辆');
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
this.form = detail;
});
this.isDetailLoading = true;
getDetail(this.form.id)
.then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
detail.vehicleType = detail.vehicleType || '车辆';
this.form = detail;
this.updateVehicleType(detail.vehicleType);
})
.finally(() => {
this.isDetailLoading = false;
});
}
done();
},
@@ -353,12 +381,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '变更记录');
},
handleExport() {
this.$confirm('是否导出变更记录数据?', '提示', {
@@ -388,7 +411,9 @@ export default {
},
handleTemplate() {
exportBlob(
`/blade-transport/transport-change-record/export-template?${this.website.tokenHeader}=${getToken()}`
`/blade-transport/transport-change-record/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '变更记录模板.xlsx');
});

View File

@@ -63,8 +63,9 @@
<el-autocomplete
v-model="form.vehicleNo"
:fetch-suggestions="fetchVehicleOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入车牌号/船号模糊查询选择"
:placeholder="vehicleNoPlaceholder"
value-key="value"
class="violation-record-page__input"
/>
@@ -74,12 +75,20 @@
v-if="form.vehicleType !== '船舶'"
v-model="form.driverName"
:fetch-suggestions="fetchDriverOptions"
:disabled="boxType === 'view'"
clearable
placeholder="输入驾驶人模糊查询选择"
value-key="value"
class="violation-record-page__input"
/>
<el-input v-else v-model="form.driverName" clearable maxlength="20" placeholder="请输入船长" />
<el-input
v-else
v-model="form.driverName"
:disabled="boxType === 'view'"
clearable
maxlength="20"
placeholder="请输入船长"
/>
</template>
<template #processStatus="{ row }">
<span :class="row.processStatus === '未处理' ? 'violation-record-page__danger' : ''">
@@ -89,6 +98,24 @@
<template #attachments="{ row }">
<span>{{ formatAttachments(row.attachments) }}</span>
</template>
<template #attachmentsForm>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
<template #attachments-form>
<vehicle-attachment-upload
v-model="form.attachments"
:readonly="boxType === 'view'"
button-text="新增"
button-icon="el-icon-plus"
:show-tip="false"
/>
</template>
</avue-crud>
<empty-pagination
:page="page"
@@ -97,7 +124,7 @@
@load="onLoad(page, query)"
/>
<el-dialog title="违章记录数据导入" append-to-body v-model="excelBox" width="555px">
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
<avue-form :option="excelOption" v-model="excelForm">
<template #excelTemplate>
<el-button type="primary" @click="handleTemplate">
点击下载<i class="el-icon-download el-icon--right"></i>
@@ -116,6 +143,7 @@ import { getList as getDriverList } from '@/api/transportCapacity/driver';
import { getDeptTree } from '@/api/system/dept';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { openImportDialog } from '@/utils/import-excel';
import { getToken } from '@/utils/auth';
import { mapGetters } from 'vuex';
import { excelOption, option } from '@/option/vehicle/violation-record';
@@ -130,6 +158,7 @@ export default {
loading: true,
excelBox: false,
excelForm: {},
isDetailLoading: false,
option,
excelOption,
page: {
@@ -139,6 +168,7 @@ export default {
total: 0,
},
selectionList: [],
boxType: '',
data: [],
};
},
@@ -159,6 +189,9 @@ export default {
editBtn: this.hasPermission('violation_record_edit'),
};
},
vehicleNoPlaceholder() {
return this.form.vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
},
ids() {
const ids = [];
this.selectionList.forEach(ele => {
@@ -169,8 +202,8 @@ export default {
},
watch: {
'form.vehicleType': {
handler(value) {
this.updateVehicleTypeDisplays(value || '车辆');
handler(value, oldValue) {
this.updateVehicleTypeDisplays(value || '车辆', oldValue);
},
immediate: true,
},
@@ -191,15 +224,22 @@ export default {
column.dicData = res.data.data;
});
},
updateVehicleTypeDisplays(vehicleType) {
updateVehicleTypeDisplays(vehicleType, oldValue) {
const vehicleTypeColumn = this.findColumn(this.option.column, 'vehicleType');
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
const driverColumn = this.findColumn(this.option.column, 'driverName');
const typeColumn = this.findColumn(this.option.column, 'violationType');
const itemColumn = this.findColumn(this.option.column, 'violationItem');
vehicleNoColumn.placeholder =
vehicleType === '船舶' ? '输入船号模糊查询选择' : '输入车牌号模糊查询选择';
driverColumn.label = vehicleType === '船舶' ? '船长' : '驾驶人';
typeColumn.display = vehicleType !== '船舶';
itemColumn.display = vehicleType === '船舶';
vehicleTypeColumn.disabled = Boolean(this.form.id);
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue) {
this.form.vehicleNo = '';
this.form.driverName = '';
}
if (vehicleType === '船舶') {
this.form.violationType = undefined;
} else {
@@ -217,9 +257,7 @@ export default {
const vehicleType = this.form.vehicleType || '车辆';
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
const params =
vehicleType === '船舶'
? { shipIdentifierNo: queryString }
: { plateNo: queryString };
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
request(1, 20, params)
.then(res => {
const records = res.data.data.records || [];
@@ -277,7 +315,10 @@ export default {
values.vehicleType = values.vehicleType || '车辆';
values.processStatus = values.processStatus || '未处理';
if (values.vehicleNo) {
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
values.vehicleNo =
values.vehicleType === '船舶'
? values.vehicleNo.trim()
: values.vehicleNo.trim().toUpperCase();
}
if (values.processStatus === '未处理') {
values.processResult = undefined;
@@ -291,7 +332,10 @@ export default {
return values;
},
validateRow(row) {
if (row.violationTime && new Date(row.violationTime.replace(/-/g, '/')).getTime() > Date.now()) {
if (
row.violationTime &&
new Date(row.violationTime.replace(/-/g, '/')).getTime() > Date.now()
) {
this.$message.warning('时间不能超过当前时间');
return false;
}
@@ -378,6 +422,7 @@ export default {
});
},
beforeOpen(done, type) {
this.boxType = type;
if (type === 'add') {
this.form = {
vehicleType: '车辆',
@@ -387,13 +432,20 @@ export default {
this.updateProcessResultDisplay('未处理');
}
if (['edit', 'view'].includes(type)) {
getDetail(this.form.id).then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
this.form = detail;
this.updateVehicleTypeDisplays(detail.vehicleType);
this.updateProcessResultDisplay(detail.processStatus);
});
this.isDetailLoading = true;
getDetail(this.form.id)
.then(res => {
const detail = res.data.data;
detail.attachments = this.parseAttachments(detail.attachments);
detail.vehicleType = detail.vehicleType || '车辆';
detail.processStatus = detail.processStatus || '未处理';
this.form = detail;
this.updateVehicleTypeDisplays(detail.vehicleType);
this.updateProcessResultDisplay(detail.processStatus);
})
.finally(() => {
this.isDetailLoading = false;
});
}
done();
},
@@ -437,12 +489,7 @@ export default {
});
},
handleImport() {
this.excelBox = true;
},
uploadAfter(res, done) {
this.excelBox = false;
this.onLoad(this.page);
done();
openImportDialog(this, '违章记录');
},
handleExport() {
this.$confirm('是否导出违章记录数据?', '提示', {
@@ -451,7 +498,10 @@ export default {
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob('/blade-transport/violation-record/export-violation-record', this.buildExportParams())
exportBlob(
'/blade-transport/violation-record/export-violation-record',
this.buildExportParams()
)
.then(res => {
downloadXls(res.data, `违章记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
})
@@ -469,7 +519,9 @@ export default {
},
handleTemplate() {
exportBlob(
`/blade-transport/violation-record/export-template?${this.website.tokenHeader}=${getToken()}`
`/blade-transport/violation-record/export-template?${
this.website.tokenHeader
}=${getToken()}`
).then(res => {
downloadXls(res.data, '违章记录模板.xlsx');
});

View File

@@ -101,7 +101,10 @@ export default {
tip: false,
simplePage: true,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -93,7 +93,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -1,6 +1,6 @@
<template>
<basic-container>
<el-form ref="form" :model="form" label-width="80px">
<el-form ref="form" :model="form" label-width="auto">
<el-row type="flex" class="row-bg" justify="end">
<el-form-item>
<el-button @click="handleCancel">关闭</el-button>

View File

@@ -1,6 +1,6 @@
<template>
<basic-container>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form ref="form" :model="form" :rules="rules" label-width="auto">
<el-row type="flex" class="row-bg" justify="end">
<el-form-item>
<el-button type="primary" @click="handleAgree">同意</el-button>

View File

@@ -96,7 +96,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -109,7 +109,10 @@ export default {
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,

View File

@@ -101,7 +101,10 @@ export default {
tip: false,
simplePage: true,
searchShow: true,
searchMenuSpan: 6,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
border: true,
index: true,
selection: true,