1、完善配载
2、调整客商 3、调整临时额度 4、新增表头拖拽排序
This commit is contained in:
@@ -89,3 +89,10 @@ export const batchComplete = ids =>
|
|||||||
ids,
|
ids,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const changeRoute = row =>
|
||||||
|
request({
|
||||||
|
url: `${baseUrl}/change-route`,
|
||||||
|
method: 'post',
|
||||||
|
data: row,
|
||||||
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default {
|
|||||||
//配置首页不可关闭
|
//配置首页不可关闭
|
||||||
setting: {
|
setting: {
|
||||||
sidebar: 'vertical',
|
sidebar: 'vertical',
|
||||||
tag: false,
|
tag: true,
|
||||||
debug: false,
|
debug: false,
|
||||||
collapse: true,
|
collapse: true,
|
||||||
search: false,
|
search: false,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import crudCommon from '@/mixins/crud.js';
|
|||||||
import { getScreen, findColumn } from './utils/util';
|
import { getScreen, findColumn } from './utils/util';
|
||||||
import { setupPaginationEnhancer } from './utils/pagination';
|
import { setupPaginationEnhancer } from './utils/pagination';
|
||||||
import { setupDialogFormLabelAlign } from './utils/dialog-form-label';
|
import { setupDialogFormLabelAlign } from './utils/dialog-form-label';
|
||||||
|
import { setupTableColumnDrag } from './utils/table-column-drag';
|
||||||
import './permission';
|
import './permission';
|
||||||
import error from './error';
|
import error from './error';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
@@ -107,6 +108,7 @@ app.use(VueClipboard, {
|
|||||||
autoSetContainer: true,
|
autoSetContainer: true,
|
||||||
appendToBody: true, // 这可以帮助解决一些更复杂的使用场景下的问题
|
appendToBody: true, // 这可以帮助解决一些更复杂的使用场景下的问题
|
||||||
});
|
});
|
||||||
|
setupTableColumnDrag(app);
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
setupPaginationEnhancer();
|
setupPaginationEnhancer();
|
||||||
setupDialogFormLabelAlign();
|
setupDialogFormLabelAlign();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export const config = {
|
|||||||
temporaryCreditLimitSelectable: true,
|
temporaryCreditLimitSelectable: true,
|
||||||
},
|
},
|
||||||
enableAttachmentTable: true,
|
enableAttachmentTable: true,
|
||||||
|
detailAttachmentUploadTimeWidth: 220,
|
||||||
enableDraftSave: true,
|
enableDraftSave: true,
|
||||||
saveAsDraft: true,
|
saveAsDraft: true,
|
||||||
defaultForm: {
|
defaultForm: {
|
||||||
@@ -271,6 +272,8 @@ export const option = {
|
|||||||
},
|
},
|
||||||
...auditColumns.map(column => ({ ...column, hide: true })),
|
...auditColumns.map(column => ({ ...column, hide: true })),
|
||||||
]),
|
]),
|
||||||
dialogWidth: 1280,
|
dialogWidth: 1400,
|
||||||
|
menuFixed: 'right',
|
||||||
|
menuWidth: 320,
|
||||||
index: false,
|
index: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -574,12 +574,10 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全站统一:表格操作列文字链接统一为主色(蓝色)
|
// 全站统一:表格普通操作链接使用主色(蓝色),删除等危险操作保持危险色(红色)。
|
||||||
// 覆盖 Avue #menu 槽位与手写表格操作列中 el-link 的 warning / success / danger 等类型,
|
|
||||||
// 同时统一 hover / active / disabled 态,保证全站操作列风格一致。
|
|
||||||
// 选择器同时覆盖 Avue 行菜单(.avue-crud__menu)与任意含 el-link 的表格体单元格(手写操作列)。
|
// 选择器同时覆盖 Avue 行菜单(.avue-crud__menu)与任意含 el-link 的表格体单元格(手写操作列)。
|
||||||
.avue-crud__menu .el-link,
|
.avue-crud__menu .el-link:not(.el-link--danger),
|
||||||
.el-table .el-table__body .cell:has(.el-link) .el-link {
|
.el-table .el-table__body .cell:has(.el-link) .el-link:not(.el-link--danger) {
|
||||||
// 覆盖 Element Plus el-link 默认的 font-weight: var(--el-font-weight-primary)(500 中等粗),
|
// 覆盖 Element Plus el-link 默认的 font-weight: var(--el-font-weight-primary)(500 中等粗),
|
||||||
// 操作列文字统一为常规字重,避免「查看/编辑/删除」等链接显得加粗。
|
// 操作列文字统一为常规字重,避免「查看/编辑/删除」等链接显得加粗。
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@@ -589,6 +587,14 @@
|
|||||||
--el-link-disabled-text-color: var(--el-color-primary-light-5);
|
--el-link-disabled-text-color: var(--el-color-primary-light-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avue-crud__menu .el-link.el-link--danger,
|
||||||
|
.el-table .el-table__body .cell:has(.el-link) .el-link.el-link--danger {
|
||||||
|
--el-link-text-color: var(--el-color-danger);
|
||||||
|
--el-link-hover-text-color: var(--el-color-danger-light-3);
|
||||||
|
--el-link-active-text-color: var(--el-color-danger-dark-2);
|
||||||
|
--el-link-disabled-text-color: var(--el-color-danger-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
// 全站统一:去除操作列 el-link 默认 margin,避免与 8px gap 叠加导致文字间距过宽
|
// 全站统一:去除操作列 el-link 默认 margin,避免与 8px gap 叠加导致文字间距过宽
|
||||||
.avue-crud .avue-crud__menu .el-link,
|
.avue-crud .avue-crud__menu .el-link,
|
||||||
.el-table .el-table__body .cell:not(.avue-crud__menu):has(> .el-link) .el-link {
|
.el-table .el-table__body .cell:not(.avue-crud__menu):has(> .el-link) .el-link {
|
||||||
@@ -652,3 +658,13 @@
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全局表格列拖拽:仅对可移动的普通列显示拖拽反馈。
|
||||||
|
.el-table th.tms-table-column-draggable {
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
|
&.is-column-dragging {
|
||||||
|
cursor: grabbing;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ const collectLabelGroups = form => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const alignFormLabelsByColumn = form => {
|
const alignFormLabelsByColumn = form => {
|
||||||
const groups = collectLabelGroups(form);
|
if (form.classList.contains('dialog-form-label-fixed')) return;
|
||||||
|
const groups = collectLabelGroups(form);
|
||||||
|
|
||||||
groups.forEach(group => {
|
groups.forEach(group => {
|
||||||
group.labels.forEach(label => {
|
group.labels.forEach(label => {
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
const STORAGE_PREFIX = 'tms:table-column-order:';
|
||||||
|
|
||||||
|
const getStore = instance =>
|
||||||
|
instance?.store ||
|
||||||
|
instance?.exposed?.store ||
|
||||||
|
instance?.proxy?.store ||
|
||||||
|
instance?.$?.exposed?.store ||
|
||||||
|
instance?.$?.ctx?.store ||
|
||||||
|
instance?.$?.store;
|
||||||
|
|
||||||
|
const getColumnKey = column =>
|
||||||
|
String(column.property || column.columnKey || column.label || column.id || '');
|
||||||
|
|
||||||
|
const getTableKey = (instance, columns) => {
|
||||||
|
const routePath = instance.$route?.path || instance.proxy?.$route?.path || window.location.pathname;
|
||||||
|
const signature = columns
|
||||||
|
.map(getColumnKey)
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort()
|
||||||
|
.join('|');
|
||||||
|
return `${STORAGE_PREFIX}${routePath}:${signature}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const canDragColumn = column =>
|
||||||
|
column && !column.fixed && !['selection', 'index', 'expand'].includes(column.type);
|
||||||
|
|
||||||
|
const getColumnFromHeader = (header, columns) =>
|
||||||
|
columns.find(column => header.classList.contains(column.id));
|
||||||
|
|
||||||
|
const updateColumnOrder = (instance, columns) => {
|
||||||
|
const store = getStore(instance);
|
||||||
|
if (!store?.states?._columns || columns.length !== store.states._columns.value.length) return;
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
column.no = index;
|
||||||
|
});
|
||||||
|
store.states._columns.value = [...columns];
|
||||||
|
store.updateColumns?.();
|
||||||
|
store.scheduleLayout?.(true, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const restoreColumnOrder = instance => {
|
||||||
|
const store = getStore(instance);
|
||||||
|
const columns = store?.states?._columns?.value;
|
||||||
|
if (!Array.isArray(columns) || columns.length < 2) return;
|
||||||
|
const saved = JSON.parse(localStorage.getItem(getTableKey(instance, columns)) || '[]');
|
||||||
|
if (!Array.isArray(saved) || !saved.length) return;
|
||||||
|
const orderMap = new Map(saved.map((key, index) => [key, index]));
|
||||||
|
const ordered = [...columns].sort((a, b) => {
|
||||||
|
const aOrder = orderMap.get(getColumnKey(a));
|
||||||
|
const bOrder = orderMap.get(getColumnKey(b));
|
||||||
|
if (aOrder === undefined && bOrder === undefined) return a.no - b.no;
|
||||||
|
if (aOrder === undefined) return 1;
|
||||||
|
if (bOrder === undefined) return -1;
|
||||||
|
return aOrder - bOrder;
|
||||||
|
});
|
||||||
|
updateColumnOrder(instance, ordered);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bindHeaderDrag = instance => {
|
||||||
|
const store = getStore(instance);
|
||||||
|
const columns = store?.states?._columns?.value;
|
||||||
|
const headerRow = instance.$el?.querySelector('.el-table__header-wrapper thead tr');
|
||||||
|
if (!headerRow || headerRow.__tmsColumnDragBound || !Array.isArray(columns)) return;
|
||||||
|
headerRow.__tmsColumnDragBound = true;
|
||||||
|
let draggingColumn = null;
|
||||||
|
|
||||||
|
[...headerRow.children].forEach(header => {
|
||||||
|
const column = getColumnFromHeader(header, columns);
|
||||||
|
if (!canDragColumn(column)) return;
|
||||||
|
header.draggable = true;
|
||||||
|
header.classList.add('tms-table-column-draggable');
|
||||||
|
});
|
||||||
|
|
||||||
|
headerRow.addEventListener('dragstart', event => {
|
||||||
|
const header = event.target.closest('th');
|
||||||
|
const currentColumns = store.states._columns.value;
|
||||||
|
const column = header && getColumnFromHeader(header, currentColumns);
|
||||||
|
if (!canDragColumn(column)) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
draggingColumn = column;
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
header.classList.add('is-column-dragging');
|
||||||
|
});
|
||||||
|
headerRow.addEventListener('dragover', event => {
|
||||||
|
const header = event.target.closest('th');
|
||||||
|
const column = header && getColumnFromHeader(header, store.states._columns.value);
|
||||||
|
if (draggingColumn && canDragColumn(column)) event.preventDefault();
|
||||||
|
});
|
||||||
|
headerRow.addEventListener('drop', event => {
|
||||||
|
const header = event.target.closest('th');
|
||||||
|
const targetColumn = header && getColumnFromHeader(header, store.states._columns.value);
|
||||||
|
if (!draggingColumn || !canDragColumn(targetColumn) || draggingColumn === targetColumn) return;
|
||||||
|
event.preventDefault();
|
||||||
|
const ordered = [...store.states._columns.value];
|
||||||
|
const from = ordered.indexOf(draggingColumn);
|
||||||
|
if (from < 0) return;
|
||||||
|
const movingRight = from < ordered.indexOf(targetColumn);
|
||||||
|
ordered.splice(from, 1);
|
||||||
|
const targetIndex = ordered.indexOf(targetColumn);
|
||||||
|
ordered.splice(targetIndex + (movingRight ? 1 : 0), 0, draggingColumn);
|
||||||
|
updateColumnOrder(instance, ordered);
|
||||||
|
localStorage.setItem(getTableKey(instance, ordered), JSON.stringify(ordered.map(getColumnKey)));
|
||||||
|
});
|
||||||
|
headerRow.addEventListener('dragend', () => {
|
||||||
|
instance.$el.querySelectorAll('.is-column-dragging').forEach(item => item.classList.remove('is-column-dragging'));
|
||||||
|
draggingColumn = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setupTableColumnDrag = app => {
|
||||||
|
let dragging = null;
|
||||||
|
const restoredTables = new WeakSet();
|
||||||
|
const getTableInstance = element => {
|
||||||
|
let instance = element.closest('.el-table')?.__vueParentComponent;
|
||||||
|
while (instance && instance.type?.name !== 'ElTable') {
|
||||||
|
instance = instance.parent;
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
const enableHeaderDrag = () => {
|
||||||
|
document.querySelectorAll('.el-table__header-wrapper th').forEach(header => {
|
||||||
|
if (header.closest('.el-table__fixed, .el-table__fixed-right')) return;
|
||||||
|
const instance = getTableInstance(header);
|
||||||
|
if (instance && !restoredTables.has(instance)) {
|
||||||
|
restoreColumnOrder(instance);
|
||||||
|
restoredTables.add(instance);
|
||||||
|
}
|
||||||
|
const column = getColumnFromHeader(header, getStore(instance)?.states?._columns?.value || []);
|
||||||
|
if (!canDragColumn(column)) return;
|
||||||
|
header.draggable = true;
|
||||||
|
header.classList.add('tms-table-column-draggable');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const tableObserver = new MutationObserver(enableHeaderDrag);
|
||||||
|
tableObserver.observe(document.documentElement, { childList: true, subtree: true });
|
||||||
|
setTimeout(enableHeaderDrag, 0);
|
||||||
|
|
||||||
|
document.addEventListener('dragstart', event => {
|
||||||
|
const header = event.target.closest?.('.el-table__header-wrapper th.tms-table-column-draggable');
|
||||||
|
const instance = header && getTableInstance(header);
|
||||||
|
const store = getStore(instance);
|
||||||
|
const column = header && getColumnFromHeader(header, store?.states?._columns?.value || []);
|
||||||
|
if (!header || !canDragColumn(column)) return;
|
||||||
|
dragging = { instance, column, header };
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
header.classList.add('is-column-dragging');
|
||||||
|
});
|
||||||
|
document.addEventListener('dragover', event => {
|
||||||
|
const header = event.target.closest?.('.el-table__header-wrapper th.tms-table-column-draggable');
|
||||||
|
const instance = header && getTableInstance(header);
|
||||||
|
if (dragging && instance === dragging.instance) event.preventDefault();
|
||||||
|
});
|
||||||
|
document.addEventListener('drop', event => {
|
||||||
|
const header = event.target.closest?.('.el-table__header-wrapper th.tms-table-column-draggable');
|
||||||
|
const instance = header && getTableInstance(header);
|
||||||
|
const store = getStore(instance);
|
||||||
|
const target = header && getColumnFromHeader(header, store?.states?._columns?.value || []);
|
||||||
|
if (!dragging || instance !== dragging.instance || !canDragColumn(target) || target === dragging.column) return;
|
||||||
|
event.preventDefault();
|
||||||
|
const ordered = [...store.states._columns.value];
|
||||||
|
const from = ordered.indexOf(dragging.column);
|
||||||
|
if (from < 0) return;
|
||||||
|
const movingRight = from < ordered.indexOf(target);
|
||||||
|
ordered.splice(from, 1);
|
||||||
|
const targetIndex = ordered.indexOf(target);
|
||||||
|
ordered.splice(targetIndex + (movingRight ? 1 : 0), 0, dragging.column);
|
||||||
|
updateColumnOrder(instance, ordered);
|
||||||
|
localStorage.setItem(getTableKey(instance, ordered), JSON.stringify(ordered.map(getColumnKey)));
|
||||||
|
});
|
||||||
|
document.addEventListener('dragend', () => {
|
||||||
|
document.querySelectorAll('.is-column-dragging').forEach(item => item.classList.remove('is-column-dragging'));
|
||||||
|
dragging = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -2179,7 +2179,12 @@
|
|||||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="uploadUserName" label="上传人" width="140" />
|
<el-table-column prop="uploadUserName" label="上传人" width="140" />
|
||||||
<el-table-column prop="uploadTime" label="上传时间" width="180" sortable/>
|
<el-table-column
|
||||||
|
prop="uploadTime"
|
||||||
|
label="上传时间"
|
||||||
|
:width="config.detailAttachmentUploadTimeWidth || 180"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -138,9 +138,9 @@
|
|||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in carrierOptions"
|
v-for="item in carrierOptions"
|
||||||
:key="item.id || item.customerName || item.carrierName"
|
:key="item.id || item.fullName || item.customerName || item.carrierName"
|
||||||
:label="item.customerName || item.carrierName || item.name"
|
:label="item.fullName || item.customerName || item.carrierName || item.name"
|
||||||
:value="item.customerName || item.carrierName || item.name"
|
:value="item.fullName || item.customerName || item.carrierName || item.name"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -214,7 +214,7 @@
|
|||||||
<div class="loading-manage-page__search-actions">
|
<div class="loading-manage-page__search-actions">
|
||||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
<el-button @click="handleResetSearch">重置</el-button>
|
<el-button @click="handleResetSearch">重置</el-button>
|
||||||
<el-link type="primary" @click="searchExpanded = !searchExpanded">{{ searchExpanded ? '收起筛选' : '展开筛选' }}</el-link>
|
<el-link type="primary" @click="searchExpanded = !searchExpanded">{{ searchExpanded ? '收起' : '展开' }}</el-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -350,7 +350,29 @@
|
|||||||
@closed="resetLoadingDialog"
|
@closed="resetLoadingDialog"
|
||||||
>
|
>
|
||||||
<div v-loading="dialogLoading" class="loading-manage-dialog__body">
|
<div v-loading="dialogLoading" class="loading-manage-dialog__body">
|
||||||
|
<div v-if="dialogReadonly" class="loading-detail">
|
||||||
|
<section-card title="配载单详情">
|
||||||
|
<div class="loading-detail__summary">
|
||||||
|
<div class="loading-detail__heading">配载单详情</div>
|
||||||
|
<div>{{ dialogForm.loadingNo || '-' }}</div>
|
||||||
|
<div><el-tag :type="detailStatusType">{{ statusText(dialogForm) }}</el-tag></div>
|
||||||
|
<div><el-tag type="warning">{{ dialogForm.transportType || '-' }}</el-tag></div>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__task-row">
|
||||||
|
<div class="loading-detail__field"><span class="loading-detail__label">任务信息</span><span class="loading-detail__value">{{ taskSummary }}</span></div>
|
||||||
|
<div class="loading-detail__field"><span class="loading-detail__label">附件</span><span class="loading-detail__value"><el-link v-for="file in detailAttachments" :key="file.url || file.name" type="primary" @click="previewAttachment(file)">{{ file.name }}</el-link><span v-if="!detailAttachments.length">-</span></span></div>
|
||||||
|
</div>
|
||||||
|
</section-card>
|
||||||
|
<section-card>
|
||||||
|
<template #title><span class="loading-detail__route-title">运输路线<el-link type="primary" @click="openRouteChangeDialog">变更运输路线</el-link></span></template>
|
||||||
|
<div v-if="routeNodes.length" class="loading-detail__steps"><div v-for="(node, index) in routeNodes" :key="node.key || index" class="loading-detail__step"><div class="loading-detail__step-line"><span :class="['loading-detail__step-dot', index === 0 ? 'start' : index === routeNodes.length - 1 ? 'end' : 'middle']">{{ index === 0 ? '起' : index === routeNodes.length - 1 ? '终' : '经' }}</span></div><div class="loading-detail__step-address">{{ node.address }}</div><div class="loading-detail__step-tags"><el-tag v-for="tag in node.tags || []" :key="tag" size="small">{{ tag }}</el-tag></div></div></div><el-empty v-else description="暂无路线" :image-size="60" />
|
||||||
|
</section-card>
|
||||||
|
<section-card title="货物信息"><el-table :data="cargoRows" border height="220"><el-table-column type="index" label="序号" width="70"/><el-table-column prop="projectName" label="项目名称" min-width="140"/><el-table-column prop="cargoName" label="货物名称" min-width="140"/><el-table-column prop="cargoType" label="货物类型" min-width="120"/><el-table-column prop="packageType" label="包装" min-width="100"/><el-table-column prop="weight" label="重量(吨)" min-width="110"/><el-table-column prop="volume" label="体积(方)" min-width="110"/><el-table-column prop="quantity" label="数量" min-width="90"/><el-table-column prop="materialCode" label="物料编码" min-width="120"/><el-table-column prop="equipmentCode" label="设备编码" min-width="120"/><el-table-column prop="brand" label="品牌" min-width="120"/><el-table-column prop="specificationModel" label="规格型号" min-width="140"/><el-table-column prop="remark" label="备注" min-width="160"/><el-table-column prop="unitPrice" label="货物单价(元)" min-width="140"/><el-table-column prop="planDate" label="计划日期" min-width="130"/></el-table></section-card>
|
||||||
|
<section-card title="关联运单信息"><el-table :data="selectedWaybillRows" border><el-table-column type="index" label="序号" width="70"/><el-table-column prop="waybillNo" label="运单号" min-width="150"><template #default="{ row }"><el-link type="primary">{{ row.waybillNo || '-' }}</el-link></template></el-table-column><el-table-column label="配载单号" min-width="150"><template #default>{{ dialogForm.loadingNo || '-' }}</template></el-table-column><el-table-column prop="projectName" label="项目名称" min-width="150"/><el-table-column prop="customerName" label="客户" min-width="140"/><el-table-column prop="departureAddress" label="发货地址" min-width="220" show-overflow-tooltip/><el-table-column prop="arrivalAddress" label="到货地址" min-width="220" show-overflow-tooltip/></el-table></section-card>
|
||||||
|
<div class="loading-detail__bottom-grid"><section-card title="运输节点"><el-timeline v-if="processNodes.length"><el-timeline-item v-for="(item, index) in processNodes" :key="item.id || index" :timestamp="item.time || ''">{{ item.name || item.nodeName || item.label }}</el-timeline-item></el-timeline><el-empty v-else description="暂无运输节点" :image-size="50"/></section-card><section-card title="物流轨迹"><template #extra><div class="loading-detail__track-actions"><el-button plain @click="handleTrackAction('playback')">轨迹回放</el-button><el-button plain @click="handleTrackAction('locate')">实时定位</el-button></div></template><div ref="detailAmap" class="loading-detail__map"></div></section-card></div>
|
||||||
|
</div>
|
||||||
<el-form
|
<el-form
|
||||||
|
v-else
|
||||||
ref="loadingFormRef"
|
ref="loadingFormRef"
|
||||||
:model="dialogForm"
|
:model="dialogForm"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@@ -358,102 +380,9 @@
|
|||||||
label-width="auto"
|
label-width="auto"
|
||||||
class="loading-manage-dialog__form"
|
class="loading-manage-dialog__form"
|
||||||
>
|
>
|
||||||
<section-card title="配载基本信息">
|
|
||||||
<div class="loading-manage-dialog__grid">
|
|
||||||
<el-form-item label="配载单号">
|
|
||||||
<el-input v-model="dialogForm.loadingNo" disabled placeholder="系统自动生成" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="运输类型" required>
|
|
||||||
<el-select
|
|
||||||
v-model="dialogForm.transportType"
|
|
||||||
filterable
|
|
||||||
clearable
|
|
||||||
placeholder="请选择"
|
|
||||||
@visible-change="visible => visible && loadTransportTypeOptions()"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in transportTypeOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="项目名称">
|
|
||||||
<el-input v-model="dialogForm.projectName" readonly placeholder="从待配载运单带出" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="客户名称">
|
|
||||||
<el-input v-model="dialogForm.customerName" readonly placeholder="从待配载运单带出" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="原始单号">
|
|
||||||
<el-input v-model="dialogForm.originalNo" readonly placeholder="从待配载运单带出" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="运单批次号">
|
|
||||||
<el-input v-model="dialogForm.batchNo" clearable placeholder="请输入" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="数据来源">
|
|
||||||
<el-select v-model="dialogForm.dataSource" clearable placeholder="请选择">
|
|
||||||
<el-option
|
|
||||||
v-for="item in dataSourceOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="计划名称">
|
|
||||||
<el-select
|
|
||||||
v-model="dialogForm.planName"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
remote
|
|
||||||
reserve-keyword
|
|
||||||
:remote-method="loadPlanOptions"
|
|
||||||
:loading="planLoading"
|
|
||||||
placeholder="请输入"
|
|
||||||
@visible-change="visible => visible && loadPlanOptions(dialogForm.planName)"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in planOptions"
|
|
||||||
:key="item.id || item.planName"
|
|
||||||
:label="item.planName"
|
|
||||||
:value="item.planName"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="开始日期">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="dialogForm.startDate"
|
|
||||||
type="date"
|
|
||||||
format="YYYY-MM-DD"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
placeholder="请选择"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="结束日期">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="dialogForm.endDate"
|
|
||||||
type="date"
|
|
||||||
format="YYYY-MM-DD"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
placeholder="请选择"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="配载子单号" class="loading-manage-dialog__span-2">
|
|
||||||
<el-input
|
|
||||||
v-model="dialogForm.loadingSubNos"
|
|
||||||
readonly
|
|
||||||
type="textarea"
|
|
||||||
:rows="2"
|
|
||||||
placeholder="添加待配载运单后自动生成"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
</section-card>
|
|
||||||
|
|
||||||
<section-card title="筛选待配载运单">
|
<section-card title="筛选待配载运单">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<el-link v-if="!dialogReadonly" type="primary" @click="loadCandidateWaybills">筛选</el-link>
|
<el-link v-if="!dialogReadonly" type="primary" @click="openCandidateSearch">筛选</el-link>
|
||||||
</template>
|
</template>
|
||||||
<div class="loading-manage-dialog__candidate-search" v-if="candidateSearchVisible">
|
<div class="loading-manage-dialog__candidate-search" v-if="candidateSearchVisible">
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
@@ -584,9 +513,9 @@
|
|||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in carrierOptions"
|
v-for="item in carrierOptions"
|
||||||
:key="item.id || item.customerName || item.carrierName"
|
:key="item.id || item.fullName || item.customerName || item.carrierName"
|
||||||
:label="item.customerName || item.carrierName || item.name"
|
:label="item.fullName || item.customerName || item.carrierName || item.name"
|
||||||
:value="item.customerName || item.carrierName || item.name"
|
:value="item.fullName || item.customerName || item.carrierName || item.name"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -641,15 +570,12 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-manage-dialog__selected-summary">
|
|
||||||
<span>已选择 {{ selectedWaybillRows.length }} 条运单</span>
|
|
||||||
</div>
|
|
||||||
<el-table
|
<el-table
|
||||||
ref="candidateTableRef"
|
ref="candidateTableRef"
|
||||||
v-loading="candidateLoading"
|
v-loading="candidateLoading"
|
||||||
:data="candidateRows"
|
:data="candidateRows"
|
||||||
border
|
border
|
||||||
height="260"
|
height="287"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
@selection-change="candidateSelectionChange"
|
@selection-change="candidateSelectionChange"
|
||||||
>
|
>
|
||||||
@@ -723,7 +649,13 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="loading-manage-dialog__pagination">
|
<div class="loading-manage-dialog__table-toolbar">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!candidateSelection.length || candidateAdding"
|
||||||
|
:loading="candidateAdding"
|
||||||
|
@click="addSelectedWaybills"
|
||||||
|
>添加配载</el-button>
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="candidatePage.currentPage"
|
v-model:current-page="candidatePage.currentPage"
|
||||||
v-model:page-size="candidatePage.pageSize"
|
v-model:page-size="candidatePage.pageSize"
|
||||||
@@ -736,64 +668,66 @@
|
|||||||
</div>
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="待配载列表">
|
<div class="loading-manage-dialog__waybill-route-layout">
|
||||||
<el-table
|
<section-card title="待配载列表">
|
||||||
:data="selectedWaybillRows"
|
<el-table
|
||||||
border
|
:data="selectedWaybillRows"
|
||||||
class="loading-manage-dialog__selected-table"
|
border
|
||||||
>
|
class="loading-manage-dialog__selected-table"
|
||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
>
|
||||||
<el-table-column prop="waybillNo" label="运单号" min-width="150">
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
<template #default="{ row }">
|
<el-table-column prop="waybillNo" label="运单号" min-width="150">
|
||||||
<el-link type="primary">{{ row.waybillNo }}</el-link>
|
<template #default="{ row }">
|
||||||
</template>
|
<el-link type="primary">{{ row.waybillNo }}</el-link>
|
||||||
</el-table-column>
|
</template>
|
||||||
<el-table-column prop="projectName" label="项目名称" min-width="160" />
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column prop="projectName" label="项目名称" min-width="160" />
|
||||||
prop="departureAddress"
|
<el-table-column
|
||||||
label="发货地"
|
prop="departureAddress"
|
||||||
min-width="220"
|
label="发货地"
|
||||||
show-overflow-tooltip
|
min-width="220"
|
||||||
/>
|
show-overflow-tooltip
|
||||||
<el-table-column
|
/>
|
||||||
prop="arrivalAddress"
|
<el-table-column
|
||||||
label="到货地"
|
prop="arrivalAddress"
|
||||||
min-width="220"
|
label="到货地"
|
||||||
show-overflow-tooltip
|
min-width="220"
|
||||||
/>
|
show-overflow-tooltip
|
||||||
<el-table-column label="操作" width="110" fixed="right">
|
/>
|
||||||
<template #default="{ $index }">
|
<el-table-column label="操作" width="110" fixed="right">
|
||||||
<el-link
|
<template #default="{ $index }">
|
||||||
v-if="!dialogReadonly"
|
<el-link
|
||||||
type="primary"
|
v-if="!dialogReadonly"
|
||||||
@click="removeSelectedWaybill($index)"
|
type="danger"
|
||||||
>移除</el-link>
|
@click="removeSelectedWaybill($index)"
|
||||||
</template>
|
>删除</el-link>
|
||||||
</el-table-column>
|
</template>
|
||||||
</el-table>
|
</el-table-column>
|
||||||
</section-card>
|
</el-table>
|
||||||
|
</section-card>
|
||||||
|
|
||||||
<section-card title="路线信息">
|
<section-card title="线路编辑">
|
||||||
<div class="loading-manage-dialog__route-list" v-if="routeNodes.length">
|
<div class="loading-manage-dialog__route-list" v-if="routeNodes.length">
|
||||||
<div
|
<div
|
||||||
v-for="(node, index) in routeNodes"
|
v-for="(node, index) in routeNodes"
|
||||||
:key="node.key"
|
:key="node.key"
|
||||||
class="loading-manage-dialog__route-item"
|
class="loading-manage-dialog__route-item"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@dragstart="handleRouteDragStart(index)"
|
@dragstart="handleRouteDragStart(index)"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop="handleRouteDrop(index)"
|
@drop="handleRouteDrop(index)"
|
||||||
>
|
>
|
||||||
<el-icon class="loading-manage-dialog__route-handle"><Rank /></el-icon>
|
<el-icon class="loading-manage-dialog__route-handle"><Rank /></el-icon>
|
||||||
<span class="loading-manage-dialog__route-type">{{ routeTypeText(index) }}</span>
|
<span class="loading-manage-dialog__route-type">{{ routeTypeText(index) }}</span>
|
||||||
<span class="loading-manage-dialog__route-address">{{ node.address }}</span>
|
<span class="loading-manage-dialog__route-address">{{ node.address }}</span>
|
||||||
<span class="loading-manage-dialog__route-tags">
|
<span class="loading-manage-dialog__route-tags">
|
||||||
<el-tag v-for="tag in node.tags" :key="tag" size="small">{{ tag }}</el-tag>
|
<el-tag v-for="tag in node.tags" :key="tag" size="small">{{ tag }}</el-tag>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-empty v-else description="添加待配载运单后自动生成路线" :image-size="72" />
|
<el-empty v-else description="添加待配载运单后自动生成路线" :image-size="72" />
|
||||||
</section-card>
|
</section-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section-card title="货物信息">
|
<section-card title="货物信息">
|
||||||
<el-table :data="cargoRows" border class="loading-manage-dialog__cargo-table">
|
<el-table :data="cargoRows" border class="loading-manage-dialog__cargo-table">
|
||||||
@@ -805,11 +739,13 @@
|
|||||||
<el-table-column prop="weight" label="重量(吨)" min-width="120" />
|
<el-table-column prop="weight" label="重量(吨)" min-width="120" />
|
||||||
<el-table-column prop="volume" label="体积(方)" min-width="120" />
|
<el-table-column prop="volume" label="体积(方)" min-width="120" />
|
||||||
<el-table-column prop="quantity" label="数量" min-width="100" />
|
<el-table-column prop="quantity" label="数量" min-width="100" />
|
||||||
<el-table-column prop="planDate" label="计划日期" min-width="130" sortable/>
|
|
||||||
<el-table-column prop="unitPrice" label="货物单价(元)" min-width="150" />
|
|
||||||
<el-table-column prop="materialCode" label="物料编码" min-width="130" />
|
<el-table-column prop="materialCode" label="物料编码" min-width="130" />
|
||||||
<el-table-column prop="equipmentCode" label="设备编码" min-width="130" />
|
<el-table-column prop="equipmentCode" label="设备编码" min-width="130" />
|
||||||
|
<el-table-column prop="brand" label="品牌" min-width="120" />
|
||||||
|
<el-table-column prop="specificationModel" label="规格型号" min-width="140" />
|
||||||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="unitPrice" label="货物单价(元)" min-width="150" />
|
||||||
|
<el-table-column prop="planDate" label="计划日期" min-width="130" sortable />
|
||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
@@ -837,9 +773,9 @@
|
|||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in carrierOptions"
|
v-for="item in carrierOptions"
|
||||||
:key="item.id || item.customerName || item.carrierName"
|
:key="item.id || item.fullName || item.customerName || item.carrierName"
|
||||||
:label="item.customerName || item.carrierName || item.name"
|
:label="item.fullName || item.customerName || item.carrierName || item.name"
|
||||||
:value="item.customerName || item.carrierName || item.name"
|
:value="item.fullName || item.customerName || item.carrierName || item.name"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -939,6 +875,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<el-dialog v-model="routeChangeVisible" title="变更运输路线" append-to-body destroy-on-close width="78%" class="loading-route-change-dialog">
|
||||||
|
<el-tabs v-model="routeChangeTab">
|
||||||
|
<el-tab-pane label="变更路线" name="change">
|
||||||
|
<section-card title="关联子运单地址">
|
||||||
|
<el-table :data="routeChangeRows" border>
|
||||||
|
<el-table-column type="index" label="序号" width="70" />
|
||||||
|
<el-table-column prop="waybillNo" label="运单号" min-width="150"><template #default="{ row }"><el-link type="primary">{{ row.waybillNo || '-' }}</el-link></template></el-table-column>
|
||||||
|
<el-table-column prop="projectName" label="项目名称" min-width="160" />
|
||||||
|
<el-table-column label="发货地" min-width="310"><template #default="{ row }"><div class="loading-route-change-dialog__original">原:{{ row.originalDepartureAddress || '-' }}</div><el-input v-model="row.departureAddress" placeholder="请选择或输入地址" @input="updateRouteChangeNodes"><template #suffix><el-icon class="loading-route-change-dialog__location" @click="openCommonAddressDialog(row, 'departure')"><Location /></el-icon></template></el-input></template></el-table-column>
|
||||||
|
<el-table-column label="到货地" min-width="310"><template #default="{ row }"><div class="loading-route-change-dialog__original">原:{{ row.originalArrivalAddress || '-' }}</div><el-input v-model="row.arrivalAddress" placeholder="请选择或输入地址" @input="updateRouteChangeNodes"><template #suffix><el-icon class="loading-route-change-dialog__location" @click="openCommonAddressDialog(row, 'arrival')"><Location /></el-icon></template></el-input></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</section-card>
|
||||||
|
<section-card title="路线编辑"><template #extra><span class="loading-route-change-dialog__hint">拖拽调整顺序</span></template><div v-if="routeChangeNodes.length" class="loading-route-change-dialog__route-list"><div v-for="(node, index) in routeChangeNodes" :key="node.key || index" class="loading-route-change-dialog__route-item" draggable="true" @dragstart="handleRouteChangeDragStart(index)" @dragover.prevent @drop="handleRouteChangeDrop(index)"><span class="loading-route-change-dialog__route-type">{{ routeChangeTypeText(index) }}</span><span>{{ node.address }}</span><span class="loading-route-change-dialog__tags"><el-tag v-for="tag in node.tags || []" :key="tag" size="small">{{ tag }}</el-tag></span><el-icon><Rank /></el-icon></div></div><el-empty v-else description="暂无路线" :image-size="60" /></section-card>
|
||||||
|
<el-form label-width="auto"><el-form-item label="变更备注"><el-input v-model="routeChangeRemark" type="textarea" :rows="1" maxlength="200" show-word-limit placeholder="请输入" /></el-form-item></el-form>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="变更记录" name="records"><el-table :data="routeChangeRecords" border><el-table-column prop="changeTime" label="变更时间" min-width="170"/><el-table-column prop="changeUser" label="变更人" min-width="120"/><el-table-column prop="content" label="变更内容" min-width="360"><template #default="{ row }"><div class="loading-route-change-dialog__record-content">{{ row.content || '-' }}</div></template></el-table-column><el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip/></el-table><el-empty v-if="!routeChangeRecords.length" description="暂无变更记录" :image-size="60"/></el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<template #footer><el-button @click="routeChangeVisible = false">关闭</el-button><el-button v-if="routeChangeTab === 'change'" type="primary" @click="confirmRouteChange">确认变更</el-button></template>
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog v-model="commonAddressVisible" title="选择常用地址" append-to-body width="900px" class="loading-common-address-dialog">
|
||||||
|
<div class="loading-common-address-dialog__toolbar"><el-input v-model="commonAddressQuery.addressName" clearable placeholder="请输入地址名称" @keyup.enter="loadCommonAddresses"/><el-button type="primary" @click="loadCommonAddresses">查询</el-button></div>
|
||||||
|
<el-table v-loading="commonAddressLoading" :data="commonAddressRows" border @row-dblclick="selectCommonAddress"><el-table-column prop="addressName" label="地址名称" min-width="180"/><el-table-column prop="regionName" label="区域" min-width="180"/><el-table-column prop="detailAddress" label="详细地址" min-width="260"/><el-table-column label="操作" width="90"><template #default="{ row }"><el-link type="primary" @click="selectCommonAddress(row)">选择</el-link></template></el-table-column></el-table>
|
||||||
|
<template #footer><el-pagination v-model:current-page="commonAddressPage.currentPage" v-model:page-size="commonAddressPage.pageSize" layout="total, prev, pager, next" :total="commonAddressPage.total" @current-change="loadCommonAddresses"/><el-button @click="commonAddressVisible = false">关闭</el-button></template>
|
||||||
|
</el-dialog>
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -956,6 +916,8 @@ import {
|
|||||||
import { getParentOptions as getCargoTypeOptions } from '@/api/base/cargo-type';
|
import { getParentOptions as getCargoTypeOptions } from '@/api/base/cargo-type';
|
||||||
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
|
import { getList as getProcessConfigList } from '@/api/business/process-config';
|
||||||
|
import { getList as getCommonAddressList } from '@/api/base/common-address';
|
||||||
import {
|
import {
|
||||||
carrierTypeOptions,
|
carrierTypeOptions,
|
||||||
loadingManageConfig,
|
loadingManageConfig,
|
||||||
@@ -963,11 +925,14 @@ import {
|
|||||||
loadingStatusOptions,
|
loadingStatusOptions,
|
||||||
} from '@/option/business/loading-manage';
|
} from '@/option/business/loading-manage';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { Rank, Refresh, Setting } from '@element-plus/icons-vue';
|
import { Location, Rank, Refresh, Setting } from '@element-plus/icons-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const defaultTransportType = '公路整车';
|
const defaultTransportType = '';
|
||||||
const defaultCandidateTransportType = '公路运输';
|
const defaultCandidateTransportType = '';
|
||||||
|
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||||||
|
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||||
|
let detailAmapLoader;
|
||||||
|
|
||||||
const createSearchForm = () => ({
|
const createSearchForm = () => ({
|
||||||
loadingNo: '',
|
loadingNo: '',
|
||||||
@@ -1091,6 +1056,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Rank,
|
Rank,
|
||||||
|
Location,
|
||||||
Refresh,
|
Refresh,
|
||||||
Setting,
|
Setting,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -1102,7 +1068,7 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
pageSizes: [10, 20, 50, 100],
|
pageSizes: [10, 20, 50, 100],
|
||||||
},
|
},
|
||||||
searchExpanded: true,
|
searchExpanded: false,
|
||||||
searchForm: createSearchForm(),
|
searchForm: createSearchForm(),
|
||||||
selectionRows: [],
|
selectionRows: [],
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
@@ -1110,9 +1076,10 @@ export default {
|
|||||||
dialogLoading: false,
|
dialogLoading: false,
|
||||||
dialogSaving: '',
|
dialogSaving: '',
|
||||||
dialogForm: createDialogForm(),
|
dialogForm: createDialogForm(),
|
||||||
candidateSearchVisible: true,
|
candidateSearchVisible: false,
|
||||||
candidateQuery: createCandidateSearchForm(),
|
candidateQuery: createCandidateSearchForm(),
|
||||||
candidateLoading: false,
|
candidateLoading: false,
|
||||||
|
candidateAdding: false,
|
||||||
candidateRows: [],
|
candidateRows: [],
|
||||||
candidateSelection: [],
|
candidateSelection: [],
|
||||||
candidatePage: {
|
candidatePage: {
|
||||||
@@ -1142,6 +1109,21 @@ export default {
|
|||||||
statusOptions: loadingStatusOptions,
|
statusOptions: loadingStatusOptions,
|
||||||
dataSourceOptions: loadingManageConfig.dataSourceOptions,
|
dataSourceOptions: loadingManageConfig.dataSourceOptions,
|
||||||
config: loadingManageConfig,
|
config: loadingManageConfig,
|
||||||
|
processNodes: [],
|
||||||
|
routeChangeVisible: false,
|
||||||
|
routeChangeTab: 'change',
|
||||||
|
routeChangeRows: [],
|
||||||
|
routeChangeNodes: [],
|
||||||
|
routeChangeRemark: '',
|
||||||
|
routeChangeRecords: [],
|
||||||
|
routeChangeDragIndex: -1,
|
||||||
|
commonAddressVisible: false,
|
||||||
|
commonAddressLoading: false,
|
||||||
|
commonAddressRows: [],
|
||||||
|
commonAddressQuery: { addressName: '' },
|
||||||
|
commonAddressPage: { currentPage: 1, pageSize: 10, total: 0 },
|
||||||
|
commonAddressTarget: null,
|
||||||
|
detailAmap: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -1158,6 +1140,20 @@ export default {
|
|||||||
};
|
};
|
||||||
return titleMap[this.dialogMode] || '配载管理';
|
return titleMap[this.dialogMode] || '配载管理';
|
||||||
},
|
},
|
||||||
|
detailStatusType() {
|
||||||
|
const typeMap = { pending: 'success', running: 'success', completed: 'info', cancelled: 'info', draft: 'warning' };
|
||||||
|
return typeMap[this.dialogForm.businessStatus] || 'info';
|
||||||
|
},
|
||||||
|
taskSummary() {
|
||||||
|
return [this.dialogForm.carrierName, this.dialogForm.driverName, this.dialogForm.vehicleNo]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' / ') || '-';
|
||||||
|
},
|
||||||
|
detailAttachments() {
|
||||||
|
return parseJsonArray(this.dialogForm.attachmentsJson || this.dialogForm.attachmentJson).map(item =>
|
||||||
|
typeof item === 'string' ? { name: item.split('/').pop(), url: item } : { name: item.name || item.fileName || '附件', url: item.url || item.link || item.fileUrl }
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadTransportTypeOptions();
|
this.loadTransportTypeOptions();
|
||||||
@@ -1294,6 +1290,9 @@ export default {
|
|||||||
this.dialogForm.loadingSubNos
|
this.dialogForm.loadingSubNos
|
||||||
);
|
);
|
||||||
this.restoreRouteAndCargo();
|
this.restoreRouteAndCargo();
|
||||||
|
this.restoreRouteChangeRecords();
|
||||||
|
await this.loadProcessNodes();
|
||||||
|
this.$nextTick(() => this.initDetailAmap());
|
||||||
await this.loadCandidateWaybills();
|
await this.loadCandidateWaybills();
|
||||||
} else {
|
} else {
|
||||||
this.dialogForm = createDialogForm();
|
this.dialogForm = createDialogForm();
|
||||||
@@ -1310,6 +1309,8 @@ export default {
|
|||||||
this.dialogLoading = false;
|
this.dialogLoading = false;
|
||||||
},
|
},
|
||||||
resetDialogData() {
|
resetDialogData() {
|
||||||
|
this.detailAmap?.destroy?.();
|
||||||
|
this.detailAmap = null;
|
||||||
this.dialogForm = createDialogForm();
|
this.dialogForm = createDialogForm();
|
||||||
this.candidateQuery = createCandidateSearchForm();
|
this.candidateQuery = createCandidateSearchForm();
|
||||||
this.candidateRows = [];
|
this.candidateRows = [];
|
||||||
@@ -1319,7 +1320,8 @@ export default {
|
|||||||
this.cargoRows = [];
|
this.cargoRows = [];
|
||||||
this.candidatePage.currentPage = 1;
|
this.candidatePage.currentPage = 1;
|
||||||
this.candidatePage.total = 0;
|
this.candidatePage.total = 0;
|
||||||
this.candidateSearchVisible = true;
|
this.candidateSearchVisible = false;
|
||||||
|
this.processNodes = [];
|
||||||
},
|
},
|
||||||
clearLoadingDialog() {
|
clearLoadingDialog() {
|
||||||
this.resetDialogData();
|
this.resetDialogData();
|
||||||
@@ -1333,6 +1335,164 @@ export default {
|
|||||||
: this.buildRouteNodes(this.selectedWaybillRows);
|
: this.buildRouteNodes(this.selectedWaybillRows);
|
||||||
this.cargoRows = cargoRows.length ? cargoRows : this.buildCargoRows(this.selectedWaybillRows);
|
this.cargoRows = cargoRows.length ? cargoRows : this.buildCargoRows(this.selectedWaybillRows);
|
||||||
},
|
},
|
||||||
|
restoreRouteChangeRecords() {
|
||||||
|
try {
|
||||||
|
const taskInfo = JSON.parse(this.dialogForm.taskInfoJson || '{}');
|
||||||
|
this.routeChangeRecords = Array.isArray(taskInfo.routeChangeRecords)
|
||||||
|
? [...taskInfo.routeChangeRecords].sort((a, b) => String(b.changeTime).localeCompare(String(a.changeTime)))
|
||||||
|
: [];
|
||||||
|
} catch (error) {
|
||||||
|
this.routeChangeRecords = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadProcessNodes() {
|
||||||
|
const waybillProcess = parseJsonArray(this.selectedWaybillRows[0]?.processJson);
|
||||||
|
if (waybillProcess.length) {
|
||||||
|
this.processNodes = waybillProcess;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const rows = unwrapRecords(await getProcessConfigList(1, 20, { projectId: this.dialogForm.projectId, status: 1 }));
|
||||||
|
const config = rows.find(item => item.nodeConfigJson) || {};
|
||||||
|
this.processNodes = parseJsonArray(config.nodeConfigJson);
|
||||||
|
} catch (error) {
|
||||||
|
this.processNodes = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
previewAttachment(file) {
|
||||||
|
if (!file.url) {
|
||||||
|
ElMessage.info('暂无可预览的附件地址');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.open(file.url, '_blank', 'noopener,noreferrer');
|
||||||
|
},
|
||||||
|
loadDetailAmap() {
|
||||||
|
if (window.AMap?.Map) return Promise.resolve();
|
||||||
|
if (!detailAmapLoader) {
|
||||||
|
detailAmapLoader = new Promise((resolve, reject) => {
|
||||||
|
window._AMapSecurityConfig = { securityJsCode: AMAP_SECURITY_CODE };
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
|
||||||
|
script.async = true;
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = () => reject(new Error('高德地图组件加载失败'));
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return detailAmapLoader;
|
||||||
|
},
|
||||||
|
initDetailAmap() {
|
||||||
|
if (!this.$refs.detailAmap) return;
|
||||||
|
this.loadDetailAmap()
|
||||||
|
.then(() => {
|
||||||
|
if (!this.detailAmap) {
|
||||||
|
this.detailAmap = new window.AMap.Map(this.$refs.detailAmap, {
|
||||||
|
center: [116.40769, 39.89945],
|
||||||
|
zoom: 11,
|
||||||
|
});
|
||||||
|
window.AMap.plugin(['AMap.ToolBar'], () => this.detailAmap?.addControl(new window.AMap.ToolBar()));
|
||||||
|
} else {
|
||||||
|
this.detailAmap.resize?.();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => ElMessage.warning('高德地图加载失败'));
|
||||||
|
},
|
||||||
|
handleTrackAction(action) {
|
||||||
|
ElMessage.info(action === 'playback' ? '暂无可回放的物流轨迹' : '暂无车辆实时定位数据');
|
||||||
|
},
|
||||||
|
openRouteChangeDialog() {
|
||||||
|
this.routeChangeTab = 'change';
|
||||||
|
this.routeChangeRemark = '';
|
||||||
|
this.routeChangeRows = this.selectedWaybillRows.map(row => ({
|
||||||
|
...row,
|
||||||
|
originalDepartureAddress: row.departureAddress || row.departureName || '',
|
||||||
|
originalArrivalAddress: row.arrivalAddress || row.arrivalName || '',
|
||||||
|
departureAddress: row.departureAddress || row.departureName || '',
|
||||||
|
arrivalAddress: row.arrivalAddress || row.arrivalName || '',
|
||||||
|
}));
|
||||||
|
this.routeChangeNodes = this.buildRouteNodes(this.routeChangeRows);
|
||||||
|
this.routeChangeVisible = true;
|
||||||
|
},
|
||||||
|
routeChangeTypeText(index) {
|
||||||
|
if (index === 0) return '起';
|
||||||
|
if (index === this.routeChangeNodes.length - 1) return '终';
|
||||||
|
return '经';
|
||||||
|
},
|
||||||
|
handleRouteChangeDragStart(index) {
|
||||||
|
this.routeChangeDragIndex = index;
|
||||||
|
},
|
||||||
|
handleRouteChangeDrop(index) {
|
||||||
|
if (this.routeChangeDragIndex < 0 || this.routeChangeDragIndex === index) return;
|
||||||
|
const nodes = [...this.routeChangeNodes];
|
||||||
|
const [node] = nodes.splice(this.routeChangeDragIndex, 1);
|
||||||
|
nodes.splice(index, 0, node);
|
||||||
|
this.routeChangeNodes = nodes;
|
||||||
|
this.routeChangeDragIndex = -1;
|
||||||
|
},
|
||||||
|
updateRouteChangeNodes() {
|
||||||
|
this.routeChangeNodes = this.buildRouteNodes(this.routeChangeRows);
|
||||||
|
},
|
||||||
|
openCommonAddressDialog(row, field) {
|
||||||
|
this.commonAddressTarget = { row, field };
|
||||||
|
this.commonAddressQuery = { addressName: '' };
|
||||||
|
this.commonAddressPage.currentPage = 1;
|
||||||
|
this.commonAddressVisible = true;
|
||||||
|
this.loadCommonAddresses();
|
||||||
|
},
|
||||||
|
async loadCommonAddresses() {
|
||||||
|
this.commonAddressLoading = true;
|
||||||
|
try {
|
||||||
|
const page = unwrapPage(await getCommonAddressList(this.commonAddressPage.currentPage, this.commonAddressPage.pageSize, { ...this.commonAddressQuery, allDept: 0 }));
|
||||||
|
this.commonAddressRows = page.records;
|
||||||
|
this.commonAddressPage.total = page.total;
|
||||||
|
} finally {
|
||||||
|
this.commonAddressLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectCommonAddress(address) {
|
||||||
|
const target = this.commonAddressTarget;
|
||||||
|
if (!target) return;
|
||||||
|
target.row[`${target.field}Address`] = [address.regionName, address.detailAddress || address.addressName].filter(Boolean).join('') || address.addressName || '';
|
||||||
|
this.updateRouteChangeNodes();
|
||||||
|
this.commonAddressVisible = false;
|
||||||
|
},
|
||||||
|
async confirmRouteChange() {
|
||||||
|
const changeRows = this.routeChangeRows.filter(row => row.departureAddress !== row.originalDepartureAddress || row.arrivalAddress !== row.originalArrivalAddress);
|
||||||
|
if (!changeRows.length && !this.routeChangeRemark) {
|
||||||
|
ElMessage.warning('请修改地址或填写变更备注');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const content = changeRows.map(row => `${row.waybillNo || '运单'}:发货地 ${row.originalDepartureAddress} → ${row.departureAddress};到货地 ${row.originalArrivalAddress} → ${row.arrivalAddress}`).join(';') || '调整运输路线顺序';
|
||||||
|
this.routeChangeRecords.unshift({ changeTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), changeUser: '当前用户', content, remark: this.routeChangeRemark });
|
||||||
|
let taskInfo = {};
|
||||||
|
try {
|
||||||
|
taskInfo = JSON.parse(this.dialogForm.taskInfoJson || '{}');
|
||||||
|
} catch (error) {
|
||||||
|
taskInfo = {};
|
||||||
|
}
|
||||||
|
this.dialogForm.taskInfoJson = JSON.stringify({
|
||||||
|
...(typeof taskInfo === 'object' && !Array.isArray(taskInfo) ? taskInfo : {}),
|
||||||
|
routeChangeRecords: this.routeChangeRecords,
|
||||||
|
});
|
||||||
|
this.selectedWaybillRows = this.routeChangeRows.map(({ originalDepartureAddress, originalArrivalAddress, ...row }) => row);
|
||||||
|
this.routeNodes = this.routeChangeNodes;
|
||||||
|
this.syncRouteAddressFields();
|
||||||
|
try {
|
||||||
|
await loadingApi.changeRoute({
|
||||||
|
id: this.dialogForm.id,
|
||||||
|
routeJson: JSON.stringify(this.routeNodes),
|
||||||
|
departureAddress: this.dialogForm.departureAddress,
|
||||||
|
transitAddress: this.dialogForm.transitAddress,
|
||||||
|
arrivalAddress: this.dialogForm.arrivalAddress,
|
||||||
|
taskInfoJson: this.dialogForm.taskInfoJson,
|
||||||
|
});
|
||||||
|
this.routeChangeVisible = false;
|
||||||
|
ElMessage.success('路线变更已保存');
|
||||||
|
} catch (error) {
|
||||||
|
this.restoreRouteChangeRecords();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
async restoreWaybillRows(idsJson, loadingSubNos) {
|
async restoreWaybillRows(idsJson, loadingSubNos) {
|
||||||
const ids = parseJsonArray(idsJson).filter(Boolean);
|
const ids = parseJsonArray(idsJson).filter(Boolean);
|
||||||
if (ids.length) {
|
if (ids.length) {
|
||||||
@@ -1369,6 +1529,10 @@ export default {
|
|||||||
this.candidateLoading = false;
|
this.candidateLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openCandidateSearch() {
|
||||||
|
this.candidateSearchVisible = true;
|
||||||
|
this.loadCandidateWaybills();
|
||||||
|
},
|
||||||
handleCandidateSearch() {
|
handleCandidateSearch() {
|
||||||
this.candidatePage.currentPage = 1;
|
this.candidatePage.currentPage = 1;
|
||||||
this.loadCandidateWaybills();
|
this.loadCandidateWaybills();
|
||||||
@@ -1390,20 +1554,38 @@ export default {
|
|||||||
candidateSelectionChange(rows) {
|
candidateSelectionChange(rows) {
|
||||||
this.candidateSelection = rows || [];
|
this.candidateSelection = rows || [];
|
||||||
},
|
},
|
||||||
addSelectedWaybills() {
|
async addSelectedWaybills() {
|
||||||
const exists = new Set(this.selectedWaybillRows.map(row => String(row.id || row.waybillNo)));
|
this.candidateAdding = true;
|
||||||
const nextRows = [...this.selectedWaybillRows];
|
try {
|
||||||
this.candidateSelection.forEach(row => {
|
const selectedRows = await Promise.all(
|
||||||
const key = String(row.id || row.waybillNo);
|
this.candidateSelection.map(async row => {
|
||||||
if (!exists.has(key)) {
|
if (!row.id) return row;
|
||||||
nextRows.push(row);
|
try {
|
||||||
exists.add(key);
|
const res = await getWaybillDetail(row.id);
|
||||||
}
|
return { ...row, ...(res.data?.data || res.data || {}) };
|
||||||
});
|
} catch (error) {
|
||||||
this.selectedWaybillRows = nextRows;
|
return row;
|
||||||
this.rebuildSummaryFromWaybills();
|
}
|
||||||
this.$refs.candidateTableRef?.clearSelection?.();
|
})
|
||||||
this.candidateSelection = [];
|
);
|
||||||
|
const exists = new Set(
|
||||||
|
this.selectedWaybillRows.map(row => String(row.id || row.waybillNo))
|
||||||
|
);
|
||||||
|
const nextRows = [...this.selectedWaybillRows];
|
||||||
|
selectedRows.forEach(row => {
|
||||||
|
const key = String(row.id || row.waybillNo);
|
||||||
|
if (!exists.has(key)) {
|
||||||
|
nextRows.push(row);
|
||||||
|
exists.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.selectedWaybillRows = nextRows;
|
||||||
|
this.rebuildSummaryFromWaybills();
|
||||||
|
this.$refs.candidateTableRef?.clearSelection?.();
|
||||||
|
this.candidateSelection = [];
|
||||||
|
} finally {
|
||||||
|
this.candidateAdding = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
removeSelectedWaybill(index) {
|
removeSelectedWaybill(index) {
|
||||||
this.selectedWaybillRows.splice(index, 1);
|
this.selectedWaybillRows.splice(index, 1);
|
||||||
@@ -1444,17 +1626,18 @@ export default {
|
|||||||
},
|
},
|
||||||
buildRouteNodes(rows) {
|
buildRouteNodes(rows) {
|
||||||
const nodeMap = new Map();
|
const nodeMap = new Map();
|
||||||
|
const appendNode = (address, tag) => {
|
||||||
|
if (!address) return;
|
||||||
|
if (!nodeMap.has(address)) nodeMap.set(address, { address, tags: [] });
|
||||||
|
nodeMap.get(address).tags.push(tag);
|
||||||
|
};
|
||||||
rows.forEach((row, rowIndex) => {
|
rows.forEach((row, rowIndex) => {
|
||||||
const departure = row.departureAddress || row.departureName;
|
const departure = row.departureAddress || row.departureName;
|
||||||
|
appendNode(departure, `装${rowIndex + 1}`);
|
||||||
|
});
|
||||||
|
rows.forEach((row, rowIndex) => {
|
||||||
const arrival = row.arrivalAddress || row.arrivalName;
|
const arrival = row.arrivalAddress || row.arrivalName;
|
||||||
if (departure) {
|
appendNode(arrival, `卸${rowIndex + 1}`);
|
||||||
if (!nodeMap.has(departure)) nodeMap.set(departure, { address: departure, tags: [] });
|
|
||||||
nodeMap.get(departure).tags.push(`装${rowIndex + 1}`);
|
|
||||||
}
|
|
||||||
if (arrival) {
|
|
||||||
if (!nodeMap.has(arrival)) nodeMap.set(arrival, { address: arrival, tags: [] });
|
|
||||||
nodeMap.get(arrival).tags.push(`卸${rowIndex + 1}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return [...nodeMap.values()].map((node, index) => ({
|
return [...nodeMap.values()].map((node, index) => ({
|
||||||
...node,
|
...node,
|
||||||
@@ -1489,7 +1672,7 @@ export default {
|
|||||||
},
|
},
|
||||||
buildCargoRows(rows) {
|
buildCargoRows(rows) {
|
||||||
return rows.flatMap(row => {
|
return rows.flatMap(row => {
|
||||||
const goodsRows = parseJsonArray(row.goodsJson);
|
const goodsRows = parseJsonArray(row.goodsList || row.goodsRows || row.goodsJson);
|
||||||
if (goodsRows.length) {
|
if (goodsRows.length) {
|
||||||
return goodsRows.map(item => this.normalizeCargoRow(row, item));
|
return goodsRows.map(item => this.normalizeCargoRow(row, item));
|
||||||
}
|
}
|
||||||
@@ -1509,6 +1692,9 @@ export default {
|
|||||||
unitPrice: item.unitPrice || waybill.unitPrice || '',
|
unitPrice: item.unitPrice || waybill.unitPrice || '',
|
||||||
materialCode: item.materialCode || '',
|
materialCode: item.materialCode || '',
|
||||||
equipmentCode: item.equipmentCode || '',
|
equipmentCode: item.equipmentCode || '',
|
||||||
|
brand: item.brand || item.brandName || waybill.brand || '',
|
||||||
|
specificationModel:
|
||||||
|
item.specificationModel || item.specModel || item.specification || waybill.specificationModel || '',
|
||||||
remark: item.remark || '',
|
remark: item.remark || '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -1694,9 +1880,6 @@ export default {
|
|||||||
label: item.dictValue || item.label || item.name,
|
label: item.dictValue || item.label || item.name,
|
||||||
value: item.dictKey || item.value || item.dictValue || item.name,
|
value: item.dictKey || item.value || item.dictValue || item.name,
|
||||||
}));
|
}));
|
||||||
if (!this.transportTypeOptions.length) {
|
|
||||||
this.transportTypeOptions = [{ label: defaultTransportType, value: defaultTransportType }];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async loadProjectOptions(keyword = '') {
|
async loadProjectOptions(keyword = '') {
|
||||||
this.projectLoading = true;
|
this.projectLoading = true;
|
||||||
@@ -1710,7 +1893,7 @@ export default {
|
|||||||
this.customerLoading = true;
|
this.customerLoading = true;
|
||||||
try {
|
try {
|
||||||
this.customerOptions = unwrapRecords(
|
this.customerOptions = unwrapRecords(
|
||||||
await getCustomerList(1, 20, { customerName: keyword })
|
await getCustomerList(1, 20, { customerName: keyword, customerType: '承运商' })
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.customerLoading = false;
|
this.customerLoading = false;
|
||||||
@@ -1892,10 +2075,17 @@ export default {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-manage-dialog__waybill-route-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 2fr) minmax(320px, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
align-items: start;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.loading-manage-dialog__candidate-actions,
|
.loading-manage-dialog__candidate-actions,
|
||||||
.loading-manage-dialog__collapsed-search,
|
.loading-manage-dialog__collapsed-search,
|
||||||
.loading-manage-dialog__table-toolbar,
|
.loading-manage-dialog__table-toolbar,
|
||||||
.loading-manage-dialog__pagination,
|
|
||||||
.loading-manage-dialog__footer {
|
.loading-manage-dialog__footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1908,16 +2098,18 @@ export default {
|
|||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-manage-dialog__pagination {
|
|
||||||
padding: 10px 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-manage-dialog__route-list {
|
.loading-manage-dialog__route-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.loading-manage-dialog__waybill-route-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.loading-manage-dialog__route-item {
|
.loading-manage-dialog__route-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 28px 36px minmax(0, 1fr) auto;
|
grid-template-columns: 28px 36px minmax(0, 1fr) auto;
|
||||||
@@ -1998,4 +2190,33 @@ export default {
|
|||||||
--el-table-border-color: #eff1f7;
|
--el-table-border-color: #eff1f7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
color: #303133;
|
||||||
|
.loading-detail__summary { display: flex; align-items: center; flex-wrap: wrap; gap: 24px; padding: 4px 0 16px; }
|
||||||
|
.loading-detail__heading { font-size: 20px; font-weight: 600; margin-right: 8px; }
|
||||||
|
.loading-detail__route-title { display: inline-flex; align-items: center; gap: 16px; }
|
||||||
|
.loading-detail__label { color: #909399; margin-right: 10px; }
|
||||||
|
.loading-detail__task-row { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding-bottom: 4px; }
|
||||||
|
.loading-detail__field { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
|
||||||
|
.loading-detail__field .loading-detail__label { margin-right: 0; }
|
||||||
|
.loading-detail__value { display: flex; flex-wrap: wrap; gap: 8px; text-align: left; }
|
||||||
|
.loading-detail__steps { display: flex; align-items: flex-start; overflow-x: auto; padding: 8px 4px 16px; }
|
||||||
|
.loading-detail__step { position: relative; min-width: 190px; flex: 1; text-align: center; }
|
||||||
|
.loading-detail__step:not(:last-child)::after { position: absolute; z-index: 0; top: 16px; left: calc(50% + 25px); width: calc(100% - 50px); height: 2px; content: ''; background: #606266; }
|
||||||
|
.loading-detail__step-line { display: flex; align-items: center; justify-content: center; }
|
||||||
|
.loading-detail__step-dot { position: relative; z-index: 1; display: inline-flex; width: 34px; height: 34px; align-items: center; justify-content: center; color: #fff; border-radius: 4px; font-weight: 600; }
|
||||||
|
.loading-detail__step-dot.start { background: #409eff; }.loading-detail__step-dot.middle { background: #67c23a; }.loading-detail__step-dot.end { background: #e6a23c; }
|
||||||
|
.loading-detail__step-address { margin-top: 8px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.loading-detail__step-tags { display: flex; justify-content: center; gap: 4px; margin-top: 6px; }
|
||||||
|
.loading-detail__bottom-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||||
|
.loading-detail__track-actions { display: flex; gap: 8px; }
|
||||||
|
.loading-detail__map { width: 100%; min-height: 260px; border: 1px solid #eff1f7; }
|
||||||
|
}
|
||||||
|
.loading-route-change-dialog { .loading-route-change-dialog__original { color: #a8abb2; line-height: 20px; margin-bottom: 4px; }.loading-route-change-dialog__location { cursor: pointer; color: #909399; }.loading-route-change-dialog__hint { color: #909399; }.loading-route-change-dialog__route-list { display: flex; flex-direction: column; gap: 8px; }.loading-route-change-dialog__route-item { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto 24px; align-items: center; gap: 12px; padding: 10px 12px; background: #f5f6f7; cursor: move; }.loading-route-change-dialog__route-type { width: 30px; height: 30px; border-radius: 50%; background: #dcdfe6; text-align: center; line-height: 30px; }.loading-route-change-dialog__tags { display: flex; gap: 4px; }.loading-route-change-dialog__record-content { white-space: normal; overflow-wrap: anywhere; line-height: 22px; } }
|
||||||
|
.loading-common-address-dialog__toolbar { display: flex; gap: 8px; margin-bottom: 12px; }.loading-common-address-dialog__toolbar .el-input { flex: 1; }
|
||||||
|
@media (max-width: 900px) { .loading-detail__task-row, .loading-detail__bottom-grid { grid-template-columns: 1fr; } }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<business-crud-page :api="api" :config="config" :crud-option="option" :menu-width="270" />
|
<business-crud-page :api="api" :config="config" :crud-option="option" :menu-width="320" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -147,9 +147,9 @@
|
|||||||
:model="archiveForm"
|
:model="archiveForm"
|
||||||
:rules="formRules"
|
:rules="formRules"
|
||||||
label-position="right"
|
label-position="right"
|
||||||
label-width="auto"
|
label-width="calc(6em + 24px)"
|
||||||
:disabled="readonly"
|
:disabled="readonly"
|
||||||
class="archive-form"
|
class="archive-form dialog-form-label-fixed"
|
||||||
>
|
>
|
||||||
<section-card title="工商信息">
|
<section-card title="工商信息">
|
||||||
<el-row :gutter="18">
|
<el-row :gutter="18">
|
||||||
@@ -614,7 +614,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="文件名称" min-width="180">
|
<el-table-column label="文件名称" min-width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span>{{ row.originalName || row.name || '-' }}</span>
|
<el-link type="primary" :disabled="!row.url" @click="previewQualificationFile(row)">{{ row.originalName || row.name || '-' }}</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="附件描述" min-width="220">
|
<el-table-column label="附件描述" min-width="220">
|
||||||
@@ -664,6 +664,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
|
<el-dialog v-model="qualificationDocumentPreviewVisible" :title="qualificationPreviewFile.name || '附件预览'" append-to-body destroy-on-close width="90%" top="4vh">
|
||||||
|
<open-file-viewer v-if="qualificationDocumentPreviewVisible && qualificationPreviewFile.url" :file="qualificationPreviewFile.url" :file-name="qualificationPreviewFile.name" :mime-type="qualificationPreviewFile.mimeType" width="100%" height="72vh" fit="contain" theme="auto" locale="zh-CN" :toolbar="qualificationViewerToolbar" :plugins="qualificationViewerPlugins" @unsupported="handleQualificationPreviewUnsupported" @error="handleQualificationPreviewError" />
|
||||||
|
</el-dialog>
|
||||||
|
<el-image-viewer v-if="qualificationImagePreviewVisible" :url-list="qualificationImagePreviewUrls" :initial-index="qualificationImagePreviewIndex" @close="qualificationImagePreviewVisible = false" />
|
||||||
|
|
||||||
<section-card title="变更记录">
|
<section-card title="变更记录">
|
||||||
<el-table :data="changeRecordPage.records" border>
|
<el-table :data="changeRecordPage.records" border>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -1230,6 +1235,11 @@ import { getToken } from '@/utils/auth';
|
|||||||
import { getUploadHeaders } from '@/utils/upload';
|
import { getUploadHeaders } from '@/utils/upload';
|
||||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||||
import { ArrowRight } from '@element-plus/icons-vue';
|
import { ArrowRight } from '@element-plus/icons-vue';
|
||||||
|
import { ElImageViewer } from 'element-plus';
|
||||||
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
|
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
|
||||||
|
import '@open-file-viewer/core/style.css';
|
||||||
|
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
@@ -1241,10 +1251,19 @@ const createTimeRangeMap = {
|
|||||||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||||||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||||
let amapLoader;
|
let amapLoader;
|
||||||
|
const qualificationViewerPlugins = [
|
||||||
|
imagePlugin(),
|
||||||
|
pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }),
|
||||||
|
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||||||
|
textPlugin(),
|
||||||
|
fallbackPlugin(),
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
|
ElImageViewer,
|
||||||
|
OpenFileViewer,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const nonNegativeLabelMap = {
|
const nonNegativeLabelMap = {
|
||||||
@@ -1363,6 +1382,19 @@ export default {
|
|||||||
qualificationFiles: [],
|
qualificationFiles: [],
|
||||||
qualificationUploadFiles: [],
|
qualificationUploadFiles: [],
|
||||||
selectedQualificationFiles: [],
|
selectedQualificationFiles: [],
|
||||||
|
qualificationImagePreviewVisible: false,
|
||||||
|
qualificationImagePreviewUrls: [],
|
||||||
|
qualificationImagePreviewIndex: 0,
|
||||||
|
qualificationDocumentPreviewVisible: false,
|
||||||
|
qualificationPreviewFile: {},
|
||||||
|
qualificationViewerPlugins,
|
||||||
|
qualificationViewerToolbar: {
|
||||||
|
download: true,
|
||||||
|
fullscreen: true,
|
||||||
|
print: true,
|
||||||
|
rotate: true,
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
deptTree: [],
|
deptTree: [],
|
||||||
deptOptions: [],
|
deptOptions: [],
|
||||||
regionOptions: [],
|
regionOptions: [],
|
||||||
@@ -2748,10 +2780,53 @@ export default {
|
|||||||
files: [file],
|
files: [file],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getQualificationFileUrl(file = {}) {
|
||||||
|
return file.url || file.link || file.domain || '';
|
||||||
|
},
|
||||||
|
getQualificationFileExtension(file = {}) {
|
||||||
|
const name = String(file.originalName || file.name || this.getQualificationFileUrl(file) || '')
|
||||||
|
.split('?')[0];
|
||||||
|
const index = name.lastIndexOf('.');
|
||||||
|
return index > -1 ? name.slice(index + 1).toLowerCase() : '';
|
||||||
|
},
|
||||||
|
isQualificationImage(file) {
|
||||||
|
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(
|
||||||
|
this.getQualificationFileExtension(file)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
previewQualificationFile(file) {
|
||||||
|
const url = this.getQualificationFileUrl(file);
|
||||||
|
if (!url) {
|
||||||
|
this.$message.warning('附件地址为空,无法预览');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isQualificationImage(file)) {
|
||||||
|
this.qualificationImagePreviewUrls = this.qualificationFiles
|
||||||
|
.filter(item => this.isQualificationImage(item) && this.getQualificationFileUrl(item))
|
||||||
|
.map(item => this.getQualificationFileUrl(item));
|
||||||
|
this.qualificationImagePreviewIndex = Math.max(
|
||||||
|
this.qualificationImagePreviewUrls.indexOf(url),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
this.qualificationImagePreviewVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.qualificationPreviewFile = {
|
||||||
|
name: file.originalName || file.name || '附件',
|
||||||
|
url,
|
||||||
|
mimeType: file.mimeType || file.contentType || '',
|
||||||
|
};
|
||||||
|
this.qualificationDocumentPreviewVisible = true;
|
||||||
|
},
|
||||||
|
handleQualificationPreviewUnsupported() {
|
||||||
|
this.$message.warning('当前文件暂不支持在线预览');
|
||||||
|
},
|
||||||
|
handleQualificationPreviewError() {
|
||||||
|
this.$message.error('附件预览失败');
|
||||||
|
},
|
||||||
resolveQualificationType(fileName) {
|
resolveQualificationType(fileName) {
|
||||||
const name = String(fileName || '').toLocaleLowerCase();
|
const name = String(fileName || '').toLocaleLowerCase();
|
||||||
return (
|
const matchedType = [...this.qualificationTypeOptions]
|
||||||
[...this.qualificationTypeOptions]
|
|
||||||
.filter(item => item?.label || item?.value)
|
.filter(item => item?.label || item?.value)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
@@ -2760,8 +2835,14 @@ export default {
|
|||||||
.find(item => {
|
.find(item => {
|
||||||
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
|
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
|
||||||
return typeText && name.includes(typeText);
|
return typeText && name.includes(typeText);
|
||||||
})?.value || ''
|
});
|
||||||
|
if (matchedType?.value) return matchedType.value;
|
||||||
|
const otherType = this.qualificationTypeOptions.find(item =>
|
||||||
|
String(item.label || item.value || '').includes('其他')
|
||||||
);
|
);
|
||||||
|
if (otherType?.value) return otherType.value;
|
||||||
|
this.qualificationTypeOptions.push({ label: '其他附件', value: '其他附件' });
|
||||||
|
return '其他附件';
|
||||||
},
|
},
|
||||||
downloadAttachments() {
|
downloadAttachments() {
|
||||||
const source = this.selectedQualificationFiles.length
|
const source = this.selectedQualificationFiles.length
|
||||||
@@ -3617,12 +3698,26 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.archive-form {
|
.archive-form {
|
||||||
:deep(.el-input-number),
|
:deep(.el-form-item__label) {
|
||||||
:deep(.el-select),
|
flex: 0 0 calc(6em + 24px) !important;
|
||||||
:deep(.el-cascader),
|
width: calc(6em + 24px) !important;
|
||||||
:deep(.el-date-editor) {
|
height: auto;
|
||||||
width: 100%;
|
min-height: 32px;
|
||||||
|
white-space: normal !important;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__content > .el-input),
|
||||||
|
:deep(.el-form-item__content > .el-select),
|
||||||
|
:deep(.el-form-item__content > .el-cascader),
|
||||||
|
:deep(.el-form-item__content > .el-input-number),
|
||||||
|
:deep(.el-form-item__content > .el-date-editor) {
|
||||||
|
width: 220px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
business-term-field,
|
business-term-field,
|
||||||
@@ -3635,7 +3730,8 @@ business-term-field,
|
|||||||
|
|
||||||
.business-term-field {
|
.business-term-field {
|
||||||
:deep(.el-date-editor) {
|
:deep(.el-date-editor) {
|
||||||
flex: 1;
|
flex: 0 0 220px;
|
||||||
|
width: 220px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3646,14 +3742,15 @@ business-term-field,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.archive-form__address {
|
.archive-form__address {
|
||||||
.el-input,
|
:deep(.el-input),
|
||||||
:deep(.el-cascader) {
|
:deep(.el-cascader) {
|
||||||
flex: 1;
|
flex: 0 0 220px;
|
||||||
|
width: 220px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-cascader) {
|
:deep(.el-cascader) {
|
||||||
flex: 0 0 36%;
|
flex-basis: 220px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user