优化
This commit is contained in:
@@ -94,7 +94,7 @@ describe('DataTable column preferences', () => {
|
||||
slots: {
|
||||
'toolbar-start': '<h2>订单列表</h2>',
|
||||
'toolbar-actions': '<button>新增订单</button>',
|
||||
'toolbar-end': '<button>刷新</button>',
|
||||
'toolbar-end': '<button>辅助操作</button>',
|
||||
},
|
||||
global: {
|
||||
provide: { [UI_PREFERENCE_SCOPE as symbol]: computed(() => 'user-1') },
|
||||
@@ -109,10 +109,32 @@ describe('DataTable column preferences', () => {
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.data-table__toolbar')).toHaveLength(1)
|
||||
expect(wrapper.get('.data-table__toolbar').classes()).not.toContain('is-compact')
|
||||
expect(wrapper.get('.data-table__toolbar-start').text()).toBe('订单列表')
|
||||
expect(wrapper.get('.data-table__toolbar-actions').text()).toContain('新增订单')
|
||||
expect(wrapper.get('.data-table__toolbar-actions').text()).toContain('刷新')
|
||||
expect(wrapper.get('.data-table__toolbar-actions').text()).toContain('辅助操作')
|
||||
expect(wrapper.get('.data-table__business-actions').text()).toBe('新增订单')
|
||||
expect(wrapper.get('.data-table__toolbar-end').text()).toBe('辅助操作')
|
||||
})
|
||||
|
||||
it('uses a compact right-aligned toolbar when no leading content is provided', () => {
|
||||
const wrapper = shallowMount(DataTable, {
|
||||
props: { data: [], columns: configurableColumns, preferenceKey: 'orders-table' },
|
||||
slots: { 'toolbar-actions': '<button>新增订单</button>' },
|
||||
global: {
|
||||
provide: { [UI_PREFERENCE_SCOPE as symbol]: computed(() => 'user-1') },
|
||||
stubs: {
|
||||
ElTable: TableStub,
|
||||
ElTableColumn: ColumnStub,
|
||||
ElButton: ButtonStub,
|
||||
ElPopover: PopoverStub,
|
||||
},
|
||||
directives: { loading: {} },
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.get('.data-table__toolbar').classes()).toContain('is-compact')
|
||||
expect(wrapper.find('.data-table__toolbar-start').exists()).toBe(false)
|
||||
expect(wrapper.get('.data-table__business-actions').text()).toBe('新增订单')
|
||||
expect(wrapper.get('.data-table__toolbar-end').text()).toBe('刷新')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -250,8 +250,12 @@ function setPage(value: number) {
|
||||
|
||||
<template>
|
||||
<div class="data-table">
|
||||
<div v-if="$slots['toolbar-start'] || $slots['toolbar-actions'] || preferenceEnabled || $slots['toolbar-end']" class="data-table__toolbar">
|
||||
<div class="data-table__toolbar-start"><slot name="toolbar-start" /></div>
|
||||
<div
|
||||
v-if="$slots['toolbar-start'] || $slots['toolbar-actions'] || preferenceEnabled || $slots['toolbar-end']"
|
||||
class="data-table__toolbar"
|
||||
:class="{ 'is-compact': !$slots['toolbar-start'] }"
|
||||
>
|
||||
<div v-if="$slots['toolbar-start']" class="data-table__toolbar-start"><slot name="toolbar-start" /></div>
|
||||
<div class="data-table__toolbar-actions">
|
||||
<div v-if="$slots['toolbar-actions']" class="data-table__business-actions"><slot name="toolbar-actions" /></div>
|
||||
<el-popover v-if="preferenceEnabled" placement="bottom-end" :width="340" trigger="click" popper-class="cc-layout-preference-popper">
|
||||
@@ -324,6 +328,7 @@ function setPage(value: number) {
|
||||
<style scoped>
|
||||
.data-table { min-width: 0; }
|
||||
.data-table__toolbar { display: flex; min-height: 72px; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 18px; border-bottom: 1px solid var(--cc-border); }
|
||||
.data-table__toolbar.is-compact { min-height: 56px; justify-content: flex-end; padding: 8px 14px; }
|
||||
.data-table__toolbar-start { min-width: 0; flex: 1; }
|
||||
.data-table__toolbar-start :deep(h2) { margin: 0; color: var(--cc-text); font-size: 16px; line-height: 1.4; }
|
||||
.data-table__toolbar-start :deep(p) { margin: 4px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
|
||||
@@ -338,5 +343,5 @@ function setPage(value: number) {
|
||||
.data-table__page-buttons { gap: 4px; }
|
||||
.data-table__page-button { width: 32px; min-width: 32px; height: 32px; padding: 0; margin-left: 0; }
|
||||
.data-table__page-ellipsis { display: inline-flex; width: 24px; height: 32px; align-items: center; justify-content: center; }
|
||||
@media (max-width: 720px) { .data-table__toolbar { align-items: stretch; flex-direction: column; padding: 14px; } .data-table__toolbar-actions { width: 100%; align-items: flex-start; flex-wrap: nowrap; justify-content: flex-start; } .data-table__business-actions { flex: 1 1 auto; justify-content: flex-start; } .data-table__toolbar-end { flex: 0 1 auto; justify-content: flex-start; } .data-table__toolbar-actions :deep(.el-button) { min-height: 40px; } .data-table__pagination { align-items: flex-start; flex-direction: column; } .data-table__pagination-left, .data-table__pagination-controls, .data-table__page-buttons { width: 100%; } .data-table__pagination-controls { justify-content: flex-start; } .data-table__page-buttons { flex-wrap: wrap; } .data-table__page-button { width: 44px; min-width: 44px; height: 44px; } .data-table__page-ellipsis { height: 44px; } }
|
||||
@media (max-width: 720px) { .data-table__toolbar, .data-table__toolbar.is-compact { min-height: 0; align-items: stretch; flex-direction: column; padding: 14px; } .data-table__toolbar-actions { width: 100%; align-items: flex-start; flex-wrap: nowrap; justify-content: flex-start; } .data-table__business-actions { flex: 1 1 auto; justify-content: flex-start; } .data-table__toolbar-end { flex: 0 1 auto; justify-content: flex-start; } .data-table__toolbar-actions :deep(.el-button) { min-height: 40px; } .data-table__pagination { align-items: flex-start; flex-direction: column; } .data-table__pagination-left, .data-table__pagination-controls, .data-table__page-buttons { width: 100%; } .data-table__pagination-controls { justify-content: flex-start; } .data-table__page-buttons { flex-wrap: wrap; } .data-table__page-button { width: 44px; min-width: 44px; height: 44px; } .data-table__page-ellipsis { height: 44px; } }
|
||||
</style>
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { Comment, Fragment, Text, isVNode, useSlots } from 'vue'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
ariaLabel?: string
|
||||
}>(), {
|
||||
ariaLabel: '列表页面',
|
||||
})
|
||||
|
||||
const slots = useSlots()
|
||||
type RegionName = 'toolbar' | 'metrics' | 'state' | 'default'
|
||||
|
||||
function hasRenderableContent(value: unknown): boolean {
|
||||
if (Array.isArray(value)) return value.some(hasRenderableContent)
|
||||
if (!isVNode(value)) return typeof value === 'string' ? value.trim().length > 0 : value != null && typeof value !== 'boolean'
|
||||
if (value.type === Comment) return false
|
||||
if (value.type === Text) return hasRenderableContent(value.children)
|
||||
if (value.type === Fragment) return hasRenderableContent(value.children)
|
||||
return true
|
||||
}
|
||||
|
||||
function hasRegion(name: RegionName) {
|
||||
return hasRenderableContent(slots[name]?.())
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="list-page-layout" :aria-label="ariaLabel">
|
||||
<div v-if="$slots.toolbar" class="list-page-layout__toolbar"><slot name="toolbar" /></div>
|
||||
<div v-if="$slots.metrics" class="list-page-layout__metrics"><slot name="metrics" /></div>
|
||||
<div v-if="$slots.state" class="list-page-layout__state"><slot name="state" /></div>
|
||||
<div v-if="$slots.default" class="list-page-layout__content"><slot /></div>
|
||||
<div v-if="hasRegion('toolbar')" class="list-page-layout__toolbar"><slot name="toolbar" /></div>
|
||||
<div v-if="hasRegion('metrics')" class="list-page-layout__metrics"><slot name="metrics" /></div>
|
||||
<div v-if="hasRegion('state')" class="list-page-layout__state"><slot name="state" /></div>
|
||||
<div v-if="hasRegion('default')" class="list-page-layout__content"><slot /></div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.list-page-layout { display: flex; min-width: 0; flex-direction: column; gap: 16px; }
|
||||
.list-page-layout__toolbar, .list-page-layout__metrics, .list-page-layout__state, .list-page-layout__content { min-width: 0; }
|
||||
.list-page-layout__toolbar:empty, .list-page-layout__metrics:empty, .list-page-layout__state:empty, .list-page-layout__content:empty { display: none; }
|
||||
</style>
|
||||
|
||||
@@ -37,6 +37,27 @@ describe('admin page layout components', () => {
|
||||
expect(wrapper.text()).toContain('表格')
|
||||
})
|
||||
|
||||
it('removes registered but empty list regions from flex layout', () => {
|
||||
const wrapper = mount(ListPageLayout, {
|
||||
slots: {
|
||||
toolbar: '<div>筛选</div>',
|
||||
state: '<template v-if="false"><div>错误</div></template>',
|
||||
default: '<div>表格</div>',
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.list-page-layout__state').exists()).toBe(false)
|
||||
expect(wrapper.text()).toBe('筛选表格')
|
||||
})
|
||||
|
||||
it('keeps plain-text list regions visible', () => {
|
||||
const wrapper = mount(ListPageLayout, {
|
||||
slots: { state: '正在同步', default: '表格内容' },
|
||||
})
|
||||
|
||||
expect(wrapper.get('.list-page-layout__state').text()).toBe('正在同步')
|
||||
expect(wrapper.get('.list-page-layout__content').text()).toBe('表格内容')
|
||||
})
|
||||
|
||||
it('supports detail regions and form section descriptions', () => {
|
||||
const detail = mount(DetailPageLayout, {
|
||||
slots: { header: '<h1>详情</h1>', default: '<p>正文</p>', footer: '<button>保存</button>' },
|
||||
|
||||
Reference in New Issue
Block a user