✨ 完善配载确认、承运商组织过滤司机与地址展示
配载草稿确认生成正式单并优化详情/列表地址市区展示;运单调度等按承运商联系人所属组织筛选司机车辆;统一地图搜索结果与登录默认账号清理。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,12 +10,8 @@
|
|||||||
<el-button type="primary" :loading="searching" @click="searchKeyword">搜索</el-button>
|
<el-button type="primary" :loading="searching" @click="searchKeyword">搜索</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="address-map-picker__content">
|
<div class="address-map-picker__content">
|
||||||
<map-search-results
|
|
||||||
v-if="searchResults.length"
|
|
||||||
:results="searchResults"
|
|
||||||
@select="selectSearchResult"
|
|
||||||
/>
|
|
||||||
<div ref="map" class="address-map-picker__map"></div>
|
<div ref="map" class="address-map-picker__map"></div>
|
||||||
|
<map-search-results :results="searchResults" @select="selectSearchResult" />
|
||||||
</div>
|
</div>
|
||||||
<div class="address-map-picker__info">{{ status }}</div>
|
<div class="address-map-picker__info">{{ status }}</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -28,6 +24,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||||
|
|
||||||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||||||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||||
let amapLoader;
|
let amapLoader;
|
||||||
@@ -214,19 +212,14 @@ export default {
|
|||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const geocodes = result.geocodes || (result.location ? [result] : []);
|
const geocodes = result.geocodes || (result.location ? [result] : []);
|
||||||
this.searchResults = geocodes.map((item, index) => ({
|
this.searchResults = normalizeMapSearchResults(geocodes, keyword);
|
||||||
id: item.id || index,
|
const point = this.searchResults[0]?.location || result.location;
|
||||||
name: item.formattedAddress || item.address || keyword,
|
|
||||||
address: item.formattedAddress || item.address || keyword,
|
|
||||||
location: item.location,
|
|
||||||
}));
|
|
||||||
const point = geocodes[0]?.location || result.location;
|
|
||||||
if (!point) {
|
if (!point) {
|
||||||
throw new Error('未找到匹配地址');
|
throw new Error('未找到匹配地址');
|
||||||
}
|
}
|
||||||
return this.$nextTick().then(() => {
|
return this.$nextTick().then(() => {
|
||||||
this.amap?.resize();
|
this.amap?.resize();
|
||||||
this.pickPoint(point, geocodes[0]?.formattedAddress || keyword);
|
this.pickPoint(point, this.searchResults[0]?.address || keyword);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -320,7 +313,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__map {
|
&__map {
|
||||||
flex: 1 1 auto;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 420px;
|
height: 420px;
|
||||||
@@ -328,9 +320,19 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
display: flex;
|
position: relative;
|
||||||
gap: 12px;
|
display: block;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
.address-map-picker__map {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.map-search-results) {
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
|
|||||||
@@ -1,20 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="results.length" class="map-search-results">
|
<div v-if="results.length" class="map-search-results">
|
||||||
<div class="map-search-results__title">搜索结果</div>
|
<div class="map-search-results__list">
|
||||||
<el-empty v-if="!results.length" description="暂无搜索结果" :image-size="60" />
|
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in results"
|
v-for="(item, index) in pageResults"
|
||||||
:key="item.id || index"
|
:key="item.id || `${currentPage}-${index}`"
|
||||||
class="map-search-results__item"
|
class="map-search-results__item"
|
||||||
@click="$emit('select', item)"
|
:class="{ 'is-active': isActive(item, index) }"
|
||||||
|
@click="handleSelect(item, index)"
|
||||||
>
|
>
|
||||||
<div class="map-search-results__name">{{ item.name || item.address || '未命名地址' }}</div>
|
<img
|
||||||
<div class="map-search-results__address">{{ item.address || item.name || '-' }}</div>
|
v-if="item.photo || item.image"
|
||||||
|
class="map-search-results__thumb"
|
||||||
|
:src="item.photo || item.image"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<div class="map-search-results__body">
|
||||||
|
<div class="map-search-results__name">
|
||||||
|
{{ item.name || item.address || '未命名地址' }}
|
||||||
|
</div>
|
||||||
|
<div class="map-search-results__address">
|
||||||
|
地址:{{ item.address || item.name || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="totalPages > 1" class="map-search-results__pager">
|
||||||
|
<button
|
||||||
|
v-for="page in visiblePages"
|
||||||
|
:key="page"
|
||||||
|
type="button"
|
||||||
|
class="map-search-results__page"
|
||||||
|
:class="{ 'is-active': page === currentPage }"
|
||||||
|
@click="currentPage = page"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="map-search-results__page map-search-results__page--next"
|
||||||
|
:disabled="currentPage >= totalPages"
|
||||||
|
@click="goNext"
|
||||||
|
>
|
||||||
|
下一页
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const PAGE_SIZE = 5;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
results: {
|
results: {
|
||||||
@@ -23,43 +58,167 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ['select'],
|
emits: ['select'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentPage: 1,
|
||||||
|
activeKey: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
totalPages() {
|
||||||
|
return Math.max(1, Math.ceil((this.results || []).length / PAGE_SIZE));
|
||||||
|
},
|
||||||
|
pageResults() {
|
||||||
|
const start = (this.currentPage - 1) * PAGE_SIZE;
|
||||||
|
return (this.results || []).slice(start, start + PAGE_SIZE);
|
||||||
|
},
|
||||||
|
visiblePages() {
|
||||||
|
const maxButtons = 3;
|
||||||
|
let start = Math.max(1, this.currentPage - 1);
|
||||||
|
let end = Math.min(this.totalPages, start + maxButtons - 1);
|
||||||
|
start = Math.max(1, end - maxButtons + 1);
|
||||||
|
const pages = [];
|
||||||
|
for (let page = start; page <= end; page += 1) {
|
||||||
|
pages.push(page);
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
results() {
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.activeKey = '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resultKey(item, index) {
|
||||||
|
return item?.id ?? `${this.currentPage}-${index}`;
|
||||||
|
},
|
||||||
|
isActive(item, index) {
|
||||||
|
return this.activeKey === this.resultKey(item, index);
|
||||||
|
},
|
||||||
|
handleSelect(item, index) {
|
||||||
|
this.activeKey = this.resultKey(item, index);
|
||||||
|
this.$emit('select', item);
|
||||||
|
},
|
||||||
|
goNext() {
|
||||||
|
if (this.currentPage < this.totalPages) {
|
||||||
|
this.currentPage += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.map-search-results {
|
.map-search-results {
|
||||||
width: 260px;
|
position: absolute;
|
||||||
height: 420px;
|
top: 0;
|
||||||
overflow-y: auto;
|
left: 0;
|
||||||
border: 1px solid #eff1f7;
|
z-index: 2000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 360px;
|
||||||
|
max-height: 360px;
|
||||||
|
overflow: hidden;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
flex: 0 0 260px;
|
border: 1px solid #dcdfe6;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
&__title {
|
&__list {
|
||||||
padding: 10px 12px;
|
flex: 1 1 auto;
|
||||||
border-bottom: 1px solid #eff1f7;
|
min-height: 0;
|
||||||
font-weight: 600;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: flex-start;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-bottom: 1px solid #f5f5f5;
|
border-bottom: 1px solid #ebeef5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item:hover {
|
&__item:hover,
|
||||||
background: #f5f9ff;
|
&__item.is-active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__thumb {
|
||||||
|
flex: 0 0 48px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
|
overflow: hidden;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__address {
|
&__address {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__pager {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__page {
|
||||||
|
min-width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 8px;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 26px;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
color: #409eff;
|
||||||
|
border-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
color: #409eff;
|
||||||
|
border-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: #c0c4cc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--next {
|
||||||
|
min-width: 56px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -543,7 +543,8 @@ export const option = {
|
|||||||
prop: 'projectName',
|
prop: 'projectName',
|
||||||
formslot: true,
|
formslot: true,
|
||||||
search: true,
|
search: true,
|
||||||
searchLabel: '项目',
|
searchLabel: '项目名称',
|
||||||
|
searchPlaceholder: '请选择或输入',
|
||||||
searchOrder: 22,
|
searchOrder: 22,
|
||||||
span: 6,
|
span: 6,
|
||||||
order: 890,
|
order: 890,
|
||||||
@@ -555,6 +556,7 @@ export const option = {
|
|||||||
prop: 'customerName',
|
prop: 'customerName',
|
||||||
search: true,
|
search: true,
|
||||||
searchLabel: '客户名称',
|
searchLabel: '客户名称',
|
||||||
|
searchPlaceholder: '请选择或输入',
|
||||||
searchOrder: 21,
|
searchOrder: 21,
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
addDisplay: false,
|
addDisplay: false,
|
||||||
@@ -576,6 +578,7 @@ export const option = {
|
|||||||
prop: 'driverName',
|
prop: 'driverName',
|
||||||
search: true,
|
search: true,
|
||||||
searchLabel: '司机名称',
|
searchLabel: '司机名称',
|
||||||
|
searchPlaceholder: '请选择或输入',
|
||||||
searchOrder: 14,
|
searchOrder: 14,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
display: false,
|
display: false,
|
||||||
@@ -626,6 +629,7 @@ export const option = {
|
|||||||
prop: 'carrierName',
|
prop: 'carrierName',
|
||||||
search: true,
|
search: true,
|
||||||
searchLabel: '承运商名称',
|
searchLabel: '承运商名称',
|
||||||
|
searchPlaceholder: '请选择或输入',
|
||||||
searchOrder: 15,
|
searchOrder: 15,
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
display: false,
|
display: false,
|
||||||
@@ -729,6 +733,7 @@ export const option = {
|
|||||||
prop: 'planName',
|
prop: 'planName',
|
||||||
formslot: true,
|
formslot: true,
|
||||||
search: true,
|
search: true,
|
||||||
|
searchPlaceholder: '请选择或输入',
|
||||||
searchOrder: 7,
|
searchOrder: 7,
|
||||||
span: 6,
|
span: 6,
|
||||||
order: 870,
|
order: 870,
|
||||||
@@ -738,6 +743,7 @@ export const option = {
|
|||||||
label: '货物类型',
|
label: '货物类型',
|
||||||
prop: 'cargoType',
|
prop: 'cargoType',
|
||||||
search: true,
|
search: true,
|
||||||
|
searchPlaceholder: '请选择或输入',
|
||||||
searchOrder: 18,
|
searchOrder: 18,
|
||||||
formatter: row => formatGoodsField(row, ['cargoType', 'goodsType', 'typeName']),
|
formatter: row => formatGoodsField(row, ['cargoType', 'goodsType', 'typeName']),
|
||||||
minWidth: 130,
|
minWidth: 130,
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: 'admin',
|
username: '',
|
||||||
password: '123456',
|
password: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ export default {
|
|||||||
//角色ID
|
//角色ID
|
||||||
roleId: '',
|
roleId: '',
|
||||||
//用户名
|
//用户名
|
||||||
username: 'admin',
|
username: '',
|
||||||
//密码
|
//密码
|
||||||
password: 'admin',
|
password: '',
|
||||||
//账号类型
|
//账号类型
|
||||||
type: 'account',
|
type: 'account',
|
||||||
//验证码的值
|
//验证码的值
|
||||||
|
|||||||
+10
-3
@@ -150,12 +150,15 @@ a {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
.map-picker-content {
|
.map-picker-content {
|
||||||
display: flex;
|
position: relative;
|
||||||
gap: 12px;
|
display: block;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
> [class$='__map'],
|
> [class$='__map'],
|
||||||
> .address-map {
|
> .address-map {
|
||||||
flex: 1 1 auto;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -165,4 +168,8 @@ a {
|
|||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .map-search-results {
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { getDetail, getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
|
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||||
|
|
||||||
|
const orgNameCache = new Map();
|
||||||
|
|
||||||
|
const extractRecords = res => {
|
||||||
|
const data = res?.data?.data ?? res?.data ?? res;
|
||||||
|
if (Array.isArray(data)) return data;
|
||||||
|
if (Array.isArray(data?.records)) return data.records;
|
||||||
|
if (Array.isArray(data?.data)) return data.data;
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeOrgName = value => String(value || '').trim();
|
||||||
|
|
||||||
|
const uniqueOrgNames = (values = []) => {
|
||||||
|
const seen = new Set();
|
||||||
|
const result = [];
|
||||||
|
values.forEach(value => {
|
||||||
|
const name = normalizeOrgName(value);
|
||||||
|
if (!name || seen.has(name)) return;
|
||||||
|
seen.add(name);
|
||||||
|
result.push(name);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析承运商客商档案上联系人的所属组织(branchName),无联系人时回退客商所属组织。
|
||||||
|
*/
|
||||||
|
export const resolveCarrierOrganizationNames = async ({ carrierId, carrierName } = {}) => {
|
||||||
|
const id = String(carrierId || '').trim();
|
||||||
|
const name = normalizeOrgName(carrierName);
|
||||||
|
const cacheKey = id || name;
|
||||||
|
if (!cacheKey) return [];
|
||||||
|
if (orgNameCache.has(cacheKey)) return orgNameCache.get(cacheKey);
|
||||||
|
|
||||||
|
let detail = null;
|
||||||
|
if (id) {
|
||||||
|
try {
|
||||||
|
const res = await getDetail(id);
|
||||||
|
detail = res?.data?.data || res?.data || null;
|
||||||
|
} catch (error) {
|
||||||
|
detail = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!detail && name) {
|
||||||
|
try {
|
||||||
|
const res = await getCustomerList(1, 20, { fullName: name });
|
||||||
|
const records = extractRecords(res);
|
||||||
|
const matched =
|
||||||
|
records.find(item => normalizeOrgName(item.fullName || item.customerName) === name) ||
|
||||||
|
records[0];
|
||||||
|
if (matched?.id) {
|
||||||
|
const detailRes = await getDetail(matched.id);
|
||||||
|
detail = detailRes?.data?.data || detailRes?.data || matched;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
detail = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contacts = Array.isArray(detail?.contacts) ? detail.contacts : [];
|
||||||
|
const orgNames = uniqueOrgNames([
|
||||||
|
...contacts.map(item => item.branchName),
|
||||||
|
detail?.deptName,
|
||||||
|
detail?.organizationName,
|
||||||
|
]);
|
||||||
|
orgNameCache.set(cacheKey, orgNames);
|
||||||
|
if (id && name) orgNameCache.set(name, orgNames);
|
||||||
|
return orgNames;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearCarrierOrganizationCache = (carrier = {}) => {
|
||||||
|
const id = String(carrier.carrierId || '').trim();
|
||||||
|
const name = normalizeOrgName(carrier.carrierName);
|
||||||
|
if (id) orgNameCache.delete(id);
|
||||||
|
if (name) orgNameCache.delete(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const matchOrganizationName = (value, orgNames = []) => {
|
||||||
|
const text = normalizeOrgName(value);
|
||||||
|
if (!text || !orgNames.length) return false;
|
||||||
|
return orgNames.some(org => text === org || text.includes(org) || org.includes(text));
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByOrganizations = (records = [], orgNames = []) => {
|
||||||
|
if (!orgNames.length) return [];
|
||||||
|
return records.filter(item => matchOrganizationName(item.organizationName, orgNames));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按承运商联系人所属组织筛选司机。
|
||||||
|
* 未选择承运商时返回空列表(需先选承运商)。
|
||||||
|
*/
|
||||||
|
export const fetchDriversByCarrierOrganizations = async (
|
||||||
|
query = {},
|
||||||
|
{ carrierId, carrierName } = {}
|
||||||
|
) => {
|
||||||
|
if (!String(carrierId || '').trim() && !normalizeOrgName(carrierName)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const orgNames = await resolveCarrierOrganizationNames({ carrierId, carrierName });
|
||||||
|
if (!orgNames.length) return [];
|
||||||
|
|
||||||
|
const size = Math.max(Number(query.size) || 50, 50);
|
||||||
|
const params = { ...query };
|
||||||
|
delete params.size;
|
||||||
|
// 单组织时用后端模糊条件缩小范围;多组织再前端精确过滤
|
||||||
|
if (orgNames.length === 1) {
|
||||||
|
params.organizationName = orgNames[0];
|
||||||
|
}
|
||||||
|
const res = await getDriverList(1, Math.min(size * 5, 200), params);
|
||||||
|
return filterByOrganizations(extractRecords(res), orgNames).slice(0, size);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按承运商联系人所属组织筛选车辆。
|
||||||
|
* 未选择承运商时返回空列表。
|
||||||
|
*/
|
||||||
|
export const fetchVehiclesByCarrierOrganizations = async (
|
||||||
|
query = {},
|
||||||
|
{ carrierId, carrierName } = {}
|
||||||
|
) => {
|
||||||
|
if (!String(carrierId || '').trim() && !normalizeOrgName(carrierName)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const orgNames = await resolveCarrierOrganizationNames({ carrierId, carrierName });
|
||||||
|
if (!orgNames.length) return [];
|
||||||
|
|
||||||
|
const size = Math.max(Number(query.size) || 50, 50);
|
||||||
|
const params = { ...query };
|
||||||
|
delete params.size;
|
||||||
|
if (orgNames.length === 1) {
|
||||||
|
params.organizationName = orgNames[0];
|
||||||
|
}
|
||||||
|
const res = await getVehicleList(1, Math.min(size * 5, 200), params);
|
||||||
|
return filterByOrganizations(extractRecords(res), orgNames).slice(0, size);
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 将高德 Geocoder 结果规范化为地图选址浮层列表数据。
|
||||||
|
* @param {Array|Object} geocodesOrResult geocodes 数组,或含 geocodes/location 的结果对象
|
||||||
|
* @param {string} keyword 搜索关键词兜底文案
|
||||||
|
* @returns {Array<{id: string|number, name: string, address: string, location: any, photo: string}>}
|
||||||
|
*/
|
||||||
|
export function normalizeMapSearchResults(geocodesOrResult, keyword = '') {
|
||||||
|
const fallback = String(keyword || '').trim();
|
||||||
|
let list = [];
|
||||||
|
if (Array.isArray(geocodesOrResult)) {
|
||||||
|
list = geocodesOrResult;
|
||||||
|
} else if (geocodesOrResult && typeof geocodesOrResult === 'object') {
|
||||||
|
if (Array.isArray(geocodesOrResult.geocodes)) {
|
||||||
|
list = geocodesOrResult.geocodes;
|
||||||
|
} else if (geocodesOrResult.location) {
|
||||||
|
list = [geocodesOrResult];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.map((item, index) => {
|
||||||
|
const buildingName = pickNamedField(item?.building);
|
||||||
|
const neighborhoodName = pickNamedField(item?.neighborhood);
|
||||||
|
const address = item?.formattedAddress || item?.address || fallback || '-';
|
||||||
|
return {
|
||||||
|
id: item?.id || index,
|
||||||
|
name: buildingName || neighborhoodName || address,
|
||||||
|
address,
|
||||||
|
location: item?.location,
|
||||||
|
photo: item?.photo || item?.image || '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickNamedField(value) {
|
||||||
|
if (!value) return '';
|
||||||
|
if (typeof value === 'string') return value.trim();
|
||||||
|
if (typeof value === 'object' && value.name) return String(value.name).trim();
|
||||||
|
return '';
|
||||||
|
}
|
||||||
@@ -233,8 +233,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<map-search-results :results="mapSearchResults" @select="selectMapSearchResult" />
|
|
||||||
<div ref="amap" class="common-address-page__map"></div>
|
<div ref="amap" class="common-address-page__map"></div>
|
||||||
|
<map-search-results :results="mapSearchResults" @select="selectMapSearchResult" />
|
||||||
</div>
|
</div>
|
||||||
<div class="common-address-page__map-info">
|
<div class="common-address-page__map-info">
|
||||||
<span>{{ mapStatus }}</span>
|
<span>{{ mapStatus }}</span>
|
||||||
@@ -270,6 +270,7 @@ import { createOption } from '@/option/base/common-address';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
|
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
@@ -1117,12 +1118,7 @@ export default {
|
|||||||
this.ensureAmapGeocoder()
|
this.ensureAmapGeocoder()
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.mapSearchResults = (result.geocodes || []).map((item, index) => ({
|
this.mapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||||
id: item.id || index,
|
|
||||||
name: item.formattedAddress || keyword,
|
|
||||||
address: item.formattedAddress || keyword,
|
|
||||||
location: item.location,
|
|
||||||
}));
|
|
||||||
const point = this.resolveMapPoint(result);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.mapStatus = '未找到匹配地址';
|
this.mapStatus = '未找到匹配地址';
|
||||||
|
|||||||
@@ -289,8 +289,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<map-search-results :results="routeMapSearchResults" @select="selectRouteMapSearchResult" />
|
|
||||||
<div ref="routeAmap" class="common-route-page__map"></div>
|
<div ref="routeAmap" class="common-route-page__map"></div>
|
||||||
|
<map-search-results :results="routeMapSearchResults" @select="selectRouteMapSearchResult" />
|
||||||
</div>
|
</div>
|
||||||
<div class="common-route-page__map-info">
|
<div class="common-route-page__map-info">
|
||||||
<span>{{ routeMapStatus }}</span>
|
<span>{{ routeMapStatus }}</span>
|
||||||
@@ -323,6 +323,7 @@ import { addressTypeOptions } from '@/option/base/common-address';
|
|||||||
import { config, excelOption, option } from '@/option/business/common-route';
|
import { config, excelOption, option } from '@/option/business/common-route';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { openImportDialog } from '@/utils/import-excel';
|
import { openImportDialog } from '@/utils/import-excel';
|
||||||
|
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import { List, Search } from '@element-plus/icons-vue';
|
import { List, Search } from '@element-plus/icons-vue';
|
||||||
@@ -616,12 +617,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.routeMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
this.routeMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||||
id: item.id || index,
|
|
||||||
name: item.formattedAddress || keyword,
|
|
||||||
address: item.formattedAddress || keyword,
|
|
||||||
location: item.location,
|
|
||||||
}));
|
|
||||||
const point = this.resolveMapPoint(result);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.routeMapStatus = '未找到匹配地址';
|
this.routeMapStatus = '未找到匹配地址';
|
||||||
|
|||||||
@@ -4007,7 +4007,7 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
<div class="business-crud-page__dispatch-quantity-hint">
|
<div class="business-crud-page__dispatch-quantity-hint">
|
||||||
剩余数量:{{
|
剩余数量:{{
|
||||||
formatDispatchQuantity(dispatchItemRemainingQuantity(dispatchItemForm))
|
formatDispatchQuantity(dispatchItemHintRemainingQuantity(dispatchItemForm))
|
||||||
}}
|
}}
|
||||||
{{ dispatchItemForm.quantityUnit || '吨' }}
|
{{ dispatchItemForm.quantityUnit || '吨' }}
|
||||||
</div>
|
</div>
|
||||||
@@ -4064,7 +4064,11 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="其他费用合计">
|
<el-form-item label="其他费用合计">
|
||||||
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入">
|
<el-input
|
||||||
|
v-model="dispatchItemForm.otherFeeTotal"
|
||||||
|
placeholder="请输入"
|
||||||
|
@input="value => handleDispatchNumberInput('otherFeeTotal', value)"
|
||||||
|
>
|
||||||
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -4996,8 +5000,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
|
||||||
<div ref="transportAmap" class="business-crud-page__map"></div>
|
<div ref="transportAmap" class="business-crud-page__map"></div>
|
||||||
|
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||||
</div>
|
</div>
|
||||||
<div class="business-crud-page__map-info">
|
<div class="business-crud-page__map-info">
|
||||||
<span>{{ transportMapStatus }}</span>
|
<span>{{ transportMapStatus }}</span>
|
||||||
@@ -5271,7 +5275,7 @@ import {
|
|||||||
getVoucherImages as getProcessConfigVoucherImages,
|
getVoucherImages as getProcessConfigVoucherImages,
|
||||||
} from '@/api/business/process-config';
|
} from '@/api/business/process-config';
|
||||||
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
|
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { addressTypeOptions } from '@/option/base/common-address';
|
import { addressTypeOptions } from '@/option/base/common-address';
|
||||||
@@ -5279,6 +5283,7 @@ import { packageOptions } from '@/option/business/common';
|
|||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { openImportDialog } from '@/utils/import-excel';
|
import { openImportDialog } from '@/utils/import-excel';
|
||||||
|
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
@@ -6340,19 +6345,21 @@ export default {
|
|||||||
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
||||||
const quantity = Number(this.dispatchItemForm.quantity || 0);
|
const quantity = Number(this.dispatchItemForm.quantity || 0);
|
||||||
const unitPrice = Number(this.dispatchItemForm.unitPrice || 0);
|
const unitPrice = Number(this.dispatchItemForm.unitPrice || 0);
|
||||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||||
|
const otherFeeTotal = Number(otherFeeRaw || 0);
|
||||||
const hasFreight = this.dispatchItemForm.unitPrice !== '' && quantity > 0;
|
const hasFreight = this.dispatchItemForm.unitPrice !== '' && quantity > 0;
|
||||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
const hasOtherFee = otherFeeRaw !== '';
|
||||||
if (!hasFreight && !hasOtherFee) return '';
|
if (!hasFreight && !hasOtherFee) return '';
|
||||||
const total = (hasFreight ? quantity * unitPrice : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
const total = (hasFreight ? quantity * unitPrice : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
}
|
}
|
||||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||||
|
const otherFeeTotal = Number(otherFeeRaw || 0);
|
||||||
const freightTotal = this.dispatchItemCargoRows.reduce(
|
const freightTotal = this.dispatchItemCargoRows.reduce(
|
||||||
(total, cargo) => total + Number(this.dispatchCargoFreightAmount(cargo) || 0),
|
(total, cargo) => total + Number(this.dispatchCargoFreightAmount(cargo) || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
const hasOtherFee = otherFeeRaw !== '';
|
||||||
if (!freightTotal && !hasOtherFee) return '';
|
if (!freightTotal && !hasOtherFee) return '';
|
||||||
const total = freightTotal + (hasOtherFee ? otherFeeTotal : 0);
|
const total = freightTotal + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
@@ -8919,6 +8926,7 @@ export default {
|
|||||||
this.form.carrierContractId = '';
|
this.form.carrierContractId = '';
|
||||||
}
|
}
|
||||||
this.form.carrierType = nextValue;
|
this.form.carrierType = nextValue;
|
||||||
|
this.clearTaskDriverVehicleFields();
|
||||||
if (this.isWaybillDetailLayout) {
|
if (this.isWaybillDetailLayout) {
|
||||||
this.taskCarrierRequestId += 1;
|
this.taskCarrierRequestId += 1;
|
||||||
this.taskCarrierOptions = [];
|
this.taskCarrierOptions = [];
|
||||||
@@ -9036,25 +9044,58 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTaskCarrierChange(value) {
|
handleTaskCarrierChange(value) {
|
||||||
const carrier = this.taskCarrierOptions.find(item =>
|
const carrier = this.taskCarrierOptions.find(item =>
|
||||||
[item.value, item.customerName, item.carrierName, item.fullName, item.name].some(name =>
|
[
|
||||||
String(name || '') === String(value || '')
|
item.value,
|
||||||
)
|
item.customerName,
|
||||||
|
item.carrierName,
|
||||||
|
item.fullName,
|
||||||
|
item.name,
|
||||||
|
item.carrierContractId,
|
||||||
|
].some(name => String(name || '') === String(value || ''))
|
||||||
);
|
);
|
||||||
if (this.isWaybillDetailLayout && this.form.carrierType === '承运商') {
|
if (this.isWaybillDetailLayout && this.form.carrierType === '承运商') {
|
||||||
this.form.carrierContractId = carrier?.carrierContractId || '';
|
this.form.carrierContractId = carrier?.carrierContractId || '';
|
||||||
this.form.carrierId = carrier?.carrierId || '';
|
this.form.carrierId = carrier?.carrierId || '';
|
||||||
this.form.carrierName = carrier?.carrierName || value || '';
|
this.form.carrierName = carrier?.carrierName || value || '';
|
||||||
return;
|
} else {
|
||||||
}
|
this.form.carrierId = carrier?.id || carrier?.carrierId || '';
|
||||||
this.form.carrierId = carrier?.id || '';
|
|
||||||
this.form.carrierName = value || '';
|
this.form.carrierName = value || '';
|
||||||
|
}
|
||||||
|
this.clearTaskDriverVehicleFields();
|
||||||
|
},
|
||||||
|
clearTaskDriverVehicleFields() {
|
||||||
|
this.form.driverId = '';
|
||||||
|
this.form.driverName = '';
|
||||||
|
this.form.driverPhone = '';
|
||||||
|
this.form.vehicleNo = '';
|
||||||
|
this.form.trailerVehicleNo = '';
|
||||||
|
this.form.escortName = '';
|
||||||
|
this.form.escortPhone = '';
|
||||||
|
this.taskDriverOptions = [];
|
||||||
|
},
|
||||||
|
getActiveCarrierFilter() {
|
||||||
|
if (this.dispatchItemBox) {
|
||||||
|
return {
|
||||||
|
carrierId: this.dispatchItemForm.carrierId || '',
|
||||||
|
carrierName: this.dispatchItemForm.carrierName || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
carrierId: this.form.carrierId || '',
|
||||||
|
carrierName: this.form.carrierName || '',
|
||||||
|
};
|
||||||
},
|
},
|
||||||
loadTaskDriverOptions() {
|
loadTaskDriverOptions() {
|
||||||
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
||||||
|
const carrier = this.getActiveCarrierFilter();
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
this.taskDriverOptions = [];
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
return getDriverList(1, 9999, {})
|
return fetchDriversByCarrierOrganizations({ size: 9999, posts: '司机' }, carrier)
|
||||||
.then(res => {
|
.then(records => {
|
||||||
this.taskDriverOptions = extractRecords(res);
|
this.taskDriverOptions = records;
|
||||||
return this.taskDriverOptions;
|
return this.taskDriverOptions;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -9063,10 +9104,17 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskDriverSuggestions(queryString, callback) {
|
fetchTaskDriverSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
|
const carrier = this.getActiveCarrierFilter();
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
getDriverList(1, 20, keyword ? { driverName: keyword } : {})
|
fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||||
const records = extractRecords(res);
|
carrier
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
this.taskDriverOptions = records;
|
this.taskDriverOptions = records;
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
records.map(item => ({
|
||||||
@@ -10239,12 +10287,7 @@ export default {
|
|||||||
this.ensureTransportAmapGeocoder()
|
this.ensureTransportAmapGeocoder()
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||||
id: item.id || index,
|
|
||||||
name: item.formattedAddress || keyword,
|
|
||||||
address: item.formattedAddress || keyword,
|
|
||||||
location: item.location,
|
|
||||||
}));
|
|
||||||
const point = this.resolveMapPoint(result);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.transportMapStatus = '未找到匹配地址';
|
this.transportMapStatus = '未找到匹配地址';
|
||||||
@@ -12760,6 +12803,9 @@ export default {
|
|||||||
...baseRow,
|
...baseRow,
|
||||||
...(index >= 0 ? row : {}),
|
...(index >= 0 ? row : {}),
|
||||||
};
|
};
|
||||||
|
this.dispatchItemForm.otherFeeTotal = this.normalizeDispatchFeeValue(
|
||||||
|
this.dispatchItemForm.otherFeeTotal
|
||||||
|
);
|
||||||
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
||||||
const goodsRows = this.parseJsonArray(this.dispatchItemForm.goodsJson);
|
const goodsRows = this.parseJsonArray(this.dispatchItemForm.goodsJson);
|
||||||
this.dispatchItemCargoRows = (goodsRows.length ? goodsRows : [this.dispatchItemForm]).map(
|
this.dispatchItemCargoRows = (goodsRows.length ? goodsRows : [this.dispatchItemForm]).map(
|
||||||
@@ -12816,6 +12862,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.dispatchItemForm.carrierType = nextValue;
|
this.dispatchItemForm.carrierType = nextValue;
|
||||||
|
this.clearDispatchDriverVehicleFields();
|
||||||
this.loadDispatchCarrierOptions(this.dispatchRow);
|
this.loadDispatchCarrierOptions(this.dispatchRow);
|
||||||
},
|
},
|
||||||
isDispatchCarrierRequired(carrierType) {
|
isDispatchCarrierRequired(carrierType) {
|
||||||
@@ -12945,20 +12992,39 @@ export default {
|
|||||||
});
|
});
|
||||||
if (this.dispatchIsCarrierMode) {
|
if (this.dispatchIsCarrierMode) {
|
||||||
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
||||||
this.dispatchItemForm.carrierId = '';
|
this.dispatchItemForm.carrierId = carrier?.carrierId || '';
|
||||||
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
this.dispatchItemForm.carrierContractId = '';
|
this.dispatchItemForm.carrierContractId = '';
|
||||||
this.dispatchItemForm.carrierId = carrier?.id || '';
|
this.dispatchItemForm.carrierId = carrier?.id || carrier?.carrierId || '';
|
||||||
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
||||||
|
}
|
||||||
|
this.clearDispatchDriverVehicleFields();
|
||||||
|
},
|
||||||
|
clearDispatchDriverVehicleFields() {
|
||||||
|
this.dispatchItemForm.driverId = '';
|
||||||
|
this.dispatchItemForm.driverName = '';
|
||||||
|
this.dispatchItemForm.driverPhone = '';
|
||||||
|
this.dispatchItemForm.vehicleNo = '';
|
||||||
|
this.dispatchItemForm.trailerVehicleNo = '';
|
||||||
|
this.dispatchItemForm.escortName = '';
|
||||||
|
this.dispatchItemForm.escortPhone = '';
|
||||||
|
this.taskDriverOptions = [];
|
||||||
},
|
},
|
||||||
loadDispatchDriverOptions() {
|
loadDispatchDriverOptions() {
|
||||||
if (this.taskDriverLoading) return Promise.resolve([]);
|
if (this.taskDriverLoading) return Promise.resolve([]);
|
||||||
|
const carrier = {
|
||||||
|
carrierId: this.dispatchItemForm.carrierId || '',
|
||||||
|
carrierName: this.dispatchItemForm.carrierName || '',
|
||||||
|
};
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
this.taskDriverOptions = [];
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
return getDriverList(1, 9999, {})
|
return fetchDriversByCarrierOrganizations({ size: 9999, posts: '司机' }, carrier)
|
||||||
.then(res => {
|
.then(records => {
|
||||||
this.taskDriverOptions = extractRecords(res);
|
this.taskDriverOptions = records;
|
||||||
return this.taskDriverOptions;
|
return this.taskDriverOptions;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -13035,6 +13101,11 @@ export default {
|
|||||||
this.dispatchItemForm[prop] =
|
this.dispatchItemForm[prop] =
|
||||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
},
|
},
|
||||||
|
normalizeDispatchFeeValue(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
if (Number(value) === -1) return '';
|
||||||
|
return value;
|
||||||
|
},
|
||||||
addDispatchCargoRow(index = -1) {
|
addDispatchCargoRow(index = -1) {
|
||||||
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
||||||
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
||||||
@@ -13118,6 +13189,12 @@ export default {
|
|||||||
}, 0)
|
}, 0)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
/** 精简录入提示:可调度余量扣减当前本次数量后的剩余 */
|
||||||
|
dispatchItemHintRemainingQuantity(row = {}) {
|
||||||
|
const available = this.dispatchItemRemainingQuantity(row);
|
||||||
|
const current = this.parseDispatchQuantity(row.quantity);
|
||||||
|
return Math.max(available - current, 0);
|
||||||
|
},
|
||||||
pruneDispatchPendingRows(rows = []) {
|
pruneDispatchPendingRows(rows = []) {
|
||||||
const planQuantities = new Map();
|
const planQuantities = new Map();
|
||||||
this.dispatchPlanGoodsRows.forEach(goods => {
|
this.dispatchPlanGoodsRows.forEach(goods => {
|
||||||
@@ -13364,14 +13441,29 @@ export default {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const firstGoods = goodsRows[0] || {};
|
const firstGoods = goodsRows[0] || {};
|
||||||
|
const quantitySum = goodsRows.reduce(
|
||||||
|
(sum, goods) => sum + this.parseDispatchQuantity(goods.quantity),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const quantityText = quantitySum
|
||||||
|
? this.formatDispatchQuantity(quantitySum)
|
||||||
|
: firstGoods.quantity || this.dispatchItemForm.quantity || '';
|
||||||
|
const otherFeeTotal = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||||
const nextRow = {
|
const nextRow = {
|
||||||
...this.dispatchItemForm,
|
...this.dispatchItemForm,
|
||||||
goodsJson: JSON.stringify(goodsRows),
|
goodsJson: JSON.stringify(goodsRows),
|
||||||
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
||||||
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
||||||
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
||||||
cargoName: firstGoods.cargoName || this.dispatchItemForm.cargoName || '',
|
cargoName:
|
||||||
quantity: firstGoods.quantity || this.dispatchItemForm.quantity || '',
|
goodsRows
|
||||||
|
.map(goods => goods.cargoName || goods.goodsName || '')
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('.') ||
|
||||||
|
firstGoods.cargoName ||
|
||||||
|
this.dispatchItemForm.cargoName ||
|
||||||
|
'',
|
||||||
|
quantity: quantityText,
|
||||||
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
||||||
unitPrice: firstGoods.unitPrice || '',
|
unitPrice: firstGoods.unitPrice || '',
|
||||||
priceUnit: firstGoods.priceUnit || '',
|
priceUnit: firstGoods.priceUnit || '',
|
||||||
@@ -13379,7 +13471,7 @@ export default {
|
|||||||
freightJson: JSON.stringify({
|
freightJson: JSON.stringify({
|
||||||
currency: this.dispatchItemForm.freightCurrency || 'CNY',
|
currency: this.dispatchItemForm.freightCurrency || 'CNY',
|
||||||
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
||||||
otherFreightAmount: this.dispatchItemForm.otherFeeTotal || '',
|
otherFreightAmount: otherFeeTotal,
|
||||||
freightItems: goodsRows.map((cargo, index) => ({
|
freightItems: goodsRows.map((cargo, index) => ({
|
||||||
cargoIndex: index,
|
cargoIndex: index,
|
||||||
cargoName: cargo.cargoName || '',
|
cargoName: cargo.cargoName || '',
|
||||||
@@ -13395,7 +13487,7 @@ export default {
|
|||||||
};
|
};
|
||||||
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
||||||
nextRow.freight = this.getDispatchFeeFields({}, nextRow).freight;
|
nextRow.freight = this.getDispatchFeeFields({}, nextRow).freight;
|
||||||
nextRow.otherFeeTotal = this.dispatchItemForm.otherFeeTotal || '';
|
nextRow.otherFeeTotal = otherFeeTotal;
|
||||||
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
||||||
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -156,11 +156,11 @@
|
|||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<template v-if="isRoad(route)">
|
<template v-if="isRoad(route)">
|
||||||
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="司机" :required="route.carrierType !== '承运商'"><el-autocomplete :ref="element => setDriverInput(route.segmentNo, element)" v-model="route.driverName" :debounce="300" :fetch-suggestions="fetchDriverSuggestions" clearable placeholder="请输入司机" :loading="driverLoading" @select="item => handleDriverSuggestionSelect(route, item)" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="司机" :required="route.carrierType !== '承运商'"><el-autocomplete :ref="element => setDriverInput(route.segmentNo, element)" v-model="route.driverName" :debounce="300" :fetch-suggestions="(query, cb) => fetchDriverSuggestions(route, query, cb)" clearable placeholder="请输入司机" :loading="driverLoading" @select="item => handleDriverSuggestionSelect(route, item)" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item :label="route.carrierType === '承运商' ? '手机号' : '司机手机号'" :required="route.carrierType !== '承运商'"><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item :label="route.carrierType === '承运商' ? '手机号' : '司机手机号'" :required="route.carrierType !== '承运商'"><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="车牌号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="车牌号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号"><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号"><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人"><el-autocomplete v-model="route.escortName" :debounce="300" :fetch-suggestions="fetchEscortSuggestions" clearable placeholder="请输入" :loading="escortLoading" @select="item => handleEscortSuggestionSelect(route, item)" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人"><el-autocomplete v-model="route.escortName" :debounce="300" :fetch-suggestions="(query, cb) => fetchEscortSuggestions(route, query, cb)" clearable placeholder="请输入" :loading="escortLoading" @select="item => handleEscortSuggestionSelect(route, item)" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号"><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号"><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as api from '@/api/business/master-order';
|
import * as api from '@/api/business/master-order';
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
|
|
||||||
const unwrapRecords = res => {
|
const unwrapRecords = res => {
|
||||||
@@ -290,7 +290,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await Promise.all([this.load(), this.loadCarrierOptions(), this.loadDriverOptions()]);
|
await Promise.all([this.load(), this.loadCarrierOptions()]);
|
||||||
this.applySelfOperatedCarrierDefaults();
|
this.applySelfOperatedCarrierDefaults();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -727,6 +727,7 @@ export default {
|
|||||||
route.carrierName = '';
|
route.carrierName = '';
|
||||||
route.carrierId = '';
|
route.carrierId = '';
|
||||||
route.carrierContractId = '';
|
route.carrierContractId = '';
|
||||||
|
this.clearRouteDriverVehicleFields(route);
|
||||||
if (value === '承运商') {
|
if (value === '承运商') {
|
||||||
route.trailerVehicleNo = '';
|
route.trailerVehicleNo = '';
|
||||||
route.escortName = '';
|
route.escortName = '';
|
||||||
@@ -746,6 +747,24 @@ export default {
|
|||||||
route.carrierContractId = route.carrierType !== '自运' ? contract?.contractId || '' : '';
|
route.carrierContractId = route.carrierType !== '自运' ? contract?.contractId || '' : '';
|
||||||
route.carrierId = route.carrierType !== '自运' ? contract?.carrierId || '' : '';
|
route.carrierId = route.carrierType !== '自运' ? contract?.carrierId || '' : '';
|
||||||
route.carrierName = contract?.carrierName || '';
|
route.carrierName = contract?.carrierName || '';
|
||||||
|
this.clearRouteDriverVehicleFields(route);
|
||||||
|
},
|
||||||
|
clearRouteDriverVehicleFields(route) {
|
||||||
|
if (!route) return;
|
||||||
|
route.driverId = '';
|
||||||
|
route.driverName = '';
|
||||||
|
route.driverPhone = '';
|
||||||
|
route.vehicleNo = '';
|
||||||
|
route.trailerVehicleNo = '';
|
||||||
|
route.escortName = '';
|
||||||
|
route.escortPhone = '';
|
||||||
|
this.driverOptions = [];
|
||||||
|
},
|
||||||
|
getRouteCarrierFilter(route = {}) {
|
||||||
|
return {
|
||||||
|
carrierId: route.carrierId || '',
|
||||||
|
carrierName: route.carrierName || '',
|
||||||
|
};
|
||||||
},
|
},
|
||||||
carrierOptionValue(item = {}) {
|
carrierOptionValue(item = {}) {
|
||||||
return item.contractId || item.id || item.value || item.carrierName || '';
|
return item.contractId || item.id || item.value || item.carrierName || '';
|
||||||
@@ -817,18 +836,30 @@ export default {
|
|||||||
route.carrierContractId = '';
|
route.carrierContractId = '';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async loadDriverOptions(keyword = '') {
|
async loadDriverOptions(route = {}, keyword = '') {
|
||||||
|
const carrier = this.getRouteCarrierFilter(route);
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
this.driverOptions = [];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
this.driverLoading = true;
|
this.driverLoading = true;
|
||||||
try {
|
try {
|
||||||
this.driverOptions = unwrapRecords(
|
this.driverOptions = await fetchDriversByCarrierOrganizations(
|
||||||
await getDriverList(1, 20, { driverName: keyword, posts: '司机' })
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||||
|
carrier
|
||||||
);
|
);
|
||||||
|
return this.driverOptions;
|
||||||
} finally {
|
} finally {
|
||||||
this.driverLoading = false;
|
this.driverLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchDriverSuggestions(queryString, callback) {
|
fetchDriverSuggestions(route, queryString, callback) {
|
||||||
this.loadDriverOptions(String(queryString || '').trim())
|
const carrier = this.getRouteCarrierFilter(route);
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loadDriverOptions(route, String(queryString || '').trim())
|
||||||
.then(() =>
|
.then(() =>
|
||||||
callback(
|
callback(
|
||||||
this.driverOptions.map(item => ({
|
this.driverOptions.map(item => ({
|
||||||
@@ -846,13 +877,21 @@ export default {
|
|||||||
const drivingVehicle = String(item.drivingVehicle || '').trim();
|
const drivingVehicle = String(item.drivingVehicle || '').trim();
|
||||||
if (drivingVehicle) route.vehicleNo = drivingVehicle;
|
if (drivingVehicle) route.vehicleNo = drivingVehicle;
|
||||||
},
|
},
|
||||||
fetchEscortSuggestions(queryString, callback) {
|
fetchEscortSuggestions(route, queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
|
const carrier = this.getRouteCarrierFilter(route);
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.escortLoading = true;
|
this.escortLoading = true;
|
||||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
||||||
|
carrier
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
callback(
|
callback(
|
||||||
unwrapRecords(res).map(item => ({
|
records.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
value: this.driverOptionLabel(item),
|
value: this.driverOptionLabel(item),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -402,8 +402,8 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<map-search-results :results="mapSearchResults" @select="selectAddressMapSearchResult" />
|
|
||||||
<div ref="addressMap" class="address-map" />
|
<div ref="addressMap" class="address-map" />
|
||||||
|
<map-search-results :results="mapSearchResults" @select="selectAddressMapSearchResult" />
|
||||||
</div>
|
</div>
|
||||||
<div class="address-map-status">
|
<div class="address-map-status">
|
||||||
{{ mapStatus
|
{{ mapStatus
|
||||||
@@ -601,6 +601,7 @@ let amapLoader;
|
|||||||
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
||||||
import * as api from '@/api/business/master-order';
|
import * as api from '@/api/business/master-order';
|
||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
|
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -1285,14 +1286,8 @@ export default {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
const geocodes = result?.geocodes || [];
|
this.mapSearchResults = normalizeMapSearchResults(result, this.mapKeyword);
|
||||||
this.mapSearchResults = geocodes.map((item, index) => ({
|
const point = this.mapSearchResults[0]?.location;
|
||||||
id: item.id || index,
|
|
||||||
name: item.formattedAddress || this.mapKeyword,
|
|
||||||
address: item.formattedAddress || this.mapKeyword,
|
|
||||||
location: item.location,
|
|
||||||
}));
|
|
||||||
const point = geocodes[0]?.location;
|
|
||||||
if (!point) return this.$message.warning('地图搜索无匹配地址');
|
if (!point) return this.$message.warning('地图搜索无匹配地址');
|
||||||
this.pickAddressMap(point, this.mapKeyword);
|
this.pickAddressMap(point, this.mapKeyword);
|
||||||
})
|
})
|
||||||
@@ -2199,8 +2194,15 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.address-map {
|
.address-map {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
height: 440px;
|
height: 440px;
|
||||||
}
|
}
|
||||||
.address-map-status {
|
.address-map-status {
|
||||||
|
|||||||
@@ -931,11 +931,11 @@
|
|||||||
/><el-button type="primary" @click="searchTransportMapKeyword">搜索</el-button>
|
/><el-button type="primary" @click="searchTransportMapKeyword">搜索</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
|
<div ref="transportMap" class="shipping-template-page__map" />
|
||||||
<map-search-results
|
<map-search-results
|
||||||
:results="transportMapSearchResults"
|
:results="transportMapSearchResults"
|
||||||
@select="selectTransportMapSearchResult"
|
@select="selectTransportMapSearchResult"
|
||||||
/>
|
/>
|
||||||
<div ref="transportMap" class="shipping-template-page__map" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="shipping-template-page__map-info">{{ transportMapStatus }}</div>
|
<div class="shipping-template-page__map-info">{{ transportMapStatus }}</div>
|
||||||
<template #footer
|
<template #footer
|
||||||
@@ -1098,6 +1098,7 @@ import {
|
|||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { packageOptions } from '@/option/business/common';
|
import { packageOptions } from '@/option/business/common';
|
||||||
|
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import { Location } from '@element-plus/icons-vue';
|
import { Location } from '@element-plus/icons-vue';
|
||||||
@@ -2615,12 +2616,7 @@ export default {
|
|||||||
.then(() => this.ensureTransportMapGeocoder())
|
.then(() => this.ensureTransportMapGeocoder())
|
||||||
.then(() => this.runTransportMapGeocode('location', keyword))
|
.then(() => this.runTransportMapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||||
id: item.id || index,
|
|
||||||
name: item.formattedAddress || keyword,
|
|
||||||
address: item.formattedAddress || keyword,
|
|
||||||
location: item.location,
|
|
||||||
}));
|
|
||||||
const point = this.resolveTransportMapPoint(result);
|
const point = this.resolveTransportMapPoint(result);
|
||||||
if (!point) throw new Error('未找到匹配地址');
|
if (!point) throw new Error('未找到匹配地址');
|
||||||
return this.$nextTick().then(() => {
|
return this.$nextTick().then(() => {
|
||||||
@@ -3754,6 +3750,12 @@ export default {
|
|||||||
.shipping-template-page__map-toolbar {
|
.shipping-template-page__map-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.shipping-template-page__dialog-search {
|
.shipping-template-page__dialog-search {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -3766,11 +3768,9 @@ export default {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
.shipping-template-page__map {
|
.shipping-template-page__map {
|
||||||
flex: 1 1 auto;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 480px;
|
height: 480px;
|
||||||
margin-top: 12px;
|
|
||||||
}
|
}
|
||||||
:deep(.shipping-template-page__map > .amap-container) {
|
:deep(.shipping-template-page__map > .amap-container) {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
|||||||
@@ -637,13 +637,14 @@
|
|||||||
<span>|</span>
|
<span>|</span>
|
||||||
{{ detailRow.planNo || '-' }}
|
{{ detailRow.planNo || '-' }}
|
||||||
</h2>
|
</h2>
|
||||||
<span class="detail-status-text is-dispatching">
|
<span class="detail-status-text" :class="detailStatusTextClass">
|
||||||
{{ displayStatus(detailRow, 'businessStatus') }}
|
{{ displayStatus(detailRow, 'businessStatus') }}
|
||||||
</span>
|
</span>
|
||||||
<span class="detail-status-text detail-transport-type">
|
<span class="detail-status-text detail-transport-type">
|
||||||
{{
|
{{
|
||||||
detailRow.transportTypeName ||
|
detailRow.transportTypeName ||
|
||||||
formatTransportPlanDetailTransportType(detailRow.transportType)
|
formatTransportPlanDetailTransportType(detailRow.transportType) ||
|
||||||
|
'-'
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1231,7 +1232,7 @@
|
|||||||
{{ dispatchDialogStatusText }}
|
{{ dispatchDialogStatusText }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<el-tag type="primary" effect="light">
|
<el-tag type="primary" effect="light">
|
||||||
{{ formatDispatchTransportType(dispatchRow.transportType) || '公路运输' }}
|
{{ formatDispatchTransportType(dispatchRow.transportType) || '-' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1931,7 +1932,7 @@
|
|||||||
class="transport-plan-page__dispatch-quantity-hint"
|
class="transport-plan-page__dispatch-quantity-hint"
|
||||||
>
|
>
|
||||||
剩余数量:{{
|
剩余数量:{{
|
||||||
formatDispatchQuantity(dispatchItemRemainingQuantity(dispatchItemForm))
|
formatDispatchQuantity(dispatchItemHintRemainingQuantity(dispatchItemForm))
|
||||||
}}
|
}}
|
||||||
{{ dispatchItemForm.quantityUnit || '吨' }}
|
{{ dispatchItemForm.quantityUnit || '吨' }}
|
||||||
</div>
|
</div>
|
||||||
@@ -1997,7 +1998,11 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="其他费用合计">
|
<el-form-item label="其他费用合计">
|
||||||
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入">
|
<el-input
|
||||||
|
v-model="dispatchItemForm.otherFeeTotal"
|
||||||
|
placeholder="请输入"
|
||||||
|
@input="value => handleDispatchNumberInput('otherFeeTotal', value)"
|
||||||
|
>
|
||||||
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -2547,8 +2552,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
|
||||||
<div ref="transportAmap" class="transport-plan-page__map"></div>
|
<div ref="transportAmap" class="transport-plan-page__map"></div>
|
||||||
|
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||||
</div>
|
</div>
|
||||||
<div class="transport-plan-page__map-info">
|
<div class="transport-plan-page__map-info">
|
||||||
<span>{{ transportMapStatus }}</span>
|
<span>{{ transportMapStatus }}</span>
|
||||||
@@ -2729,13 +2734,14 @@ import {
|
|||||||
getList as getProjectList,
|
getList as getProjectList,
|
||||||
} from '@/api/business/project-apply';
|
} from '@/api/business/project-apply';
|
||||||
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { addressTypeOptions } from '@/option/base/common-address';
|
import { addressTypeOptions } from '@/option/base/common-address';
|
||||||
import { packageOptions } from '@/option/business/common';
|
import { packageOptions } from '@/option/business/common';
|
||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
|
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
@@ -3284,6 +3290,23 @@ export default {
|
|||||||
isTransportPlanDetailLayout() {
|
isTransportPlanDetailLayout() {
|
||||||
return this.detailBox;
|
return this.detailBox;
|
||||||
},
|
},
|
||||||
|
detailStatusTextClass() {
|
||||||
|
const status = this.detailRow?.businessStatus;
|
||||||
|
if (status === 2 || status === '2' || status === 'completed') {
|
||||||
|
return 'status-text-success';
|
||||||
|
}
|
||||||
|
if (status === 1 || status === '1' || status === 'dispatching') {
|
||||||
|
return 'is-dispatching';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
status === 3 ||
|
||||||
|
status === '3' ||
|
||||||
|
['rejected', 'change_rejected', 'cancelled', 'withdrawn'].includes(status)
|
||||||
|
) {
|
||||||
|
return 'is-danger';
|
||||||
|
}
|
||||||
|
return 'is-info';
|
||||||
|
},
|
||||||
transportPlanGoodsText() {
|
transportPlanGoodsText() {
|
||||||
const text = this.formatTransportPlanGoodsInfo(this.detailRow);
|
const text = this.formatTransportPlanGoodsInfo(this.detailRow);
|
||||||
return text && text !== '-' ? text : '暂无货物信息';
|
return text && text !== '-' ? text : '暂无货物信息';
|
||||||
@@ -3526,17 +3549,19 @@ export default {
|
|||||||
},
|
},
|
||||||
dispatchItemFreightTotal() {
|
dispatchItemFreightTotal() {
|
||||||
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
||||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||||
|
const otherFeeTotal = Number(otherFeeRaw || 0);
|
||||||
const freight = this.dispatchItemFreightSubtotal;
|
const freight = this.dispatchItemFreightSubtotal;
|
||||||
const hasFreight = freight !== '';
|
const hasFreight = freight !== '';
|
||||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
const hasOtherFee = otherFeeRaw !== '';
|
||||||
if (!hasFreight && !hasOtherFee) return '';
|
if (!hasFreight && !hasOtherFee) return '';
|
||||||
const total = (hasFreight ? Number(freight) : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
const total = (hasFreight ? Number(freight) : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
}
|
}
|
||||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||||
|
const otherFeeTotal = Number(otherFeeRaw || 0);
|
||||||
const freightSubtotal = this.dispatchItemFreightSubtotal;
|
const freightSubtotal = this.dispatchItemFreightSubtotal;
|
||||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
const hasOtherFee = otherFeeRaw !== '';
|
||||||
if (freightSubtotal === '' && !hasOtherFee) return '';
|
if (freightSubtotal === '' && !hasOtherFee) return '';
|
||||||
const total = Number(freightSubtotal || 0) + (hasOtherFee ? otherFeeTotal : 0);
|
const total = Number(freightSubtotal || 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
@@ -4841,10 +4866,17 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskDriverSuggestions(queryString, callback) {
|
fetchTaskDriverSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
|
const carrier = this.getDispatchCarrierFilter();
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||||
const records = extractRecords(res);
|
carrier
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
this.taskDriverOptions = records;
|
this.taskDriverOptions = records;
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
records.map(item => ({
|
||||||
@@ -4863,10 +4895,17 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskEscortSuggestions(queryString, callback) {
|
fetchTaskEscortSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
|
const carrier = this.getDispatchCarrierFilter();
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.taskEscortLoading = true;
|
this.taskEscortLoading = true;
|
||||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
||||||
const records = extractRecords(res);
|
carrier
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
this.taskEscortOptions = records;
|
this.taskEscortOptions = records;
|
||||||
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
||||||
})
|
})
|
||||||
@@ -5738,12 +5777,7 @@ export default {
|
|||||||
this.ensureTransportAmapGeocoder()
|
this.ensureTransportAmapGeocoder()
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||||
id: item.id || index,
|
|
||||||
name: item.formattedAddress || keyword,
|
|
||||||
address: item.formattedAddress || keyword,
|
|
||||||
location: item.location,
|
|
||||||
}));
|
|
||||||
const point = this.resolveMapPoint(result);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.transportMapStatus = '未找到匹配地址';
|
this.transportMapStatus = '未找到匹配地址';
|
||||||
@@ -7241,11 +7275,13 @@ export default {
|
|||||||
freightInfo.freightItems?.[0]?.unitPrice ??
|
freightInfo.freightItems?.[0]?.unitPrice ??
|
||||||
'';
|
'';
|
||||||
const otherFeeTotal =
|
const otherFeeTotal =
|
||||||
|
this.normalizeDispatchFeeValue(
|
||||||
item.otherFeeTotal ??
|
item.otherFeeTotal ??
|
||||||
plan.otherFeeTotal ??
|
plan.otherFeeTotal ??
|
||||||
freightInfo.otherFeeTotal ??
|
freightInfo.otherFeeTotal ??
|
||||||
freightInfo.otherFreightAmount ??
|
freightInfo.otherFreightAmount ??
|
||||||
'';
|
''
|
||||||
|
);
|
||||||
const quantity = Number(
|
const quantity = Number(
|
||||||
item.quantity ||
|
item.quantity ||
|
||||||
item.cargoQuantity ||
|
item.cargoQuantity ||
|
||||||
@@ -7560,6 +7596,9 @@ export default {
|
|||||||
...baseRow,
|
...baseRow,
|
||||||
...(index >= 0 ? row : {}),
|
...(index >= 0 ? row : {}),
|
||||||
};
|
};
|
||||||
|
this.dispatchItemForm.otherFeeTotal = this.normalizeDispatchFeeValue(
|
||||||
|
this.dispatchItemForm.otherFeeTotal
|
||||||
|
);
|
||||||
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
||||||
this.loadDispatchCarrierOptions(this.dispatchRow).then(() => {
|
this.loadDispatchCarrierOptions(this.dispatchRow).then(() => {
|
||||||
this.applyDispatchSelfCarrierDefaults();
|
this.applyDispatchSelfCarrierDefaults();
|
||||||
@@ -7611,6 +7650,7 @@ export default {
|
|||||||
this.dispatchItemForm.carrierName = '';
|
this.dispatchItemForm.carrierName = '';
|
||||||
this.dispatchItemForm.carrierId = '';
|
this.dispatchItemForm.carrierId = '';
|
||||||
this.dispatchItemForm.carrierContractId = '';
|
this.dispatchItemForm.carrierContractId = '';
|
||||||
|
this.clearDispatchDriverVehicleFields();
|
||||||
if (nextValue === '承运商') {
|
if (nextValue === '承运商') {
|
||||||
this.dispatchItemForm.trailerVehicleNo = '';
|
this.dispatchItemForm.trailerVehicleNo = '';
|
||||||
this.dispatchItemForm.escortName = '';
|
this.dispatchItemForm.escortName = '';
|
||||||
@@ -7798,12 +7838,29 @@ export default {
|
|||||||
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
||||||
this.dispatchItemForm.carrierId = carrier?.carrierId || '';
|
this.dispatchItemForm.carrierId = carrier?.carrierId || '';
|
||||||
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
this.dispatchItemForm.carrierContractId = '';
|
this.dispatchItemForm.carrierContractId = '';
|
||||||
this.dispatchItemForm.carrierId =
|
this.dispatchItemForm.carrierId = '';
|
||||||
this.dispatchItemForm.carrierType === '自运' ? '' : carrier?.id || '';
|
|
||||||
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
||||||
|
}
|
||||||
|
this.clearDispatchDriverVehicleFields();
|
||||||
|
},
|
||||||
|
clearDispatchDriverVehicleFields() {
|
||||||
|
this.dispatchItemForm.driverId = '';
|
||||||
|
this.dispatchItemForm.driverName = '';
|
||||||
|
this.dispatchItemForm.driverPhone = '';
|
||||||
|
this.dispatchItemForm.vehicleNo = '';
|
||||||
|
this.dispatchItemForm.trailerVehicleNo = '';
|
||||||
|
this.dispatchItemForm.escortName = '';
|
||||||
|
this.dispatchItemForm.escortPhone = '';
|
||||||
|
this.taskDriverOptions = [];
|
||||||
|
this.taskEscortOptions = [];
|
||||||
|
},
|
||||||
|
getDispatchCarrierFilter() {
|
||||||
|
return {
|
||||||
|
carrierId: this.dispatchItemForm.carrierId || '',
|
||||||
|
carrierName: this.dispatchItemForm.carrierName || '',
|
||||||
|
};
|
||||||
},
|
},
|
||||||
handleDispatchCargoQuantityInput(row, value) {
|
handleDispatchCargoQuantityInput(row, value) {
|
||||||
const text = String(value || '').replace(/[^\d.]/g, '');
|
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||||
@@ -7820,6 +7877,11 @@ export default {
|
|||||||
this.dispatchItemForm[prop] =
|
this.dispatchItemForm[prop] =
|
||||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
},
|
},
|
||||||
|
normalizeDispatchFeeValue(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
if (Number(value) === -1) return '';
|
||||||
|
return value;
|
||||||
|
},
|
||||||
addDispatchCargoRow(index = -1) {
|
addDispatchCargoRow(index = -1) {
|
||||||
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
||||||
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
||||||
@@ -7972,6 +8034,12 @@ export default {
|
|||||||
}, 0)
|
}, 0)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
/** 精简录入提示:可调度余量扣减当前本次数量后的剩余 */
|
||||||
|
dispatchItemHintRemainingQuantity(row = {}) {
|
||||||
|
const available = this.dispatchItemRemainingQuantity(row);
|
||||||
|
const current = this.parseDispatchQuantity(row.quantity);
|
||||||
|
return Math.max(available - current, 0);
|
||||||
|
},
|
||||||
pruneDispatchPendingRows(rows = []) {
|
pruneDispatchPendingRows(rows = []) {
|
||||||
const planQuantities = new Map();
|
const planQuantities = new Map();
|
||||||
this.dispatchPlanGoodsRows.forEach(goods => {
|
this.dispatchPlanGoodsRows.forEach(goods => {
|
||||||
@@ -8220,14 +8288,29 @@ export default {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const firstGoods = goodsRows[0] || {};
|
const firstGoods = goodsRows[0] || {};
|
||||||
|
const quantitySum = goodsRows.reduce(
|
||||||
|
(sum, goods) => sum + this.parseDispatchQuantity(goods.quantity),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const quantityText = quantitySum
|
||||||
|
? this.formatDispatchQuantity(quantitySum)
|
||||||
|
: firstGoods.quantity || this.dispatchItemForm.quantity || '';
|
||||||
|
const otherFeeTotal = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||||
const nextRow = {
|
const nextRow = {
|
||||||
...this.dispatchItemForm,
|
...this.dispatchItemForm,
|
||||||
goodsJson: JSON.stringify(goodsRows),
|
goodsJson: JSON.stringify(goodsRows),
|
||||||
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
||||||
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
||||||
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
||||||
cargoName: firstGoods.cargoName || this.dispatchItemForm.cargoName || '',
|
cargoName:
|
||||||
quantity: firstGoods.quantity || this.dispatchItemForm.quantity || '',
|
goodsRows
|
||||||
|
.map(goods => goods.cargoName || goods.goodsName || '')
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('.') ||
|
||||||
|
firstGoods.cargoName ||
|
||||||
|
this.dispatchItemForm.cargoName ||
|
||||||
|
'',
|
||||||
|
quantity: quantityText,
|
||||||
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
||||||
unitPrice: firstGoods.unitPrice || '',
|
unitPrice: firstGoods.unitPrice || '',
|
||||||
priceUnit: firstGoods.priceUnit || '',
|
priceUnit: firstGoods.priceUnit || '',
|
||||||
@@ -8235,7 +8318,7 @@ export default {
|
|||||||
freightJson: JSON.stringify({
|
freightJson: JSON.stringify({
|
||||||
currency: this.dispatchContractCurrency || this.dispatchItemForm.freightCurrency || 'RMB',
|
currency: this.dispatchContractCurrency || this.dispatchItemForm.freightCurrency || 'RMB',
|
||||||
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
||||||
otherFreightAmount: this.dispatchItemForm.otherFeeTotal || '',
|
otherFreightAmount: otherFeeTotal,
|
||||||
freightItems: goodsRows.map((cargo, index) => ({
|
freightItems: goodsRows.map((cargo, index) => ({
|
||||||
cargoIndex: index,
|
cargoIndex: index,
|
||||||
cargoName: cargo.cargoName || '',
|
cargoName: cargo.cargoName || '',
|
||||||
@@ -8251,7 +8334,7 @@ export default {
|
|||||||
};
|
};
|
||||||
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
||||||
nextRow.freight = this.dispatchItemFreightSubtotal;
|
nextRow.freight = this.dispatchItemFreightSubtotal;
|
||||||
nextRow.otherFeeTotal = this.dispatchItemForm.otherFeeTotal || '';
|
nextRow.otherFeeTotal = otherFeeTotal;
|
||||||
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
||||||
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
||||||
if (
|
if (
|
||||||
@@ -9348,19 +9431,36 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-tag) {
|
.detail-status-text {
|
||||||
height: 36px;
|
display: inline-flex;
|
||||||
padding: 0 14px;
|
align-items: center;
|
||||||
border: 0;
|
height: 28px;
|
||||||
|
padding: 0 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
line-height: 36px;
|
line-height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.is-dispatching) {
|
.status-text-success,
|
||||||
|
.is-dispatching {
|
||||||
color: #67c23a;
|
color: #67c23a;
|
||||||
background: #e1f3d8;
|
background: #e1f3d8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-transport-type {
|
||||||
|
color: #409eff;
|
||||||
|
background: #ecf5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-danger {
|
||||||
|
color: #f56c6c;
|
||||||
|
background: #fef0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-info {
|
||||||
|
color: #909399;
|
||||||
|
background: #f4f4f5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__transport-plan-detail-overview {
|
&__transport-plan-detail-overview {
|
||||||
|
|||||||
@@ -1939,12 +1939,17 @@
|
|||||||
}}</strong
|
}}</strong
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
v-if="!waybillIsCarrier(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>里程(km)</span
|
<span>里程(km)</span
|
||||||
><strong>{{ normalizeMileageValue(detailRow.mileage) || '-' }}</strong>
|
><strong>{{ waybillDetailMileage(detailRow) || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>计划发货日期</span
|
||||||
|
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>计划完成日期</span
|
||||||
|
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="waybillIsNonRoadTransport(detailRow)">
|
<template v-else-if="waybillIsNonRoadTransport(detailRow)">
|
||||||
@@ -1966,12 +1971,32 @@
|
|||||||
<div class="waybill-manage-page__waybill-detail-field">
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
<span>仓位</span><strong>{{ detailRow.cabinNo || '-' }}</strong>
|
<span>仓位</span><strong>{{ detailRow.cabinNo || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>预计发货日期</span
|
||||||
|
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>预计完成日期</span
|
||||||
|
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
<span>{{ waybillVehicleNoLabel(detailRow) }}</span>
|
<span>{{ waybillVehicleNoLabel(detailRow) }}</span>
|
||||||
<strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
<strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>里程(km)</span
|
||||||
|
><strong>{{ waybillDetailMileage(detailRow) || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>计划发货日期</span
|
||||||
|
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>计划完成日期</span
|
||||||
|
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
class="waybill-manage-page__waybill-detail-field"
|
||||||
@@ -2851,8 +2876,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
|
||||||
<div ref="transportAmap" class="waybill-manage-page__map"></div>
|
<div ref="transportAmap" class="waybill-manage-page__map"></div>
|
||||||
|
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||||
</div>
|
</div>
|
||||||
<div class="waybill-manage-page__map-info">
|
<div class="waybill-manage-page__map-info">
|
||||||
<span>{{ transportMapStatus }}</span>
|
<span>{{ transportMapStatus }}</span>
|
||||||
@@ -3112,6 +3137,9 @@ import {
|
|||||||
import { getPunchRecords as getWaybillPunchRecords, locateVehicle, trackVehicle } from '@/api/business/waybill-manage';
|
import { getPunchRecords as getWaybillPunchRecords, locateVehicle, trackVehicle } from '@/api/business/waybill-manage';
|
||||||
import { getMkPublicDetail } from '@/api/mk-process';
|
import { getMkPublicDetail } from '@/api/mk-process';
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
|
import {
|
||||||
|
fetchDriversByCarrierOrganizations,
|
||||||
|
} from '@/utils/carrier-org-resource';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { addressTypeOptions } from '@/option/base/common-address';
|
import { addressTypeOptions } from '@/option/base/common-address';
|
||||||
@@ -3120,11 +3148,12 @@ import { formatUpdateUserName } from '@/utils/audit';
|
|||||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { openImportDialog } from '@/utils/import-excel';
|
import { openImportDialog } from '@/utils/import-excel';
|
||||||
|
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import { InfoFilled, Location, OfficeBuilding, Search, WarnTriangleFilled } from '@element-plus/icons-vue';
|
import { InfoFilled, Location, OfficeBuilding, Search, WarnTriangleFilled } from '@element-plus/icons-vue';
|
||||||
import { ElImageViewer } from 'element-plus';
|
import { ElAutocomplete, ElImageViewer } from 'element-plus';
|
||||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
import {
|
import {
|
||||||
fallbackPlugin,
|
fallbackPlugin,
|
||||||
@@ -3634,6 +3663,7 @@ export default {
|
|||||||
this.detailPageLocked = this.isStandaloneWaybillDetailPage;
|
this.detailPageLocked = this.isStandaloneWaybillDetailPage;
|
||||||
this.formModeLocked = this.$route.query.mode || '';
|
this.formModeLocked = this.$route.query.mode || '';
|
||||||
if (this.isPublicWaybillView) return;
|
if (this.isPublicWaybillView) return;
|
||||||
|
this.setupSearchAutocompletes();
|
||||||
if (this.config.enableAllDept && this.isAdmin) {
|
if (this.config.enableAllDept && this.isAdmin) {
|
||||||
this.allDept = 1;
|
this.allDept = 1;
|
||||||
}
|
}
|
||||||
@@ -4292,8 +4322,12 @@ export default {
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// 进行中的运单隐藏取消(与 config.canCancel / isInProgressStatus 对齐)
|
||||||
|
if (typeof this.config.canCancel === 'function' && !this.config.canCancel(row)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const status = this.statusValue(row);
|
const status = this.statusValue(row);
|
||||||
return ['pending', 'processing', 'running'].includes(status);
|
return status === 'pending';
|
||||||
},
|
},
|
||||||
canComplete(row) {
|
canComplete(row) {
|
||||||
if (
|
if (
|
||||||
@@ -4524,6 +4558,42 @@ export default {
|
|||||||
waybillIsCarrier(row = {}) {
|
waybillIsCarrier(row = {}) {
|
||||||
return String(row.carrierTypeName || row.carrierType || '').trim() === '承运商';
|
return String(row.carrierTypeName || row.carrierType || '').trim() === '承运商';
|
||||||
},
|
},
|
||||||
|
waybillDetailTaskInfo(row = {}) {
|
||||||
|
return this.parseJsonObject(row.taskInfoJson || row.taskInfo || {});
|
||||||
|
},
|
||||||
|
waybillDetailMileage(row = {}) {
|
||||||
|
const taskInfo = this.waybillDetailTaskInfo(row);
|
||||||
|
const goodsRows = this.parseJsonArray(row.goodsJson);
|
||||||
|
const firstGoods = goodsRows[0] || {};
|
||||||
|
return this.normalizeMileageValue(
|
||||||
|
row.mileage || taskInfo.mileage || firstGoods.mileage || ''
|
||||||
|
);
|
||||||
|
},
|
||||||
|
waybillDetailEstimatedStartTime(row = {}) {
|
||||||
|
const taskInfo = this.waybillDetailTaskInfo(row);
|
||||||
|
const value =
|
||||||
|
row.estimatedStartTime ||
|
||||||
|
row.planStartDate ||
|
||||||
|
taskInfo.estimatedStartTime ||
|
||||||
|
taskInfo.planStartDate ||
|
||||||
|
'';
|
||||||
|
return this.formatWaybillDetailDate(value);
|
||||||
|
},
|
||||||
|
waybillDetailEstimatedEndTime(row = {}) {
|
||||||
|
const taskInfo = this.waybillDetailTaskInfo(row);
|
||||||
|
const value =
|
||||||
|
row.estimatedEndTime ||
|
||||||
|
row.planEndDate ||
|
||||||
|
taskInfo.estimatedEndTime ||
|
||||||
|
taskInfo.planEndDate ||
|
||||||
|
'';
|
||||||
|
return this.formatWaybillDetailDate(value);
|
||||||
|
},
|
||||||
|
formatWaybillDetailDate(value) {
|
||||||
|
const text = String(value || '').trim();
|
||||||
|
if (!text) return '';
|
||||||
|
return text.slice(0, 10);
|
||||||
|
},
|
||||||
waybillVehicleNoLabel(row = {}) {
|
waybillVehicleNoLabel(row = {}) {
|
||||||
return this.waybillIsRoadTransport(row) ? '车牌号' : '船/航/班列号';
|
return this.waybillIsRoadTransport(row) ? '车牌号' : '船/航/班列号';
|
||||||
},
|
},
|
||||||
@@ -6398,6 +6468,15 @@ export default {
|
|||||||
this.form.carrierName = '';
|
this.form.carrierName = '';
|
||||||
this.form.carrierId = '';
|
this.form.carrierId = '';
|
||||||
this.form.carrierContractId = '';
|
this.form.carrierContractId = '';
|
||||||
|
this.form.driverId = '';
|
||||||
|
this.form.driverName = '';
|
||||||
|
this.form.driverPhone = '';
|
||||||
|
this.form.vehicleNo = '';
|
||||||
|
this.form.trailerVehicleNo = '';
|
||||||
|
this.form.escortName = '';
|
||||||
|
this.form.escortPhone = '';
|
||||||
|
this.taskDriverOptions = [];
|
||||||
|
this.taskEscortOptions = [];
|
||||||
if (nextValue === '承运商') {
|
if (nextValue === '承运商') {
|
||||||
this.form.trailerVehicleNo = '';
|
this.form.trailerVehicleNo = '';
|
||||||
this.form.escortName = '';
|
this.form.escortName = '';
|
||||||
@@ -6651,17 +6730,36 @@ export default {
|
|||||||
this.form.carrierContractId = carrier?.carrierContractId || '';
|
this.form.carrierContractId = carrier?.carrierContractId || '';
|
||||||
this.form.carrierId = carrier?.carrierId || '';
|
this.form.carrierId = carrier?.carrierId || '';
|
||||||
this.form.carrierName = carrier?.carrierName || '';
|
this.form.carrierName = carrier?.carrierName || '';
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
this.form.carrierId = carrier?.carrierContractId ? '' : carrier?.id || '';
|
this.form.carrierId = carrier?.carrierContractId ? '' : carrier?.id || '';
|
||||||
this.form.carrierName = value || '';
|
this.form.carrierName = value || '';
|
||||||
|
}
|
||||||
|
// 承运商变更后清空司机/车辆,避免跨组织脏数据
|
||||||
|
this.form.driverId = '';
|
||||||
|
this.form.driverName = '';
|
||||||
|
this.form.driverPhone = '';
|
||||||
|
this.form.vehicleNo = '';
|
||||||
|
this.form.trailerVehicleNo = '';
|
||||||
|
this.form.escortName = '';
|
||||||
|
this.form.escortPhone = '';
|
||||||
|
this.taskDriverOptions = [];
|
||||||
|
this.taskEscortOptions = [];
|
||||||
|
},
|
||||||
|
getTaskCarrierFilter() {
|
||||||
|
return {
|
||||||
|
carrierId: this.form.carrierId || '',
|
||||||
|
carrierName: this.form.carrierName || '',
|
||||||
|
};
|
||||||
},
|
},
|
||||||
loadTaskDriverOptions() {
|
loadTaskDriverOptions() {
|
||||||
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
return getDriverList(1, 9999, { posts: '司机' })
|
return fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 9999, posts: '司机' },
|
||||||
this.taskDriverOptions = extractRecords(res);
|
this.getTaskCarrierFilter()
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
|
this.taskDriverOptions = records;
|
||||||
return this.taskDriverOptions;
|
return this.taskDriverOptions;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -6670,10 +6768,17 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskDriverSuggestions(queryString, callback) {
|
fetchTaskDriverSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
|
const carrier = this.getTaskCarrierFilter();
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||||
const records = extractRecords(res);
|
carrier
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
this.taskDriverOptions = records;
|
this.taskDriverOptions = records;
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
records.map(item => ({
|
||||||
@@ -6692,10 +6797,17 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskEscortSuggestions(queryString, callback) {
|
fetchTaskEscortSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
|
const carrier = this.getTaskCarrierFilter();
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.taskEscortLoading = true;
|
this.taskEscortLoading = true;
|
||||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
||||||
const records = extractRecords(res);
|
carrier
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
this.taskEscortOptions = records;
|
this.taskEscortOptions = records;
|
||||||
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
||||||
})
|
})
|
||||||
@@ -7835,12 +7947,7 @@ export default {
|
|||||||
this.ensureTransportAmapGeocoder()
|
this.ensureTransportAmapGeocoder()
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||||
id: item.id || index,
|
|
||||||
name: item.formattedAddress || keyword,
|
|
||||||
address: item.formattedAddress || keyword,
|
|
||||||
location: item.location,
|
|
||||||
}));
|
|
||||||
const point = this.resolveMapPoint(result);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.transportMapStatus = '未找到匹配地址';
|
this.transportMapStatus = '未找到匹配地址';
|
||||||
@@ -8463,6 +8570,123 @@ export default {
|
|||||||
this.page.currentPage = 1;
|
this.page.currentPage = 1;
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
},
|
},
|
||||||
|
setupSearchAutocompletes() {
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
prop: 'projectName',
|
||||||
|
placeholder: '请选择或输入项目名称',
|
||||||
|
fetch: (queryString, callback) =>
|
||||||
|
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||||
|
request: keyword =>
|
||||||
|
getProjectList(1, 20, keyword ? { projectName: keyword } : {}),
|
||||||
|
getValue: item => item.projectName || item.name || '',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'customerName',
|
||||||
|
placeholder: '请选择或输入客户名称',
|
||||||
|
fetch: (queryString, callback) =>
|
||||||
|
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||||
|
request: keyword =>
|
||||||
|
getCustomerArchiveList(1, 20, {
|
||||||
|
...(keyword ? { fullName: keyword } : {}),
|
||||||
|
customerType: '客户',
|
||||||
|
}),
|
||||||
|
getValue: item => item.fullName || item.customerName || item.name || '',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'cargoType',
|
||||||
|
placeholder: '请选择或输入货物类型',
|
||||||
|
fetch: (queryString, callback) =>
|
||||||
|
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||||
|
request: keyword =>
|
||||||
|
getCargoTypeList(1, 20, keyword ? { cargoName: keyword } : {}),
|
||||||
|
getValue: item => item.cargoName || item.typeName || item.name || item.label || '',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'carrierName',
|
||||||
|
placeholder: '请选择或输入承运商名称',
|
||||||
|
fetch: (queryString, callback) =>
|
||||||
|
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||||
|
request: keyword =>
|
||||||
|
getCustomerArchiveList(1, 20, {
|
||||||
|
...(keyword ? { fullName: keyword } : {}),
|
||||||
|
customerType: '承运商',
|
||||||
|
}),
|
||||||
|
getValue: item =>
|
||||||
|
item.fullName || item.carrierName || item.customerName || item.name || '',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'driverName',
|
||||||
|
placeholder: '请选择或输入司机名称',
|
||||||
|
fetch: (queryString, callback) =>
|
||||||
|
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||||
|
request: keyword =>
|
||||||
|
getDriverList(1, 20, {
|
||||||
|
...(keyword ? { driverName: keyword } : {}),
|
||||||
|
posts: '司机',
|
||||||
|
}),
|
||||||
|
getValue: item => item.driverName || item.name || '',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'planName',
|
||||||
|
placeholder: '请选择或输入计划名称',
|
||||||
|
fetch: (queryString, callback) =>
|
||||||
|
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||||
|
request: keyword =>
|
||||||
|
getShippingPlanList(1, 20, keyword ? { planName: keyword } : {}),
|
||||||
|
getValue: item => item.planName || item.name || '',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
fields.forEach(field => {
|
||||||
|
const column = this.findColumn?.(this.option.column, field.prop);
|
||||||
|
if (!column) return;
|
||||||
|
column.searchPlaceholder = field.placeholder;
|
||||||
|
column.renderSearch = scope =>
|
||||||
|
this.renderSearchAutocomplete(scope, field.prop, field.placeholder, field.fetch);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
renderSearchAutocomplete(scope, prop, placeholder, fetchSuggestions) {
|
||||||
|
return h(ElAutocomplete, {
|
||||||
|
modelValue: scope.row?.[prop] ?? '',
|
||||||
|
'onUpdate:modelValue': value => {
|
||||||
|
if (scope.row) scope.row[prop] = value ?? '';
|
||||||
|
},
|
||||||
|
fetchSuggestions,
|
||||||
|
debounce: 300,
|
||||||
|
clearable: true,
|
||||||
|
triggerOnFocus: true,
|
||||||
|
placeholder,
|
||||||
|
style: 'width: 100%',
|
||||||
|
valueKey: 'value',
|
||||||
|
fitInputWidth: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetchSearchNameSuggestions(queryString, callback, { request, getValue }) {
|
||||||
|
const keyword = String(queryString || '').trim();
|
||||||
|
Promise.resolve(request(keyword))
|
||||||
|
.then(res => {
|
||||||
|
const records = extractRecords(res);
|
||||||
|
const seen = new Set();
|
||||||
|
const options = [];
|
||||||
|
records.forEach(item => {
|
||||||
|
const value = String(getValue(item) || '').trim();
|
||||||
|
if (!value || seen.has(value)) return;
|
||||||
|
seen.add(value);
|
||||||
|
options.push({ ...item, value });
|
||||||
|
});
|
||||||
|
callback(options);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
window.console.log(error);
|
||||||
|
callback([]);
|
||||||
|
});
|
||||||
|
},
|
||||||
searchChange(params, done) {
|
searchChange(params, done) {
|
||||||
this.query = {
|
this.query = {
|
||||||
...params,
|
...params,
|
||||||
@@ -8728,10 +8952,21 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchReassignDriverSuggestions(queryString, callback) {
|
fetchReassignDriverSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
|
const row = this.reassignDrawer.row || {};
|
||||||
|
const carrier = {
|
||||||
|
carrierId: row.carrierId || '',
|
||||||
|
carrierName: row.carrierName || '',
|
||||||
|
};
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.reassignDrawer.driverLoading = true;
|
this.reassignDrawer.driverLoading = true;
|
||||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||||
const records = extractRecords(res);
|
carrier
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
this.reassignDriverOptions = records;
|
this.reassignDriverOptions = records;
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
records.map(item => ({
|
||||||
|
|||||||
@@ -289,29 +289,53 @@
|
|||||||
<el-table-column label="承运类型" prop="carrierType" min-width="120" show-overflow-tooltip />
|
<el-table-column label="承运类型" prop="carrierType" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column label="发货地" prop="departureAddress" min-width="180">
|
<el-table-column label="发货地" prop="departureAddress" min-width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip :content="formatFullRouteAddress(row, 'departure')" placement="top">
|
<el-tooltip
|
||||||
|
v-if="formatFullRouteAddress(row, 'departure')"
|
||||||
|
:content="formatFullRouteAddress(row, 'departure')"
|
||||||
|
placement="top"
|
||||||
|
:show-after="120"
|
||||||
|
>
|
||||||
<div class="loading-manage-page__address-cell">
|
<div class="loading-manage-page__address-cell">
|
||||||
{{ formatRouteAddress(row, 'departure') }}
|
{{ formatRouteAddress(row, 'departure') }}
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<div v-else class="loading-manage-page__address-cell">
|
||||||
|
{{ formatRouteAddress(row, 'departure') }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="途经地" prop="transitAddress" min-width="190">
|
<el-table-column label="途经地" prop="transitAddress" min-width="190">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip :content="formatFullRouteAddress(row, 'transit')" placement="top">
|
<el-tooltip
|
||||||
|
v-if="formatFullRouteAddress(row, 'transit')"
|
||||||
|
:content="formatFullRouteAddress(row, 'transit')"
|
||||||
|
placement="top"
|
||||||
|
:show-after="120"
|
||||||
|
>
|
||||||
<div class="loading-manage-page__address-cell">
|
<div class="loading-manage-page__address-cell">
|
||||||
{{ formatRouteAddress(row, 'transit') }}
|
{{ formatRouteAddress(row, 'transit') }}
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<div v-else class="loading-manage-page__address-cell">
|
||||||
|
{{ formatRouteAddress(row, 'transit') }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="到货地" prop="arrivalAddress" min-width="180">
|
<el-table-column label="到货地" prop="arrivalAddress" min-width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip :content="formatFullRouteAddress(row, 'arrival')" placement="top">
|
<el-tooltip
|
||||||
|
v-if="formatFullRouteAddress(row, 'arrival')"
|
||||||
|
:content="formatFullRouteAddress(row, 'arrival')"
|
||||||
|
placement="top"
|
||||||
|
:show-after="120"
|
||||||
|
>
|
||||||
<div class="loading-manage-page__address-cell">
|
<div class="loading-manage-page__address-cell">
|
||||||
{{ formatRouteAddress(row, 'arrival') }}
|
{{ formatRouteAddress(row, 'arrival') }}
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<div v-else class="loading-manage-page__address-cell">
|
||||||
|
{{ formatRouteAddress(row, 'arrival') }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="承运商" prop="carrierName" min-width="160" show-overflow-tooltip />
|
<el-table-column label="承运商" prop="carrierName" min-width="160" show-overflow-tooltip />
|
||||||
@@ -386,26 +410,74 @@
|
|||||||
v-if="isStandalonePage"
|
v-if="isStandalonePage"
|
||||||
class="loading-manage-dialog loading-manage-form-dialog loading-manage-form-page"
|
class="loading-manage-dialog loading-manage-form-dialog loading-manage-form-page"
|
||||||
>
|
>
|
||||||
<div class="archive-page-form__title">{{
|
<div v-if="!isStandaloneDetailPage" class="archive-page-form__title">{{ formPageTitle }}</div>
|
||||||
isStandaloneDetailPage ? detailPageTitle : formPageTitle
|
|
||||||
}}</div>
|
|
||||||
<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">
|
<div v-if="dialogReadonly" class="loading-detail">
|
||||||
<section-card title="配载单详情">
|
<section-card title="配载单详情">
|
||||||
<div class="loading-detail__summary">
|
<div class="loading-detail__summary">
|
||||||
<div class="loading-detail__heading">配载单详情</div>
|
|
||||||
<div>{{ dialogForm.loadingNo || '-' }}</div>
|
<div>{{ dialogForm.loadingNo || '-' }}</div>
|
||||||
<el-tag :type="detailStatusType">{{ statusText(dialogForm) }}</el-tag>
|
<el-tag :type="detailStatusType">{{ statusText(dialogForm) }}</el-tag>
|
||||||
<el-tag type="primary">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
|
<el-tag type="primary">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-detail__task-row">
|
<div class="loading-detail__task-row">
|
||||||
|
<div class="loading-detail__task-main">
|
||||||
|
<span class="loading-detail__label">任务信息</span>
|
||||||
|
<div class="loading-detail__task-grid">
|
||||||
<div class="loading-detail__field">
|
<div class="loading-detail__field">
|
||||||
<span class="loading-detail__label">任务信息</span
|
<span class="loading-detail__label">承运商</span>
|
||||||
><span class="loading-detail__value">{{ taskSummary }}</span>
|
<span class="loading-detail__value">{{ detailTaskField('carrierName') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-detail__field">
|
<div class="loading-detail__field">
|
||||||
<span class="loading-detail__label">附件</span
|
<span class="loading-detail__label">司机</span>
|
||||||
><span class="loading-detail__value"
|
<span class="loading-detail__value">{{ detailTaskField('driverName') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__field">
|
||||||
|
<span class="loading-detail__label">车牌号</span>
|
||||||
|
<span class="loading-detail__value">{{ detailTaskField('vehicleNo') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__field">
|
||||||
|
<span class="loading-detail__label">手机号</span>
|
||||||
|
<span class="loading-detail__value">{{ detailTaskField('driverPhone') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__field">
|
||||||
|
<span class="loading-detail__label">里程</span>
|
||||||
|
<span class="loading-detail__value">{{
|
||||||
|
detailTaskMileage ? `${detailTaskMileage} km` : '-'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__field">
|
||||||
|
<span class="loading-detail__label">预计发货日期</span>
|
||||||
|
<span class="loading-detail__value">{{
|
||||||
|
detailTaskDate('estimatedStartDate')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__field">
|
||||||
|
<span class="loading-detail__label">预计完成日期</span>
|
||||||
|
<span class="loading-detail__value">{{
|
||||||
|
detailTaskDate('estimatedEndDate')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<template v-if="isSelfCarrierDetail">
|
||||||
|
<div class="loading-detail__field">
|
||||||
|
<span class="loading-detail__label">挂车车牌号</span>
|
||||||
|
<span class="loading-detail__value">{{
|
||||||
|
detailTaskField('trailerVehicleNo')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__field">
|
||||||
|
<span class="loading-detail__label">押运人</span>
|
||||||
|
<span class="loading-detail__value">{{ detailTaskField('escortName') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__field">
|
||||||
|
<span class="loading-detail__label">押运人手机号</span>
|
||||||
|
<span class="loading-detail__value">{{ detailTaskField('escortPhone') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="loading-detail__field loading-detail__attachment-field">
|
||||||
|
<span class="loading-detail__label">附件</span>
|
||||||
|
<span class="loading-detail__value"
|
||||||
><el-link
|
><el-link
|
||||||
v-for="file in detailAttachments"
|
v-for="file in detailAttachments"
|
||||||
:key="file.url || file.name"
|
:key="file.url || file.name"
|
||||||
@@ -441,7 +513,19 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-detail__step-address">
|
<div class="loading-detail__step-address">
|
||||||
{{ node.displayAddress || formatAddressText(node.address) }}
|
<el-tooltip
|
||||||
|
v-if="formatDetailRouteTooltip(node)"
|
||||||
|
:content="formatDetailRouteTooltip(node)"
|
||||||
|
placement="top"
|
||||||
|
:show-after="120"
|
||||||
|
>
|
||||||
|
<span class="loading-detail__step-address-text">{{
|
||||||
|
formatDetailRouteShort(node)
|
||||||
|
}}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-else class="loading-detail__step-address-text">{{
|
||||||
|
formatDetailRouteShort(node)
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-detail__step-tags">
|
<div class="loading-detail__step-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>
|
||||||
@@ -1175,9 +1259,10 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="里程(km)">
|
<el-form-item label="里程(km)">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="dialogForm.mileage"
|
:model-value="normalizeLoadingMileage(dialogForm.mileage)"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
|
:disabled="dialogReadonly"
|
||||||
@input="handleMileageInput"
|
@input="handleMileageInput"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -1408,6 +1493,7 @@ import { getList as getContractList } from '@/api/business/contract-manage';
|
|||||||
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 { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
||||||
import { getList as getProcessConfigList } from '@/api/business/process-config';
|
import { getList as getProcessConfigList } from '@/api/business/process-config';
|
||||||
import { getList as getCommonAddressList } from '@/api/base/common-address';
|
import { getList as getCommonAddressList } from '@/api/base/common-address';
|
||||||
import {
|
import {
|
||||||
@@ -1427,6 +1513,7 @@ const defaultCandidateTransportType = '';
|
|||||||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||||||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||||
const loadingCreateWaybillsStorageKey = 'loading-manage-create-waybills';
|
const loadingCreateWaybillsStorageKey = 'loading-manage-create-waybills';
|
||||||
|
const loadingManageListRefreshKey = 'loading-manage-list-refresh';
|
||||||
let detailAmapLoader;
|
let detailAmapLoader;
|
||||||
|
|
||||||
const createSearchForm = () => ({
|
const createSearchForm = () => ({
|
||||||
@@ -1511,6 +1598,14 @@ const normalizeLoadingMileage = value => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 提交时未填写里程传 null,禁止自动填充 -1
|
||||||
|
const toSubmitMileage = value => {
|
||||||
|
const normalized = normalizeLoadingMileage(value);
|
||||||
|
if (normalized === '') return null;
|
||||||
|
const number = Number(normalized);
|
||||||
|
return Number.isFinite(number) ? number : null;
|
||||||
|
};
|
||||||
|
|
||||||
const createCopiedDialogForm = (detail = {}) => {
|
const createCopiedDialogForm = (detail = {}) => {
|
||||||
const form = createDialogForm();
|
const form = createDialogForm();
|
||||||
Object.keys(form).forEach(prop => {
|
Object.keys(form).forEach(prop => {
|
||||||
@@ -1691,6 +1786,12 @@ export default {
|
|||||||
.join(' / ') || '-'
|
.join(' / ') || '-'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
isSelfCarrierDetail() {
|
||||||
|
return String(this.dialogForm.carrierType || '').trim() === '自运';
|
||||||
|
},
|
||||||
|
detailTaskMileage() {
|
||||||
|
return normalizeLoadingMileage(this.dialogForm.mileage);
|
||||||
|
},
|
||||||
detailAttachments() {
|
detailAttachments() {
|
||||||
return parseJsonArray(this.dialogForm.attachmentsJson || this.dialogForm.attachmentJson).map(
|
return parseJsonArray(this.dialogForm.attachmentsJson || this.dialogForm.attachmentJson).map(
|
||||||
item =>
|
item =>
|
||||||
@@ -1748,12 +1849,29 @@ export default {
|
|||||||
deactivated() {
|
deactivated() {
|
||||||
this.closeInnerDialogs();
|
this.closeInnerDialogs();
|
||||||
},
|
},
|
||||||
|
activated() {
|
||||||
|
if (this.isStandalonePage) return;
|
||||||
|
if (sessionStorage.getItem(loadingManageListRefreshKey)) {
|
||||||
|
sessionStorage.removeItem(loadingManageListRefreshKey);
|
||||||
|
this.loadTable();
|
||||||
|
}
|
||||||
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.closeInnerDialogs();
|
this.closeInnerDialogs();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route(to, from) {
|
$route(to, from) {
|
||||||
if (!this.isStandalonePage) return;
|
if (!this.isStandalonePage) {
|
||||||
|
// 从表单/详情返回列表时刷新,避免草稿确认后仍显示旧状态
|
||||||
|
if (
|
||||||
|
from?.path?.includes('/loading-manage/form') ||
|
||||||
|
from?.path?.includes('/loading-manage/detail')
|
||||||
|
) {
|
||||||
|
sessionStorage.removeItem(loadingManageListRefreshKey);
|
||||||
|
this.loadTable();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.syncStandaloneTagTitle();
|
this.syncStandaloneTagTitle();
|
||||||
if (this.isStandaloneDetailPage) {
|
if (this.isStandaloneDetailPage) {
|
||||||
// 记录变化或上一个页面不是详情态(例如从表单页/列表页进来)时重新拉取详情
|
// 记录变化或上一个页面不是详情态(例如从表单页/列表页进来)时重新拉取详情
|
||||||
@@ -1982,6 +2100,157 @@ export default {
|
|||||||
value.includes('highway')
|
value.includes('highway')
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
// 与总单详情一致:公路地址压缩为「市 区县」
|
||||||
|
formatRoadLocation(value) {
|
||||||
|
const text = String(value || '')
|
||||||
|
.replace(/[\//\s]+/g, '')
|
||||||
|
.trim();
|
||||||
|
if (!text) return '';
|
||||||
|
|
||||||
|
const parts = this.parseWaybillAddress(text);
|
||||||
|
let city = String(parts.city || '').trim();
|
||||||
|
let district = String(parts.district || '').trim();
|
||||||
|
let detail = String(parts.detailAddress || '').trim();
|
||||||
|
|
||||||
|
// 自治州/地区/盟下还有「XX市 + 区县」时,以市级作为展示主体
|
||||||
|
if (/(?:自治州|地区|盟)$/.test(city) && detail) {
|
||||||
|
const nestedCityMatch = detail.match(/^(.+?市)/);
|
||||||
|
if (nestedCityMatch) {
|
||||||
|
city = nestedCityMatch[1];
|
||||||
|
detail = detail.slice(nestedCityMatch[1].length);
|
||||||
|
const nestedDistrictMatch = detail.match(
|
||||||
|
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
|
||||||
|
);
|
||||||
|
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
|
||||||
|
} else if (!district) {
|
||||||
|
const nestedDistrictMatch = detail.match(
|
||||||
|
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
|
||||||
|
);
|
||||||
|
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 市级后紧跟区县,但 parse 未拆出时再补一次
|
||||||
|
if (city && !district && detail) {
|
||||||
|
const nestedDistrictMatch = detail.match(
|
||||||
|
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
|
||||||
|
);
|
||||||
|
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (city && district) {
|
||||||
|
// 兼容「梅县区」:命中「县」后还需保留后面的「区」
|
||||||
|
if (district.endsWith('县') && detail.startsWith(`${district}区`)) {
|
||||||
|
district = `${district}区`;
|
||||||
|
}
|
||||||
|
return `${city} ${district}`;
|
||||||
|
}
|
||||||
|
if (city) return city;
|
||||||
|
if (district) return district;
|
||||||
|
|
||||||
|
// 兜底压缩
|
||||||
|
const withoutProvince = text.replace(/^.*?(?:省|自治区|特别行政区)/, '');
|
||||||
|
const cityMatch = withoutProvince.match(/.+?(?:市|自治州|州(?!市)|盟(?!市)|地区)/);
|
||||||
|
if (!cityMatch) return text;
|
||||||
|
const fallbackCity = cityMatch[0];
|
||||||
|
const districtSource = withoutProvince.slice(fallbackCity.length);
|
||||||
|
const districtMatch = districtSource.match(
|
||||||
|
/^[^市州盟]*?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/
|
||||||
|
);
|
||||||
|
if (!districtMatch) {
|
||||||
|
// 州后面是县级市时,继续解析市 + 区
|
||||||
|
const nestedCityMatch = districtSource.match(/^(.+?市)/);
|
||||||
|
if (nestedCityMatch) {
|
||||||
|
const nestedDistrictSource = districtSource.slice(nestedCityMatch[1].length);
|
||||||
|
const nestedDistrictMatch = nestedDistrictSource.match(
|
||||||
|
/^[^市州盟]*?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/
|
||||||
|
);
|
||||||
|
if (nestedDistrictMatch) {
|
||||||
|
return `${nestedCityMatch[1]} ${nestedDistrictMatch[0]}`;
|
||||||
|
}
|
||||||
|
return nestedCityMatch[1];
|
||||||
|
}
|
||||||
|
return fallbackCity;
|
||||||
|
}
|
||||||
|
const fallbackDistrict = `${districtMatch[0]}${
|
||||||
|
districtSource.slice(districtMatch[0].length).startsWith('区') ? '区' : ''
|
||||||
|
}`;
|
||||||
|
return `${fallbackCity} ${fallbackDistrict}`;
|
||||||
|
},
|
||||||
|
isStationLikeName(value) {
|
||||||
|
const text = String(value || '').trim();
|
||||||
|
if (!text) return false;
|
||||||
|
return (
|
||||||
|
/(?:火车站|高铁站|客运站|货运站|港口|码头|机场|空港|航站楼)$/.test(text) ||
|
||||||
|
/(?<!市|州|盟|区|县|旗)站$/.test(text)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// 详情运输路线短展示:公路仅市区,非公路仅名称
|
||||||
|
formatDetailRouteShort(node = {}) {
|
||||||
|
const name = String(node.name || '').trim();
|
||||||
|
const address = String(node.address || '').trim();
|
||||||
|
const displayAddress = String(node.displayAddress || '').trim();
|
||||||
|
if (!this.isRoadTransportType(this.dialogForm.transportType)) {
|
||||||
|
if (name) return name;
|
||||||
|
if (this.isStationLikeName(displayAddress)) return displayAddress;
|
||||||
|
if (this.isStationLikeName(address)) return address;
|
||||||
|
return displayAddress || address || '-';
|
||||||
|
}
|
||||||
|
const enriched = this.enrichRouteNodeRegion(node);
|
||||||
|
// 合并节点地址与运单上的市/区字段,避免只解析到市
|
||||||
|
const region = this.mergeRouteAddressParts(enriched.cityName, enriched.districtName, '');
|
||||||
|
const source = this.mergeRouteAddressParts(
|
||||||
|
address,
|
||||||
|
displayAddress,
|
||||||
|
region,
|
||||||
|
enriched.name || name
|
||||||
|
);
|
||||||
|
const formatted = this.formatRoadLocation(source);
|
||||||
|
if (enriched.cityName && enriched.districtName) {
|
||||||
|
return `${enriched.cityName} ${enriched.districtName}`;
|
||||||
|
}
|
||||||
|
if (/(?:市|州|盟|区|县|旗)/.test(formatted)) return formatted;
|
||||||
|
return formatted || name || displayAddress || address || '-';
|
||||||
|
},
|
||||||
|
// 公路悬停展示完整地址;非公路不展示 tooltip
|
||||||
|
formatDetailRouteTooltip(node = {}) {
|
||||||
|
if (!this.isRoadTransportType(this.dialogForm.transportType)) return '';
|
||||||
|
const enriched = this.enrichRouteNodeRegion(node);
|
||||||
|
const region = this.mergeRouteAddressParts(enriched.cityName, enriched.districtName, '');
|
||||||
|
const full = this.mergeRouteAddressParts(
|
||||||
|
String(node.address || '').trim(),
|
||||||
|
String(node.displayAddress || '').trim(),
|
||||||
|
region,
|
||||||
|
String(node.name || enriched.name || '').trim()
|
||||||
|
);
|
||||||
|
const short = this.formatDetailRouteShort(node);
|
||||||
|
if (!full) return '';
|
||||||
|
return full === short ? '' : full;
|
||||||
|
},
|
||||||
|
// 按装/卸标签从已选运单回填市、区,补充 routeJson 中缺失的区县
|
||||||
|
enrichRouteNodeRegion(node = {}) {
|
||||||
|
const tags = Array.isArray(node.tags) ? node.tags : [];
|
||||||
|
const result = { cityName: '', districtName: '', name: '' };
|
||||||
|
for (const tag of tags) {
|
||||||
|
const text = String(tag || '');
|
||||||
|
const loadMatch = text.match(/^装(\d+)$/);
|
||||||
|
const unloadMatch = text.match(/^卸(\d+)$/);
|
||||||
|
const index = Number((loadMatch || unloadMatch)?.[1] || 0) - 1;
|
||||||
|
if (index < 0) continue;
|
||||||
|
const row = this.selectedWaybillRows[index];
|
||||||
|
if (!row) continue;
|
||||||
|
const prefix = loadMatch ? 'departure' : 'arrival';
|
||||||
|
result.cityName = String(
|
||||||
|
row[`${prefix}CityName`] || row[`${prefix}City`] || ''
|
||||||
|
).trim();
|
||||||
|
result.districtName = String(
|
||||||
|
row[`${prefix}DistrictName`] || row[`${prefix}District`] || ''
|
||||||
|
).trim();
|
||||||
|
result.name = String(row[`${prefix}Name`] || '').trim();
|
||||||
|
if (result.cityName || result.districtName || result.name) break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
splitText(value) {
|
splitText(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
return String(value)
|
return String(value)
|
||||||
@@ -2019,37 +2288,72 @@ export default {
|
|||||||
formatRouteAddress(row, type) {
|
formatRouteAddress(row, type) {
|
||||||
const prefix =
|
const prefix =
|
||||||
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
|
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
|
||||||
if (type !== 'transit') {
|
if (type === 'transit') {
|
||||||
const address = row[`${prefix}Address`];
|
const values = this.splitText(row.transitAddress || row.transitName || '')
|
||||||
const region = this.routeProvinceName(row, prefix);
|
.map(value => this.formatListRouteShort(row, value, value))
|
||||||
const district = row[`${prefix}DistrictName`];
|
.filter(value => value && value !== '-');
|
||||||
const source = this.mergeRouteAddressParts(region, district, address);
|
return [...new Set(values)].join(',') || '-';
|
||||||
return this.formatProvinceCityDistrict(source) || '-';
|
|
||||||
}
|
}
|
||||||
const values = [
|
const name = String(row[`${prefix}Name`] || '').trim();
|
||||||
row[`${prefix}RegionName`],
|
const address = String(row[`${prefix}Address`] || '').trim();
|
||||||
row[`${prefix}DistrictName`],
|
const cityName = String(
|
||||||
row[`${prefix}Address`],
|
row[`${prefix}CityName`] || row[`${prefix}City`] || ''
|
||||||
]
|
).trim();
|
||||||
.filter(Boolean)
|
const districtName = String(
|
||||||
.flatMap(value => this.splitText(value))
|
row[`${prefix}DistrictName`] || row[`${prefix}District`] || ''
|
||||||
.map(value => this.formatProvinceCityDistrict(value))
|
).trim();
|
||||||
.filter(Boolean);
|
if (
|
||||||
return [...new Set(values)].join(',') || '-';
|
this.isRoadTransportType(row?.transportType) &&
|
||||||
|
cityName &&
|
||||||
|
districtName
|
||||||
|
) {
|
||||||
|
return `${cityName} ${districtName}`;
|
||||||
|
}
|
||||||
|
const region = this.mergeRouteAddressParts(
|
||||||
|
this.routeProvinceName(row, prefix),
|
||||||
|
cityName,
|
||||||
|
districtName
|
||||||
|
);
|
||||||
|
const source = this.mergeRouteAddressParts(region, '', address || name);
|
||||||
|
return this.formatListRouteShort(row, name, source || address || name);
|
||||||
},
|
},
|
||||||
formatFullRouteAddress(row, type) {
|
formatFullRouteAddress(row, type) {
|
||||||
|
if (!this.isRoadTransportType(row?.transportType)) return '';
|
||||||
const prefix =
|
const prefix =
|
||||||
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
|
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
|
||||||
if (type !== 'transit') {
|
if (type === 'transit') {
|
||||||
const address = row[`${prefix}Address`];
|
const values = this.splitText(row.transitAddress || '')
|
||||||
const region = this.routeProvinceName(row, prefix);
|
.map(value => String(value || '').trim())
|
||||||
const district = row[`${prefix}DistrictName`];
|
.filter(Boolean);
|
||||||
return this.mergeRouteAddressParts(region, district, address) || '-';
|
const full = [...new Set(values)].join(',');
|
||||||
|
const short = this.formatRouteAddress(row, 'transit');
|
||||||
|
return full && full !== short ? full : '';
|
||||||
}
|
}
|
||||||
const values = [row.transitRegionName, row.transitDistrictName, row.transitAddress]
|
const name = String(row[`${prefix}Name`] || '').trim();
|
||||||
.filter(Boolean)
|
const address = String(row[`${prefix}Address`] || '').trim();
|
||||||
.flatMap(value => this.splitText(value));
|
const region = this.mergeRouteAddressParts(
|
||||||
return [...new Set(values)].join(',') || '-';
|
this.routeProvinceName(row, prefix),
|
||||||
|
row[`${prefix}CityName`] || row[`${prefix}City`],
|
||||||
|
row[`${prefix}DistrictName`] || row[`${prefix}District`]
|
||||||
|
);
|
||||||
|
const full = this.mergeRouteAddressParts(region, '', address || name);
|
||||||
|
const short = this.formatRouteAddress(row, type);
|
||||||
|
if (!full) return '';
|
||||||
|
return full === short ? '' : full;
|
||||||
|
},
|
||||||
|
// 列表短地址:公路「市 区县」,非公路仅名称
|
||||||
|
formatListRouteShort(row = {}, name = '', address = '') {
|
||||||
|
const rawName = String(name || '').trim();
|
||||||
|
const rawAddress = String(address || '').trim();
|
||||||
|
if (!this.isRoadTransportType(row?.transportType)) {
|
||||||
|
if (rawName) return rawName;
|
||||||
|
if (this.isStationLikeName(rawAddress)) return rawAddress;
|
||||||
|
return rawAddress || '-';
|
||||||
|
}
|
||||||
|
const source = rawAddress || rawName;
|
||||||
|
const formatted = this.formatRoadLocation(source);
|
||||||
|
if (/(?:市|州|盟)/.test(formatted)) return formatted;
|
||||||
|
return formatted || rawName || rawAddress || '-';
|
||||||
},
|
},
|
||||||
formatWaybillAddress(row, type) {
|
formatWaybillAddress(row, type) {
|
||||||
const prefix = type === 'departure' ? 'departure' : 'arrival';
|
const prefix = type === 'departure' ? 'departure' : 'arrival';
|
||||||
@@ -2275,7 +2579,8 @@ export default {
|
|||||||
this.dialogForm = row.copy
|
this.dialogForm = row.copy
|
||||||
? createCopiedDialogForm(detail)
|
? createCopiedDialogForm(detail)
|
||||||
: { ...createDialogForm(), ...detail };
|
: { ...createDialogForm(), ...detail };
|
||||||
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
|
this.applyLoadingTaskInfoFromDetail(this.dialogForm);
|
||||||
|
this.applyLoadingMileageFromDetail(this.dialogForm);
|
||||||
await this.restoreWaybillRows(
|
await this.restoreWaybillRows(
|
||||||
this.dialogForm.waybillIdsJson,
|
this.dialogForm.waybillIdsJson,
|
||||||
this.dialogForm.loadingSubNos
|
this.dialogForm.loadingSubNos
|
||||||
@@ -2612,6 +2917,8 @@ export default {
|
|||||||
...this.buildQueryParams(this.candidateQuery),
|
...this.buildQueryParams(this.candidateQuery),
|
||||||
businessStatus: 'pending',
|
businessStatus: 'pending',
|
||||||
onlyUnassignedLoading: 1,
|
onlyUnassignedLoading: 1,
|
||||||
|
// 仅查当前账户所属组织及子组织下的运单
|
||||||
|
allDept: 0,
|
||||||
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
|
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
|
||||||
};
|
};
|
||||||
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
|
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
|
||||||
@@ -2706,12 +3013,16 @@ export default {
|
|||||||
.map(row => row.waybillNo)
|
.map(row => row.waybillNo)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(',');
|
.join(',');
|
||||||
this.dialogForm.waybillIdsJson = JSON.stringify(
|
const rowIds = rows
|
||||||
rows
|
|
||||||
.map(row => row.id)
|
.map(row => row.id)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map(id => String(id))
|
.map(id => String(id));
|
||||||
);
|
// 回显失败仅有运单号时,保留原 waybillIdsJson,避免确认提交清空运单绑定
|
||||||
|
if (rowIds.length) {
|
||||||
|
this.dialogForm.waybillIdsJson = JSON.stringify(rowIds);
|
||||||
|
} else if (!String(this.dialogForm.waybillIdsJson || '').trim()) {
|
||||||
|
this.dialogForm.waybillIdsJson = '[]';
|
||||||
|
}
|
||||||
this.dialogForm.projectName = uniqueText(rows, 'projectName');
|
this.dialogForm.projectName = uniqueText(rows, 'projectName');
|
||||||
this.dialogForm.customerName = uniqueText(rows, 'customerName');
|
this.dialogForm.customerName = uniqueText(rows, 'customerName');
|
||||||
this.dialogForm.originalNo = uniqueText(rows, 'originalNo');
|
this.dialogForm.originalNo = uniqueText(rows, 'originalNo');
|
||||||
@@ -2838,15 +3149,73 @@ export default {
|
|||||||
remark: item.remark || '',
|
remark: item.remark || '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
applyLoadingMileageFromDetail(form = this.dialogForm) {
|
||||||
|
let taskMileage = '';
|
||||||
|
try {
|
||||||
|
const taskInfo = JSON.parse(form.taskInfoJson || '{}');
|
||||||
|
taskMileage = taskInfo?.mileage;
|
||||||
|
} catch (error) {
|
||||||
|
taskMileage = '';
|
||||||
|
}
|
||||||
|
form.mileage = normalizeLoadingMileage(
|
||||||
|
form.mileage !== undefined && form.mileage !== null && form.mileage !== ''
|
||||||
|
? form.mileage
|
||||||
|
: taskMileage
|
||||||
|
);
|
||||||
|
},
|
||||||
|
applyLoadingTaskInfoFromDetail(form = this.dialogForm) {
|
||||||
|
let taskInfo = {};
|
||||||
|
try {
|
||||||
|
taskInfo = JSON.parse(form.taskInfoJson || '{}') || {};
|
||||||
|
} catch (error) {
|
||||||
|
taskInfo = {};
|
||||||
|
}
|
||||||
|
if (!taskInfo || typeof taskInfo !== 'object' || Array.isArray(taskInfo)) return;
|
||||||
|
const fields = [
|
||||||
|
'carrierType',
|
||||||
|
'carrierName',
|
||||||
|
'carrierContractId',
|
||||||
|
'driverName',
|
||||||
|
'driverPhone',
|
||||||
|
'vehicleNo',
|
||||||
|
'trailerVehicleNo',
|
||||||
|
'escortName',
|
||||||
|
'escortPhone',
|
||||||
|
'estimatedStartDate',
|
||||||
|
'estimatedEndDate',
|
||||||
|
'taskRemark',
|
||||||
|
];
|
||||||
|
fields.forEach(prop => {
|
||||||
|
if (
|
||||||
|
(form[prop] === undefined || form[prop] === null || form[prop] === '') &&
|
||||||
|
taskInfo[prop] !== undefined &&
|
||||||
|
taskInfo[prop] !== null &&
|
||||||
|
taskInfo[prop] !== ''
|
||||||
|
) {
|
||||||
|
form[prop] = taskInfo[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
(form.mileage === undefined || form.mileage === null || form.mileage === '') &&
|
||||||
|
taskInfo.mileage !== undefined
|
||||||
|
) {
|
||||||
|
form.mileage = normalizeLoadingMileage(taskInfo.mileage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detailTaskField(prop) {
|
||||||
|
const value = String(this.dialogForm?.[prop] ?? '').trim();
|
||||||
|
return value || '-';
|
||||||
|
},
|
||||||
|
detailTaskDate(prop) {
|
||||||
|
const value = String(this.dialogForm?.[prop] ?? '').trim();
|
||||||
|
return value ? value.slice(0, 10) : '-';
|
||||||
|
},
|
||||||
normalizePayload() {
|
normalizePayload() {
|
||||||
this.syncRouteAddressFields();
|
this.syncRouteAddressFields();
|
||||||
this.rebuildSummaryFromWaybills(false);
|
this.rebuildSummaryFromWaybills(false);
|
||||||
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
|
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
|
||||||
return {
|
const mileage = toSubmitMileage(this.dialogForm.mileage);
|
||||||
...this.dialogForm,
|
const taskInfo = {
|
||||||
routeJson: JSON.stringify(this.routeNodes),
|
|
||||||
goodsJson: JSON.stringify(this.cargoRows),
|
|
||||||
taskInfoJson: JSON.stringify({
|
|
||||||
carrierType: this.dialogForm.carrierType,
|
carrierType: this.dialogForm.carrierType,
|
||||||
carrierName: this.dialogForm.carrierName,
|
carrierName: this.dialogForm.carrierName,
|
||||||
carrierContractId: this.dialogForm.carrierContractId,
|
carrierContractId: this.dialogForm.carrierContractId,
|
||||||
@@ -2856,11 +3225,19 @@ export default {
|
|||||||
trailerVehicleNo: this.dialogForm.trailerVehicleNo,
|
trailerVehicleNo: this.dialogForm.trailerVehicleNo,
|
||||||
escortName: this.dialogForm.escortName,
|
escortName: this.dialogForm.escortName,
|
||||||
escortPhone: this.dialogForm.escortPhone,
|
escortPhone: this.dialogForm.escortPhone,
|
||||||
mileage: this.dialogForm.mileage,
|
|
||||||
estimatedStartDate: this.dialogForm.estimatedStartDate,
|
estimatedStartDate: this.dialogForm.estimatedStartDate,
|
||||||
estimatedEndDate: this.dialogForm.estimatedEndDate,
|
estimatedEndDate: this.dialogForm.estimatedEndDate,
|
||||||
taskRemark: this.dialogForm.taskRemark,
|
taskRemark: this.dialogForm.taskRemark,
|
||||||
}),
|
};
|
||||||
|
if (mileage !== null) {
|
||||||
|
taskInfo.mileage = mileage;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...this.dialogForm,
|
||||||
|
mileage,
|
||||||
|
routeJson: JSON.stringify(this.routeNodes),
|
||||||
|
goodsJson: JSON.stringify(this.cargoRows),
|
||||||
|
taskInfoJson: JSON.stringify(taskInfo),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
validatePayload(draftMode = false) {
|
validatePayload(draftMode = false) {
|
||||||
@@ -2915,7 +3292,7 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async saveDraft() {
|
async saveDraft() {
|
||||||
if (!this.validatePayload(true)) return;
|
// 暂存不做任何业务校验,直接保存
|
||||||
this.dialogSaving = 'draft';
|
this.dialogSaving = 'draft';
|
||||||
try {
|
try {
|
||||||
const res = await loadingApi.saveDraft(this.normalizePayload());
|
const res = await loadingApi.saveDraft(this.normalizePayload());
|
||||||
@@ -2923,7 +3300,7 @@ export default {
|
|||||||
...this.dialogForm,
|
...this.dialogForm,
|
||||||
...(res.data?.data || res.data || {}),
|
...(res.data?.data || res.data || {}),
|
||||||
};
|
};
|
||||||
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
|
this.applyLoadingMileageFromDetail(this.dialogForm);
|
||||||
ElMessage.success('暂存成功');
|
ElMessage.success('暂存成功');
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -2934,7 +3311,11 @@ export default {
|
|||||||
if (!this.validatePayload(false)) return;
|
if (!this.validatePayload(false)) return;
|
||||||
this.dialogSaving = 'submit';
|
this.dialogSaving = 'submit';
|
||||||
try {
|
try {
|
||||||
const payload = this.normalizePayload();
|
const payload = {
|
||||||
|
...this.normalizePayload(),
|
||||||
|
// 确认生成正式配载单(待执行),覆盖草稿状态
|
||||||
|
businessStatus: 'pending',
|
||||||
|
};
|
||||||
if (this.dialogMode === 'reassign') {
|
if (this.dialogMode === 'reassign') {
|
||||||
await loadingApi.reassign(payload);
|
await loadingApi.reassign(payload);
|
||||||
ElMessage.success('派单成功');
|
ElMessage.success('派单成功');
|
||||||
@@ -2942,6 +3323,7 @@ export default {
|
|||||||
await loadingApi.submit(payload);
|
await loadingApi.submit(payload);
|
||||||
ElMessage.success('确认成功');
|
ElMessage.success('确认成功');
|
||||||
}
|
}
|
||||||
|
sessionStorage.setItem(loadingManageListRefreshKey, '1');
|
||||||
this.closeLoadingDialog();
|
this.closeLoadingDialog();
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -3027,6 +3409,7 @@ export default {
|
|||||||
this.dialogForm.carrierContractId = '';
|
this.dialogForm.carrierContractId = '';
|
||||||
this.dialogForm.carrierName = '';
|
this.dialogForm.carrierName = '';
|
||||||
this.dialogForm.carrierId = '';
|
this.dialogForm.carrierId = '';
|
||||||
|
this.clearDialogDriverVehicleFields();
|
||||||
if (this.dialogForm.carrierType === '承运商') {
|
if (this.dialogForm.carrierType === '承运商') {
|
||||||
this.dialogForm.trailerVehicleNo = '';
|
this.dialogForm.trailerVehicleNo = '';
|
||||||
this.dialogForm.escortName = '';
|
this.dialogForm.escortName = '';
|
||||||
@@ -3046,6 +3429,23 @@ export default {
|
|||||||
this.dialogForm.carrierContractId =
|
this.dialogForm.carrierContractId =
|
||||||
this.dialogForm.carrierType === '承运商' && contract?.id ? contract.id : '';
|
this.dialogForm.carrierType === '承运商' && contract?.id ? contract.id : '';
|
||||||
this.dialogForm.carrierName = contract?.carrierName || '';
|
this.dialogForm.carrierName = contract?.carrierName || '';
|
||||||
|
this.dialogForm.carrierId = contract?.carrierId || contract?.customerId || '';
|
||||||
|
this.clearDialogDriverVehicleFields();
|
||||||
|
},
|
||||||
|
clearDialogDriverVehicleFields() {
|
||||||
|
this.dialogForm.driverName = '';
|
||||||
|
this.dialogForm.driverPhone = '';
|
||||||
|
this.dialogForm.vehicleNo = '';
|
||||||
|
this.dialogForm.trailerVehicleNo = '';
|
||||||
|
this.dialogForm.escortName = '';
|
||||||
|
this.dialogForm.escortPhone = '';
|
||||||
|
this.driverOptions = [];
|
||||||
|
},
|
||||||
|
getDialogCarrierFilter() {
|
||||||
|
return {
|
||||||
|
carrierId: this.dialogForm.carrierId || '',
|
||||||
|
carrierName: this.dialogForm.carrierName || '',
|
||||||
|
};
|
||||||
},
|
},
|
||||||
handleDriverChange(value) {
|
handleDriverChange(value) {
|
||||||
const driver = this.driverOptions.find(item => (item.driverName || item.name) === value);
|
const driver = this.driverOptions.find(item => (item.driverName || item.name) === value);
|
||||||
@@ -3057,7 +3457,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchDriverSuggestions(queryString, callback) {
|
fetchDriverSuggestions(queryString, callback) {
|
||||||
this.loadDriverOptions(String(queryString || '').trim())
|
this.loadDialogDriverOptions(String(queryString || '').trim())
|
||||||
.then(() =>
|
.then(() =>
|
||||||
callback(
|
callback(
|
||||||
this.driverOptions.map(item => ({
|
this.driverOptions.map(item => ({
|
||||||
@@ -3080,11 +3480,19 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchEscortSuggestions(queryString, callback) {
|
fetchEscortSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
|
const carrier = this.getDialogCarrierFilter();
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
callback([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.escortLoading = true;
|
this.escortLoading = true;
|
||||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
fetchDriversByCarrierOrganizations(
|
||||||
.then(res => {
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
||||||
|
carrier
|
||||||
|
)
|
||||||
|
.then(records => {
|
||||||
callback(
|
callback(
|
||||||
unwrapRecords(res).map(item => ({
|
records.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
value: item.driverName || item.name || '',
|
value: item.driverName || item.name || '',
|
||||||
}))
|
}))
|
||||||
@@ -3104,6 +3512,7 @@ export default {
|
|||||||
handleMileageInput(value) {
|
handleMileageInput(value) {
|
||||||
this.dialogForm.mileage = String(value || '').replace(/[^\d.]/g, '');
|
this.dialogForm.mileage = String(value || '').replace(/[^\d.]/g, '');
|
||||||
},
|
},
|
||||||
|
normalizeLoadingMileage,
|
||||||
transportTypeLabel(value) {
|
transportTypeLabel(value) {
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
return (
|
return (
|
||||||
@@ -3153,12 +3562,29 @@ export default {
|
|||||||
this.driverLoading = true;
|
this.driverLoading = true;
|
||||||
try {
|
try {
|
||||||
this.driverOptions = unwrapRecords(
|
this.driverOptions = unwrapRecords(
|
||||||
await getDriverList(1, 20, { driverName: keyword, posts: '司机' })
|
await getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.driverLoading = false;
|
this.driverLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async loadDialogDriverOptions(keyword = '') {
|
||||||
|
const carrier = this.getDialogCarrierFilter();
|
||||||
|
if (!carrier.carrierId && !carrier.carrierName) {
|
||||||
|
this.driverOptions = [];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
this.driverLoading = true;
|
||||||
|
try {
|
||||||
|
this.driverOptions = await fetchDriversByCarrierOrganizations(
|
||||||
|
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||||
|
carrier
|
||||||
|
);
|
||||||
|
return this.driverOptions;
|
||||||
|
} finally {
|
||||||
|
this.driverLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
async loadCarrierOptions(keyword = '') {
|
async loadCarrierOptions(keyword = '') {
|
||||||
this.carrierLoading = true;
|
this.carrierLoading = true;
|
||||||
try {
|
try {
|
||||||
@@ -3220,15 +3646,39 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.dialogForm.carrierContractId = '';
|
this.dialogForm.carrierContractId = '';
|
||||||
this.dialogForm.carrierName = '';
|
this.dialogForm.carrierName = '';
|
||||||
|
this.dialogForm.carrierId = '';
|
||||||
}
|
}
|
||||||
return this.taskCarrierOptions;
|
return this.taskCarrierOptions;
|
||||||
}
|
}
|
||||||
if (this.dialogForm.carrierType === '自运' && contracts.length) {
|
if (this.dialogForm.carrierType === '自运' && contracts.length) {
|
||||||
this.taskCarrierOptions = contracts;
|
this.taskCarrierOptions = contracts
|
||||||
const current = contracts.find(item => item.carrierName === this.dialogForm.carrierName);
|
.map(item => ({
|
||||||
|
...item,
|
||||||
|
carrierName:
|
||||||
|
item.partyB ||
|
||||||
|
item.partyBName ||
|
||||||
|
item.contractPartyB ||
|
||||||
|
item.customerContractPartyB ||
|
||||||
|
item.carrierName ||
|
||||||
|
'',
|
||||||
|
carrierId:
|
||||||
|
item.partyBId ||
|
||||||
|
item.partyBUserId ||
|
||||||
|
item.contractPartyBId ||
|
||||||
|
item.customerContractPartyBId ||
|
||||||
|
item.carrierId ||
|
||||||
|
'',
|
||||||
|
}))
|
||||||
|
.filter(item => item.carrierName);
|
||||||
|
const current = this.taskCarrierOptions.find(
|
||||||
|
item => item.carrierName === this.dialogForm.carrierName
|
||||||
|
);
|
||||||
this.dialogForm.carrierContractId = '';
|
this.dialogForm.carrierContractId = '';
|
||||||
this.dialogForm.carrierName = current?.carrierName || contracts[0].carrierName || '';
|
this.dialogForm.carrierName =
|
||||||
return contracts;
|
current?.carrierName || this.taskCarrierOptions[0]?.carrierName || '';
|
||||||
|
this.dialogForm.carrierId =
|
||||||
|
current?.carrierId || this.taskCarrierOptions[0]?.carrierId || '';
|
||||||
|
return this.taskCarrierOptions;
|
||||||
}
|
}
|
||||||
if (this.dialogForm.carrierType !== '自运') {
|
if (this.dialogForm.carrierType !== '自运') {
|
||||||
this.taskCarrierOptions = [];
|
this.taskCarrierOptions = [];
|
||||||
@@ -3262,6 +3712,7 @@ export default {
|
|||||||
}));
|
}));
|
||||||
this.dialogForm.carrierContractId = '';
|
this.dialogForm.carrierContractId = '';
|
||||||
this.dialogForm.carrierName = partyBNames[0] || '';
|
this.dialogForm.carrierName = partyBNames[0] || '';
|
||||||
|
this.dialogForm.carrierId = '';
|
||||||
return this.taskCarrierOptions;
|
return this.taskCarrierOptions;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (requestId !== this.taskCarrierRequestId) return [];
|
if (requestId !== this.taskCarrierRequestId) return [];
|
||||||
@@ -3611,11 +4062,6 @@ export default {
|
|||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.loading-detail__heading {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
.loading-detail__route-title {
|
.loading-detail__route-title {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -3627,9 +4073,24 @@ export default {
|
|||||||
}
|
}
|
||||||
.loading-detail__task-row {
|
.loading-detail__task-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: minmax(0, 1fr) 220px;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
.loading-detail__task-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.loading-detail__task-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 16px 24px;
|
||||||
|
}
|
||||||
|
.loading-detail__attachment-field {
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.loading-detail__field {
|
.loading-detail__field {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -3645,6 +4106,8 @@ export default {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
color: #303133;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
.loading-detail__steps {
|
.loading-detail__steps {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -3701,6 +4164,14 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
.loading-detail__step-address-text {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
vertical-align: bottom;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
.loading-detail__step-tags {
|
.loading-detail__step-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -3887,5 +4358,9 @@ export default {
|
|||||||
.loading-detail__bottom-grid {
|
.loading-detail__bottom-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-detail__task-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{{ planData.businessStatusName || '-' }}
|
{{ planData.businessStatusName || '-' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="detail-status-text detail-transport-type">
|
<span class="detail-status-text detail-transport-type">
|
||||||
{{ planData.transportTypeName || '公路运输' }}
|
{{ transportTypeLabel }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="transport-plan-dispatch-page__summary-grid">
|
<div class="transport-plan-dispatch-page__summary-grid">
|
||||||
@@ -123,16 +123,20 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-table
|
<el-table
|
||||||
:data="dispatchList"
|
:data="pagedDispatchList"
|
||||||
border
|
border
|
||||||
height="100%"
|
height="100%"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
class="transport-plan-dispatch-page__table"
|
class="transport-plan-dispatch-page__table"
|
||||||
>
|
>
|
||||||
<el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
|
<el-table-column label="序号" width="70" align="center" fixed="left">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ dispatchRowIndex($index) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="运输方式" min-width="180" align="center" show-overflow-tooltip>
|
<el-table-column label="运输方式" min-width="180" align="center" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.transportTypeName || '-' }}
|
{{ formatTransportType(row.transportTypeName || row.transportType) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="carrierType" label="承运类型" min-width="150" align="center" show-overflow-tooltip />
|
<el-table-column prop="carrierType" label="承运类型" min-width="150" align="center" show-overflow-tooltip />
|
||||||
@@ -177,6 +181,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<div class="transport-plan-dispatch-page__pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="dispatchPage.currentPage"
|
||||||
|
v-model:page-size="dispatchPage.pageSize"
|
||||||
|
:page-sizes="dispatchPage.pageSizes"
|
||||||
|
layout="total, sizes, prev, pager, next"
|
||||||
|
:total="dispatchList.length"
|
||||||
|
@current-change="handleDispatchCurrentChange"
|
||||||
|
@size-change="handleDispatchSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<!-- 底部按钮 -->
|
<!-- 底部按钮 -->
|
||||||
@@ -202,6 +217,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getDetail, dispatch } from '@/api/business/transport-plan';
|
import { getDetail, dispatch } from '@/api/business/transport-plan';
|
||||||
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import SectionCard from '@/components/section-card/main.vue';
|
import SectionCard from '@/components/section-card/main.vue';
|
||||||
import TransportPlanPage from './components/transport-plan-page.vue';
|
import TransportPlanPage from './components/transport-plan-page.vue';
|
||||||
import * as api from '@/api/business/transport-plan';
|
import * as api from '@/api/business/transport-plan';
|
||||||
@@ -209,6 +225,12 @@ import { config, option } from '@/option/business/transport-plan';
|
|||||||
|
|
||||||
const TRANSPORT_PLAN_QUANTITY_UNIT = '吨';
|
const TRANSPORT_PLAN_QUANTITY_UNIT = '吨';
|
||||||
|
|
||||||
|
const normalizeDictOptions = (list = []) =>
|
||||||
|
(Array.isArray(list) ? list : []).map(item => ({
|
||||||
|
label: item.dictValue || item.label || '',
|
||||||
|
value: item.dictKey ?? item.value ?? '',
|
||||||
|
}));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TransportPlanDispatch',
|
name: 'TransportPlanDispatch',
|
||||||
components: {
|
components: {
|
||||||
@@ -223,6 +245,12 @@ export default {
|
|||||||
dispatchList: [],
|
dispatchList: [],
|
||||||
attachmentList: [],
|
attachmentList: [],
|
||||||
transportPlanPageVisible: false,
|
transportPlanPageVisible: false,
|
||||||
|
transportTypeOptions: [],
|
||||||
|
dispatchPage: {
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
pageSizes: [10, 20, 50, 100],
|
||||||
|
},
|
||||||
api,
|
api,
|
||||||
config,
|
config,
|
||||||
option,
|
option,
|
||||||
@@ -232,6 +260,15 @@ export default {
|
|||||||
planId() {
|
planId() {
|
||||||
return this.$route.query.planId;
|
return this.$route.query.planId;
|
||||||
},
|
},
|
||||||
|
transportTypeLabel() {
|
||||||
|
return this.formatTransportType(
|
||||||
|
this.planData.transportTypeName || this.planData.transportType
|
||||||
|
);
|
||||||
|
},
|
||||||
|
pagedDispatchList() {
|
||||||
|
const start = (this.dispatchPage.currentPage - 1) * this.dispatchPage.pageSize;
|
||||||
|
return this.dispatchList.slice(start, start + this.dispatchPage.pageSize);
|
||||||
|
},
|
||||||
statusTextClass() {
|
statusTextClass() {
|
||||||
const status = this.planData.businessStatus;
|
const status = this.planData.businessStatus;
|
||||||
if (status === 2 || status === '2') return 'status-text-success';
|
if (status === 2 || status === '2') return 'status-text-success';
|
||||||
@@ -306,13 +343,34 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
this.loadTransportTypeOptions().finally(() => {
|
||||||
this.loadPlanData();
|
this.loadPlanData();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化 transport-plan-page 组件用于打开弹窗
|
// 初始化 transport-plan-page 组件用于打开弹窗
|
||||||
this.transportPlanPageVisible = true;
|
this.transportPlanPageVisible = true;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
loadTransportTypeOptions() {
|
||||||
|
return getDictionary({ code: 'transport_type' })
|
||||||
|
.then(res => {
|
||||||
|
this.transportTypeOptions = normalizeDictOptions(res.data?.data || []);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.transportTypeOptions = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatTransportType(value) {
|
||||||
|
const transportType = String(value || '').trim();
|
||||||
|
if (!transportType) return '-';
|
||||||
|
// 已是中文标签时直接展示
|
||||||
|
if (/[\u4e00-\u9fff]/.test(transportType)) return transportType;
|
||||||
|
const item = this.transportTypeOptions.find(
|
||||||
|
option => String(option.value ?? '').toLowerCase() === transportType.toLowerCase()
|
||||||
|
);
|
||||||
|
return item?.label || transportType;
|
||||||
|
},
|
||||||
loadPlanData() {
|
loadPlanData() {
|
||||||
if (!this.planId) {
|
if (!this.planId) {
|
||||||
this.$message.error('缺少计划ID参数');
|
this.$message.error('缺少计划ID参数');
|
||||||
@@ -327,6 +385,7 @@ export default {
|
|||||||
this.planData = res.data.data || {};
|
this.planData = res.data.data || {};
|
||||||
this.attachmentList = this.parseAttachments(this.planData.attachmentsJson);
|
this.attachmentList = this.parseAttachments(this.planData.attachmentsJson);
|
||||||
this.dispatchList = this.parseDispatchList(this.planData);
|
this.dispatchList = this.parseDispatchList(this.planData);
|
||||||
|
this.dispatchPage.currentPage = 1;
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.data?.msg || '加载数据失败');
|
this.$message.error(res.data?.msg || '加载数据失败');
|
||||||
}
|
}
|
||||||
@@ -448,8 +507,30 @@ export default {
|
|||||||
}
|
}
|
||||||
this.openPageDispatchItemDialog(-1);
|
this.openPageDispatchItemDialog(-1);
|
||||||
},
|
},
|
||||||
|
dispatchRowIndex(index) {
|
||||||
|
return (this.dispatchPage.currentPage - 1) * this.dispatchPage.pageSize + index + 1;
|
||||||
|
},
|
||||||
|
resolveDispatchGlobalIndex(pageIndex) {
|
||||||
|
return (this.dispatchPage.currentPage - 1) * this.dispatchPage.pageSize + pageIndex;
|
||||||
|
},
|
||||||
|
syncDispatchPage() {
|
||||||
|
const maxPage = Math.max(
|
||||||
|
1,
|
||||||
|
Math.ceil(this.dispatchList.length / this.dispatchPage.pageSize) || 1
|
||||||
|
);
|
||||||
|
if (this.dispatchPage.currentPage > maxPage) {
|
||||||
|
this.dispatchPage.currentPage = maxPage;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleDispatchCurrentChange(currentPage) {
|
||||||
|
this.dispatchPage.currentPage = currentPage;
|
||||||
|
},
|
||||||
|
handleDispatchSizeChange(pageSize) {
|
||||||
|
this.dispatchPage.pageSize = pageSize;
|
||||||
|
this.dispatchPage.currentPage = 1;
|
||||||
|
},
|
||||||
handleEdit(row, index) {
|
handleEdit(row, index) {
|
||||||
this.openPageDispatchItemDialog(index, row);
|
this.openPageDispatchItemDialog(this.resolveDispatchGlobalIndex(index), row);
|
||||||
},
|
},
|
||||||
openPageDispatchItemDialog(index = -1, row) {
|
openPageDispatchItemDialog(index = -1, row) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -469,6 +550,7 @@ export default {
|
|||||||
originalSave.apply(component, args);
|
originalSave.apply(component, args);
|
||||||
if (!component.dispatchItemBox) {
|
if (!component.dispatchItemBox) {
|
||||||
this.dispatchList = [...(component.dispatchRows || [])];
|
this.dispatchList = [...(component.dispatchRows || [])];
|
||||||
|
this.syncDispatchPage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
component.__dispatchListSaveSynced = true;
|
component.__dispatchListSaveSynced = true;
|
||||||
@@ -479,7 +561,8 @@ export default {
|
|||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.dispatchList.splice(index, 1);
|
this.dispatchList.splice(this.resolveDispatchGlobalIndex(index), 1);
|
||||||
|
this.syncDispatchPage();
|
||||||
this.$message.success('删除成功');
|
this.$message.success('删除成功');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -782,6 +865,12 @@ export default {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|||||||
@@ -1132,17 +1132,18 @@
|
|||||||
<el-input v-model="row.email" maxlength="100" :disabled="readonly" />
|
<el-input v-model="row.email" maxlength="100" :disabled="readonly" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="所属部门" min-width="200">
|
<el-table-column label="所属部门" min-width="220">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input
|
<el-input
|
||||||
v-if="!isInternalCustomer"
|
v-if="!isInternalCustomer"
|
||||||
:model-value="row.deptNames"
|
:model-value="row.deptNames"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<el-select
|
<el-cascader
|
||||||
v-else
|
v-else
|
||||||
v-model="row.deptIdList"
|
v-model="row.deptIdList"
|
||||||
multiple
|
:options="deptTree"
|
||||||
|
:props="invoiceDeptCascaderProps"
|
||||||
collapse-tags
|
collapse-tags
|
||||||
collapse-tags-tooltip
|
collapse-tags-tooltip
|
||||||
filterable
|
filterable
|
||||||
@@ -1150,14 +1151,7 @@
|
|||||||
:disabled="readonly"
|
:disabled="readonly"
|
||||||
placeholder="请选择所属部门"
|
placeholder="请选择所属部门"
|
||||||
@change="() => handleInvoiceContactDeptChange(row)"
|
@change="() => handleInvoiceContactDeptChange(row)"
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in invoiceDeptOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
/>
|
||||||
</el-select>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="备注" min-width="160">
|
<el-table-column label="备注" min-width="160">
|
||||||
@@ -2095,6 +2089,18 @@ export default {
|
|||||||
expandTrigger: 'click',
|
expandTrigger: 'click',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
// 发票联系信息所属部门:级联多选,可勾选任意层级
|
||||||
|
invoiceDeptCascaderProps() {
|
||||||
|
return {
|
||||||
|
value: 'value',
|
||||||
|
label: 'label',
|
||||||
|
children: 'children',
|
||||||
|
multiple: true,
|
||||||
|
checkStrictly: true,
|
||||||
|
emitPath: false,
|
||||||
|
expandTrigger: 'click',
|
||||||
|
};
|
||||||
|
},
|
||||||
// 所属组织级联值:archiveForm.deptId 只存选中的部门 ID(单选),
|
// 所属组织级联值:archiveForm.deptId 只存选中的部门 ID(单选),
|
||||||
// 级联控件需要根到选中节点的完整路径,两者在此做双向转换——
|
// 级联控件需要根到选中节点的完整路径,两者在此做双向转换——
|
||||||
// 部门树是异步加载的,依赖 deptTree 可保证树加载完成后回显自动刷新。
|
// 部门树是异步加载的,依赖 deptTree 可保证树加载完成后回显自动刷新。
|
||||||
@@ -2156,12 +2162,6 @@ export default {
|
|||||||
isInternalCustomer() {
|
isInternalCustomer() {
|
||||||
return this.archiveForm.customerKind === 'internal';
|
return this.archiveForm.customerKind === 'internal';
|
||||||
},
|
},
|
||||||
invoiceDeptOptions() {
|
|
||||||
return this.flattenDept(this.deptTree).map(item => ({
|
|
||||||
label: item.rawLabel,
|
|
||||||
value: String(item.value),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
isTemporaryToFormal() {
|
isTemporaryToFormal() {
|
||||||
return (
|
return (
|
||||||
Boolean(this.archiveForm.id) &&
|
Boolean(this.archiveForm.id) &&
|
||||||
@@ -3432,7 +3432,17 @@ export default {
|
|||||||
this.invoiceForm.contacts.splice(index, 1);
|
this.invoiceForm.contacts.splice(index, 1);
|
||||||
},
|
},
|
||||||
handleInvoiceContactDeptChange(row) {
|
handleInvoiceContactDeptChange(row) {
|
||||||
const ids = Array.isArray(row.deptIdList) ? row.deptIdList.map(item => String(item)) : [];
|
// emitPath=false 时,多选级联值为选中节点 id 数组
|
||||||
|
const rawList = Array.isArray(row.deptIdList) ? row.deptIdList : [];
|
||||||
|
const ids = rawList
|
||||||
|
.map(item => {
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
return item.length ? String(item[item.length - 1]) : '';
|
||||||
|
}
|
||||||
|
return item !== undefined && item !== null && item !== '' ? String(item) : '';
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
row.deptIdList = ids;
|
||||||
row.deptIds = ids.join(',');
|
row.deptIds = ids.join(',');
|
||||||
row.deptNames = ids.map(id => this.findDeptLabel(id)).filter(Boolean).join('、');
|
row.deptNames = ids.map(id => this.findDeptLabel(id)).filter(Boolean).join('、');
|
||||||
},
|
},
|
||||||
@@ -5434,7 +5444,12 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
:deep(.el-input),
|
:deep(.el-input),
|
||||||
:deep(.el-select) {
|
:deep(.el-select),
|
||||||
|
:deep(.el-cascader) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-cascader .el-input) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user