front/src/views/h5/customer-center/components/H5WarehouseAddressSheet.vue
zzh 640562654d
All checks were successful
Build and Deploy Front / deploy (push) Successful in 6m25s
feat: 仓库地址排序备注与映射明细性能、脏检查误报修复
下单/H5 仓库地址展示与排序备注;映射明细加载优化与发布后保留选中;接口明细加载期不再误报未保存。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-09 14:12:32 +08:00

279 lines
7.1 KiB
Vue

<template>
<a-drawer
:visible="visible"
placement="bottom"
height="auto"
:footer="false"
:header="false"
unmount-on-close
:drawer-style="drawerStyle"
popup-container="body"
class="h5-wh-drawer"
@cancel="close"
>
<div class="h5-wh">
<div class="h5-wh__handle" aria-hidden="true" />
<header class="h5-wh__head">
<h3>{{ t('h5.journey.warehouse.title') }}</h3>
<button type="button" class="h5-wh__close" @click="close">
<icon-close />
</button>
</header>
<p class="h5-wh__lead">{{ t('h5.journey.warehouse.lead') }}</p>
<a-spin :loading="loading" class="h5-wh__spin">
<H5EmptyState
v-if="!list.length && !loading"
:icon="IconHome"
:text="t('h5.journey.warehouse.empty')"
/>
<article v-for="item in list" :key="item.id" class="h5-wh__card">
<div class="h5-wh__card-head">
<strong>{{ item.warehouseName || '-' }}</strong>
<button type="button" class="h5-wh__copy" @click="copyItem(item)">
{{ t('h5.journey.warehouse.copy') }}
</button>
</div>
<p><span>{{ t('h5.journey.warehouse.recipient') }}</span>{{ displayRecipient(item) }}</p>
<p><span>{{ t('h5.journey.warehouse.phone') }}</span>{{ item.contactPhone || '-' }}</p>
<p class="is-addr">
<span>{{ t('h5.journey.warehouse.address') }}</span>
{{ displayShippingAddress(item) }}
</p>
<p v-if="item.remark">
<span>{{ t('h5.journey.warehouse.remark') }}</span>
{{ item.remark }}
</p>
</article>
</a-spin>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { Message } from '@arco-design/web-vue';
import { IconClose, IconHome } from '@arco-design/web-vue/es/icon';
import { getWarehouseList, type WarehouseDto } from '@/api/system/warehouse';
import { resolveCurrentCustomer } from '@/api/customer/resolve';
import {
compareWarehouseSort,
formatWarehouseShipAddress,
formatWarehouseShipRecipient,
} from '@/utils/warehouseShipAddress';
import { getMapIdFromAccessToken } from '@/utils/jwt';
import { useRbacUserStore } from '@/store/modules/rbac-user';
import H5EmptyState from './H5EmptyState.vue';
defineOptions({ name: 'H5WarehouseAddressSheet' });
const props = defineProps<{ visible: boolean }>();
const emit = defineEmits<{ 'update:visible': [boolean] }>();
const { t } = useI18n();
const rbacUserStore = useRbacUserStore();
const loading = ref(false);
const list = ref<WarehouseDto[]>([]);
const currentCustomerCode = ref('');
const drawerStyle = {
borderRadius: '20px 20px 0 0',
overflow: 'hidden',
maxHeight: 'min(88vh, 720px)',
};
function close() {
emit('update:visible', false);
}
function pick(raw: any, ...keys: string[]) {
for (const k of keys) {
const v = raw?.[k];
if (v !== undefined && v !== null && String(v).trim() !== '') return String(v);
}
return '';
}
function normalize(row: any): WarehouseDto {
const sortRaw = row?.sort ?? row?.Sort;
const sortNum = sortRaw == null || sortRaw === '' ? null : Number(sortRaw);
return {
id: row?.id ?? row?.Id,
warehouseName: pick(row, 'warehouseName', 'WarehouseName'),
recipient: pick(row, 'recipient', 'Recipient'),
contactPhone: pick(row, 'contactPhone', 'ContactPhone'),
shippingAddress: pick(row, 'shippingAddress', 'ShippingAddress'),
sort: sortNum != null && Number.isFinite(sortNum) ? sortNum : null,
remark: pick(row, 'remark', 'Remark') || undefined,
};
}
function displayRecipient(item: WarehouseDto) {
return formatWarehouseShipRecipient(item.recipient, currentCustomerCode.value);
}
function displayShippingAddress(item: WarehouseDto) {
return formatWarehouseShipAddress(item.shippingAddress, currentCustomerCode.value);
}
async function loadCurrentCustomerCode() {
try {
const mapId = getMapIdFromAccessToken(rbacUserStore.accessToken);
const account = rbacUserStore.userInfo?.account?.trim() || '';
const data = await resolveCurrentCustomer({ mapId, account });
currentCustomerCode.value = String(data?.customerCode ?? '').trim();
} catch {
currentCustomerCode.value = '';
}
}
async function loadList() {
loading.value = true;
try {
const [res] = await Promise.all([
getWarehouseList({ page: 1, pageSize: 100 }),
loadCurrentCustomerCode(),
]);
list.value = (res.items ?? []).map(normalize).sort(compareWarehouseSort);
} catch {
list.value = [];
} finally {
loading.value = false;
}
}
async function copyItem(item: WarehouseDto) {
const text = [
item.warehouseName,
`${t('h5.journey.warehouse.recipient')}${displayRecipient(item)}`,
`${t('h5.journey.warehouse.phone')}${item.contactPhone || '-'}`,
`${t('h5.journey.warehouse.address')}${displayShippingAddress(item)}`,
].join('\n');
try {
await navigator.clipboard.writeText(text);
Message.success(t('h5.me.copySuccess'));
} catch {
Message.error(t('h5.orders.copyFailed'));
}
}
watch(
() => props.visible,
(v) => {
if (v) void loadList();
},
{ immediate: true },
);
</script>
<style lang="less">
.h5-wh-drawer {
.arco-drawer-content {
border-radius: 20px 20px 0 0;
overflow: hidden;
max-height: min(88vh, 720px);
}
.arco-drawer-body {
padding: 0;
max-height: min(88vh, 720px);
}
}
</style>
<style lang="less" scoped>
.h5-wh {
display: flex;
flex-direction: column;
max-height: min(88vh, 720px);
padding-bottom: env(safe-area-inset-bottom, 0px);
background: #f5f7f8;
}
.h5-wh__handle {
width: 36px;
height: 4px;
margin: 10px auto 0;
border-radius: 999px;
background: #c9cdd4;
}
.h5-wh__head {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 48px;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
}
.h5-wh__close {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 36px;
height: 36px;
border: none;
background: transparent;
color: #86909c;
font-size: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.h5-wh__lead {
margin: 0 16px 12px;
font-size: 13px;
color: #4e5969;
line-height: 1.5;
}
.h5-wh__spin {
flex: 1;
overflow: auto;
padding: 0 16px 16px;
display: block;
}
.h5-wh__card {
background: #fff;
border-radius: 16px;
padding: 14px;
margin-bottom: 10px;
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.04);
p {
margin: 8px 0 0;
font-size: 13px;
color: #1d2129;
line-height: 1.45;
span {
color: #86909c;
margin-right: 6px;
}
}
.is-addr {
word-break: break-all;
}
}
.h5-wh__card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
strong {
font-size: 15px;
}
}
.h5-wh__copy {
border: none;
background: color-mix(in srgb, var(--app-primary-color) 12%, #fff);
color: var(--app-primary-color);
font-size: 12px;
font-weight: 600;
height: 28px;
padding: 0 10px;
border-radius: 14px;
cursor: pointer;
}
</style>