2.4 KiB
Vue Page — 页面模板与 Store
主流程见 SKILL.md,本文档为列表页/详情页/路由/Pinia/Provider 的完整实现。
⚠️ 双前端区分:本文件中使用
el-*组件的模板仅适用于管理端 (Case-Database-Frontend-admin/)。 用户端 (Case-Database-Frontend-user/) 使用 Headless UI + Tailwind CSS,禁止引入 Element Plus。
列表页模板
<script setup>
import { useTable } from '@/hooks/useTable'
document.title = '{{PageTitle}} - {{AppName}}'
const { loading, dataList, pagination, loadData } = useTable((params) => {{resource}}Api.list(params))
const searchForm = reactive({ keyword: '', status: '' })
function handleSearch() { pagination.current = 1; loadData() }
onMounted(() => loadData())
</script>
<template>
<div class="p-4 space-y-4">
<el-card><el-form :model="searchForm" inline>...</el-form></el-card>
<el-card>
<template #header><span>{{PageTitle}}</span><el-button @click="handleCreate">新增</el-button></template>
<el-table v-loading="loading" :data="dataList" border stripe>...</el-table>
<el-pagination v-model:current-page="pagination.current" ... @current-change="loadData" />
</el-card>
</div>
</template>
详情页模板
使用 useRoute/useRouter,fetchDetail 加载数据,el-skeleton 加载态,el-page-header + el-descriptions。
路由配置
const routes = [
{ path: '/{{module}}/{{route-path}}', name: '{{RouteName}}', component: () => import('@/views/...'), meta: { title, requiresAuth: true, permission } },
{ path: '/{{module}}/{{route-path}}/:id', name: '{{RouteName}}Detail', ... }
]
Pinia Store — List 与 Detail 分离
ListItem 类型:轻量,排除大文本/复杂对象/大数组。Detail 类型:完整字段。Store:list (array)、detailMap (Record<id, Detail>)、loadingDetailIds、getDetail(id)、isDetailLoading(id)、fetchList、fetchDetail(有缓存则返回)、invalidateDetail、invalidateAll。
页面级 Provider 模式
当 ≥3 个子组件共享状态时:provider.vue provide 状态+actions,keys.ts 定义 PAGE_KEY + usePageContext,子组件 inject。文件结构:index.vue、provider.vue、keys.ts、components/SearchBar/DataTable/BatchActions、composables/usePageData。
动态路由 (RBAC)
loadDynamicRoutes:menuStore.fetchMenus(),router.addRoute('layout', { path, name, component: () => import(...), meta })。