Files
admin-web/src/shared/components/ListPageLayout.vue
T
2026-08-17 16:45:46 +08:00

45 lines
1.7 KiB
Vue

<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="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; }
@media (min-width: 1920px) {
.list-page-layout { gap: 12px; }
}
</style>