This commit is contained in:
2026-08-12 16:58:52 +08:00
parent a44fd5f235
commit 16c5ed6301
13 changed files with 254 additions and 73 deletions
@@ -1,4 +1,6 @@
import ElementPlus from 'element-plus'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -15,6 +17,8 @@ import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/compone
import DataTable from '@/shared/components/DataTable.vue'
import LocationsView from './LocationsView.vue'
const locationsSource = readFileSync(resolve(process.cwd(), 'src/modules/warehouse-resources/locations/pages/LocationsView.vue'), 'utf8')
vi.mock('../../../warehouses/api/platformWarehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
vi.mock('../api/platformLocations', () => ({
listPlatformLocations: vi.fn(),
@@ -138,6 +142,12 @@ enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('LocationsView', () => {
it('uses the prototype-sized centered editor while retaining the detail drawer', () => {
expect(locationsSource).toContain("drawerMode.value === 'view' ? AppDrawer : AppDialog")
expect(locationsSource).toContain("width: 'min(560px, calc(100vw - 28px))'")
expect(locationsSource).toContain(':global(.location-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }')
expect(locationsSource).toContain('.drawer-form :deep(.form-grid.two-cols) { grid-template-columns: minmax(0, 1fr); }')
})
beforeEach(() => {
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
document.body.innerHTML = ''
@@ -7,6 +7,7 @@ import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRe
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import FormSection from '@/shared/components/FormSection.vue'
@@ -149,6 +150,10 @@ const domesticWarehouses = computed(() => warehouses.value.filter((warehouse) =>
const editorOpen = computed(() => drawerOpen.value && drawerMode.value !== 'view')
const editorTitle = computed(() => drawerMode.value === 'create' ? '新增库位' : '编辑库位')
const drawerTitle = computed(() => drawerMode.value === 'view' ? '库位详情' : editorTitle.value)
const drawerContainer = computed(() => drawerMode.value === 'view' ? AppDrawer : AppDialog)
const drawerContainerProps = computed(() => drawerMode.value === 'view'
? { size: 'min(620px, 94vw)', showConfirm: false }
: { width: 'min(560px, calc(100vw - 28px))', showConfirm: false, destroyOnClose: true })
const editorPermission = computed(() => drawerMode.value === 'create' ? 'platform:location:create' : 'platform:location:edit')
const selectionDisabled = computed(() => drawerOpen.value || saving.value || actionLoadingIds.value.size > 0)
const filtered = computed(() => Object.values(appliedFilters.value).some((value) => String(value || '').trim()))
@@ -646,14 +651,14 @@ onMounted(() => {
</section>
</ListPageLayout>
<AppDrawer
<component
:is="drawerContainer"
v-model="drawerOpen"
:title="drawerTitle"
size="min(620px, 94vw)"
class="location-drawer"
v-bind="drawerContainerProps"
:class="drawerMode === 'view' ? 'location-drawer' : 'location-editor-dialog'"
:loading="drawerLoading"
:confirm-loading="saving"
:show-confirm="false"
:close-on-click-modal="false"
:close-on-press-escape="!saving"
:show-close="!saving"
@@ -785,7 +790,7 @@ onMounted(() => {
<el-button v-if="drawerMode !== 'view'" type="primary" :loading="saving" @click="saveLocation"><Save :size="16" />保存库位</el-button>
</div>
</template>
</AppDrawer>
</component>
</AdminPage>
</template>
@@ -801,6 +806,9 @@ onMounted(() => {
.location-cell small, .stacked-value small { color: var(--cc-muted); font-size: 11px; }
.remark-cell { display: -webkit-box; overflow: hidden; line-height: 1.5; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.drawer-content { min-height: 260px; }
:global(.location-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; }
:global(.location-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
:global(.location-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }
.drawer-content > .el-alert + .el-alert, .detail-descriptions + .el-alert { margin-top: 14px; }
.detail-identity { display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; align-items: center; gap: 12px; margin-bottom: 18px; }
.detail-identity__icon { display: grid; width: 48px; height: 48px; place-items: center; border-radius: 10px; background: #eef3fa; color: var(--cc-primary); }
@@ -820,6 +828,7 @@ onMounted(() => {
:global(.location-action-menu .location-action-menu__danger:not(.is-disabled)) { color: var(--el-color-danger); }
@media (max-width: 720px) {
.drawer-form :deep(.form-grid.two-cols) { grid-template-columns: minmax(0, 1fr); }
.detail-identity { grid-template-columns: 44px minmax(0, 1fr); }
.detail-identity > .el-tag { grid-column: 2; justify-self: start; }
.detail-descriptions :deep(.el-descriptions__body), .detail-descriptions :deep(table), .detail-descriptions :deep(tbody) { display: block; width: 100%; }
@@ -1,4 +1,6 @@
import ElementPlus from 'element-plus'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -15,6 +17,8 @@ import {
} from '../api/platformPallets'
import PalletsView from './PalletsView.vue'
const palletsSource = readFileSync(resolve(process.cwd(), 'src/modules/warehouse-resources/pallets/pages/PalletsView.vue'), 'utf8')
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
vi.mock('../api/platformPallets', () => ({
listPlatformPallets: vi.fn(),
@@ -108,6 +112,13 @@ enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('PalletsView', () => {
it('uses the prototype-sized centered editor while retaining the detail drawer', () => {
expect(palletsSource).toContain('<AppDrawer v-model="detailOpen"')
expect(palletsSource).toContain('<AppDialog v-model="editorOpen" :title="editorTitle" width="min(560px, calc(100vw - 28px))"')
expect(palletsSource).not.toContain('<el-drawer v-model="editorOpen"')
expect(palletsSource).toContain(':global(.pallet-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }')
expect(palletsSource).toContain('.editor-field-grid { grid-template-columns: minmax(0, 1fr); }')
})
beforeEach(() => {
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
document.body.innerHTML = ''
@@ -272,12 +283,9 @@ describe('PalletsView', () => {
await createButton?.trigger('click')
await flushPromises()
expect(document.body.querySelector('.pallet-editor-header')?.textContent).toContain('新增托盘')
expect(document.body.querySelector('.pallet-editor-header')?.textContent).toContain('录入归属仓库与基础规格')
const editorDialog = document.body.querySelector('.pallet-editor-drawer[role="dialog"]')
const labelledBy = editorDialog?.getAttribute('aria-labelledby')
expect(labelledBy).toBeTruthy()
expect(document.getElementById(labelledBy || '')?.textContent).toBe('新增托盘')
const editorDialog = document.body.querySelector('.pallet-editor-dialog')
expect(editorDialog?.textContent).toContain('新增托盘')
expect(editorDialog?.querySelector('.el-dialog__title')?.textContent).toBe('新增托盘')
expect(document.body.querySelectorAll('.editor-section')).toHaveLength(2)
expect(document.body.querySelector('#pallet-ownership-heading')?.textContent).toBe('归属仓库')
expect(document.body.querySelector('#pallet-spec-heading')?.textContent).toBe('托盘规格')
@@ -7,6 +7,7 @@ import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRe
import { listAllPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import FormSection from '@/shared/components/FormSection.vue'
@@ -474,16 +475,7 @@ onMounted(() => {
<template #footer><el-button @click="detailOpen = false">关闭</el-button></template>
</AppDrawer>
<el-drawer v-model="editorOpen" size="560px" class="business-drawer pallet-editor-drawer" :close-on-click-modal="false">
<template #header="{ titleId, titleClass }">
<div class="pallet-editor-header">
<span aria-hidden="true"><Boxes :size="20" /></span>
<div>
<h2 :id="titleId" :class="titleClass">{{ editorTitle }}</h2>
<p>{{ form.id ? '调整托盘规格与现场识别信息' : '录入归属仓库与基础规格,创建后即可用于仓储作业' }}</p>
</div>
</div>
</template>
<AppDialog v-model="editorOpen" :title="editorTitle" width="min(560px, calc(100vw - 28px))" class="pallet-editor-dialog" :close-on-click-modal="false" :confirm-loading="saving" :show-confirm="false" destroy-on-close>
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="editor-form">
<FormSection class="editor-section" title="归属仓库" description="托盘创建后固定归属于所选仓库,不支持跨仓移动。" aria-labelledby="pallet-ownership-heading">
<span id="pallet-ownership-heading" class="sr-only">归属仓库</span>
@@ -543,7 +535,7 @@ onMounted(() => {
<div><el-button :disabled="saving" @click="editorOpen = false">取消</el-button><el-button type="primary" :loading="saving" @click="savePallet"><Save :size="16" />保存托盘</el-button></div>
</div>
</template>
</el-drawer>
</AppDialog>
</AdminPage>
</template>
@@ -569,10 +561,9 @@ onMounted(() => {
.detail-hero p { margin-top: 4px; color: var(--cc-muted); font-size: 13px; }
.detail-section-title { margin: 20px 0 8px; color: var(--cc-text); font-size: 14px; }
.detail-section-title:first-of-type { margin-top: 0; }
.pallet-editor-header { display: flex; min-width: 0; align-items: center; gap: 12px; }
.pallet-editor-header > span { display: grid; width: 38px; height: 38px; flex: 0 0 auto; place-items: center; border-radius: 8px; background: var(--el-color-primary-light-9); color: var(--cc-primary); }
.pallet-editor-header h2 { margin: 0; color: #172033; font-size: 17px; line-height: 1.4; }
.pallet-editor-header p { margin: 3px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
:global(.pallet-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; }
:global(.pallet-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
:global(.pallet-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }
.editor-form { padding: 4px 24px 28px; }
.editor-section { padding: 20px 0 4px; border-bottom: 1px solid var(--cc-border); }
.editor-section:last-child { padding-bottom: 0; border-bottom: 0; }
@@ -588,6 +579,9 @@ onMounted(() => {
.editor-form :deep(.el-input-number), .editor-form :deep(.el-select) { width: 100%; }
.editor-form :deep(.el-form-item) { margin-bottom: 18px; }
.editor-form :deep(.el-form-item__label) { padding-bottom: 7px; color: #344054; font-size: 12px; font-weight: 650; line-height: 1.4; }
@media (max-width: 720px) {
.editor-field-grid { grid-template-columns: minmax(0, 1fr); }
}
.editor-remark-field { margin-bottom: 0 !important; }
.field-help { margin: 6px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
.drawer-footer { display: flex; justify-content: flex-end; gap: 8px; }