fix(admin): restore manifest menu and overseas container access

This commit is contained in:
2026-10-08 11:16:58 +08:00
parent 080042d941
commit d7911ad09d
17 changed files with 197 additions and 28 deletions
@@ -39,11 +39,11 @@ describe('ContainerDetailView customs integration', () => {
expect(source).toContain(':sections="[\'pda\']"')
})
it('keeps the confirmed loading list inside the domestic container detail only', () => {
it('keeps the container detail entry alongside the standalone manifest', () => {
expect(routeSource).toContain("name: 'domestic-container-detail'")
expect(routeSource).toContain("name: 'overseas-container-detail'")
expect(loadingRouteSource).not.toContain("name: 'loading-lists'")
expect(navigationSource).not.toContain("title: '装柜清单'")
expect(loadingRouteSource).toContain("name: 'loading-lists'")
expect(navigationSource).toContain("title: '装柜清单'")
expect(source).toContain('<el-tabs v-model="activeTab"')
expect(source).toContain("v-if=\"isDomestic && activeTab === 'loading-list'\"")
expect(source).toContain("loadingDetail.value = await getConfirmedLoadingList(aggregate.loadingPlanId, resource.warehouseId)")
@@ -0,0 +1,33 @@
import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ContainerWorkbenchView from './ContainerWorkbenchView.vue'
const mocks = vi.hoisted(() => ({ permissions: new Set<string>(), push: vi.fn() }))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: (permission: string) => mocks.permissions.has(permission) }) }))
vi.mock('vue-router', () => ({ useRouter: () => ({ push: mocks.push }) }))
vi.mock('./ContainersView.vue', () => ({ default: { props: ['workbench'], template: '<div data-testid="workbench">{{ workbench }}</div>' } }))
function render(stage: 'DOMESTIC' | 'OVERSEAS' = 'DOMESTIC') {
return mount(ContainerWorkbenchView, { props: { stage }, global: { stubs: { ElButton: { template: '<button><slot /></button>' } } } })
}
describe('container stage entry permissions', () => {
beforeEach(() => { mocks.permissions.clear(); mocks.push.mockReset() })
it('opens overseas operations for an overseas-only user entering the common menu', () => {
mocks.permissions.add('platform:firstleg:terminal-drayage:list')
const wrapper = render()
expect(wrapper.get('[data-testid="workbench"]').text()).toBe('OVERSEAS')
expect(wrapper.text()).not.toContain('国内装柜')
})
it('preserves a direct overseas visit and allows switching back to domestic', async () => {
mocks.permissions.add('platform:firstleg:terminal-drayage:list')
mocks.permissions.add('platform:firstleg:container:list')
const wrapper = render('OVERSEAS')
expect(wrapper.get('[data-testid="workbench"]').text()).toBe('OVERSEAS')
await wrapper.findAll('button')[0]!.trigger('click')
expect(mocks.push).toHaveBeenCalledWith({ name: 'domestic-container-workbench' })
})
it('does not mount data consumers without either list permission', () => {
expect(render().find('[data-testid="workbench"]').exists()).toBe(false)
})
})
@@ -0,0 +1,24 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import ContainersView from './ContainersView.vue'
const props = defineProps<{ stage: 'DOMESTIC' | 'OVERSEAS' }>()
const access = useAccessPreviewStore()
const router = useRouter()
const canDomestic = computed(() => access.can('platform:firstleg:container:list'))
const canOverseas = computed(() => access.can('platform:firstleg:terminal-drayage:list'))
const current = computed(() => props.stage === 'DOMESTIC' && !canDomestic.value && canOverseas.value ? 'OVERSEAS' : props.stage)
</script>
<template>
<div class="admin-density">
<nav class="density-status-tabs" aria-label="货柜业务阶段">
<el-button v-if="canDomestic" :class="{ 'is-active': current === 'DOMESTIC' }" :aria-current="current === 'DOMESTIC' ? 'page' : undefined" @click="router.push({ name: 'domestic-container-workbench' })">📦 国内装柜</el-button>
<el-button v-if="canOverseas" :class="{ 'is-active': current === 'OVERSEAS' }" :aria-current="current === 'OVERSEAS' ? 'page' : undefined" @click="router.push({ name: 'overseas-container-workbench' })">🚢 海外清关提柜</el-button>
</nav>
<ContainersView v-if="canDomestic || canOverseas" :key="current" :workbench="current" />
</div>
</template>
@@ -2,7 +2,7 @@
import '@/styles/adminDensity.css'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import ContainerSimulationCreate from '../components/ContainerSimulationCreate.vue'
import { useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
@@ -46,6 +46,7 @@ type ContainerForm = ContainerSaveRequest & Pick<ContainerBookingResource, 'cabi
const props = withDefaults(defineProps<{ workbench?: 'DOMESTIC' | 'OVERSEAS' }>(), { workbench: 'DOMESTIC' })
const router = useRouter()
const route = useRoute()
const access = useAccessPreviewStore()
const rows = ref<ContainerWorkbenchRow[]>([])
const customers = ref<PlatformTenantResource[]>([])
@@ -67,7 +68,7 @@ const page = ref(1)
const pageSize = ref(20)
const total = ref(0)
const scope = ref<'ACTIVE' | 'HISTORY'>('ACTIVE')
const filters = ref<Filters>({ customerId: '', keyword: '', containerType: '', status: '', sailingRange: [] })
const filters = ref<Filters>({ customerId: '', keyword: typeof route.query.keyword === 'string' ? route.query.keyword : '', containerType: '', status: '', sailingRange: [] })
const applied = ref<Filters>({ ...filters.value })
const detail = ref<ContainerBookingResource | null>(null)
const editorOpen = ref(false)
@@ -1,5 +1,5 @@
<script setup lang="ts">
import ContainersView from './ContainersView.vue'
import ContainerWorkbenchView from './ContainerWorkbenchView.vue'
</script>
<template><ContainersView workbench="DOMESTIC" /></template>
<template><ContainerWorkbenchView stage="DOMESTIC" /></template>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import ContainersView from './ContainersView.vue'
import ContainerWorkbenchView from './ContainerWorkbenchView.vue'
</script>
<template><ContainersView workbench="OVERSEAS" /></template>
<template><ContainerWorkbenchView stage="OVERSEAS" /></template>
+1 -1
View File
@@ -5,7 +5,7 @@ export const containerLoadingRoutes = [
path: 'container-loading/domestic',
name: 'domestic-container-workbench',
component: () => import('./pages/DomesticContainersView.vue'),
meta: { showPageHeading: false, title: '货柜列表', description: '管理国内送柜、装柜、出口报关与出仓', permission: 'platform:firstleg:container:list' },
meta: { showPageHeading: false, title: '货柜列表', description: '管理国内装柜、海外清关提柜与到仓', permission: 'platform:firstleg:container:list', permissionAnyOf: ['platform:firstleg:terminal-drayage:list'] },
},
{
path: 'container-loading/domestic/:containerId',