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

1322 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<basic-container class="port-terminal-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="hasPermission('port_terminal_import')"
@click="handleImport"
>批量导入
</el-button>
<el-button
type="primary"
icon="el-icon-download"
plain
v-if="hasPermission('port_terminal_template')"
@click="handleTemplate"
>下载模板
</el-button>
<el-button
type="primary"
icon="el-icon-download"
plain
v-if="hasPermission('port_terminal_export')"
@click="handleExport"
>批量导出
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
plain
v-if="hasPermission('port_terminal_delete')"
@click="handleDelete"
>批量删除
</el-button>
</template>
<template #parentName="{ row }">
<span>{{ row.category === '港口' ? '-' : row.parentName }}</span>
</template>
<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 ? '启用' : '停用' }}
</el-tag>
</template>
<template #country-form>
<el-select
v-model="form.countryCode"
clearable
filterable
:disabled="isParentRegionLocked"
:placeholder="isParentRegionLocked ? '自动带出' : '请选择国家'"
style="width: 100%"
@change="handleCountryChange"
>
<el-option
v-for="item in countryOptions"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</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-link
type="primary"
v-if="hasPermission('port_terminal_edit')"
@click="$refs.crud.rowEdit(row, index)"
>
编辑
</el-link>
<el-link
type="primary"
v-if="hasPermission('port_terminal_status')"
@click="handleStatus(row)"
>
{{ row.status === 1 ? '停用' : '启用' }}
</el-link>
</template>
<template #cityCode-form>
<el-select
v-model="form.cityCode"
clearable
filterable
:loading="cityLoading"
:disabled="isCityDisabled"
:placeholder="isParentRegionLocked ? '自动带出' : '请先选择国家'"
style="width: 100%"
@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 #detailAddress="{ row }">
<span class="port-terminal-page__detail-address">{{ row.detailAddress || '-' }}</span>
</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">
<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 {
getList,
getDetail,
submit,
remove,
changeStatus,
getPortSelect,
} from '@/api/base/port-terminal';
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';
const DEFAULT_COUNTRY_CODE = '+86';
const EXCLUDED_EXCEL_HEADERS = ['数据来源', '启停状态'];
const REQUIRED_EXPORT_HEADERS = ['港口码头名称', '港口/码头名称', '类型', '国家', '城市', '经度', '纬度'];
const getExcelCellText = value => {
if (value === undefined || value === null) return '';
if (typeof value === 'object') {
if (Array.isArray(value.richText)) {
return value.richText.map(item => item.text || '').join('');
}
if (value.text) return value.text;
if (value.result) return value.result;
}
return String(value);
};
const removeWorkbookColumnsByHeaders = workbook => {
workbook.eachSheet(worksheet => {
const maxHeaderRow = Math.min(worksheet.rowCount || 0, 10);
for (let rowIndex = 1; rowIndex <= maxHeaderRow; rowIndex += 1) {
const row = worksheet.getRow(rowIndex);
const indexes = [];
row.eachCell((cell, colNumber) => {
const text = getExcelCellText(cell.value).trim();
if (EXCLUDED_EXCEL_HEADERS.includes(text)) {
indexes.push(colNumber);
}
});
if (indexes.length) {
indexes
.sort((a, b) => b - a)
.forEach(colNumber => worksheet.spliceColumns(colNumber, 1));
break;
}
}
});
};
const addWorkbookRequiredHeaderMarks = workbook => {
workbook.eachSheet(worksheet => {
const maxHeaderRow = Math.min(worksheet.rowCount || 0, 10);
for (let rowIndex = 1; rowIndex <= maxHeaderRow; rowIndex += 1) {
const row = worksheet.getRow(rowIndex);
let hasRequiredHeader = false;
row.eachCell(cell => {
const text = getExcelCellText(cell.value).trim();
const header = text.replace(/\*+$/g, '').trim();
if (REQUIRED_EXPORT_HEADERS.includes(header)) {
cell.value = `${header}*`;
hasRequiredHeader = true;
}
});
if (hasRequiredHeader) break;
}
});
};
const removeExcelColumnsByHeaders = async (blob, options = {}) => {
const { markRequiredHeaders = false } = options;
const ExcelJS = await import('exceljs');
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(await blob.arrayBuffer());
removeWorkbookColumnsByHeaders(workbook);
if (markRequiredHeaders) {
addWorkbookRequiredHeaderMarks(workbook);
}
const buffer = await workbook.xlsx.writeBuffer();
return new Blob([buffer], { type: 'application/vnd.ms-excel' });
};
export default {
data() {
const validateCode = (rule, value, callback) => {
const code = String(value || '').toUpperCase();
if (this.form.category === '港口') {
if (!/^[A-Z]{5}$/.test(code)) {
callback(new Error('港口编码为5位大写字母CNSHG'));
} else {
callback();
}
return;
}
if (this.form.category === '码头') {
if (!/^[A-Z]{5}-[A-Z0-9]+$/.test(code)) {
callback(new Error('码头编码格式为“港口编码-码头标识”CNSHG-CT1'));
} else if (this.form.parentCode && !code.startsWith(`${this.form.parentCode}-`)) {
callback(new Error(`码头编码须以所选上级港口编码 ${this.form.parentCode}- 开头`));
} else {
callback();
}
return;
}
callback();
};
const validateLongitude = (rule, value, callback) => {
const message = getCoordinateValidationMessage(value, 'longitude');
if (message) {
callback(new Error(message));
} else {
callback();
}
};
const validateLatitude = (rule, value, callback) => {
const message = getCoordinateValidationMessage(value, 'latitude');
if (message) {
callback(new Error(message));
} else {
callback();
}
};
return {
form: {},
query: {},
loading: true,
data: [],
excelBox: false,
excelForm: {},
portOptions: [],
countryOptions: [],
cityOptions: [],
cityLoading: false,
cityOptionCache: {},
districtOptions: [],
districtLoading: false,
districtOptionCache: {},
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
currentPage: 1,
total: 0,
},
selectionList: [],
option: {
height: 'auto',
calcHeight: 32,
dialogWidth: 980,
labelPosition: 'right',
labelWidth: 'auto',
tip: false,
searchShow: true,
searchLabelWidth: 160,
searchIcon: true,
searchIndex: 4,
searchMenuSpan: 24,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
indexWidth: 70,
viewBtn: false,
delBtn: false,
editBtn: false,
selection: true,
dialogClickModal: false,
menuWidth: 160,
column: [
{
label: '类型',
prop: 'category',
type: 'select',
search: true,
formslot: true,
span: 24,
dicData: [
{ label: '全部', value: '' },
{ label: '港口', value: '港口' },
{ label: '码头', value: '码头' },
],
rules: [{ required: true, message: '请选择类型', trigger: 'change' }],
change: ({ value }) => this.handleCategoryChange(value),
},
{
label: '上级港口',
prop: 'parentId',
type: 'select',
props: {
label: 'name',
value: 'id',
},
dicData: [],
hide: true,
span: 12,
placeholder: '请选择',
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
change: ({ value }) => this.handleParentChange(value),
},
{
label: '上级港口',
prop: 'parentName',
minWidth: 120,
slot: true,
addDisplay: false,
editDisplay: false,
},
{
label: '上级港口编码',
prop: 'parentCode',
minWidth: 120,
slot: 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: 140,
overHidden: false,
placeholder: '自动带出',
searchPlaceholder: '请选择 国家',
change: ({ value }) => this.handleCountryNameChange(value),
rules: [{ required: true, message: '请选择国家', trigger: 'change' }],
},
{
label: '城市',
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',
slot: true,
minRows: 3,
span: 24,
minWidth: 220,
overHidden: false,
placeholder: '请输入',
maxlength: 255,
showWordLimit: true,
rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }],
},
{
label: '经度',
prop: 'longitude',
formslot: true,
minWidth: 130,
span: 12,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入经度', trigger: 'blur' },
{ validator: validateLongitude, trigger: 'blur' },
],
},
{
label: '纬度',
prop: 'latitude',
formslot: true,
minWidth: 130,
span: 12,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入纬度', trigger: 'blur' },
{ validator: validateLatitude, trigger: 'blur' },
],
},
{
label: '数据来源',
prop: 'dataSource',
type: 'select',
search: true,
minWidth: 120,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '初始化导入', value: '初始化导入' },
{ label: '批量导入', value: '批量导入' },
{ label: '手工导入', value: '手工导入' },
],
},
{
label: '启停状态',
prop: 'status',
type: 'select',
search: true,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 4,
span: 24,
hide: true,
maxlength: 200,
showWordLimit: true,
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
},
{
label: '更新人',
prop: 'updateUserName',
formatter: formatUpdateUserName,
addDisplay: false,
editDisplay: false,
minWidth: 120,
},
{
label: '更新时间',
prop: 'updateTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
addDisplay: false,
editDisplay: 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: {
labelPosition: 'right',
submitBtn: false,
emptyBtn: false,
column: [
{
label: '模板上传',
prop: 'excelFile',
type: 'upload',
drag: true,
loadText: '模板上传中,请稍等',
span: 24,
propsHttp: {
res: 'data',
},
tip: '请上传 .xls,.xlsx 标准格式文件',
accept: '.xls,.xlsx',
action: '/blade-system/port-terminal/import-port-terminal',
},
{
label: '模板下载',
prop: 'excelTemplate',
formslot: true,
span: 24,
},
],
},
};
},
computed: {
...mapGetters(['permission', 'userInfo']),
permissionList() {
return {
addBtn: this.hasPermission('port_terminal_add'),
};
},
isAdmin() {
const authority = this.userInfo && this.userInfo.authority;
return Array.isArray(authority)
? authority.includes('admin')
: String(authority || '').includes('admin');
},
isParentRegionLocked() {
return this.form.category === '码头';
},
isCityDisabled() {
return this.isParentRegionLocked || !this.form.countryCode;
},
isDistrictDisabled() {
return this.isParentRegionLocked || !this.form.cityCode;
},
ids() {
let ids = [];
this.selectionList.forEach(ele => {
ids.push(ele.id);
});
return ids.join(',');
},
},
created() {
this.loadCountryOptions();
this.loadPortOptions();
},
methods: {
hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
},
handleCodeChange(value) {
const code = String(value || '').toUpperCase();
if (this.form.category === '港口') {
this.form.code = code.replace(/[^A-Z]/g, '').slice(0, 5);
return;
}
if (this.form.category === '码头') {
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, '');
}
}
},
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);
}
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 || []).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() {
getPortSelect().then(res => {
this.portOptions = res.data.data;
this.findColumn(this.option.column, 'parentId').dicData = this.portOptions;
});
},
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.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);
if (country) {
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) {
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;
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);
return this.loadCityOptions(countryCode);
})
.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);
});
},
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) {
return Promise.resolve('');
}
const countryOption = this.countryOptions.find(
item => item.title === country || item.id === country
);
if (countryOption) {
return Promise.resolve(countryOption.id);
}
return this.loadCountryOptions().then(() => {
const option = this.countryOptions.find(
item => item.title === country || item.id === country
);
return option ? option.id : DEFAULT_COUNTRY_CODE;
});
},
normalizeRow(row) {
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 (!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() === '';
if (isBlank(row.code)) {
this.$message.warning('请输入编码');
return false;
}
if (isBlank(row.name)) {
this.$message.warning('请输入港口/码头名称');
return false;
}
if (isBlank(row.category)) {
this.$message.warning('请选择类型');
return false;
}
if (row.category === '码头' && isBlank(row.parentId)) {
this.$message.warning('请选择上级港口');
return false;
}
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;
},
stopSubmitLoading(loading) {
if (typeof loading === 'function') {
loading();
}
},
rowSave(row, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateRequiredFields(submitRow)) {
this.stopSubmitLoading(loading);
return;
}
submit(submitRow).then(
() => {
this.onLoad(this.page);
this.loadPortOptions();
this.$message({ type: 'success', message: '操作成功!' });
done();
},
error => {
window.console.log(error);
loading();
}
);
},
rowUpdate(row, index, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateRequiredFields(submitRow)) {
this.stopSubmitLoading(loading);
return;
}
submit(submitRow).then(
() => {
this.onLoad(this.page);
this.loadPortOptions();
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.onLoad(this.page);
this.loadPortOptions();
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.onLoad(this.page);
this.loadPortOptions();
this.$message({ type: 'success', message: '操作成功!' });
this.$refs.crud.toggleSelection();
});
},
handleStatus(row) {
const nextStatus = row.status === 1 ? 2 : 1;
const actionName = nextStatus === 1 ? '启用' : '停用';
this.$confirm(`是否确认${actionName}该条数据?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => changeStatus(row.id, nextStatus))
.then(() => {
this.onLoad(this.page);
this.loadPortOptions();
this.$message({ type: 'success', message: '操作成功!' });
});
},
beforeOpen(done, type) {
if (type === 'add') {
this.form.category = this.form.category || '港口';
this.form.dataSource = this.form.dataSource || '手工导入';
this.form.status = this.form.status || 1;
}
this.handleCategoryChange(this.form.category || '港口');
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);
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);
}
this.handleCategoryChange(this.form.category, false);
this.syncTerminalCode();
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) {
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,
'港口码头主数据',
() => {
this.loadPortOptions();
},
{
failDetailDecorator: removeWorkbookColumnsByHeaders,
}
);
},
handleExport() {
this.$confirm('是否导出港口码头主数据?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob(
'/blade-system/port-terminal/export-port-terminal',
this.buildExportParams(),
{ feedback: true }
)
.then(async res => {
const blob = await removeExcelColumnsByHeaders(res.data, {
markRequiredHeaders: true,
});
downloadXls(
blob,
`港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
);
})
.finally(() => {
NProgress.done();
});
});
},
buildExportParams() {
return {
...this.query,
ids: this.ids,
[this.website.tokenHeader]: getToken(),
};
},
handleTemplate() {
exportBlob(
`/blade-system/port-terminal/export-template?${this.website.tokenHeader}=${getToken()}`,
undefined,
{ feedback: true }
).then(async res => {
const blob = await removeExcelColumnsByHeaders(res.data);
downloadXls(blob, '港口码头主数据模板.xlsx');
});
},
},
};
</script>
<style lang="scss" scoped>
.port-terminal-page__detail-address {
display: block;
white-space: normal;
word-break: break-all;
line-height: 20px;
padding: 4px 0;
}
</style>