优化
This commit is contained in:
@@ -136,10 +136,7 @@ async function mountView() {
|
||||
}
|
||||
|
||||
async function selectScope(wrapper: VueWrapper) {
|
||||
let toolbar = wrapper.findComponent(QueryToolbar)
|
||||
toolbar.vm.$emit('update:modelValue', { ...(toolbar.props('modelValue') as QueryModel), customerId: tenantId })
|
||||
await flushPromises()
|
||||
toolbar = wrapper.findComponent(QueryToolbar)
|
||||
const toolbar = wrapper.findComponent(QueryToolbar)
|
||||
toolbar.vm.$emit('update:modelValue', { ...(toolbar.props('modelValue') as QueryModel), warehouseId })
|
||||
await flushPromises()
|
||||
const searchButton = toolbar.findAll('button').find((button) => button.text().trim() === '查询')
|
||||
@@ -168,20 +165,19 @@ describe('LocationsView', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('starts with all customers and queries the full list without empty IDs', async () => {
|
||||
it('loads all warehouses and queries the full location list without requiring a customer or warehouse', async () => {
|
||||
const wrapper = await mountView()
|
||||
const toolbar = wrapper.findComponent(QueryToolbar)
|
||||
const fields = toolbar.props('fields') as QueryField[]
|
||||
|
||||
expect(listPlatformTenants).toHaveBeenCalledWith({ page: 1, size: 200 })
|
||||
expect(wrapper.find('[aria-label="所属企业"]').exists()).toBe(true)
|
||||
expect(fields[0]?.placeholder).toBe('全部客户')
|
||||
expect((toolbar.props('modelValue') as QueryModel).customerId).toBe('')
|
||||
expect(wrapper.find('[aria-label="所属企业"]').exists()).toBe(false)
|
||||
expect(fields[0]?.placeholder).toBe('全部仓库')
|
||||
expect(fields.map(({ key, width }) => [key, width])).toEqual([
|
||||
['customerId', 260], ['warehouseId', 240], ['zoneCode', 210],
|
||||
['warehouseId', 300], ['zoneCode', 210],
|
||||
['locationCode', 220], ['locationType', 170], ['status', 160],
|
||||
])
|
||||
expect(listPlatformWarehouses).not.toHaveBeenCalled()
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledWith(undefined, 1, 200)
|
||||
expect(listPlatformLocations).toHaveBeenCalledWith({
|
||||
page: 1,
|
||||
size: 20,
|
||||
@@ -200,16 +196,15 @@ describe('LocationsView', () => {
|
||||
expect(wrapper.text()).not.toContain('企业 ID')
|
||||
})
|
||||
|
||||
it('loads the selected customer, warehouse, and location hierarchy without narrowing snowflake IDs', async () => {
|
||||
it('filters by the selected warehouse without exposing or sending a customer id', async () => {
|
||||
const wrapper = await mountView()
|
||||
await selectScope(wrapper)
|
||||
|
||||
expect(wrapper.find('.table-panel .panel-heading').exists()).toBe(false)
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledWith(tenantId, 1, 200)
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledWith(undefined, 1, 200)
|
||||
expect(listPlatformLocations).toHaveBeenLastCalledWith({
|
||||
page: 1,
|
||||
size: 20,
|
||||
tenantId,
|
||||
warehouseId,
|
||||
zoneCode: '',
|
||||
locationCode: '',
|
||||
@@ -223,6 +218,19 @@ describe('LocationsView', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('opens creation without a list filter and requires the warehouse inside the form', async () => {
|
||||
const wrapper = await mountView()
|
||||
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增库位')
|
||||
|
||||
expect(createButton?.attributes('disabled')).toBeUndefined()
|
||||
await createButton!.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(document.body.textContent).toContain('新增库位')
|
||||
expect(document.body.textContent).toContain('所属仓库')
|
||||
expect(document.body.textContent).toContain('请选择所属仓库')
|
||||
})
|
||||
|
||||
it('keeps the operation column first and hides edit actions for occupied resources', async () => {
|
||||
const wrapper = await mountView()
|
||||
await selectScope(wrapper)
|
||||
@@ -267,7 +275,7 @@ describe('LocationsView', () => {
|
||||
expect(document.body.textContent).toContain('当前库位正在被业务使用,只允许查看。')
|
||||
})
|
||||
|
||||
it('clears customer and warehouse on reset and immediately restores the full query', async () => {
|
||||
it('clears the warehouse on reset and immediately restores the full query', async () => {
|
||||
const wrapper = await mountView()
|
||||
await selectScope(wrapper)
|
||||
const toolbar = wrapper.findComponent(QueryToolbar)
|
||||
@@ -277,7 +285,6 @@ describe('LocationsView', () => {
|
||||
await flushPromises()
|
||||
|
||||
const model = wrapper.findComponent(QueryToolbar).props('modelValue') as QueryModel
|
||||
expect(model.customerId).toBe('')
|
||||
expect(model.warehouseId).toBe('')
|
||||
expect(listPlatformLocations).toHaveBeenLastCalledWith({
|
||||
page: 1,
|
||||
|
||||
@@ -28,7 +28,6 @@ import { listPlatformWarehouses, type PlatformWarehouseResource } from '../../..
|
||||
|
||||
type DrawerMode = 'view' | 'create' | 'edit'
|
||||
type LocationFilters = QueryModel & {
|
||||
customerId: string
|
||||
warehouseId: string
|
||||
zoneCode: string
|
||||
locationCode: string
|
||||
@@ -36,6 +35,7 @@ type LocationFilters = QueryModel & {
|
||||
status: PlatformLocationStatus | ''
|
||||
}
|
||||
type AppliedLocationFilters = {
|
||||
warehouseId: string
|
||||
zoneCode: string
|
||||
locationCode: string
|
||||
locationType: PlatformLocationType | ''
|
||||
@@ -46,7 +46,6 @@ const access = useAccessPreviewStore()
|
||||
const customers = ref<PlatformTenantResource[]>([])
|
||||
const customerLoading = ref(false)
|
||||
const customerError = ref<ApiErrorDetails | null>(null)
|
||||
const loadedCustomerId = ref<string | null>(null)
|
||||
const warehouses = ref<PlatformWarehouseResource[]>([])
|
||||
const warehouseLoading = ref(false)
|
||||
const warehouseError = ref<ApiErrorDetails | null>(null)
|
||||
@@ -64,39 +63,26 @@ const detail = ref<PlatformLocationDetail | null>(null)
|
||||
const saving = ref(false)
|
||||
const actionLoadingIds = ref(new Set<string>())
|
||||
const formRef = ref<FormInstance>()
|
||||
let customerLoadSequence = 0
|
||||
let warehouseLoadSequence = 0
|
||||
let locationLoadSequence = 0
|
||||
|
||||
const filters = ref<LocationFilters>({ customerId: '', warehouseId: '', zoneCode: '', locationCode: '', locationType: '', status: '' })
|
||||
const appliedFilters = ref<AppliedLocationFilters>({ zoneCode: '', locationCode: '', locationType: '', status: '' })
|
||||
const selectedCustomerId = computed(() => String(filters.value.customerId || ''))
|
||||
const filters = ref<LocationFilters>({ warehouseId: '', zoneCode: '', locationCode: '', locationType: '', status: '' })
|
||||
const appliedFilters = ref<AppliedLocationFilters>({ warehouseId: '', zoneCode: '', locationCode: '', locationType: '', status: '' })
|
||||
const selectedWarehouseId = computed(() => String(filters.value.warehouseId || ''))
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{
|
||||
key: 'customerId',
|
||||
type: 'select',
|
||||
width: 260,
|
||||
placeholder: customerError.value ? '客户列表加载失败' : '全部客户',
|
||||
props: { 'aria-label': '所属企业', filterable: true, disabled: customerLoading.value || Boolean(customerError.value) || selectionDisabled.value },
|
||||
options: customers.value.map((customer) => ({
|
||||
value: String(customer.id),
|
||||
label: `${customer.tenantName}(${customer.tenantCode || customer.id})`,
|
||||
})),
|
||||
},
|
||||
{
|
||||
key: 'warehouseId',
|
||||
type: 'select',
|
||||
width: 240,
|
||||
placeholder: warehouseLoading.value ? '仓库加载中' : '所属仓库',
|
||||
width: 300,
|
||||
placeholder: warehouseLoading.value ? '仓库加载中' : '全部仓库',
|
||||
props: {
|
||||
'aria-label': '所属仓库',
|
||||
filterable: true,
|
||||
disabled: selectionDisabled.value || warehouseLoading.value || !customerReady.value || warehouses.value.length === 0,
|
||||
disabled: selectionDisabled.value || warehouseLoading.value || Boolean(warehouseError.value),
|
||||
},
|
||||
options: warehouses.value.map((warehouse) => ({
|
||||
value: String(warehouse.id),
|
||||
label: `${warehouse.warehouseName}(${warehouse.warehouseCode})`,
|
||||
disabled: warehouse.status !== 'ACTIVE',
|
||||
label: `${warehouse.warehouseName}(${warehouse.warehouseCode}) · ${customerLabel(warehouse.tenantId)}`,
|
||||
})),
|
||||
},
|
||||
{ key: 'zoneCode', type: 'input', width: 210, placeholder: '库区编码', props: { maxlength: 50, 'aria-label': '库区编码' } },
|
||||
@@ -123,8 +109,8 @@ const queryFields = computed<QueryField[]>(() => [
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: '',
|
||||
customerId: selectedCustomerId.value,
|
||||
warehouseId: selectedWarehouseId.value,
|
||||
customerId: '',
|
||||
warehouseId: '',
|
||||
zoneCode: '',
|
||||
zoneName: '',
|
||||
locationCode: '',
|
||||
@@ -137,6 +123,7 @@ const emptyForm = () => ({
|
||||
})
|
||||
const form = reactive(emptyForm())
|
||||
const rules: FormRules<typeof form> = {
|
||||
warehouseId: [{ required: true, message: '请选择所属仓库', trigger: 'change' }],
|
||||
zoneCode: [{ required: true, message: '请输入库区编码', trigger: 'blur' }],
|
||||
zoneName: [{ required: true, message: '请输入库区名称', trigger: 'blur' }],
|
||||
locationCode: [
|
||||
@@ -153,9 +140,7 @@ const rules: FormRules<typeof form> = {
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
|
||||
}
|
||||
|
||||
const selectedWarehouse = computed(() => warehouses.value.find((item) => String(item.id) === selectedWarehouseId.value) ?? null)
|
||||
const customerReady = computed(() => Boolean(selectedCustomerId.value) && loadedCustomerId.value === selectedCustomerId.value && !warehouseLoading.value)
|
||||
const createReady = computed(() => customerReady.value && Boolean(selectedWarehouse.value) && !loading.value)
|
||||
const selectedWarehouse = computed(() => warehouses.value.find((item) => String(item.id) === form.warehouseId) ?? null)
|
||||
const editorOpen = computed(() => drawerOpen.value && drawerMode.value !== 'view')
|
||||
const editorTitle = computed(() => drawerMode.value === 'create' ? '新增库位' : '编辑库位')
|
||||
const drawerTitle = computed(() => drawerMode.value === 'view' ? '库位详情' : editorTitle.value)
|
||||
@@ -236,11 +221,11 @@ function actionError(error: unknown, fallback: string) {
|
||||
}
|
||||
|
||||
function requireCreateScope() {
|
||||
if (!createReady.value || !selectedCustomerId.value || !selectedWarehouseId.value) {
|
||||
ElMessage.warning('所属企业和仓库数据尚未加载完成,请稍后重试')
|
||||
if (!selectedWarehouse.value || !form.warehouseId) {
|
||||
ElMessage.warning('请选择所属仓库')
|
||||
return null
|
||||
}
|
||||
return { customerId: apiId(selectedCustomerId.value), warehouseId: apiId(selectedWarehouseId.value) }
|
||||
return { customerId: apiId(String(selectedWarehouse.value.tenantId)), warehouseId: apiId(form.warehouseId) }
|
||||
}
|
||||
|
||||
function recordScope(row: PlatformLocationResource) {
|
||||
@@ -266,24 +251,20 @@ async function loadCustomers() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWarehouses(customerId = selectedCustomerId.value) {
|
||||
if (!customerId) return
|
||||
const sequence = ++customerLoadSequence
|
||||
async function loadWarehouses() {
|
||||
const sequence = ++warehouseLoadSequence
|
||||
warehouseLoading.value = true
|
||||
warehouseError.value = null
|
||||
filters.value = { ...filters.value, warehouseId: '' }
|
||||
warehouses.value = []
|
||||
try {
|
||||
const result = await listPlatformWarehouses(apiId(customerId), 1, 200)
|
||||
if (sequence !== customerLoadSequence) return
|
||||
const result = await listPlatformWarehouses(undefined, 1, 200)
|
||||
if (sequence !== warehouseLoadSequence) return
|
||||
warehouses.value = result.records
|
||||
loadedCustomerId.value = customerId
|
||||
} catch (error) {
|
||||
if (sequence !== customerLoadSequence) return
|
||||
loadedCustomerId.value = null
|
||||
if (sequence !== warehouseLoadSequence) return
|
||||
warehouseError.value = getApiErrorDetails(error, '仓库列表加载失败,请稍后重试')
|
||||
} finally {
|
||||
if (sequence === customerLoadSequence) warehouseLoading.value = false
|
||||
if (sequence === warehouseLoadSequence) warehouseLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,8 +281,7 @@ async function loadLocations() {
|
||||
locationType: appliedFilters.value.locationType,
|
||||
status: appliedFilters.value.status,
|
||||
}
|
||||
if (selectedCustomerId.value) query.tenantId = apiId(selectedCustomerId.value)
|
||||
if (selectedWarehouseId.value) query.warehouseId = apiId(selectedWarehouseId.value)
|
||||
if (appliedFilters.value.warehouseId) query.warehouseId = apiId(appliedFilters.value.warehouseId)
|
||||
const result = await listPlatformLocations(query)
|
||||
if (sequence !== locationLoadSequence) return
|
||||
rows.value = result.records
|
||||
@@ -318,6 +298,7 @@ async function loadLocations() {
|
||||
|
||||
function applyFilters(model: QueryModel) {
|
||||
appliedFilters.value = {
|
||||
warehouseId: String(model.warehouseId || ''),
|
||||
zoneCode: String(model.zoneCode || ''),
|
||||
locationCode: String(model.locationCode || ''),
|
||||
locationType: (model.locationType || '') as PlatformLocationType | '',
|
||||
@@ -345,10 +326,10 @@ function assignForm(resource: PlatformLocationResource) {
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
if (!requireCreateScope()) return
|
||||
Object.assign(form, emptyForm(), {
|
||||
status: selectedWarehouse.value?.status === 'ACTIVE' ? 'AVAILABLE' : 'DISABLED',
|
||||
})
|
||||
Object.assign(form, emptyForm())
|
||||
form.warehouseId = selectedWarehouseId.value
|
||||
form.customerId = selectedWarehouse.value ? String(selectedWarehouse.value.tenantId) : ''
|
||||
form.status = selectedWarehouse.value?.status === 'INACTIVE' ? 'DISABLED' : 'AVAILABLE'
|
||||
detail.value = null
|
||||
drawerError.value = null
|
||||
drawerMode.value = 'create'
|
||||
@@ -395,12 +376,12 @@ function buildPayload(): SavePlatformLocationRequest {
|
||||
}
|
||||
|
||||
async function saveLocation() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
const scope = form.id
|
||||
? { customerId: apiId(form.customerId), warehouseId: apiId(form.warehouseId) }
|
||||
: requireCreateScope()
|
||||
if (!scope) return
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = buildPayload()
|
||||
@@ -493,19 +474,16 @@ watch(
|
||||
)
|
||||
|
||||
watch([page, pageSize], () => { void loadLocations() })
|
||||
watch(selectedCustomerId, (customerId) => {
|
||||
drawerOpen.value = false
|
||||
loadedCustomerId.value = null
|
||||
warehouses.value = []
|
||||
filters.value = { ...filters.value, warehouseId: '' }
|
||||
if (customerId) void loadWarehouses(customerId)
|
||||
})
|
||||
watch(selectedWarehouseId, () => {
|
||||
drawerOpen.value = false
|
||||
watch(() => form.warehouseId, (warehouseId) => {
|
||||
if (drawerMode.value !== 'create') return
|
||||
const warehouse = warehouses.value.find((item) => String(item.id) === warehouseId)
|
||||
form.customerId = warehouse ? String(warehouse.tenantId) : ''
|
||||
form.status = warehouse?.status === 'INACTIVE' ? 'DISABLED' : 'AVAILABLE'
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void loadCustomers()
|
||||
void loadWarehouses()
|
||||
void loadLocations()
|
||||
})
|
||||
</script>
|
||||
@@ -522,7 +500,6 @@ onMounted(() => {
|
||||
<el-button
|
||||
v-if="access.can('platform:location:create')"
|
||||
type="primary"
|
||||
:disabled="!createReady"
|
||||
@click="openCreate"
|
||||
><Plus :size="16" />新增库位</el-button>
|
||||
</template>
|
||||
@@ -540,7 +517,7 @@ onMounted(() => {
|
||||
<ApiErrorMeta :details="warehouseError" />
|
||||
<el-button size="small" @click="loadWarehouses()">重新加载仓库</el-button>
|
||||
</el-alert>
|
||||
<el-alert v-else-if="customerReady && warehouses.length === 0" type="info" :closable="false" show-icon title="当前所属企业暂无仓库">
|
||||
<el-alert v-else-if="!warehouseLoading && warehouses.length === 0" type="info" :closable="false" show-icon title="暂无可选仓库">
|
||||
请先在仓库管理中建立仓库,再维护库位。
|
||||
</el-alert>
|
||||
<el-alert v-if="loadError" type="error" :closable="false" show-icon class="state-alert">
|
||||
@@ -610,8 +587,8 @@ onMounted(() => {
|
||||
</template>
|
||||
<template #warehouse="{ row }">
|
||||
<div class="stacked-value">
|
||||
<strong>{{ (row as PlatformLocationResource).warehouseName || selectedWarehouse?.warehouseName || '—' }}</strong>
|
||||
<small>{{ (row as PlatformLocationResource).warehouseCode || selectedWarehouse?.warehouseCode || '—' }}</small>
|
||||
<strong>{{ (row as PlatformLocationResource).warehouseName || warehouses.find((item) => String(item.id) === String((row as PlatformLocationResource).warehouseId))?.warehouseName || '—' }}</strong>
|
||||
<small>{{ (row as PlatformLocationResource).warehouseCode || warehouses.find((item) => String(item.id) === String((row as PlatformLocationResource).warehouseId))?.warehouseCode || '—' }}</small>
|
||||
</div>
|
||||
</template>
|
||||
<template #locationType="{ row }">{{ typeLabels[(row as PlatformLocationResource).locationType] }}</template>
|
||||
@@ -625,7 +602,7 @@ onMounted(() => {
|
||||
<template #empty>
|
||||
<el-empty v-if="!loading" :description="emptyDescription">
|
||||
<el-button
|
||||
v-if="selectedWarehouseId && !filtered && access.can('platform:location:create')"
|
||||
v-if="!filtered && access.can('platform:location:create')"
|
||||
type="primary"
|
||||
@click="openCreate"
|
||||
><Plus :size="16" />新增第一个库位</el-button>
|
||||
@@ -698,10 +675,20 @@ onMounted(() => {
|
||||
class="drawer-form"
|
||||
:disabled="saving"
|
||||
>
|
||||
<div class="editor-scope">
|
||||
<div v-if="drawerMode === 'edit'" class="editor-scope">
|
||||
<MapPin :size="18" />
|
||||
<div><strong>{{ detail?.warehouseName || selectedWarehouse?.warehouseName }}({{ detail?.warehouseCode || selectedWarehouse?.warehouseCode }})</strong><span>所属企业:{{ customerLabel(form.customerId) }} · 保存后不可跨仓移动</span></div>
|
||||
</div>
|
||||
<el-form-item v-if="drawerMode === 'create'" label="所属仓库" prop="warehouseId" required>
|
||||
<el-select v-model="form.warehouseId" filterable placeholder="请选择所属仓库" :loading="warehouseLoading">
|
||||
<el-option
|
||||
v-for="warehouse in warehouses"
|
||||
:key="String(warehouse.id)"
|
||||
:label="`${warehouse.warehouseName}(${warehouse.warehouseCode}) · ${customerLabel(warehouse.tenantId)}`"
|
||||
:value="String(warehouse.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<section class="form-section">
|
||||
<h3>库位标识</h3>
|
||||
<p class="section-description">库位编码在当前企业和仓库内唯一;编辑后不能移动到其他仓库。</p>
|
||||
|
||||
@@ -84,17 +84,8 @@ async function mountView(permissions = [
|
||||
return wrapper
|
||||
}
|
||||
|
||||
async function setFilterContext(wrapper: VueWrapper, customerId = '10001', warehouseId = '') {
|
||||
async function setWarehouseFilter(wrapper: VueWrapper, warehouseId = '') {
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('update:modelValue', {
|
||||
customerId,
|
||||
warehouseId: '',
|
||||
palletCode: '',
|
||||
palletType: '',
|
||||
})
|
||||
await flushPromises()
|
||||
if (!warehouseId) return
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('update:modelValue', {
|
||||
customerId,
|
||||
warehouseId,
|
||||
palletCode: '',
|
||||
palletType: '',
|
||||
@@ -103,9 +94,8 @@ async function setFilterContext(wrapper: VueWrapper, customerId = '10001', wareh
|
||||
}
|
||||
|
||||
async function loadRows(wrapper: VueWrapper, warehouseId = '') {
|
||||
await setFilterContext(wrapper, '10001', warehouseId)
|
||||
await setWarehouseFilter(wrapper, warehouseId)
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
|
||||
customerId: '10001',
|
||||
warehouseId,
|
||||
palletCode: '',
|
||||
palletType: '',
|
||||
@@ -129,33 +119,33 @@ describe('PalletsView', () => {
|
||||
vi.mocked(createPlatformPallet).mockReset().mockResolvedValue('20002')
|
||||
})
|
||||
|
||||
it('starts with all customers and uses compact semantic filter widths', async () => {
|
||||
it('starts with all customers and all warehouses using compact filters', async () => {
|
||||
const wrapper = await mountView()
|
||||
const toolbar = wrapper.findComponent(QueryToolbar)
|
||||
const fields = toolbar.props('fields')
|
||||
|
||||
expect(toolbar.props('modelValue')).toMatchObject({ customerId: '', warehouseId: '' })
|
||||
expect(toolbar.props('modelValue')).toMatchObject({ warehouseId: '' })
|
||||
expect(fields.map((field) => [field.key, field.width])).toEqual([
|
||||
['customerId', 260],
|
||||
['warehouseId', 240],
|
||||
['warehouseId', 300],
|
||||
['palletCode', 240],
|
||||
['palletType', 180],
|
||||
])
|
||||
expect(wrapper.find('.pallet-filter-toolbar').exists()).toBe(true)
|
||||
expect(fields.find((field) => field.key === 'customerId')?.placeholder).toBe('全部客户')
|
||||
expect(listPlatformWarehouses).not.toHaveBeenCalled()
|
||||
expect(fields.find((field) => field.key === 'warehouseId')?.placeholder).toBe('全部仓库')
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledWith(undefined, 1, 200)
|
||||
expect(listPlatformPallets).toHaveBeenCalledWith({
|
||||
page: 1, size: 20,
|
||||
})
|
||||
})
|
||||
|
||||
it('loads warehouses after customer selection and omits an empty warehouse id when searching', async () => {
|
||||
it('loads all warehouses independently and omits an empty warehouse id when searching', async () => {
|
||||
const wrapper = await mountView()
|
||||
await loadRows(wrapper)
|
||||
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledWith(10001, 1, 200)
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledTimes(1)
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledWith(undefined, 1, 200)
|
||||
expect(listPlatformPallets).toHaveBeenCalledWith({
|
||||
page: 1, size: 20, tenantId: 10001,
|
||||
page: 1, size: 20,
|
||||
})
|
||||
expect(wrapper.text()).toContain('TP-001')
|
||||
expect(wrapper.text()).toContain('木托盘')
|
||||
@@ -165,7 +155,7 @@ describe('PalletsView', () => {
|
||||
expect(wrapper.text()).not.toContain('启停托盘')
|
||||
expect(wrapper.text()).not.toContain('全部状态')
|
||||
expect(wrapper.text()).toContain('全部托盘类型')
|
||||
expect(wrapper.find('input[aria-label="所属企业"]').exists()).toBe(true)
|
||||
expect(wrapper.find('input[aria-label="客户"]').exists()).toBe(false)
|
||||
expect(wrapper.find('input[aria-label="所属仓库"]').exists()).toBe(true)
|
||||
expect(wrapper.find('.platform-tenant-selector').exists()).toBe(false)
|
||||
expect(wrapper.text()).not.toContain('租户')
|
||||
@@ -179,7 +169,7 @@ describe('PalletsView', () => {
|
||||
const wrapper = await mountView()
|
||||
const toolbar = wrapper.findComponent(QueryToolbar)
|
||||
|
||||
toolbar.vm.$emit('search', { customerId: '', warehouseId: '', palletCode: '', palletType: '' })
|
||||
toolbar.vm.$emit('search', { warehouseId: '', palletCode: '', palletType: '' })
|
||||
await flushPromises()
|
||||
expect(listPlatformPallets).toHaveBeenLastCalledWith({
|
||||
page: 1, size: 20,
|
||||
@@ -187,56 +177,49 @@ describe('PalletsView', () => {
|
||||
|
||||
})
|
||||
|
||||
it('resets customer and dependent warehouse filters without sending an empty id', async () => {
|
||||
it('resets the warehouse filter without sending an empty id', async () => {
|
||||
const wrapper = await mountView()
|
||||
await setFilterContext(wrapper, '10001', '19001')
|
||||
await setWarehouseFilter(wrapper, '19001')
|
||||
const toolbar = wrapper.findComponent(QueryToolbar)
|
||||
|
||||
toolbar.vm.reset()
|
||||
await flushPromises()
|
||||
|
||||
expect(toolbar.props('modelValue')).toMatchObject({ customerId: '', warehouseId: '' })
|
||||
expect(toolbar.props('modelValue')).toMatchObject({ warehouseId: '' })
|
||||
expect(listPlatformPallets).toHaveBeenCalledTimes(2)
|
||||
expect(listPlatformPallets).toHaveBeenLastCalledWith({
|
||||
page: 1, size: 20,
|
||||
})
|
||||
expect(toolbar.props('fields').find((field) => field.key === 'customerId')?.placeholder).toBe('全部客户')
|
||||
expect(toolbar.props('fields').find((field) => field.key === 'warehouseId')?.placeholder).toBe('全部仓库')
|
||||
})
|
||||
|
||||
it('preserves a snowflake customer id when switching the enterprise filter', async () => {
|
||||
it('does not expose customer ownership in warehouse choices', async () => {
|
||||
const wrapper = await mountView()
|
||||
const snowflakeId = '9223372036854775806'
|
||||
const warehouseField = wrapper.findComponent(QueryToolbar).props('fields').find((field) => field.key === 'warehouseId')
|
||||
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('update:modelValue', {
|
||||
customerId: snowflakeId,
|
||||
warehouseId: '',
|
||||
palletCode: '',
|
||||
palletType: '',
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(listPlatformWarehouses).toHaveBeenLastCalledWith(snowflakeId, 1, 200)
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledTimes(1)
|
||||
expect(warehouseField?.options).toEqual([{ label: '义乌收货仓(CN-YW-01)', value: '19001' }])
|
||||
})
|
||||
|
||||
it('applies pallet filters only after search', async () => {
|
||||
const wrapper = await mountView()
|
||||
await setFilterContext(wrapper, '10001', '19001')
|
||||
await setWarehouseFilter(wrapper, '19001')
|
||||
await wrapper.find('input[placeholder="托盘编码(模糊查询)"]').setValue('TP-00')
|
||||
expect(listPlatformPallets).toHaveBeenCalledTimes(1)
|
||||
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
|
||||
customerId: '10001', warehouseId: '19001', palletCode: 'TP-00', palletType: '木托盘',
|
||||
warehouseId: '19001', palletCode: 'TP-00', palletType: '木托盘',
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(listPlatformPallets).toHaveBeenLastCalledWith({
|
||||
page: 1, size: 20, tenantId: 10001, warehouseId: 19001, palletCode: 'TP-00', palletType: '木托盘',
|
||||
page: 1, size: 20, warehouseId: 19001, palletCode: 'TP-00', palletType: '木托盘',
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a pallet in the selected warehouse with the real request fields', async () => {
|
||||
const wrapper = await mountView()
|
||||
await setFilterContext(wrapper, '10001', '19001')
|
||||
await setWarehouseFilter(wrapper, '19001')
|
||||
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增托盘')
|
||||
await createButton?.trigger('click')
|
||||
await flushPromises()
|
||||
@@ -276,13 +259,13 @@ describe('PalletsView', () => {
|
||||
expect(wrapper.text()).not.toContain('TP-001')
|
||||
})
|
||||
|
||||
it('stops dependent requests when the enterprise filter cannot load', async () => {
|
||||
it('keeps platform warehouse and pallet queries available when customer names cannot load', async () => {
|
||||
vi.mocked(listPlatformCustomers).mockRejectedValueOnce(new Error('Gateway unavailable'))
|
||||
const wrapper = await mountView()
|
||||
|
||||
expect(wrapper.text()).toContain('所属企业列表暂时无法加载')
|
||||
expect(wrapper.text()).toContain('客户列表暂时无法加载')
|
||||
expect(wrapper.text()).toContain('重新加载')
|
||||
expect(listPlatformWarehouses).not.toHaveBeenCalled()
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledWith(undefined, 1, 200)
|
||||
expect(listPlatformPallets).toHaveBeenCalledWith({
|
||||
page: 1, size: 20,
|
||||
})
|
||||
|
||||
@@ -24,8 +24,6 @@ const access = useAccessPreviewStore()
|
||||
const customers = ref<PlatformCustomerResource[]>([])
|
||||
const customerLoading = ref(false)
|
||||
const customerError = ref<ApiErrorDetails | null>(null)
|
||||
const selectedCustomerId = ref('')
|
||||
const selectedWarehouseId = ref('')
|
||||
const warehouses = ref<PlatformWarehouseResource[]>([])
|
||||
const warehouseLoading = ref(false)
|
||||
const warehouseError = ref<ApiErrorDetails | null>(null)
|
||||
@@ -45,36 +43,24 @@ const formRef = ref<FormInstance>()
|
||||
let warehouseSequence = 0
|
||||
let palletSequence = 0
|
||||
|
||||
type PalletFilters = QueryModel & { customerId: string; warehouseId: string; palletCode: string; palletType: string }
|
||||
type AppliedPalletFilters = Pick<PalletFilters, 'palletCode' | 'palletType'>
|
||||
const filters = ref<PalletFilters>({ customerId: '', warehouseId: '', palletCode: '', palletType: '' })
|
||||
const appliedFilters = ref<AppliedPalletFilters>({ palletCode: '', palletType: '' })
|
||||
type PalletFilters = QueryModel & { warehouseId: string; palletCode: string; palletType: string }
|
||||
type AppliedPalletFilters = Pick<PalletFilters, 'warehouseId' | 'palletCode' | 'palletType'>
|
||||
const filters = ref<PalletFilters>({ warehouseId: '', palletCode: '', palletType: '' })
|
||||
const appliedFilters = ref<AppliedPalletFilters>({ warehouseId: '', palletCode: '', palletType: '' })
|
||||
const palletTypeOptions = ['木托盘', '塑料托盘', '金属托盘', '纸托盘', '免熏蒸托盘']
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{
|
||||
key: 'customerId',
|
||||
type: 'select',
|
||||
width: 260,
|
||||
placeholder: customerLoading.value ? '正在加载客户' : customerError.value ? '客户列表加载失败' : '全部客户',
|
||||
options: customers.value.map((customer) => ({
|
||||
label: `${customer.tenantName}(${customer.tenantCode || customer.id})`,
|
||||
value: String(customer.id),
|
||||
})),
|
||||
props: { filterable: true, disabled: contextLocked.value || customerLoading.value || Boolean(customerError.value), 'aria-label': '所属企业' },
|
||||
},
|
||||
{
|
||||
key: 'warehouseId',
|
||||
type: 'select',
|
||||
width: 240,
|
||||
width: 300,
|
||||
placeholder: warehouseLoading.value ? '正在加载所属仓库' : warehouseError.value ? '所属仓库加载失败' : '全部仓库',
|
||||
options: warehouses.value.map((warehouse) => ({
|
||||
label: `${warehouse.warehouseName}(${warehouse.warehouseCode})`,
|
||||
value: String(warehouse.id),
|
||||
disabled: warehouse.status !== 'ACTIVE',
|
||||
})),
|
||||
props: {
|
||||
filterable: true,
|
||||
disabled: contextLocked.value || !selectedCustomerId.value || warehouseLoading.value || Boolean(warehouseError.value),
|
||||
disabled: contextLocked.value || warehouseLoading.value || Boolean(warehouseError.value),
|
||||
'aria-label': '所属仓库',
|
||||
},
|
||||
},
|
||||
@@ -92,6 +78,7 @@ const queryFields = computed<QueryField[]>(() => [
|
||||
const emptyForm = () => ({ id: '', customerId: '', warehouseId: '', palletCode: '', palletType: '', capacityCbm: null as number | null, remark: '' })
|
||||
const form = reactive(emptyForm())
|
||||
const rules: FormRules<typeof form> = {
|
||||
warehouseId: [{ required: true, message: '请选择所属仓库', trigger: 'change' }],
|
||||
palletCode: [
|
||||
{ required: true, message: '请输入托盘编码', trigger: 'blur' },
|
||||
{ max: 50, message: '托盘编码不能超过 50 个字符', trigger: 'blur' },
|
||||
@@ -110,10 +97,7 @@ const rules: FormRules<typeof form> = {
|
||||
remark: [{ max: 500, message: '备注不能超过 500 个字符', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const currentWarehouse = computed(() => warehouses.value.find((item) => String(item.id) === selectedWarehouseId.value) || null)
|
||||
const editorWarehouse = computed(() => warehouses.value.find((item) => String(item.id) === form.warehouseId) || null)
|
||||
const currentCustomer = computed(() => customers.value.find((item) => String(item.id) === selectedCustomerId.value) || null)
|
||||
const contextReady = computed(() => Boolean(selectedCustomerId.value && selectedWarehouseId.value && currentWarehouse.value))
|
||||
const editorTitle = computed(() => form.id ? '编辑托盘' : '新增托盘')
|
||||
const editorPermission = computed(() => form.id ? 'platform:pallet:edit' : 'platform:pallet:create')
|
||||
const contextLocked = computed(() => editorOpen.value || detailOpen.value || saving.value || deletingIds.value.size > 0)
|
||||
@@ -187,18 +171,13 @@ async function loadCustomers() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWarehouses(customerId = selectedCustomerId.value) {
|
||||
async function loadWarehouses() {
|
||||
const sequence = ++warehouseSequence
|
||||
selectedWarehouseId.value = ''
|
||||
filters.value = { ...filters.value, warehouseId: '' }
|
||||
warehouses.value = []
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
warehouseError.value = null
|
||||
if (!customerId) return
|
||||
warehouseLoading.value = true
|
||||
try {
|
||||
const result = await listPlatformWarehouses(apiId(customerId), 1, 200)
|
||||
const result = await listPlatformWarehouses(undefined, 1, 200)
|
||||
if (sequence !== warehouseSequence) return
|
||||
warehouses.value = result.records
|
||||
} catch (error) {
|
||||
@@ -210,8 +189,6 @@ async function loadWarehouses(customerId = selectedCustomerId.value) {
|
||||
}
|
||||
|
||||
async function loadPallets() {
|
||||
const customerId = selectedCustomerId.value
|
||||
const warehouseId = selectedWarehouseId.value
|
||||
const sequence = ++palletSequence
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
@@ -221,8 +198,7 @@ async function loadPallets() {
|
||||
const result = await listPlatformPallets({
|
||||
page: page.value,
|
||||
size: pageSize.value,
|
||||
...(customerId ? { tenantId: apiId(customerId) } : {}),
|
||||
...(warehouseId ? { warehouseId: apiId(warehouseId) } : {}),
|
||||
...(appliedFilters.value.warehouseId ? { warehouseId: apiId(appliedFilters.value.warehouseId) } : {}),
|
||||
...(appliedFilters.value.palletCode ? { palletCode: appliedFilters.value.palletCode } : {}),
|
||||
...(appliedFilters.value.palletType ? { palletType: appliedFilters.value.palletType } : {}),
|
||||
})
|
||||
@@ -238,29 +214,25 @@ async function loadPallets() {
|
||||
}
|
||||
|
||||
function applyFilters(model: QueryModel) {
|
||||
selectedCustomerId.value = String(model.customerId || '')
|
||||
selectedWarehouseId.value = String(model.warehouseId || '')
|
||||
appliedFilters.value = { palletCode: String(model.palletCode || ''), palletType: String(model.palletType || '') }
|
||||
appliedFilters.value = {
|
||||
warehouseId: String(model.warehouseId || ''),
|
||||
palletCode: String(model.palletCode || ''),
|
||||
palletType: String(model.palletType || ''),
|
||||
}
|
||||
if (page.value === 1) void loadPallets()
|
||||
else page.value = 1
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
selectedCustomerId.value = ''
|
||||
selectedWarehouseId.value = ''
|
||||
appliedFilters.value = { palletCode: '', palletType: '' }
|
||||
appliedFilters.value = { warehouseId: '', palletCode: '', palletType: '' }
|
||||
if (page.value === 1) void loadPallets()
|
||||
else page.value = 1
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
if (!contextReady.value) {
|
||||
ElMessage.warning('请先选择所属企业和仓库')
|
||||
return
|
||||
}
|
||||
Object.assign(form, emptyForm())
|
||||
form.customerId = selectedCustomerId.value
|
||||
form.warehouseId = selectedWarehouseId.value
|
||||
form.warehouseId = filters.value.warehouseId
|
||||
form.customerId = editorWarehouse.value ? String(editorWarehouse.value.tenantId) : ''
|
||||
editorOpen.value = true
|
||||
captureBaseline()
|
||||
void formRef.value?.clearValidate()
|
||||
@@ -308,7 +280,13 @@ async function openEdit(row: PlatformPalletResource) {
|
||||
}
|
||||
|
||||
async function savePallet() {
|
||||
if ((!form.id && !contextReady.value) || !form.customerId || !form.warehouseId || !await formRef.value?.validate().catch(() => false) || form.capacityCbm === null) return
|
||||
if (!await formRef.value?.validate().catch(() => false) || form.capacityCbm === null) return
|
||||
const warehouse = editorWarehouse.value
|
||||
if (!warehouse) {
|
||||
ElMessage.error('所选仓库已不存在,请重新选择')
|
||||
return
|
||||
}
|
||||
form.customerId = String(warehouse.tenantId)
|
||||
saving.value = true
|
||||
const payload: SavePlatformPalletRequest = {
|
||||
warehouseId: apiId(form.warehouseId),
|
||||
@@ -357,18 +335,6 @@ async function removePallet(row: PlatformPalletResource) {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => filters.value.customerId, (customerId) => {
|
||||
selectedCustomerId.value = String(customerId || '')
|
||||
page.value = 1
|
||||
void loadWarehouses(selectedCustomerId.value)
|
||||
})
|
||||
watch(() => filters.value.warehouseId, (warehouseId) => {
|
||||
selectedWarehouseId.value = String(warehouseId || '')
|
||||
page.value = 1
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
loadError.value = null
|
||||
})
|
||||
watch([page, pageSize], ([nextPage, nextSize], [previousPage, previousSize]) => {
|
||||
if (nextPage === previousPage && nextSize === previousSize) return
|
||||
void loadPallets()
|
||||
@@ -378,6 +344,7 @@ watch(() => access.can(editorPermission.value), (canWrite) => {
|
||||
}, { flush: 'sync' })
|
||||
onMounted(() => {
|
||||
void loadCustomers()
|
||||
void loadWarehouses()
|
||||
void loadPallets()
|
||||
})
|
||||
</script>
|
||||
@@ -386,7 +353,7 @@ onMounted(() => {
|
||||
<section class="pallet-page">
|
||||
<LoadErrorState
|
||||
v-if="customerError"
|
||||
title="所属企业列表暂时无法加载"
|
||||
title="客户列表暂时无法加载"
|
||||
:message="customerError.message"
|
||||
:loading="customerLoading"
|
||||
@retry="loadCustomers"
|
||||
@@ -407,7 +374,7 @@ onMounted(() => {
|
||||
title="仓库列表暂时无法加载"
|
||||
:message="warehouseError.message"
|
||||
:loading="warehouseLoading"
|
||||
@retry="loadWarehouses()"
|
||||
@retry="loadWarehouses"
|
||||
/>
|
||||
|
||||
<LoadErrorState
|
||||
@@ -420,8 +387,8 @@ onMounted(() => {
|
||||
|
||||
<section class="table-panel surface-panel" aria-label="托盘列表区域">
|
||||
<div class="panel-heading">
|
||||
<div><h2>托盘列表</h2><p>{{ currentCustomer?.tenantName || '全部客户' }} · {{ currentWarehouse?.warehouseName || '全部仓库' }} · 共 {{ total }} 个托盘</p></div>
|
||||
<el-button v-if="access.can('platform:pallet:create')" type="primary" :disabled="!contextReady" @click="openCreate"><Plus :size="16" />新增托盘</el-button>
|
||||
<div><h2>托盘列表</h2><p>共 {{ total }} 个托盘</p></div>
|
||||
<el-button v-if="access.can('platform:pallet:create')" type="primary" @click="openCreate"><Plus :size="16" />新增托盘</el-button>
|
||||
</div>
|
||||
<DataTable
|
||||
v-model:page="page"
|
||||
@@ -431,7 +398,7 @@ onMounted(() => {
|
||||
:loading="loading || warehouseLoading"
|
||||
:total="total"
|
||||
row-key="id"
|
||||
:empty-text="filtered ? '暂无符合筛选条件的托盘' : selectedCustomerId ? '当前客户暂无托盘' : '暂无托盘数据'"
|
||||
:empty-text="filtered ? '暂无符合筛选条件的托盘' : '暂无托盘数据'"
|
||||
aria-label="托盘列表"
|
||||
>
|
||||
<template #actions="{ row }">
|
||||
@@ -484,11 +451,21 @@ onMounted(() => {
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer v-model="editorOpen" :title="editorTitle" size="520px" class="business-drawer" :close-on-click-modal="false">
|
||||
<div class="editor-context">
|
||||
<div v-if="form.id" class="editor-context">
|
||||
<WarehouseIcon :size="18" />
|
||||
<div><strong>{{ editorWarehouse?.warehouseName || '当前仓库' }}({{ editorWarehouse?.warehouseCode || form.warehouseId }})</strong><span>所属企业 {{ customerLabel(form.customerId) }} · 托盘保存后不可跨仓移动</span></div>
|
||||
<div><strong>{{ editorWarehouse?.warehouseName || '当前仓库' }}({{ editorWarehouse?.warehouseCode || form.warehouseId }})</strong><span>客户 {{ customerLabel(form.customerId) }} · 托盘保存后不可跨仓移动</span></div>
|
||||
</div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="editor-form">
|
||||
<el-form-item v-if="!form.id" label="所属仓库" prop="warehouseId" required>
|
||||
<el-select v-model="form.warehouseId" filterable placeholder="请选择所属仓库" :loading="warehouseLoading">
|
||||
<el-option
|
||||
v-for="warehouse in warehouses"
|
||||
:key="String(warehouse.id)"
|
||||
:label="`${warehouse.warehouseName}(${warehouse.warehouseCode})`"
|
||||
:value="String(warehouse.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="托盘编码" prop="palletCode" required>
|
||||
<el-input v-model="form.palletCode" maxlength="50" show-word-limit placeholder="例如 TP-001" />
|
||||
</el-form-item>
|
||||
|
||||
Reference in New Issue
Block a user