Merge remote-tracking branch 'origin/release/test' into dev

# Conflicts:
#	src/modules/container-loading/pages/ContainersView.vue
This commit is contained in:
2026-09-09 15:42:42 +08:00
22 changed files with 677 additions and 123 deletions
@@ -0,0 +1,65 @@
import ElementPlus from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { createMemoryHistory, createRouter } from 'vue-router'
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import LoadingListsView from './LoadingListsView.vue'
import { ContainerSimulationCreate } from '@/modules/container-loading'
import { listPreloadPlans } from '../api/platformLoadingLists'
const accessCan = vi.hoisted(() => vi.fn())
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: accessCan }) }))
vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [] }) }))
vi.mock('@/modules/container-loading', () => ({ ContainerSimulationCreate: { emits: ['created'], template: '<button @click="$emit(\'created\')">新增模拟柜</button>' } }))
vi.mock('../api/platformLoadingLists', () => ({
listPreloadPlans: vi.fn().mockResolvedValue({ records: [], total: 0 }),
listConfirmedLoadingLists: vi.fn().mockResolvedValue({ records: [], total: 0 }),
}))
async function mountList(mode: 'preload' | 'confirmed' = 'preload') {
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/', component: { template: '<div />' } }] })
await router.push('/')
await router.isReady()
const wrapper = mount(LoadingListsView, { props: { mode }, global: { plugins: [ElementPlus, router] } })
await flushPromises()
return wrapper
}
describe('preload simulation entry', () => {
beforeEach(() => {
vi.clearAllMocks()
setActivePinia(createPinia())
accessCan.mockReturnValue(true)
})
it('refreshes the first page without old filters after creation, staying on the list', async () => {
const wrapper = await mountList()
const state = wrapper.vm as unknown as { page: number; query: { customerId: string; keyword: string; createdRange: string[] } }
Object.assign(state.query, { customerId: '11', keyword: 'OLD', createdRange: ['2026-01-01', '2026-01-02'] })
state.page = 3
await flushPromises()
vi.mocked(listPreloadPlans).mockClear()
wrapper.findComponent(ContainerSimulationCreate).vm.$emit('created')
await flushPromises()
expect(listPreloadPlans).toHaveBeenCalledTimes(1)
expect(listPreloadPlans).toHaveBeenLastCalledWith({ page: 1, size: 20, customerId: undefined, keyword: undefined, createdFrom: undefined, createdTo: undefined })
expect(wrapper.vm.$route.path).toBe('/')
wrapper.findComponent(ContainerSimulationCreate).vm.$emit('created')
await flushPromises()
expect(listPreloadPlans).toHaveBeenCalledTimes(2)
wrapper.unmount()
})
it('hides creation without container create permission', async () => {
accessCan.mockImplementation((code: string) => code !== 'platform:firstleg:container:create')
const wrapper = await mountList()
expect(wrapper.findComponent(ContainerSimulationCreate).exists()).toBe(false)
wrapper.unmount()
})
it('does not add creation to confirmed loading lists', async () => {
const wrapper = await mountList('confirmed')
expect(wrapper.findComponent(ContainerSimulationCreate).exists()).toBe(false)
wrapper.unmount()
})
})
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ContainerSimulationCreate } from '@/modules/container-loading'
import { Eye } from '@lucide/vue'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
@@ -43,6 +44,7 @@ const isPreload = computed(() => props.mode === 'preload')
const title = computed(() => isPreload.value ? '预装清单' : '装柜清单')
const listPermission = computed(() => isPreload.value ? 'platform:firstleg:preload:list' : 'platform:firstleg:loading-list:list')
const canList = computed(() => access.can(listPermission.value))
const canCreate = computed(() => isPreload.value && canList.value && access.can('platform:firstleg:container:create'))
const canView = computed(() => canList.value)
const fields = computed<QueryField[]>(() => [
{ key: 'customerId', type: 'select', label: '客户', placeholder: '全部客户', width: 220, options: customers.value.filter((customer) => customer.status === 'ACTIVE').map((customer) => ({ label: customer.tenantName, value: String(customer.id) })), props: { filterable: true, loading: customersLoading.value } },
@@ -138,6 +140,11 @@ async function openDeepLinkedDetail() {
await router.replace({ name: 'loading-list-detail', params: { planId }, query: { warehouseId } })
}
function onSimulationCreated() {
Object.assign(query, { customerId: '', keyword: '', createdRange: [] })
search()
}
function search() {
if (page.value !== 1) page.value = 1
else void load()
@@ -207,6 +214,9 @@ onMounted(() => {
:empty-text="`暂无符合筛选条件的${title}`"
:aria-label="title"
>
<template #toolbar-actions>
<ContainerSimulationCreate v-if="canCreate" success-message="模拟柜及预装清单已创建" @created="onSimulationCreated" />
</template>
<template #actions="{ row }">
<div class="row-actions">
<el-button v-if="canView" size="small" type="primary" plain @click="openDetail(row)"><Eye :size="15" />查看</el-button>