style(admin): unify dialog layouts and dense readonly presentation
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import ElementPlus from 'element-plus'
|
||||
import ElementPlus, { ElDialog, ElFormItem, ElMessageBox, ElRadioButton, ElRadioGroup, ElSelect, ElUpload, type UploadFile } from 'element-plus'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
||||
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import DataTable from '@/shared/components/DataTable.vue'
|
||||
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
|
||||
import {
|
||||
changePlatformWarehouseStatus,
|
||||
createFbaWarehouse,
|
||||
createPlatformWarehouse,
|
||||
getPlatformWarehouse,
|
||||
@@ -188,8 +190,35 @@ function setField(selector: string, value: string) {
|
||||
field!.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
}
|
||||
|
||||
function getImportDialog(wrapper: Awaited<ReturnType<typeof mountView>>) {
|
||||
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title') === '导入公共仓库')
|
||||
expect(dialog).toBeDefined()
|
||||
return dialog!
|
||||
}
|
||||
function getEditorDialog(wrapper: Awaited<ReturnType<typeof mountView>>) {
|
||||
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title') !== '导入公共仓库')
|
||||
expect(dialog).toBeDefined()
|
||||
return dialog!
|
||||
}
|
||||
function modalButton(text: string, selector = '.warehouse-import-dialog') {
|
||||
const button = Array.from(document.querySelectorAll<HTMLButtonElement>(`${selector} .el-dialog__footer button`)).find(item => item.textContent?.trim() === text)
|
||||
expect(button, text).toBeDefined()
|
||||
return button!
|
||||
}
|
||||
function chooseImportFile(wrapper: Awaited<ReturnType<typeof mountView>>, file: File) {
|
||||
const raw = Object.assign(file, { uid: 1 })
|
||||
const upload: UploadFile = { name: file.name, status: 'ready', uid: 1, raw }
|
||||
getImportDialog(wrapper).findComponent(ElUpload).props('onChange')?.(upload, [upload])
|
||||
}
|
||||
async function openImportDialog(wrapper: Awaited<ReturnType<typeof mountView>>) {
|
||||
await wrapper.findAll('button').find(button => button.text().trim() === '导入仓库')!.trigger('click')
|
||||
await flushPromises()
|
||||
}
|
||||
const validImport: WarehouseImportResult = { ...importPreview, totalRows: 3, validRows: 3, errorCount: 0, errors: [] }
|
||||
|
||||
enableAutoUnmount(afterEach)
|
||||
afterAll(() => vi.unstubAllGlobals())
|
||||
afterEach(() => ElMessageBox.close())
|
||||
|
||||
describe('WarehousesView', () => {
|
||||
it('uses bordered striped tables and semantic operation buttons', () => {
|
||||
@@ -205,8 +234,11 @@ describe('WarehousesView', () => {
|
||||
it('uses the prototype-sized centered warehouse editor with a fixed footer', () => {
|
||||
expect(warehousesSource).toContain('<AppDialog v-model="editorOpen" :title="editorLabel" width="min(650px, calc(100vw - 28px))"')
|
||||
expect(warehousesSource).not.toContain('<AppDrawer v-model="editorOpen"')
|
||||
expect(warehousesSource).toContain(':global(.warehouse-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }')
|
||||
expect(warehousesSource).toContain('.density-form :deep(.form-grid.two-cols) { grid-template-columns: minmax(0, 1fr); }')
|
||||
expect(warehousesSource).toContain('fullscreenable')
|
||||
expect(warehousesSource).toContain('max-height:min(800px, calc(100vh - 32px))')
|
||||
expect(warehousesSource).not.toContain(':global(.warehouse-editor-dialog .el-dialog__body)')
|
||||
expect(warehousesSource).toContain('density-table density-table--editable warehouse-editor-table')
|
||||
expect(warehousesSource).toContain('label-position="left" label-width="104px"')
|
||||
})
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
|
||||
@@ -217,6 +249,7 @@ describe('WarehousesView', () => {
|
||||
vi.mocked(createFbaWarehouse).mockReset().mockResolvedValue('19006')
|
||||
vi.mocked(updatePlatformWarehouse).mockReset().mockResolvedValue(null)
|
||||
vi.mocked(importWarehouses).mockReset().mockResolvedValue(importPreview)
|
||||
vi.mocked(changePlatformWarehouseStatus).mockReset().mockResolvedValue(null)
|
||||
})
|
||||
|
||||
it('loads a server-paged warehouse list with backend address summaries and no row detail fan-out', async () => {
|
||||
@@ -237,7 +270,7 @@ describe('WarehousesView', () => {
|
||||
expect(wrapper.find('.data-table').text()).not.toContain('Asia/Shanghai')
|
||||
expect(wrapper.text()).toContain('共 1 个公共仓库')
|
||||
expect(wrapper.find('.data-table').exists()).toBe(true)
|
||||
const editButton = wrapper.findAll('.table-actions button').find((button) => button.text().trim() === '编辑')
|
||||
const editButton = wrapper.findAll('.el-table__body .table-actions button').find((button) => button.text().trim() === '编辑')
|
||||
await editButton?.trigger('click')
|
||||
await flushPromises()
|
||||
expect((wrapper.find('textarea[placeholder*="街道"]').element as HTMLTextAreaElement).value).toBe('真实物流园 1 号库')
|
||||
@@ -327,9 +360,8 @@ describe('WarehousesView', () => {
|
||||
expect(dialogText).not.toContain('收货时间')
|
||||
const countrySelect = wrapper.findAllComponents({ name: 'ElSelect' })
|
||||
.find((select) => select.props('placeholder') === '美国 US')
|
||||
expect(countrySelect).toBeDefined()
|
||||
expect(countrySelect!.props('modelValue')).toBe('US')
|
||||
expect(countrySelect!.props('disabled')).toBe(true)
|
||||
expect(countrySelect).toBeUndefined()
|
||||
expect(dialogText).toContain('美国 US')
|
||||
|
||||
const stateSelect = wrapper.findAllComponents({ name: 'ElSelect' })
|
||||
.find((select) => select.props('placeholder') === '请选择州')
|
||||
@@ -564,8 +596,8 @@ describe('WarehousesView', () => {
|
||||
})
|
||||
|
||||
it('renders FBA import precheck totals and row-level errors without committing by default', async () => {
|
||||
expect(warehousesSource).toContain('<el-table v-if="importResult.errors.length" :data="importResult.errors" max-height="260" size="small">')
|
||||
expect(warehousesSource).not.toContain('importResult.errors" max-height="260" size="small" border stripe')
|
||||
expect(warehousesSource).toContain('<el-table :data="importResult.errors" size="small">')
|
||||
expect(warehousesSource).not.toContain('max-height="260"')
|
||||
const wrapper = await mountView()
|
||||
|
||||
await wrapper.findAll('button').find((button) => button.text().trim() === '导入仓库')?.trigger('click')
|
||||
@@ -589,4 +621,152 @@ describe('WarehousesView', () => {
|
||||
expect(document.body.textContent).toContain('文件内编码重复')
|
||||
expect(importWarehouses).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it('retains domestic draft values and the fixed defaults through fullscreen before saving', async () => {
|
||||
const wrapper = await mountView()
|
||||
wrapper.findComponent({ name: 'ElDropdown' }).vm.$emit('command', 'DOMESTIC')
|
||||
await flushPromises()
|
||||
setField('input[placeholder="请输入仓库名称"]', '全屏草稿仓库')
|
||||
setField('input[placeholder="例如 义乌市"]', '义乌市')
|
||||
setField('textarea[placeholder*="街道"]', '完整地址')
|
||||
await flushPromises()
|
||||
document.querySelector<HTMLButtonElement>('.warehouse-editor-dialog button[aria-label="展开全屏"]')!.click()
|
||||
await flushPromises()
|
||||
expect(document.querySelector('.warehouse-editor-dialog.is-fullscreen')).not.toBeNull()
|
||||
expect(document.querySelector<HTMLInputElement>('.warehouse-editor-dialog input[placeholder="请输入仓库名称"]')?.value).toBe('全屏草稿仓库')
|
||||
document.querySelector<HTMLButtonElement>('.warehouse-editor-dialog button[aria-label="退出全屏"]')!.click()
|
||||
await flushPromises()
|
||||
modalButton('确认新增', '.warehouse-editor-dialog').click(); await flushPromises()
|
||||
expect(createPlatformWarehouse).toHaveBeenCalledWith(expect.objectContaining({ warehouseName: '全屏草稿仓库', warehouseType: 'DOMESTIC', countryCode: 'CN', timezone: 'Asia/Shanghai', address: expect.objectContaining({ addressLine1: '完整地址', city: '义乌市' }) }))
|
||||
})
|
||||
|
||||
it('uses readonly values for fixed domestic edit fields and identifies the warehouse by code', async () => {
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-table__body .table-actions button').find(button => button.text().trim() === '编辑')!.trigger('click')
|
||||
await flushPromises()
|
||||
const editor = getEditorDialog(wrapper)
|
||||
expect(editor.props('title')).toBe('编辑国内收货仓 · CN-YW-01')
|
||||
expect(getPlatformWarehouse).toHaveBeenCalledWith(domesticWarehouse.id)
|
||||
for (const label of ['国家或地区', '时区']) {
|
||||
const item = editor.findAllComponents(ElFormItem).find(field => field.props('label') === label)!
|
||||
expect(item.findComponent(ElSelect).exists()).toBe(false)
|
||||
}
|
||||
expect(document.querySelector('.warehouse-editor-dialog')?.textContent).toContain('中国 CN')
|
||||
expect(document.querySelector('.warehouse-editor-dialog')?.textContent).toContain('Asia/Shanghai')
|
||||
})
|
||||
|
||||
it('does not let an older edit request overwrite a newly opened create draft', async () => {
|
||||
let finish!: (value: PlatformWarehouseDetailResource) => void
|
||||
vi.mocked(getPlatformWarehouse).mockReturnValueOnce(new Promise(resolve => { finish = resolve }))
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-table__body .table-actions button').find(button => button.text().trim() === '编辑')!.trigger('click')
|
||||
await flushPromises()
|
||||
wrapper.findComponent({ name: 'ElDropdown' }).vm.$emit('command', 'FBA')
|
||||
await flushPromises()
|
||||
setField('input[placeholder="例如 LAX9"]', 'NEW2')
|
||||
finish(detail); await flushPromises()
|
||||
expect(getEditorDialog(wrapper).props('title')).toBe('新增FBA 地址')
|
||||
expect(document.querySelector<HTMLInputElement>('input[placeholder="例如 LAX9"]')?.value).toBe('NEW2')
|
||||
expect(document.querySelector('.warehouse-editor-dialog')?.textContent).not.toContain('义乌收货仓')
|
||||
})
|
||||
|
||||
it('keeps import file, mode and preview through fullscreen and commits only after a successful precheck', async () => {
|
||||
vi.mocked(importWarehouses).mockResolvedValueOnce(validImport).mockResolvedValueOnce({ ...validImport, committed: true })
|
||||
const wrapper = await mountView(); await openImportDialog(wrapper)
|
||||
const file = new File(['content'], 'warehouses.xlsx')
|
||||
chooseImportFile(wrapper, file); await flushPromises()
|
||||
expect(modalButton('确认导入').disabled).toBe(true)
|
||||
expect(importWarehouses).not.toHaveBeenCalled()
|
||||
modalButton('预检文件').click(); await flushPromises()
|
||||
expect(importWarehouses).toHaveBeenCalledWith(file, true, 'CREATE_ONLY')
|
||||
expect(document.querySelector('.warehouse-import-summary')?.textContent).toContain('总行数')
|
||||
expect(modalButton('确认导入').disabled).toBe(false)
|
||||
document.querySelector<HTMLButtonElement>('.warehouse-import-dialog button[aria-label="展开全屏"]')!.click()
|
||||
await flushPromises()
|
||||
expect(document.querySelector('.warehouse-import-dialog.is-fullscreen')).not.toBeNull()
|
||||
expect(importWarehouses).toHaveBeenCalledTimes(1)
|
||||
modalButton('确认导入').click(); await flushPromises()
|
||||
expect(importWarehouses).toHaveBeenLastCalledWith(file, false, 'CREATE_ONLY')
|
||||
expect(document.querySelector('.warehouse-import-dialog')?.textContent).toContain('导入已提交')
|
||||
expect(modalButton('确认导入').disabled).toBe(true)
|
||||
expect(getImportDialog(wrapper).props('modelValue')).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['extension', 'size'] as const)('invalidates the previous preview when a new file fails the %s check', async invalid => {
|
||||
vi.mocked(importWarehouses).mockResolvedValue(validImport)
|
||||
const wrapper = await mountView(); await openImportDialog(wrapper)
|
||||
chooseImportFile(wrapper, new File(['content'], 'valid.xlsx')); await flushPromises()
|
||||
modalButton('预检文件').click(); await flushPromises()
|
||||
expect(modalButton('确认导入').disabled).toBe(false)
|
||||
const file = new File(['content'], invalid === 'extension' ? 'bad.csv' : 'big.xlsx')
|
||||
if (invalid === 'size') Object.defineProperty(file, 'size', { value: 5 * 1024 * 1024 + 1 })
|
||||
chooseImportFile(wrapper, file); await flushPromises()
|
||||
expect(document.querySelector('.warehouse-import-summary')).toBeNull()
|
||||
expect(modalButton('确认导入').disabled).toBe(true)
|
||||
expect(modalButton('预检文件').disabled).toBe(true)
|
||||
expect(importWarehouses).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('locks import closing, file changes and mode controls while the precheck is running', async () => {
|
||||
let finish!: (value: WarehouseImportResult) => void
|
||||
vi.mocked(importWarehouses).mockReturnValueOnce(new Promise(resolve => { finish = resolve }))
|
||||
const wrapper = await mountView(); await openImportDialog(wrapper)
|
||||
const file = new File(['content'], 'current.xlsx')
|
||||
chooseImportFile(wrapper, file); await flushPromises()
|
||||
modalButton('预检文件').click(); await flushPromises()
|
||||
const dialog = getImportDialog(wrapper)
|
||||
expect(dialog.findComponent(ElDialog).props()).toMatchObject({ showClose: false, closeOnPressEscape: false, closeOnClickModal: false })
|
||||
expect(dialog.findComponent(ElUpload).props('disabled')).toBe(true)
|
||||
expect(dialog.findComponent(ElRadioGroup).props('disabled')).toBe(true)
|
||||
expect(modalButton('关闭').disabled).toBe(true)
|
||||
chooseImportFile(wrapper, new File(['other'], 'other.xlsx'))
|
||||
finish(validImport); await flushPromises()
|
||||
vi.mocked(importWarehouses).mockResolvedValueOnce({ ...validImport, committed: true })
|
||||
modalButton('确认导入').click(); await flushPromises()
|
||||
expect(importWarehouses).toHaveBeenLastCalledWith(file, false, 'CREATE_ONLY')
|
||||
})
|
||||
|
||||
it('invalidates a precheck when changing import mode and retains the update-permission gate', async () => {
|
||||
vi.mocked(importWarehouses).mockResolvedValue(validImport)
|
||||
const wrapper = await mountView(); await openImportDialog(wrapper)
|
||||
const dialog = getImportDialog(wrapper)
|
||||
dialog.findComponent(ElRadioGroup).vm.$emit('update:modelValue', 'UPSERT')
|
||||
chooseImportFile(wrapper, new File(['content'], 'upsert.xlsx')); await flushPromises()
|
||||
modalButton('预检文件').click(); await flushPromises()
|
||||
expect(importWarehouses).toHaveBeenLastCalledWith(expect.any(File), true, 'UPSERT')
|
||||
expect(modalButton('确认导入').disabled).toBe(false)
|
||||
useAuthStore().permissions = ['platform:warehouse:list', 'platform:warehouse:create']
|
||||
useAccessPreviewStore().initializeAfterLogin(); await flushPromises()
|
||||
expect(dialog.findAllComponents(ElRadioButton).find(button => button.props('value') === 'UPSERT')!.props('disabled')).toBe(true)
|
||||
expect(modalButton('确认导入').disabled).toBe(true)
|
||||
dialog.findComponent(ElRadioGroup).vm.$emit('update:modelValue', 'CREATE_ONLY'); await flushPromises()
|
||||
expect(document.querySelector('.warehouse-import-summary')).toBeNull()
|
||||
expect(modalButton('确认导入').disabled).toBe(true)
|
||||
expect(importWarehouses).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('retains an import draft after failure so the same file can be retried', async () => {
|
||||
vi.mocked(importWarehouses).mockRejectedValueOnce(new Error('network')).mockResolvedValueOnce(validImport)
|
||||
const wrapper = await mountView(); await openImportDialog(wrapper)
|
||||
const file = new File(['content'], 'retry.xlsx')
|
||||
chooseImportFile(wrapper, file); await flushPromises()
|
||||
modalButton('预检文件').click(); await flushPromises()
|
||||
expect(getImportDialog(wrapper).props('modelValue')).toBe(true)
|
||||
expect(modalButton('预检文件').disabled).toBe(false)
|
||||
modalButton('预检文件').click(); await flushPromises()
|
||||
expect(vi.mocked(importWarehouses).mock.calls).toEqual([[file, true, 'CREATE_ONLY'], [file, true, 'CREATE_ONLY']])
|
||||
expect(modalButton('确认导入').disabled).toBe(false)
|
||||
})
|
||||
|
||||
it.each(['ACTIVE', 'INACTIVE'] as const)('keeps warehouse %s confirmation and the original status payload', async status => {
|
||||
const id = '9007199254740993123'
|
||||
vi.mocked(listPlatformWarehouses).mockResolvedValue({ records: [{ ...domesticWarehouse, id, status }], page: 1, size: 20, total: 1, pages: 1 })
|
||||
const wrapper = await mountView()
|
||||
const verb = status === 'ACTIVE' ? '停用' : '启用'
|
||||
await wrapper.findAll('.el-table__body .table-actions button').find(button => button.text().trim() === verb)!.trigger('click'); await flushPromises()
|
||||
expect(document.querySelector('.el-message-box__title')?.textContent).toBe(`${verb}公共仓库 · CN-YW-01`)
|
||||
expect(changePlatformWarehouseStatus).not.toHaveBeenCalled()
|
||||
document.querySelector<HTMLButtonElement>('.el-message-box .el-button--primary')!.click(); await flushPromises()
|
||||
expect(changePlatformWarehouseStatus).toHaveBeenCalledWith(id, status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE')
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ChevronDown, Download, FileSpreadsheet, FileUp, MapPin, Plus, Save } fr
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import FormSection from '@/shared/components/FormSection.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
@@ -161,6 +160,7 @@ const queryFields: QueryField[] = [
|
||||
},
|
||||
]
|
||||
let loadSequence = 0
|
||||
let editorLoadSequence = 0
|
||||
|
||||
interface WarehouseRow extends Warehouse {
|
||||
warehouseType: WarehouseType
|
||||
@@ -238,8 +238,12 @@ const formErrors = reactive<Record<WarehouseRequiredField, string>>({
|
||||
code: '', name: '', province: '', countryCode: '', timezone: '', city: '', postalCode: '', address: '',
|
||||
})
|
||||
const editorLabel = computed(() => editorMode.value === 'EDIT'
|
||||
? `编辑${warehouseTypeLabels[form.warehouseType]}`
|
||||
? warehouseDialogTitle(`编辑${warehouseTypeLabels[form.warehouseType]}`, form.code)
|
||||
: `新增${warehouseTypeLabels[createType.value]}`)
|
||||
|
||||
function warehouseDialogTitle(action: string, code: string) {
|
||||
return code.trim() ? `${action} · ${code.trim()}` : action
|
||||
}
|
||||
const editorPermission = computed(() => editorMode.value === 'EDIT' ? 'platform:warehouse:edit' : 'platform:warehouse:create')
|
||||
const isFbaCreate = computed(() => editorMode.value === 'CREATE' && createType.value === 'FBA')
|
||||
const isDomesticCreate = computed(() => editorMode.value === 'CREATE' && createType.value === 'DOMESTIC')
|
||||
@@ -434,6 +438,8 @@ function requireWarehouseReady() {
|
||||
|
||||
function openCreate(type: WarehouseType) {
|
||||
if (!requireWarehouseReady()) return
|
||||
editorLoadSequence += 1
|
||||
editLoading.value = false
|
||||
editorMode.value = 'CREATE'
|
||||
createType.value = type
|
||||
Object.assign(form, emptyForm(type))
|
||||
@@ -457,10 +463,12 @@ async function ensureDetail(warehouse: WarehouseRow) {
|
||||
}
|
||||
|
||||
async function openEdit(warehouse: WarehouseRow) {
|
||||
const sequence = ++editorLoadSequence
|
||||
const listSequence = loadSequence
|
||||
editLoading.value = true
|
||||
try {
|
||||
const detail = await ensureDetail(warehouse)
|
||||
if (!detail || !loaded.value) return
|
||||
if (sequence !== editorLoadSequence || listSequence !== loadSequence || !detail || !loaded.value) return
|
||||
const address = detail.addresses[0]
|
||||
const receiving = detail.capabilities.find((item) => item.capabilityCode === 'RECEIVING')
|
||||
editorMode.value = 'EDIT'
|
||||
@@ -486,7 +494,7 @@ async function openEdit(warehouse: WarehouseRow) {
|
||||
captureBaseline()
|
||||
editorOpen.value = true
|
||||
} finally {
|
||||
editLoading.value = false
|
||||
if (sequence === editorLoadSequence) editLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +593,7 @@ async function toggleStatus(warehouse: WarehouseRow) {
|
||||
target === 'INACTIVE'
|
||||
? `停用“${warehouse.name}”后,客户新建头程订单时将无法选择该公共仓库。`
|
||||
: `确认重新启用“${warehouse.name}”供客户下单选择吗?`,
|
||||
`${targetText}公共仓库`,
|
||||
warehouseDialogTitle(`${targetText}公共仓库`, warehouse.code),
|
||||
{ customClass: 'admin-density-confirm', confirmButtonText: `确认${targetText}`, cancelButtonText: '取消', type: target === 'INACTIVE' ? 'warning' : 'info' },
|
||||
)
|
||||
statusLoadingIds.value = new Set(statusLoadingIds.value).add(warehouse.id)
|
||||
@@ -612,6 +620,8 @@ function openImport() {
|
||||
}
|
||||
|
||||
function selectImportFile(uploadFile: UploadFile) {
|
||||
if (importing.value) return
|
||||
importResult.value = null
|
||||
const file = uploadFile.raw ?? null
|
||||
if (!file) return
|
||||
if (!file.name.toLowerCase().endsWith('.xlsx')) {
|
||||
@@ -625,10 +635,10 @@ function selectImportFile(uploadFile: UploadFile) {
|
||||
return
|
||||
}
|
||||
importFile.value = file
|
||||
importResult.value = null
|
||||
}
|
||||
|
||||
function removeImportFile() {
|
||||
if (importing.value) return
|
||||
importFile.value = null
|
||||
importResult.value = null
|
||||
}
|
||||
@@ -655,6 +665,7 @@ async function downloadTemplate() {
|
||||
}
|
||||
|
||||
async function runImport(dryRun: boolean) {
|
||||
if (importing.value || (!dryRun && !canCommitImport.value)) return
|
||||
if (!importFile.value) {
|
||||
ElMessage.warning('请先选择 XLSX 文件')
|
||||
return
|
||||
@@ -761,103 +772,90 @@ onMounted(() => void loadWarehouses())
|
||||
</DataTable></section>
|
||||
</ListPageLayout>
|
||||
|
||||
<AppDialog v-model="editorOpen" :title="editorLabel" width="min(650px, calc(100vw - 28px))" class="warehouse-editor-dialog admin-density-dialog" :close-on-click-modal="false" :confirm-loading="saving" :show-confirm="false" destroy-on-close>
|
||||
<el-form :model="form" label-position="top" class="density-form" :disabled="saving">
|
||||
<FormSection class="density-form-section" title="基础信息">
|
||||
<div class="form-grid two-cols">
|
||||
<el-form-item v-if="isFbaCreate" label="FBA 编码" required :class="{ 'has-field-error': formErrors.code }"><el-input v-model="form.code" maxlength="50" placeholder="例如 LAX9" @input="form.code = String($event).toUpperCase(); clearFieldError('code')" /><p v-if="formErrors.code" class="field-error" role="alert">{{ formErrors.code }}</p></el-form-item>
|
||||
<el-form-item v-if="isFbaCreate" label="国家或地区" required>
|
||||
<el-select v-model="form.countryCode" disabled placeholder="美国 US">
|
||||
<el-option label="美国 US" value="US" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-else>
|
||||
<el-form-item label="仓库名称" required :class="{ 'has-field-error': formErrors.name }"><el-input v-model="form.name" maxlength="120" placeholder="请输入仓库名称" @input="clearFieldError('name')" /><p v-if="formErrors.name" class="field-error" role="alert">{{ formErrors.name }}</p></el-form-item>
|
||||
<el-form-item v-if="editorMode === 'EDIT'" label="仓库类型" required><el-select v-model="form.warehouseType"><el-option v-for="(label, value) in warehouseTypeLabels" :key="value" :label="label" :value="value" /></el-select></el-form-item>
|
||||
<el-form-item v-if="!isDomesticCreate" label="国家或地区" required :class="{ 'has-field-error': formErrors.countryCode }">
|
||||
<el-select v-if="isDomesticForm" v-model="form.countryCode" disabled placeholder="中国 CN"><el-option label="中国 CN" value="CN" /></el-select>
|
||||
<AppDialog v-model="editorOpen" :title="editorLabel" width="min(650px, calc(100vw - 28px))" class="warehouse-editor-dialog admin-density-dialog" fullscreenable :close-on-click-modal="false" :confirm-loading="saving" :show-confirm="false" destroy-on-close>
|
||||
<el-form :model="form" label-position="left" label-width="104px" class="density-form density-dialog-sections" :disabled="saving">
|
||||
<section class="density-dialog-section" data-tone="work"><h3 class="density-dialog-section__title">基础信息</h3>
|
||||
<table class="density-table density-table--editable warehouse-editor-table" aria-label="仓库基础信息"><tbody>
|
||||
<template v-if="isFbaCreate"><tr><td><el-form-item label="FBA 编码" required :class="{ 'has-field-error': formErrors.code }"><el-input v-model="form.code" maxlength="50" placeholder="例如 LAX9" @input="form.code = String($event).toUpperCase(); clearFieldError('code')" /><p v-if="formErrors.code" class="field-error" role="alert">{{ formErrors.code }}</p></el-form-item></td><td><el-form-item label="国家或地区" required>
|
||||
<span>{{ formatCountryDisplay(form.countryCode) }}</span>
|
||||
</el-form-item></td></tr></template>
|
||||
<template v-else><tr v-if="editorMode === 'EDIT'"><td><el-form-item label="仓库名称" required :class="{ 'has-field-error': formErrors.name }"><el-input v-model="form.name" maxlength="120" placeholder="请输入仓库名称" @input="clearFieldError('name')" /><p v-if="formErrors.name" class="field-error" role="alert">{{ formErrors.name }}</p></el-form-item></td><td><el-form-item label="仓库类型" required><el-select v-model="form.warehouseType"><el-option v-for="(label, value) in warehouseTypeLabels" :key="value" :label="label" :value="value" /></el-select></el-form-item></td></tr><tr v-else><td colspan="2"><el-form-item label="仓库名称" required :class="{ 'has-field-error': formErrors.name }"><el-input v-model="form.name" maxlength="120" placeholder="请输入仓库名称" @input="clearFieldError('name')" /><p v-if="formErrors.name" class="field-error" role="alert">{{ formErrors.name }}</p></el-form-item></td></tr><tr v-if="!isDomesticCreate"><td><el-form-item label="国家或地区" required :class="{ 'has-field-error': formErrors.countryCode }">
|
||||
<span v-if="isDomesticForm">{{ formatCountryDisplay(form.countryCode) }}</span>
|
||||
<el-select v-else v-model="form.countryCode" filterable placeholder="请选择国家或地区" @change="onCountryChange"><el-option v-for="country in countryOptions" :key="country.value" :label="country.label" :value="country.value" /></el-select>
|
||||
<p v-if="formErrors.countryCode" class="field-error" role="alert">{{ formErrors.countryCode }}</p>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!isDomesticCreate" label="时区" required :class="{ 'has-field-error': formErrors.timezone }">
|
||||
<el-select v-if="isDomesticForm" v-model="form.timezone" disabled placeholder="Asia/Shanghai"><el-option label="Asia/Shanghai" value="Asia/Shanghai" /></el-select>
|
||||
</el-form-item></td><td><el-form-item label="时区" required :class="{ 'has-field-error': formErrors.timezone }">
|
||||
<span v-if="isDomesticForm">{{ form.timezone }}</span>
|
||||
<el-select v-else v-model="form.timezone" filterable placeholder="请选择时区" @change="clearFieldError('timezone')"><el-option v-for="timezone in timezoneOptions" :key="timezone" :label="timezone" :value="timezone" /></el-select>
|
||||
<p v-if="formErrors.timezone" class="field-error" role="alert">{{ formErrors.timezone }}</p>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item v-if="activeWarehouseType === 'FBA'" label="内部编号"><el-input v-model="form.internalCode" maxlength="16" placeholder="例如 154 或 CADF" @input="form.internalCode = String($event ?? '').replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 16)" /></el-form-item>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection class="density-form-section" title="地址信息">
|
||||
<div class="form-grid two-cols">
|
||||
<el-form-item :label="addressRegionLabel" :required="isFbaCreate || usesUsStateSelect" :class="{ 'has-field-error': formErrors.province }">
|
||||
</el-form-item></td></tr></template>
|
||||
<tr v-if="activeWarehouseType === 'FBA'"><td colspan="2"><el-form-item label="内部编号"><el-input v-model="form.internalCode" maxlength="16" placeholder="例如 154 或 CADF" @input="form.internalCode = String($event ?? '').replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 16)" /></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section class="density-dialog-section" data-tone="work"><h3 class="density-dialog-section__title">地址信息</h3>
|
||||
<table class="density-table density-table--editable warehouse-editor-table" aria-label="仓库地址信息"><tbody>
|
||||
<tr><td><el-form-item :label="addressRegionLabel" :required="isFbaCreate || usesUsStateSelect" :class="{ 'has-field-error': formErrors.province }">
|
||||
<el-select v-if="usesUsStateSelect" v-model="form.province" filterable :placeholder="regionPlaceholder" @change="onRegionChange"><el-option v-for="state in usStateOptions" :key="state.value" :label="state.label" :value="state.value" /></el-select>
|
||||
<el-input v-else v-model="form.province" maxlength="100" :placeholder="regionPlaceholder" @input="isFbaCreate && (form.province = String($event).toUpperCase()); clearFieldError('province')" />
|
||||
<p v-if="formErrors.province" class="field-error" role="alert">{{ formErrors.province }}</p>
|
||||
</el-form-item>
|
||||
<el-form-item label="城市" required :class="{ 'has-field-error': formErrors.city }"><el-input v-model="form.city" maxlength="100" :placeholder="cityPlaceholder" @input="clearFieldError('city')" /><p v-if="formErrors.city" class="field-error" role="alert">{{ formErrors.city }}</p></el-form-item>
|
||||
<el-form-item label="邮编" :required="isFbaCreate" :class="{ 'has-field-error': formErrors.postalCode }"><el-input v-model="form.postalCode" maxlength="20" :placeholder="isDomesticForm ? '例如 322000' : '例如 90802'" @input="clearFieldError('postalCode')" /><p v-if="formErrors.postalCode" class="field-error" role="alert">{{ formErrors.postalCode }}</p></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="详细地址" required :class="{ 'has-field-error': formErrors.address }"><el-input v-model="form.address" maxlength="255" type="textarea" :rows="3" :placeholder="addressPlaceholder" @input="clearFieldError('address')" /><p v-if="formErrors.address" class="field-error" role="alert">{{ formErrors.address }}</p></el-form-item>
|
||||
</FormSection>
|
||||
|
||||
<FormSection class="density-form-section" v-if="!isFbaCreate" title="编码与状态">
|
||||
<div class="form-grid two-cols compact-grid">
|
||||
<el-form-item label="仓库编码" :class="{ 'has-field-error': formErrors.code }"><el-input v-model="form.code" maxlength="50" placeholder="系统自动生成" @input="form.code = String($event).toUpperCase(); clearFieldError('code')" /><p v-if="formErrors.code" class="field-error" role="alert">{{ formErrors.code }}</p></el-form-item>
|
||||
<el-form-item label="状态"><el-radio-group v-model="form.status" class="warehouse-status-toggle"><el-radio-button value="ACTIVE">启用</el-radio-button><el-radio-button value="INACTIVE">停用</el-radio-button></el-radio-group></el-form-item>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection class="density-form-section" v-if="!isFbaCreate" title="联系信息">
|
||||
<div class="form-grid two-cols compact-grid">
|
||||
<el-form-item label="联系人"><el-input v-model="form.contactName" maxlength="64" placeholder="选填" /></el-form-item>
|
||||
<el-form-item label="联系电话"><el-input v-model="form.contactPhone" maxlength="32" inputmode="numeric" placeholder="选填" @input="form.contactPhone = digitsOnly($event)" /></el-form-item>
|
||||
<el-form-item label="收货时间"><el-time-picker v-model="form.receivingCutoffTime" value-format="HH:mm:ss" format="HH:mm" placeholder="请选择收货截止时间" /></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" maxlength="500" type="textarea" :rows="3" show-word-limit placeholder="选填" /></el-form-item>
|
||||
</FormSection>
|
||||
</el-form-item></td><td><el-form-item label="城市" required :class="{ 'has-field-error': formErrors.city }"><el-input v-model="form.city" maxlength="100" :placeholder="cityPlaceholder" @input="clearFieldError('city')" /><p v-if="formErrors.city" class="field-error" role="alert">{{ formErrors.city }}</p></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="邮编" :required="isFbaCreate" :class="{ 'has-field-error': formErrors.postalCode }"><el-input v-model="form.postalCode" maxlength="20" :placeholder="isDomesticForm ? '例如 322000' : '例如 90802'" @input="clearFieldError('postalCode')" /><p v-if="formErrors.postalCode" class="field-error" role="alert">{{ formErrors.postalCode }}</p></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="详细地址" required :class="{ 'has-field-error': formErrors.address }"><el-input v-model="form.address" maxlength="255" type="textarea" :rows="3" :placeholder="addressPlaceholder" @input="clearFieldError('address')" /><p v-if="formErrors.address" class="field-error" role="alert">{{ formErrors.address }}</p></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section class="density-dialog-section" data-tone="work" v-if="!isFbaCreate"><h3 class="density-dialog-section__title">编码与状态</h3>
|
||||
<table class="density-table density-table--editable warehouse-editor-table" aria-label="仓库编码与状态"><tbody>
|
||||
<tr><td><el-form-item label="仓库编码" :class="{ 'has-field-error': formErrors.code }"><el-input v-model="form.code" maxlength="50" placeholder="系统自动生成" @input="form.code = String($event).toUpperCase(); clearFieldError('code')" /><p v-if="formErrors.code" class="field-error" role="alert">{{ formErrors.code }}</p></el-form-item></td><td><el-form-item label="状态"><el-radio-group v-model="form.status" class="warehouse-status-toggle"><el-radio-button value="ACTIVE">启用</el-radio-button><el-radio-button value="INACTIVE">停用</el-radio-button></el-radio-group></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section class="density-dialog-section" data-tone="work" v-if="!isFbaCreate"><h3 class="density-dialog-section__title">联系信息</h3>
|
||||
<table class="density-table density-table--editable warehouse-editor-table" aria-label="仓库联系信息"><tbody>
|
||||
<tr><td><el-form-item label="联系人"><el-input v-model="form.contactName" maxlength="64" placeholder="选填" /></el-form-item></td><td><el-form-item label="联系电话"><el-input v-model="form.contactPhone" maxlength="32" inputmode="numeric" placeholder="选填" @input="form.contactPhone = digitsOnly($event)" /></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="收货时间"><el-time-picker v-model="form.receivingCutoffTime" value-format="HH:mm:ss" format="HH:mm" placeholder="请选择收货截止时间" /></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="备注"><el-input v-model="form.remark" maxlength="500" type="textarea" :rows="3" show-word-limit placeholder="选填" /></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
</el-form>
|
||||
<template #footer><div class="drawer-footer"><el-button :disabled="saving" @click="editorOpen = false">取消</el-button><el-button type="primary" :loading="saving" @click="saveWarehouse"><Save :size="16" />{{ editorMode === 'EDIT' ? '保存修改' : '确认新增' }}</el-button></div></template>
|
||||
<template #footer><div class="density-actions density-actions--footer"><el-button :disabled="saving" @click="editorOpen = false">取消</el-button><el-button type="primary" :loading="saving" @click="saveWarehouse"><Save :size="16" />{{ editorMode === 'EDIT' ? '保存修改' : '确认新增' }}</el-button></div></template>
|
||||
</AppDialog>
|
||||
|
||||
<el-dialog class="admin-density-dialog" v-model="importOpen" title="导入公共仓库" width="min(760px, calc(100vw - 32px))" :close-on-click-modal="!importing" :close-on-press-escape="!importing">
|
||||
<div class="import-content">
|
||||
<div class="import-toolbar">
|
||||
<AppDialog class="warehouse-import-dialog admin-density-dialog" v-model="importOpen" fullscreenable :confirm-loading="importing" :show-confirm="false" title="导入公共仓库" width="min(760px, calc(100vw - 32px))" :close-on-click-modal="!importing" :close-on-press-escape="!importing">
|
||||
<div class="density-dialog-sections">
|
||||
<section class="density-dialog-section" data-tone="work"><h3 class="density-dialog-section__title">文件与导入模式</h3>
|
||||
<div class="import-toolbar">
|
||||
<el-button :loading="downloadingTemplate" @click="downloadTemplate"><Download :size="16" />下载 XLSX 模板</el-button>
|
||||
<el-radio-group v-model="importMode" :disabled="importing">
|
||||
<el-radio-button value="CREATE_ONLY">仅新增</el-radio-button>
|
||||
<el-radio-button value="UPSERT" :disabled="!access.can('platform:warehouse:edit')">新增并更新</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-upload accept=".xlsx" :auto-upload="false" :limit="1" :on-change="selectImportFile" :on-remove="removeImportFile">
|
||||
<el-upload :disabled="importing" accept=".xlsx" :auto-upload="false" :limit="1" :on-change="selectImportFile" :on-remove="removeImportFile">
|
||||
<el-button><FileUp :size="16" />选择 XLSX 文件</el-button>
|
||||
<template #tip><div class="el-upload__tip">最多 5MB、1000 行;请使用标准模板,FBA 名称可不填并默认使用编码。</div></template>
|
||||
</el-upload>
|
||||
<div v-if="importResult" class="import-result" role="status">
|
||||
<dl class="import-summary density-detail-overview">
|
||||
<div><dt>总行数</dt><dd>{{ importResult.totalRows }}</dd></div>
|
||||
<div><dt>有效</dt><dd>{{ importResult.validRows }}</dd></div>
|
||||
<div><dt>新增</dt><dd>{{ importResult.createCount }}</dd></div>
|
||||
<div><dt>更新</dt><dd>{{ importResult.updateCount }}</dd></div>
|
||||
<div><dt>无变化</dt><dd>{{ importResult.unchangedCount }}</dd></div>
|
||||
<div><dt>错误</dt><dd :class="{ 'is-error': importResult.errorCount }">{{ importResult.errorCount }}</dd></div>
|
||||
</dl>
|
||||
<div class="import-result-heading"><strong>{{ importResult.committed ? '导入已提交' : (importResult.errorCount ? '预检未通过,未写入数据' : '预检通过,尚未写入数据') }}</strong><el-button v-if="importResult.errors.length" size="small" @click="downloadImportErrors"><Download :size="15" />下载错误清单</el-button></div>
|
||||
<el-table v-if="importResult.errors.length" :data="importResult.errors" max-height="260" size="small">
|
||||
</section>
|
||||
<section v-if="importResult" class="density-dialog-section" data-tone="summary" role="status">
|
||||
<h3 class="density-dialog-section__title warehouse-import-heading">导入结果<el-tag :type="importResult.committed ? 'success' : importResult.errorCount ? 'danger' : 'warning'">{{ importResult.committed ? '导入已提交' : (importResult.errorCount ? '预检未通过,未写入数据' : '预检通过,尚未写入数据') }}</el-tag></h3>
|
||||
<table class="density-table warehouse-import-summary" aria-label="仓库导入统计"><tbody>
|
||||
<tr><th scope="row">总行数</th><td>{{ importResult.totalRows }}</td><th scope="row">有效</th><td>{{ importResult.validRows }}</td><th scope="row">新增</th><td>{{ importResult.createCount }}</td></tr>
|
||||
<tr><th scope="row">更新</th><td>{{ importResult.updateCount }}</td><th scope="row">无变化</th><td>{{ importResult.unchangedCount }}</td><th scope="row">错误</th><td><el-tag v-if="importResult.errorCount" type="danger">{{ importResult.errorCount }}</el-tag><span v-else>{{ importResult.errorCount }}</span></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section v-if="importResult?.errors.length" class="density-dialog-section" data-tone="source">
|
||||
<h3 class="density-dialog-section__title warehouse-import-heading">逐行校验结果<el-button size="small" @click="downloadImportErrors"><Download :size="15" />下载错误清单</el-button></h3>
|
||||
<el-table :data="importResult.errors" size="small">
|
||||
<el-table-column prop="row" label="行号" width="70" /><el-table-column prop="field" label="字段" width="110" /><el-table-column prop="code" label="错误码" width="130" :formatter="formatImportErrorCode" /><el-table-column prop="message" label="原因" min-width="220" />
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<template #footer><el-button :disabled="importing" @click="importOpen = false">关闭</el-button><el-button :loading="importing" :disabled="!importFile" @click="runImport(true)">预检文件</el-button><el-button type="primary" :loading="importing" :disabled="!canCommitImport" @click="runImport(false)"><FileUp :size="16" />确认导入</el-button></template>
|
||||
</el-dialog>
|
||||
<template #footer><div class="density-actions density-actions--footer"><el-button :disabled="importing" @click="importOpen = false">关闭</el-button><el-button :loading="importing" :disabled="!importFile" @click="runImport(true)">预检文件</el-button><el-button type="primary" :loading="importing" :disabled="!canCommitImport" @click="runImport(false)"><FileUp :size="16" />确认导入</el-button></div></template>
|
||||
</AppDialog>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.state-alert p { margin: var(--admin-space-2) 0 var(--admin-space-4); line-height: 1.55; }
|
||||
.warehouse-heading { gap: var(--admin-space-6); }
|
||||
.warehouse-primary-actions, .import-toolbar, .import-result-heading { display: flex; flex-wrap: wrap; align-items: center; gap: var(--admin-space-4); }
|
||||
.warehouse-primary-actions, .import-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--admin-space-4); }
|
||||
.warehouse-cell { display: flex; align-items: center; gap: var(--admin-space-4); }
|
||||
.warehouse-cell > span { display: grid; width: 34px; height: 34px; flex: 0 0 34px; place-items: center; border-radius: var(--admin-radius); background: #eef3fa; color: var(--cc-primary); }
|
||||
.warehouse-cell > div { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
|
||||
@@ -867,18 +865,9 @@ onMounted(() => void loadWarehouses())
|
||||
.warehouse-address svg { flex: 0 0 15px; margin-top: 2px; color: var(--cc-muted); }
|
||||
.warehouse-address span { min-width: 0; }
|
||||
.field-error { width: 100%; margin: var(--admin-space-2) 0 0; color: var(--el-color-danger); font-size: var(--admin-font-caption); line-height: 1.35; overflow-wrap: anywhere; }
|
||||
.compact-grid { row-gap: var(--admin-space-4); }
|
||||
:global(.warehouse-editor-dialog) { max-height: min(800px, calc(100vh - 32px)); display: flex; flex-direction: column; }
|
||||
:global(.warehouse-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
|
||||
:global(.warehouse-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }
|
||||
.warehouse-status-toggle :deep(.el-radio-button__inner) { min-width: 74px; }
|
||||
@media (max-width: 720px) {
|
||||
.density-form :deep(.form-grid.two-cols) { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
.import-content { display: grid; gap: var(--admin-space-7); }
|
||||
|
||||
.import-toolbar { justify-content: space-between; }
|
||||
.import-result { display: grid; gap: var(--admin-space-5); padding-top: var(--admin-space-6); border-top: 1px solid var(--cc-border); }
|
||||
.import-result-heading { justify-content: space-between; }
|
||||
|
||||
:deep(.has-field-error .el-input__wrapper) { box-shadow: 0 0 0 1px var(--el-color-danger) inset; }
|
||||
:deep(.has-field-error .el-textarea__inner) { box-shadow: 0 0 0 1px var(--el-color-danger) inset; }
|
||||
@@ -891,17 +880,16 @@ onMounted(() => void loadWarehouses())
|
||||
.warehouse-primary-actions :deep(.el-dropdown .el-button) { width: 100%; min-height: 44px; }
|
||||
.warehouse-primary-actions > .el-button { min-height: 44px; }
|
||||
.import-toolbar { align-items: stretch; flex-direction: column; }
|
||||
.import-summary { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.import-summary div:nth-child(3) { border-right: 0; }
|
||||
.import-summary div:nth-child(-n+3) { border-bottom: 1px solid var(--cc-border); }
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.warehouse-primary-actions { flex-direction: column; }
|
||||
.warehouse-primary-actions > .el-button, .warehouse-primary-actions > .el-dropdown { width: 100%; flex-basis: auto; }
|
||||
.import-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.import-summary div:nth-child(3) { border-right: 1px solid var(--cc-border); }
|
||||
.import-summary div:nth-child(2n) { border-right: 0; }
|
||||
.import-summary div:nth-child(-n+4) { border-bottom: 1px solid var(--cc-border); }
|
||||
}
|
||||
:global(.warehouse-editor-dialog.app-dialog.admin-density-dialog.el-dialog:not(.is-fullscreen)) { max-height:min(800px, calc(100vh - 32px)); }
|
||||
.warehouse-editor-table { table-layout:fixed; }
|
||||
.warehouse-editor-table td { width:50%; }
|
||||
.warehouse-import-heading { justify-content:space-between; }
|
||||
.import-toolbar { margin-bottom:var(--admin-space-4); }
|
||||
@media (max-width:600px) { .warehouse-editor-table tbody { display:block; } .warehouse-editor-table tr { display:grid; grid-template-columns:minmax(0,1fr); } .warehouse-editor-table td { width:auto; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user