优化
This commit is contained in:
@@ -200,7 +200,12 @@ describe('LocationsView', () => {
|
|||||||
const wrapper = await mountView()
|
const wrapper = await mountView()
|
||||||
await selectScope(wrapper)
|
await selectScope(wrapper)
|
||||||
|
|
||||||
expect(wrapper.find('.table-panel .panel-heading').exists()).toBe(false)
|
const panelHeading = wrapper.find('.table-panel .panel-heading')
|
||||||
|
expect(panelHeading.exists()).toBe(true)
|
||||||
|
expect(panelHeading.text()).toContain('库位列表')
|
||||||
|
expect(panelHeading.text()).toContain('共 2 个库位')
|
||||||
|
expect(panelHeading.text()).toContain('新增库位')
|
||||||
|
expect(wrapper.findComponent(QueryToolbar).text()).not.toContain('新增库位')
|
||||||
expect(listPlatformWarehouses).toHaveBeenCalledWith(undefined, 1, 200)
|
expect(listPlatformWarehouses).toHaveBeenCalledWith(undefined, 1, 200)
|
||||||
expect(listPlatformLocations).toHaveBeenLastCalledWith({
|
expect(listPlatformLocations).toHaveBeenLastCalledWith({
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -220,7 +225,7 @@ describe('LocationsView', () => {
|
|||||||
|
|
||||||
it('opens creation without a list filter and requires the warehouse inside the form', async () => {
|
it('opens creation without a list filter and requires the warehouse inside the form', async () => {
|
||||||
const wrapper = await mountView()
|
const wrapper = await mountView()
|
||||||
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增库位')
|
const createButton = wrapper.find('.table-panel .panel-heading').findAll('button').find((button) => button.text().trim() === '新增库位')
|
||||||
|
|
||||||
expect(createButton?.attributes('disabled')).toBeUndefined()
|
expect(createButton?.attributes('disabled')).toBeUndefined()
|
||||||
await createButton!.trigger('click')
|
await createButton!.trigger('click')
|
||||||
|
|||||||
@@ -495,15 +495,7 @@ onMounted(() => {
|
|||||||
ariaLabel="库位筛选"
|
ariaLabel="库位筛选"
|
||||||
:fields="queryFields"
|
:fields="queryFields"
|
||||||
@search="applyFilters"
|
@search="applyFilters"
|
||||||
>
|
/>
|
||||||
<template #actions>
|
|
||||||
<el-button
|
|
||||||
v-if="access.can('platform:location:create')"
|
|
||||||
type="primary"
|
|
||||||
@click="openCreate"
|
|
||||||
><Plus :size="16" />新增库位</el-button>
|
|
||||||
</template>
|
|
||||||
</QueryToolbar>
|
|
||||||
|
|
||||||
<el-alert v-if="customerError" type="warning" :closable="false" show-icon class="state-alert">
|
<el-alert v-if="customerError" type="warning" :closable="false" show-icon class="state-alert">
|
||||||
<template #title>所属企业列表加载失败</template>
|
<template #title>所属企业列表加载失败</template>
|
||||||
@@ -527,7 +519,15 @@ onMounted(() => {
|
|||||||
<el-button size="small" @click="loadLocations()">重新查询</el-button>
|
<el-button size="small" @click="loadLocations()">重新查询</el-button>
|
||||||
</el-alert>
|
</el-alert>
|
||||||
|
|
||||||
<section class="surface-panel table-panel">
|
<section class="surface-panel table-panel" aria-label="库位列表区域">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<div><h2>库位列表</h2><p>共 {{ total }} 个库位</p></div>
|
||||||
|
<el-button
|
||||||
|
v-if="access.can('platform:location:create')"
|
||||||
|
type="primary"
|
||||||
|
@click="openCreate"
|
||||||
|
><Plus :size="16" />新增库位</el-button>
|
||||||
|
</div>
|
||||||
<DataTable
|
<DataTable
|
||||||
v-model:page="page"
|
v-model:page="page"
|
||||||
v-model:page-size="pageSize"
|
v-model:page-size="pageSize"
|
||||||
|
|||||||
@@ -239,6 +239,24 @@ describe('PalletsView', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('groups the pallet editor into clear ownership and specification sections', async () => {
|
||||||
|
const wrapper = await mountView()
|
||||||
|
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增托盘')
|
||||||
|
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('新增托盘')
|
||||||
|
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('托盘规格')
|
||||||
|
expect(document.body.querySelector('.pallet-editor-footer')?.textContent).toContain('为必填项')
|
||||||
|
})
|
||||||
|
|
||||||
it('hides all write actions without write permissions', async () => {
|
it('hides all write actions without write permissions', async () => {
|
||||||
const wrapper = await mountView(['platform:pallet:list'])
|
const wrapper = await mountView(['platform:pallet:list'])
|
||||||
|
|
||||||
|
|||||||
@@ -450,47 +450,73 @@ onMounted(() => {
|
|||||||
<template #footer><el-button @click="detailOpen = false">关闭</el-button></template>
|
<template #footer><el-button @click="detailOpen = false">关闭</el-button></template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
<el-drawer v-model="editorOpen" :title="editorTitle" size="520px" class="business-drawer" :close-on-click-modal="false">
|
<el-drawer v-model="editorOpen" size="560px" class="business-drawer pallet-editor-drawer" :close-on-click-modal="false">
|
||||||
<div v-if="form.id" class="editor-context">
|
<template #header="{ titleId, titleClass }">
|
||||||
<WarehouseIcon :size="18" />
|
<div class="pallet-editor-header">
|
||||||
<div><strong>{{ editorWarehouse?.warehouseName || '当前仓库' }}({{ editorWarehouse?.warehouseCode || form.warehouseId }})</strong><span>客户 {{ customerLabel(form.customerId) }} · 托盘保存后不可跨仓移动</span></div>
|
<span aria-hidden="true"><Boxes :size="20" /></span>
|
||||||
</div>
|
<div>
|
||||||
|
<h2 :id="titleId" :class="titleClass">{{ editorTitle }}</h2>
|
||||||
|
<p>{{ form.id ? '调整托盘规格与现场识别信息' : '录入归属仓库与基础规格,创建后即可用于仓储作业' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="editor-form">
|
<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>
|
<section class="editor-section" aria-labelledby="pallet-ownership-heading">
|
||||||
<el-select v-model="form.warehouseId" filterable placeholder="请选择所属仓库" :loading="warehouseLoading">
|
<div class="editor-section-heading">
|
||||||
<el-option
|
<WarehouseIcon :size="18" aria-hidden="true" />
|
||||||
v-for="warehouse in warehouses"
|
<div><h3 id="pallet-ownership-heading">归属仓库</h3><p>托盘创建后固定归属于所选仓库,不支持跨仓移动。</p></div>
|
||||||
:key="String(warehouse.id)"
|
</div>
|
||||||
:label="`${warehouse.warehouseName}(${warehouse.warehouseCode})`"
|
<div v-if="form.id" class="editor-context">
|
||||||
:value="String(warehouse.id)"
|
<div><strong>{{ editorWarehouse?.warehouseName || '当前仓库' }}</strong><span>{{ editorWarehouse?.warehouseCode || form.warehouseId }} · 客户 {{ customerLabel(form.customerId) }}</span></div>
|
||||||
/>
|
</div>
|
||||||
</el-select>
|
<el-form-item v-else label="所属仓库" prop="warehouseId" required>
|
||||||
</el-form-item>
|
<el-select v-model="form.warehouseId" filterable placeholder="请选择所属仓库" :loading="warehouseLoading">
|
||||||
<el-form-item label="托盘编码" prop="palletCode" required>
|
<el-option
|
||||||
<el-input v-model="form.palletCode" maxlength="50" show-word-limit placeholder="例如 TP-001" />
|
v-for="warehouse in warehouses"
|
||||||
</el-form-item>
|
:key="String(warehouse.id)"
|
||||||
<el-form-item label="托盘类型" prop="palletType" required>
|
:label="`${warehouse.warehouseName}(${warehouse.warehouseCode})`"
|
||||||
<el-select
|
:value="String(warehouse.id)"
|
||||||
v-model="form.palletType"
|
/>
|
||||||
filterable
|
</el-select>
|
||||||
allow-create
|
</el-form-item>
|
||||||
default-first-option
|
</section>
|
||||||
placeholder="请选择或输入托盘类型"
|
|
||||||
aria-label="编辑托盘类型"
|
<section class="editor-section" aria-labelledby="pallet-spec-heading">
|
||||||
>
|
<div class="editor-section-heading">
|
||||||
<el-option v-for="type in palletTypeOptions" :key="type" :label="type" :value="type" />
|
<Boxes :size="18" aria-hidden="true" />
|
||||||
</el-select>
|
<div><h3 id="pallet-spec-heading">托盘规格</h3><p>完善编码、类型与实际可用容积。</p></div>
|
||||||
</el-form-item>
|
</div>
|
||||||
<el-form-item label="托盘容积(m³)" prop="capacityCbm" required>
|
<div class="editor-field-grid">
|
||||||
<el-input-number v-model="form.capacityCbm" :min="0.000001" :max="999999999999.999999" :precision="6" :step="0.1" controls-position="right" aria-label="标准容积" />
|
<el-form-item label="托盘编码" prop="palletCode" required>
|
||||||
<p class="field-help">用于服务端按总体积计算所需托盘数量;请输入托盘实际可用容积。</p>
|
<el-input v-model="form.palletCode" maxlength="50" show-word-limit placeholder="例如 TP-001" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="托盘类型" prop="palletType" required>
|
||||||
<el-input v-model="form.remark" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="可填写规格、材质或现场识别说明" />
|
<el-select
|
||||||
</el-form-item>
|
v-model="form.palletType"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
placeholder="请选择或输入托盘类型"
|
||||||
|
aria-label="编辑托盘类型"
|
||||||
|
>
|
||||||
|
<el-option v-for="type in palletTypeOptions" :key="type" :label="type" :value="type" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<el-form-item label="托盘容积(m³)" prop="capacityCbm" required>
|
||||||
|
<el-input-number v-model="form.capacityCbm" :min="0.000001" :max="999999999999.999999" :precision="6" :step="0.1" controls-position="right" aria-label="标准容积" />
|
||||||
|
<p class="field-help">用于服务端按总体积计算所需托盘数量,请填写托盘实际可用容积。</p>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark" class="editor-remark-field">
|
||||||
|
<el-input v-model="form.remark" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="可填写规格、材质或现场识别说明" />
|
||||||
|
</el-form-item>
|
||||||
|
</section>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="drawer-footer"><el-button :disabled="saving" @click="editorOpen = false">取消</el-button><el-button type="primary" :loading="saving" @click="savePallet">保存托盘</el-button></div>
|
<div class="drawer-footer pallet-editor-footer">
|
||||||
|
<span><i aria-hidden="true">*</i> 为必填项</span>
|
||||||
|
<div><el-button :disabled="saving" @click="editorOpen = false">取消</el-button><el-button type="primary" :loading="saving" @click="savePallet">保存托盘</el-button></div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</section>
|
</section>
|
||||||
@@ -518,20 +544,45 @@ onMounted(() => {
|
|||||||
.detail-hero p { margin-top: 4px; color: var(--cc-muted); font-size: 13px; }
|
.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 { margin: 20px 0 8px; color: var(--cc-text); font-size: 14px; }
|
||||||
.detail-section-title:first-of-type { margin-top: 0; }
|
.detail-section-title:first-of-type { margin-top: 0; }
|
||||||
.editor-context { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; padding: 12px; border: 1px solid var(--el-color-primary-light-7); border-radius: 8px; background: var(--el-color-primary-light-9); color: var(--cc-primary); }
|
.pallet-editor-header { display: flex; min-width: 0; align-items: center; gap: 12px; }
|
||||||
.editor-context div { display: grid; gap: 2px; }
|
.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); }
|
||||||
.editor-context span { color: var(--cc-muted); font-size: 12px; }
|
.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; }
|
||||||
|
.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; }
|
||||||
|
.editor-section-heading { display: flex; align-items: flex-start; gap: 9px; margin-bottom: 18px; }
|
||||||
|
.editor-section-heading > svg { margin-top: 1px; flex: 0 0 auto; color: var(--cc-primary); }
|
||||||
|
.editor-section-heading h3 { margin: 0; color: #172033; font-size: 14px; line-height: 1.4; }
|
||||||
|
.editor-section-heading p { margin: 3px 0 0; color: var(--cc-muted); font-size: 11px; line-height: 1.5; }
|
||||||
|
.editor-field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }
|
||||||
|
.editor-context { margin-bottom: 18px; padding: 12px 14px; border-radius: 6px; background: #f8fafc; }
|
||||||
|
.editor-context div { display: grid; gap: 3px; }
|
||||||
|
.editor-context strong { color: #172033; font-size: 13px; }
|
||||||
|
.editor-context span { color: var(--cc-muted); font-size: 11px; }
|
||||||
.editor-form :deep(.el-input-number), .editor-form :deep(.el-select) { width: 100%; }
|
.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; }
|
||||||
|
.editor-remark-field { margin-bottom: 0 !important; }
|
||||||
.field-help { margin: 6px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
|
.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; }
|
.drawer-footer { display: flex; justify-content: flex-end; gap: 8px; }
|
||||||
.drawer-footer :deep(.el-button + .el-button) { margin-left: 0; }
|
.drawer-footer :deep(.el-button + .el-button) { margin-left: 0; }
|
||||||
|
.pallet-editor-footer { align-items: center; justify-content: space-between; }
|
||||||
|
.pallet-editor-footer > span { color: var(--cc-muted); font-size: 11px; }
|
||||||
|
.pallet-editor-footer > span i { color: var(--el-color-danger); font-style: normal; }
|
||||||
|
.pallet-editor-footer > div { display: flex; gap: 8px; }
|
||||||
|
.pallet-editor-footer > div :deep(.el-button + .el-button) { margin-left: 0; }
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.panel-heading { align-items: stretch; flex-direction: column; }
|
.panel-heading { align-items: stretch; flex-direction: column; }
|
||||||
.panel-heading > .el-button { width: 100%; min-height: 44px; }
|
.panel-heading > .el-button { width: 100%; min-height: 44px; }
|
||||||
.pallet-row-actions :deep(.el-button) { min-height: 36px; padding-inline: 5px; }
|
.pallet-row-actions :deep(.el-button) { min-height: 36px; padding-inline: 5px; }
|
||||||
.drawer-footer { display: grid; grid-template-columns: 1fr 1fr; }
|
.editor-form { padding-right: 16px; padding-left: 16px; }
|
||||||
.drawer-footer :deep(.el-button) { min-height: 44px; margin-left: 0; }
|
.editor-field-grid { grid-template-columns: 1fr; }
|
||||||
|
.pallet-editor-footer { display: block; }
|
||||||
|
.pallet-editor-footer > span { display: none; }
|
||||||
|
.pallet-editor-footer > div { display: grid; grid-template-columns: 1fr 1fr; }
|
||||||
|
.pallet-editor-footer :deep(.el-button) { min-height: 44px; margin-left: 0; }
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.pallet-filter-toolbar :deep(.query-toolbar__filters) { width: 100%; flex-direction: column; }
|
.pallet-filter-toolbar :deep(.query-toolbar__filters) { width: 100%; flex-direction: column; }
|
||||||
|
|||||||
Reference in New Issue
Block a user