296 lines
7.3 KiB
Vue
296 lines
7.3 KiB
Vue
<script setup lang="ts">
|
|
import { nextTick, ref, watch } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import ModalPro from '@/components/ModalPro/index.vue'
|
|
import useModal from '@/components/ModalPro/useModal'
|
|
import PaginationPro from '@/components/PaginationPro/index.vue'
|
|
import { getWarehouseSkuList } from '@/api/warehouse'
|
|
import { resolveOmsAssetUrl } from '@/api/oms/common'
|
|
import { buildWarehouseSkuSelectPayload, normalizeListRow, pickField, pickSkuEnglishName } from '@/utils/warehouseListMapper'
|
|
|
|
const props = defineProps<{
|
|
visible: boolean
|
|
customerId?: number
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:visible': [value: boolean]
|
|
select: [row: Record<string, unknown>]
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
const [registerModal, modalMethods] = useModal()
|
|
|
|
const loading = ref(false)
|
|
const keyword = ref('')
|
|
const currentPage = ref(1)
|
|
const pageSize = ref(10)
|
|
const totalCount = ref(0)
|
|
const tableRows = ref<any[]>([])
|
|
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
function handlePageSizeChange() {
|
|
currentPage.value = 1
|
|
void loadList()
|
|
}
|
|
|
|
function close() {
|
|
emit('update:visible', false)
|
|
modalMethods.closeModal()
|
|
}
|
|
|
|
function imgSrc(url?: string | null) {
|
|
return resolveOmsAssetUrl(url || '') || ''
|
|
}
|
|
|
|
function mapRow(raw: Record<string, unknown>) {
|
|
const row = normalizeListRow(raw)
|
|
return {
|
|
id: Number(pickField(row, 'id', 'Id') ?? 0),
|
|
skuCode: String(pickField(row, 'skuCode', 'SkuCode') ?? ''),
|
|
skuName: String(pickField(row, 'skuName', 'SkuName') ?? ''),
|
|
skuNameEn: pickSkuEnglishName(row),
|
|
unitPrice: pickField(row, 'unitPrice', 'UnitPrice'),
|
|
exHsCode: String(pickField(row, 'exHsCode', 'ExHsCode') ?? ''),
|
|
imHsCode: String(pickField(row, 'imHsCode', 'ImHsCode') ?? ''),
|
|
declareElement: String(pickField(row, 'declareElement', 'DeclareElement') ?? ''),
|
|
skuImageUrl: String(pickField(row, 'skuImageUrl', 'SkuImageUrl') ?? ''),
|
|
weightG: Number(pickField(row, 'weightG', 'WeightG') ?? 0),
|
|
remark: String(pickField(row, 'remark', 'Remark') ?? ''),
|
|
_raw: raw,
|
|
}
|
|
}
|
|
|
|
async function loadList() {
|
|
loading.value = true
|
|
try {
|
|
const { items, total } = await getWarehouseSkuList({
|
|
keyword: keyword.value.trim() || undefined,
|
|
page: currentPage.value,
|
|
pageSize: pageSize.value,
|
|
...(props.customerId && props.customerId > 0 ? { customerId: props.customerId } : {}),
|
|
})
|
|
tableRows.value = (items || []).map((x) => mapRow(x as Record<string, unknown>))
|
|
totalCount.value = Number(total ?? tableRows.value.length)
|
|
} catch {
|
|
tableRows.value = []
|
|
totalCount.value = 0
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function scheduleSearch() {
|
|
if (searchTimer) clearTimeout(searchTimer)
|
|
searchTimer = setTimeout(() => {
|
|
currentPage.value = 1
|
|
void loadList()
|
|
}, 280)
|
|
}
|
|
|
|
function handleSelect(row: ReturnType<typeof mapRow>) {
|
|
emit('select', buildWarehouseSkuSelectPayload(row._raw as Record<string, unknown>, row))
|
|
close()
|
|
}
|
|
|
|
watch(
|
|
() => props.visible,
|
|
async (open) => {
|
|
if (!open) return
|
|
keyword.value = ''
|
|
currentPage.value = 1
|
|
modalMethods.setModalProps({
|
|
title: t('order.placeOrder.single.declarationSkuModalTitle'),
|
|
width: 880,
|
|
footer: false,
|
|
})
|
|
modalMethods.openModal(true)
|
|
await nextTick()
|
|
await loadList()
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<ModalPro @register="registerModal" @cancel="close">
|
|
<div class="sku-pick">
|
|
<p class="sku-pick__tip">{{ t('order.placeOrder.single.declarationSkuModalTip') }}</p>
|
|
<div class="sku-pick__toolbar">
|
|
<a-input
|
|
v-model="keyword"
|
|
allow-clear
|
|
size="small"
|
|
class="sku-pick__search"
|
|
:placeholder="t('order.placeOrder.single.declarationSkuSearch')"
|
|
@input="scheduleSearch"
|
|
@press-enter="loadList"
|
|
@clear="loadList"
|
|
/>
|
|
</div>
|
|
<div v-if="loading" class="sku-pick__loading">{{ t('common.loading') }}</div>
|
|
<div v-else class="sku-pick__list">
|
|
<button
|
|
v-for="row in tableRows"
|
|
:key="row.id"
|
|
type="button"
|
|
class="sku-pick__item"
|
|
@click="handleSelect(row)"
|
|
>
|
|
<div class="sku-pick__thumb">
|
|
<img v-if="imgSrc(row.skuImageUrl)" :src="imgSrc(row.skuImageUrl)" alt="" referrerpolicy="no-referrer" />
|
|
<span v-else>SKU</span>
|
|
</div>
|
|
<div class="sku-pick__main">
|
|
<strong>{{ row.skuCode || '--' }}</strong>
|
|
<span>{{ row.skuName || '--' }}</span>
|
|
<span v-if="row.skuNameEn" class="sku-pick__en">{{ row.skuNameEn }}</span>
|
|
</div>
|
|
<div class="sku-pick__meta">
|
|
<em v-if="row.weightG > 0">{{ row.weightG }} g</em>
|
|
</div>
|
|
</button>
|
|
<a-empty v-if="!tableRows.length" :description="t('order.placeOrder.single.declarationSkuEmpty')" />
|
|
</div>
|
|
<div class="sku-pick__pager">
|
|
<PaginationPro
|
|
:total-count="totalCount"
|
|
v-model:current-page="currentPage"
|
|
v-model:page-size="pageSize"
|
|
:page-sizes="[10, 30, 50]"
|
|
@page-change="loadList"
|
|
@page-size-change="handlePageSizeChange"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</ModalPro>
|
|
</template>
|
|
|
|
<style scoped lang="less">
|
|
.sku-pick {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
min-height: 420px;
|
|
}
|
|
|
|
.sku-pick__tip {
|
|
margin: 0;
|
|
font-size: 13px;
|
|
color: var(--color-text-2);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.sku-pick__toolbar {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.sku-pick__search {
|
|
width: 320px;
|
|
}
|
|
|
|
.sku-pick__loading {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--color-text-3);
|
|
}
|
|
|
|
.sku-pick__list {
|
|
flex: 1;
|
|
min-height: 280px;
|
|
max-height: 420px;
|
|
overflow: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.sku-pick__item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
width: 100%;
|
|
padding: 10px 12px;
|
|
border: 1px solid var(--color-border-2);
|
|
border-radius: 4px;
|
|
background: var(--app-card-bg);
|
|
text-align: left;
|
|
cursor: pointer;
|
|
transition: border-color 0.15s, background 0.15s;
|
|
|
|
&:hover {
|
|
border-color: color-mix(in srgb, var(--app-primary-color) 45%, var(--color-border-2));
|
|
background: color-mix(in srgb, var(--app-primary-color) 6%, var(--app-card-bg));
|
|
}
|
|
}
|
|
|
|
.sku-pick__thumb {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
background: var(--color-fill-2);
|
|
border: 1px solid var(--color-border-2);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 11px;
|
|
color: var(--color-text-3);
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
.sku-pick__main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
|
|
strong {
|
|
font-size: 13px;
|
|
color: var(--color-text-1);
|
|
font-family: ui-monospace, Consolas, monospace;
|
|
}
|
|
|
|
span {
|
|
font-size: 12px;
|
|
color: var(--color-text-2);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.sku-pick__en {
|
|
font-size: 11px;
|
|
color: var(--color-text-3);
|
|
}
|
|
}
|
|
|
|
.sku-pick__meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 4px;
|
|
|
|
em {
|
|
font-style: normal;
|
|
font-size: 12px;
|
|
color: var(--color-text-3);
|
|
}
|
|
}
|
|
|
|
.sku-pick__pager {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
</style>
|