调整 iam

This commit is contained in:
2026-09-16 06:30:30 +08:00
parent 6a87066097
commit c1c4fa18c9
26 changed files with 1517 additions and 666 deletions
+215 -111
View File
@@ -1,57 +1,58 @@
<template>
<basic-container class="master-order-page">
<template v-if="mode === 'list'">
<el-form :model="query" class="master-search" label-width="160px" @submit.prevent>
<el-row :gutter="16">
<el-col v-for="field in primaryFields" :key="field.prop" :span="6"
><el-form-item :label="field.label"
><el-input v-model="query[field.prop]" placeholder="请输入" /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="状态"
><el-select v-model="query.businessStatus" placeholder="全部"
><el-option label="全部" value="" /><el-option
<div class="master-order-page__search">
<el-form :model="query" label-position="right" label-width="88px" @submit.prevent>
<div class="master-order-page__search-grid">
<el-form-item v-for="field in primaryFields" :key="field.prop" :label="field.label">
<el-input v-model="query[field.prop]" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="query.businessStatus" clearable placeholder="全部">
<el-option label="全部" value="" />
<el-option
v-for="item in statuses"
:key="item.value"
:label="item.label"
:value="item.value" /></el-select></el-form-item
></el-col>
<template v-if="searchExpanded">
<el-col v-for="field in secondaryFields" :key="field.prop" :span="6"
><el-form-item :label="field.label"
><el-input v-model="query[field.prop]" placeholder="请输入" /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="计划开始日期"
><el-date-picker
:value="item.value"
/>
</el-select>
</el-form-item>
<template v-if="searchExpanded">
<el-form-item v-for="field in secondaryFields" :key="field.prop" :label="field.label">
<el-input v-model="query[field.prop]" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="计划开始日期">
<el-date-picker
v-model="query.planStartRange"
type="datetimerange"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="请选择"
end-placeholder="请选择" /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="计划结束日期"
><el-date-picker
end-placeholder="请选择"
/>
</el-form-item>
<el-form-item label="计划结束日期">
<el-date-picker
v-model="query.planEndRange"
type="datetimerange"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="请选择"
end-placeholder="请选择" /></el-form-item
></el-col>
</template>
<el-col :span="24" class="search-actions"
><el-button type="primary" @click="search">查询</el-button
><el-button @click="reset">重置</el-button
><el-link type="primary" @click="searchExpanded = !searchExpanded"
><el-icon><component :is="searchExpanded ? ArrowUp : ArrowDown" /></el-icon
>{{ searchExpanded ? '折叠' : '展开' }}</el-link
></el-col
>
</el-row>
</el-form>
end-placeholder="请选择"
/>
</el-form-item>
</template>
<div class="master-order-page__search-actions">
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
<el-link type="primary" @click="searchExpanded = !searchExpanded">{{
searchExpanded ? '收起' : '展开'
}}</el-link>
</div>
</div>
</el-form>
</div>
<section class="master-list-panel">
<div class="toolbar">
<el-button type="primary" @click="goCreate()">新建多联总单</el-button
@@ -76,7 +77,13 @@
<div class="route-point">
<span :class="['route-node', node.type]">{{ node.text }}</span>
<div class="route-detail">
<el-tooltip placement="top"><template #content>{{ node.name }}</template><strong>{{ node.name }}</strong></el-tooltip>
<el-tooltip
:content="node.address || node.name || '-'"
placement="top"
:show-after="120"
>
<strong>{{ node.name }}</strong>
</el-tooltip>
<span v-for="line in node.lines" :key="line">{{ line }}</span>
</div>
</div>
@@ -125,7 +132,7 @@
/>
</template>
<master-order-dispatch v-else-if="mode === 'dispatch'" :id="routeId" @back="goList" />
<master-order-detail v-else :id="routeId" @back="goList" />
<master-order-detail v-else :id="routeId" />
<el-dialog v-model="confirm.visible" title="提示" width="400px"
><span>{{ confirm.message }}</span
><template #footer
@@ -138,7 +145,6 @@
<script>
import * as api from '@/api/business/master-order';
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import MasterOrderEditor from './components/master-order-editor.vue';
import MasterOrderDispatch from './components/master-order-dispatch.vue';
import MasterOrderDetail from './components/master-order-detail.vue';
@@ -147,8 +153,6 @@ export default {
components: { MasterOrderEditor, MasterOrderDispatch, MasterOrderDetail },
data() {
return {
ArrowDown,
ArrowUp,
searchExpanded: false,
loading: false,
records: [],
@@ -303,7 +307,7 @@ export default {
},
timeRange(row) {
return row.planStartTime && row.planEndTime
? `${String(row.planStartTime).slice(0, 16)} - ${String(row.planEndTime).slice(0, 16)}`
? `${String(row.planStartTime).slice(0, 10)} - ${String(row.planEndTime).slice(0, 10)}`
: '-';
},
goodsSummary(row) {
@@ -334,6 +338,19 @@ export default {
const value = String(type || '').trim().toLowerCase();
return value === 'road' || value.includes('公路');
},
isStationLikeName(value) {
const text = String(value || '').trim();
if (!text) return false;
return /(?:火车站|高铁站|客运站|货运站|港口|码头|机场|空港|航站楼)$/.test(text) || /(?<!市|州|盟|区|县|旗)站$/.test(text);
},
isNonRoadLocation({ name, siteCode, transportType, nextTransportType } = {}) {
const code = String(siteCode || '').trim();
if (code && code !== '/') return true;
if (this.isStationLikeName(name)) return true;
if (transportType && !this.isRoadTransportType(transportType)) return true;
if (nextTransportType && !this.isRoadTransportType(nextTransportType)) return true;
return false;
},
formatRoadAddress(value) {
const text = String(value || '').replace(/\s+/g, '');
if (!text) return '-';
@@ -355,10 +372,16 @@ export default {
routeNodeName(value, address, transportType, region = {}) {
const text = String(value || '').trim();
if (!text) return '-';
const siteCode = String(region.siteCode || '').trim();
// 运输方式切换不应改变地址本身的展示模式:有站点编码时显示站点名称,
// 没有站点编码的区域地址统一按“市 区县”格式展示。
if (siteCode && siteCode !== '/') return text;
if (
this.isNonRoadLocation({
name: text,
siteCode: region.siteCode,
transportType,
nextTransportType: region.nextTransportType,
})
) {
return text;
}
// 优先从完整的地址名称/详细地址解析,避免后端只返回“梅州”等不完整的市名称时
// 提前返回城市,导致区县被遗漏。
const parsedText = this.formatRoadAddress(text);
@@ -383,86 +406,149 @@ export default {
const source = /省|自治区|特别行政区|市|州|盟/.test(text) ? text : address || text;
const formatted = this.formatRoadAddress(source);
if (/(?:区|县|旗)$/.test(text) && !formatted.includes(text)) {
const city = formatted.split(' ')[0];
return city && city !== formatted ? `${city} ${text}` : formatted;
const cityName = formatted.split(' ')[0];
return cityName && cityName !== formatted ? `${cityName} ${text}` : formatted;
}
return formatted;
},
resolveRouteNodeLocation({
name,
address,
transportType,
nextTransportType,
cityName,
districtName,
siteCode,
} = {}) {
const rawName = String(name || '').trim();
const rawAddress = String(address || '').trim();
const regionText = [cityName, districtName].filter(Boolean).join(' ');
const nonRoad = this.isNonRoadLocation({
name: rawName,
siteCode,
transportType,
nextTransportType,
});
if (nonRoad) {
// 非公路:展示站点名称,悬停展示详细地址。
// 兼容历史数据把站点名称写在 address、区域信息写在 name 的情况。
if (this.isStationLikeName(rawAddress) && !this.isStationLikeName(rawName)) {
return {
name: rawAddress,
address: rawName || regionText || rawAddress,
};
}
const displayName = rawName || rawAddress || '-';
const tipAddress =
(rawAddress && rawAddress !== displayName ? rawAddress : '') ||
regionText ||
rawAddress ||
displayName;
return { name: displayName, address: tipAddress };
}
return {
name: this.routeNodeName(rawName || rawAddress, rawAddress, transportType, {
cityName,
districtName,
siteCode,
nextTransportType,
}),
address: rawAddress || rawName || regionText || '-',
};
},
routeNodes(row = {}) {
const routes = row.routeProgress || [];
const total = this.routeNumber(row.totalQuantity);
const firstTransportType = routes[0]?.transportType || row.routes?.[0]?.transportType || row.transportType || '';
if (!routes.length) {
const start = this.resolveRouteNodeLocation({
name: row.departureName || row.departureAddress,
address: row.departureAddress,
transportType: firstTransportType,
cityName: row.departureCityName,
districtName: row.departureDistrictName,
siteCode: row.departureSiteCode,
});
const end = this.resolveRouteNodeLocation({
name: row.arrivalName || row.arrivalAddress,
address: row.arrivalAddress,
transportType: row.finalTransportType || firstTransportType,
cityName: row.arrivalCityName,
districtName: row.arrivalDistrictName,
siteCode: row.arrivalSiteCode,
});
return [
{
key: 'start',
type: 'start',
text: '起',
name: this.routeNodeName(
row.departureName || row.departureAddress,
row.departureAddress,
firstTransportType,
{
cityName: row.departureCityName,
districtName: row.departureDistrictName,
siteCode: row.departureSiteCode,
}
),
...start,
lines: [`已调度0/${total}`],
},
{
key: 'end',
type: 'end',
text: '终',
name: this.routeNodeName(
row.arrivalName || row.arrivalAddress,
row.arrivalAddress,
row.finalTransportType || firstTransportType,
{
cityName: row.arrivalCityName,
districtName: row.arrivalDistrictName,
siteCode: row.arrivalSiteCode,
}
),
...end,
lines: [`到达0/${total}`],
},
];
}
const start = this.resolveRouteNodeLocation({
name: row.departureName || row.departureAddress,
address: row.departureAddress,
transportType: firstTransportType,
nextTransportType: firstTransportType,
cityName: row.departureCityName,
districtName: row.departureDistrictName,
siteCode: row.departureSiteCode,
});
return [
{
key: 'start',
type: 'start',
text: '起',
name: this.routeNodeName(
row.departureName || row.departureAddress,
row.departureAddress,
firstTransportType,
{
cityName: row.departureCityName,
districtName: row.departureDistrictName,
siteCode: row.departureSiteCode,
}
),
...start,
lines: [`已调度${this.routeNumber(routes[0]?.dispatchedQuantity)}/${total}`],
},
...routes.map((route, index) => {
const isEnd = index === routes.length - 1;
const arrived = this.routeNumber(this.routeArrivedQuantity(route));
const dispatched = this.routeNumber(routes[index + 1]?.dispatchedQuantity);
const address =
route.arrivalAddress ||
route.departureAddress ||
(isEnd ? row.arrivalAddress : '') ||
'';
const location = this.resolveRouteNodeLocation({
name:
route.arrivalName ||
route.departureName ||
(isEnd ? row.arrivalName : '') ||
'',
address,
transportType: route.transportType,
nextTransportType: isEnd
? row.finalTransportType || route.transportType
: routes[index + 1]?.transportType || '',
cityName:
route.arrivalCityName ||
route.departureCityName ||
(isEnd ? row.arrivalCityName : ''),
districtName:
route.arrivalDistrictName ||
route.departureDistrictName ||
(isEnd ? row.arrivalDistrictName : ''),
siteCode:
route.arrivalSiteCode ||
route.departureSiteCode ||
(isEnd ? row.arrivalSiteCode : ''),
});
return {
key: route.segmentNo || `route-${index}`,
type: isEnd ? 'end' : 'middle',
text: isEnd ? '终' : '经',
name: this.routeNodeName(
route.arrivalName || route.departureName || row.arrivalName,
route.arrivalAddress || route.departureAddress,
route.transportType,
{
cityName: route.arrivalCityName || route.departureCityName,
districtName: route.arrivalDistrictName || route.departureDistrictName,
siteCode: route.arrivalSiteCode || route.departureSiteCode,
}
),
...location,
lines: isEnd
? [`到达${arrived}/${total}`]
: [`到达 ${arrived}/${total}`, `已调度 ${dispatched}/到达${arrived}/${total}`],
@@ -494,31 +580,43 @@ export default {
</script>
<style scoped lang="scss">
.master-search {
margin-bottom: 12px;
padding: 12px 12px 4px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
.el-form-item {
.master-order-page {
&__search {
padding: 12px 12px 4px;
margin-bottom: 8px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
&__search-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px 24px;
}
&__search-actions {
display: flex;
grid-column: 1 / -1;
justify-content: flex-end;
align-items: center;
gap: 8px;
min-height: 32px;
}
:deep(.el-form-item) {
margin-bottom: 8px;
}
.el-input,
.el-select,
.el-date-editor {
width: 100%;
}
:deep(.el-form-item__label) {
white-space: nowrap;
}
.search-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
min-height: 40px;
.el-icon {
margin-right: 4px;
}
:deep(.el-input),
:deep(.el-select),
:deep(.el-date-editor.el-input),
:deep(.el-date-editor.el-input__wrapper),
:deep(.el-date-editor--datetimerange) {
width: 100%;
}
}
.master-list-panel {
@@ -612,10 +710,16 @@ export default {
line-height: 1.8;
white-space: nowrap;
strong {
display: inline-block;
max-width: 150px;
overflow: hidden;
font-size: 14px;
font-weight: 600;
line-height: 1.4;
max-width: 180px;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
cursor: default;
}
}
.route-connector {