<script setup lang="ts">
import { inject, provide, defineAsyncComponent } from 'vue';
import type { Component } from 'vue';
import { useRoute } from 'vue-router';
import {
ListPageHeaderKey,
ListSearchBarKey,
ListToolbarKey,
ListTableContentKey,
ListPaginationKey,
ListPageFooterKey,
PageSectionRegistryKey,
SectionKeyMap,
} from '@core/composables/useSections';
import DefaultListPageHeader from '@core/views/components/ListPageHeader.vue';
import DefaultListSearchBar from '@core/views/components/ListSearchBar.vue';
import DefaultListToolbar from '@core/views/components/ListToolbar.vue';
import DefaultListTableContent from '@core/views/components/ListTableContent.vue';
import DefaultListPagination from '@core/views/components/ListPagination.vue';
import DefaultListPageFooter from '@core/views/components/ListPageFooter.vue';
interface Column {
key: string;
label: string;
width?: string;
align?: 'left' | 'center' | 'right';
mono?: boolean;
}
interface SearchField {
key: string;
label: string;
type: 'text' | 'select';
options?: Array<{ value: string; label: string }>;
}
interface Props {
title?: string;
subtitle?: string;
columns?: Column[];
data?: Record<string, unknown>[];
loading?: boolean;
total?: number;
currentPage?: number;
pageSize?: number;
searchFields?: SearchField[];
}
const props = withDefaults(defineProps<Props>(), {
loading: false,
total: 0,
currentPage: 1,
pageSize: 20,
});
const emit = defineEmits<{
search: [params: Record<string, string>];
reset: [];
new: [];
delete: [];
export: [];
refresh: [];
'update:currentPage': [page: number];
'update:pageSize': [size: number];
}>();
// ─── 约定式自动发现:读取当前路由对应的覆盖组件 ──────────────────
const route = useRoute();
const registry = inject(
PageSectionRegistryKey,
{} as Record<string, Record<string, () => Promise<{ default: unknown }>>>,
);
const pageOverrides = registry[route.path] ?? {};
for (const [name, loader] of Object.entries(pageOverrides)) {
const key = SectionKeyMap[name];
if (key) {
provide(key, defineAsyncComponent(loader as () => Promise<{ default: Component }>));
}
}
// ─── inject 回退到框架默认 ────────────────────────────────────────
const PageHeaderComp = inject(ListPageHeaderKey, DefaultListPageHeader);
const SearchBarComp = inject(ListSearchBarKey, DefaultListSearchBar);
const ToolbarComp = inject(ListToolbarKey, DefaultListToolbar);
const TableContentComp = inject(ListTableContentKey, DefaultListTableContent);
const PaginationComp = inject(ListPaginationKey, DefaultListPagination);
const PageFooterComp = inject(ListPageFooterKey, DefaultListPageFooter);
</script>
<template>
<div class="list-page">
<!-- 页头(在 body 外,贴顶) -->
<slot name="header">
<component :is="PageHeaderComp" :title="title" :subtitle="subtitle" />
</slot>
<div class="lp-body">
<!-- 搜索栏 -->
<slot name="search">
<component
:is="SearchBarComp"
:fields="searchFields"
@search="emit('search', $event)"
@reset="emit('reset')"
/>
</slot>
<!-- 工具栏 -->
<slot name="toolbar">
<component
:is="ToolbarComp"
@new="emit('new')"
@delete="emit('delete')"
@export="emit('export')"
@refresh="emit('refresh')"
/>
</slot>
<!-- 表格 -->
<slot name="table">
<component :is="TableContentComp" :columns="columns" :data="data" :loading="loading" />
</slot>
<!-- 分页 -->
<slot name="pagination">
<component
:is="PaginationComp"
:total="total"
:current-page="currentPage"
:page-size="pageSize"
@update:current-page="emit('update:currentPage', $event)"
@update:page-size="emit('update:pageSize', $event)"
/>
</slot>
<!-- 底部 -->
<slot name="footer">
<component :is="PageFooterComp" />
</slot>
</div>
</div>
</template>
<style lang="scss" scoped>
.list-page {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.lp-body {
flex: 1;
overflow-y: auto;
padding: 20px 24px;
display: flex;
flex-direction: column;
gap: 12px;
background: var(--bg);
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #c8d4c8;
border-radius: 3px;
}
}
</style>
|