✨ 完善配载确认、承运商组织过滤司机与地址展示

配载草稿确认生成正式单并优化详情/列表地址市区展示;运单调度等按承运商联系人所属组织筛选司机车辆;统一地图搜索结果与登录默认账号清理。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-23 18:35:27 +08:00
parent 694fe1a433
commit a919781d4f
19 changed files with 1701 additions and 309 deletions
+18 -16
View File
@@ -10,12 +10,8 @@
<el-button type="primary" :loading="searching" @click="searchKeyword">搜索</el-button>
</div>
<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>
<map-search-results :results="searchResults" @select="selectSearchResult" />
</div>
<div class="address-map-picker__info">{{ status }}</div>
<template #footer>
@@ -28,6 +24,8 @@
</template>
<script>
import { normalizeMapSearchResults } from '@/utils/map-search';
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
let amapLoader;
@@ -214,19 +212,14 @@ export default {
.then(result => {
if (!result) return;
const geocodes = result.geocodes || (result.location ? [result] : []);
this.searchResults = geocodes.map((item, index) => ({
id: item.id || index,
name: item.formattedAddress || item.address || keyword,
address: item.formattedAddress || item.address || keyword,
location: item.location,
}));
const point = geocodes[0]?.location || result.location;
this.searchResults = normalizeMapSearchResults(geocodes, keyword);
const point = this.searchResults[0]?.location || result.location;
if (!point) {
throw new Error('未找到匹配地址');
}
return this.$nextTick().then(() => {
this.amap?.resize();
this.pickPoint(point, geocodes[0]?.formattedAddress || keyword);
this.pickPoint(point, this.searchResults[0]?.address || keyword);
});
})
.catch(() => {
@@ -320,7 +313,6 @@ export default {
}
&__map {
flex: 1 1 auto;
width: 100%;
min-width: 0;
height: 420px;
@@ -328,9 +320,19 @@ export default {
}
&__content {
display: flex;
gap: 12px;
position: relative;
display: block;
min-width: 0;
overflow: visible;
.address-map-picker__map {
position: relative;
z-index: 1;
}
:deep(.map-search-results) {
z-index: 2000;
}
}
&__info {
+181 -22
View File
@@ -1,20 +1,55 @@
<template>
<div v-if="results.length" class="map-search-results">
<div class="map-search-results__title">搜索结果</div>
<el-empty v-if="!results.length" description="暂无搜索结果" :image-size="60" />
<div
v-for="(item, index) in results"
:key="item.id || index"
class="map-search-results__item"
@click="$emit('select', item)"
>
<div class="map-search-results__name">{{ item.name || item.address || '未命名地址' }}</div>
<div class="map-search-results__address">{{ item.address || item.name || '-' }}</div>
<div class="map-search-results__list">
<div
v-for="(item, index) in pageResults"
:key="item.id || `${currentPage}-${index}`"
class="map-search-results__item"
:class="{ 'is-active': isActive(item, index) }"
@click="handleSelect(item, index)"
>
<img
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>
</template>
<script>
const PAGE_SIZE = 5;
export default {
props: {
results: {
@@ -23,43 +58,167 @@ export default {
},
},
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>
<style lang="scss" scoped>
.map-search-results {
width: 260px;
height: 420px;
overflow-y: auto;
border: 1px solid #eff1f7;
position: absolute;
top: 0;
left: 0;
z-index: 2000;
display: flex;
flex-direction: column;
width: 360px;
max-height: 360px;
overflow: hidden;
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 {
padding: 10px 12px;
border-bottom: 1px solid #eff1f7;
font-weight: 600;
&__list {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
&__item {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 10px 12px;
cursor: pointer;
border-bottom: 1px solid #f5f5f5;
border-bottom: 1px solid #ebeef5;
}
&__item:hover {
background: #f5f9ff;
&__item:hover,
&__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 {
overflow: hidden;
color: #303133;
font-size: 14px;
font-weight: 600;
line-height: 1.4;
white-space: nowrap;
text-overflow: ellipsis;
}
&__address {
margin-top: 4px;
color: #909399;
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>