shared-layout-components
v1.0.22
Published
基于 Vue3 和 Ant Design Vue 的公共布局组件库
Maintainers
Readme
shared-layout-components
基于 Vue3 和 Ant Design Vue 的公共布局组件库,提供完整的后台管理系统布局解决方案。
特性
- 🎯 开箱即用 - 提供完整的后台布局组件
- 🔗 联动交互 - 菜单、标签页、面包屑自动联动
- 💾 状态持久化 - 基于 Pinia 的标签页状态管理,支持刷新保持
- 🎨 高度可定制 - 支持插槽自定义各区域内容
- 📦 按需引入 - 支持 UMD 和 ES Module 两种格式
- 🔄 对等依赖 - 通过函数参数接收宿主应用的依赖实例,避免重复打包
- 🔌 微前端支持 - 内置 qiankun 微应用生命周期支持
组件列表
| 组件 | 说明 | | --- | --- | | PageLayout | 主布局容器,整合侧边栏、标签页、面包屑和内容区域 | | SidebarLayout | 侧边栏组件,支持菜单折叠和层级展开 | | Tabs | 标签页组件,支持关闭和切换 | | BreadCrumb | 面包屑组件,自动根据当前菜单生成路径 |
安装
npm install shared-layout-components前置依赖(对等依赖)
本库使用**对等依赖(peer dependencies)**方式管理依赖,需要在宿主应用中安装以下依赖:
npm install vue@^3.3.4 vue-router@^4.0.0 pinia@^3.0.0 ant-design-vue@^4.2.6 @ant-design/icons-vue@^7.0.0{
"vue": "^3.3.4",
"vue-router": "^4.0.0",
"pinia": "^3.0.0",
"ant-design-vue": "^4.2.6",
"@ant-design/icons-vue": "^7.0.0"
}可选依赖
| 依赖 | 说明 |
|------|------|
| pinia-plugin-persistedstate | 标签页状态持久化,不传则状态在页面刷新后丢失 |
说明:本库不会将依赖打包到构建产物中,而是通过函数参数从宿主应用接收实例,避免版本冲突和重复打包。
快速开始
1. 定义路由配置
// router.js
export const routes = [
{
path: '/',
redirect: '/dashboard'
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('./pages/Dashboard.vue'),
meta: { title: '仪表盘' }
},
{
path: '/user/list',
name: 'UserList',
component: () => import('./pages/UserList.vue'),
meta: { title: '用户列表' }
}
];2. 初始化应用
// main.js
import { createApp } from 'vue';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import App from './App.vue';
import { routes } from './router';
// 导入对等依赖模块
import * as VueRouter from 'vue-router';
import * as pinia from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
// 导入组件库
import { createSharedLayout } from 'shared-layout-components';
const app = createApp(App);
// 创建并安装 Pinia(宿主应用自行管理)
const piniaInstance = pinia.createPinia();
piniaInstance.use(piniaPluginPersistedstate);
app.use(piniaInstance);
// 创建并安装 Vue Router(宿主应用自行管理)
const router = VueRouter.createRouter({
history: VueRouter.createWebHistory(),
routes
});
app.use(router);
// 使用组件库,传入宿主应用的依赖实例
const sharedLayout = createSharedLayout({
pinia, // 传入 pinia 模块
piniaPluginPersistedstate, // 传入持久化插件
router, // 传入已创建的路由实例
vueRouter: VueRouter // 传入 vue-router 模块
});
app.use(sharedLayout);
app.use(Antd);
app.mount('#app');说明:
- 宿主应用需要自行创建并安装 router 和 pinia 实例
- 将依赖模块通过参数传递给组件库
- 组件库通过注入机制将依赖提供给内部组件使用
- 这种方式避免了依赖重复打包和版本冲突
3. 使用 PageLayout 组件
<template>
<PageLayout
ref="pageLayoutRef"
:menuData="menuData"
:systemTitle="systemTitle"
>
<template #default="{ activeTab }">
<router-view></router-view>
</template>
</PageLayout>
</template>
<script setup>
import { ref, provide } from 'vue';
import { PageLayout } from 'shared-layout-components';
const pageLayoutRef = ref(null);
const systemTitle = ref('管理系统');
const menuData = ref([
{
key: 'dashboard',
label: '仪表盘',
title: '仪表盘',
path: '/dashboard'
},
{
key: 'user',
label: '用户管理',
title: '用户管理',
children: [
{ key: 'user-list', label: '用户列表', title: '用户列表', path: '/user/list' },
{ key: 'user-role', label: '角色管理', title: '角色管理', path: '/user/role' }
]
}
]);
// 提供打开新 tab 的方法给子页面使用
provide('openTab', (tab) => {
const layout = pageLayoutRef.value;
if (layout) {
layout.addTab(tab);
}
});
</script>组件会自动处理:
- 路由变化时自动添加对应标签页
- 点击菜单时自动跳转路由
- 切换标签页时自动跳转路由
- 关闭标签页时自动切换到相邻标签页并跳转
4. 子页面打开新 Tab 示例
<!-- UserList.vue -->
<template>
<div class="page-user-list">
<h2>用户列表</h2>
<a-card>
<div class="table-toolbar">
<a-button type="primary" @click="handleAdd">
<plus-outlined />
新增用户
</a-button>
</div>
<!-- 表格内容 -->
</a-card>
</div>
</template>
<script setup>
import { inject } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue';
const openTab = inject('openTab', null);
const handleAdd = () => {
if (openTab) {
openTab({
key: 'user-list-add', // 唯一标识
title: '新增用户', // 标签页标题
closable: true, // 是否可关闭
path: '/user/list/add' // 路由路径
});
}
};
</script>API
createSharedLayout(options)
创建组件库插件,需要在宿主应用中调用。
| 参数 | 说明 | 类型 | 必需 |
| --- | --- | --- | --- |
| pinia | Pinia 模块(import * as pinia from 'pinia') | Object | 是 |
| piniaPluginPersistedstate | Pinia 持久化插件 | Object | 否 |
| router | 已创建的 Vue Router 实例 | Object | 否 |
| vueRouter | Vue Router 模块(import * as VueRouter from 'vue-router') | Object | 否 |
| routes | 路由配置数组(如未传入 router 则需要) | Array | 否 |
| routerOptions | 路由选项 | Object | 否 |
PageLayout Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| menuData | 菜单数据 | Array | [] |
| systemTitle | 系统标题 | String | '管理系统' |
| showSidebar | 是否显示侧边栏 | Boolean | true |
| showTabs | 是否显示标签页 | Boolean | true |
| showBreadcrumb | 是否显示面包屑 | Boolean | true |
| autoAddTab | 是否自动添加标签页 | Boolean | true |
| enableRouterSync | 是否启用路由联动 | Boolean | true |
| defaultTabs | 默认标签页列表 | Array | [] |
| homeBreadcrumb | 首页面包屑配置 | Object | {} |
PageLayout Events
| 事件名 | 说明 | 回调参数 |
| --- | --- | --- |
| menu-click | 菜单点击事件 | (item: MenuItem) |
| tab-change | 标签页切换事件 | (key: String) |
| tab-click | 标签页点击事件 | (tab: Tab) |
| tab-close | 标签页关闭事件 | (tab: Tab) |
| update:activeTab | 当前激活标签页变化 | (key: String) |
| breadcrumb-click | 面包屑点击事件 | (data: Object) |
PageLayout Methods
通过 ref 调用:
| 方法名 | 说明 | 参数 |
| --- | --- | --- |
| addTab | 手动添加标签页,自动跳转路由 | (tab: Tab) |
| removeTab | 手动移除标签页,删除后自动切换到相邻标签页并跳转路由;仅剩一个标签页时不允许删除 | (key: String) |
| addTabByMenuKey | 根据菜单 key 添加标签页 | (key: String) |
| getCurrentTab | 获取当前标签页数据 | - |
| clearTabs | 清空所有标签页 | - |
| findMenuItem | 根据菜单 key 查找菜单项 | (items: Array, key: String) |
| findMenuKeyByPath | 根据路由路径查找菜单 key | (items: Array, path: String) |
暴露的响应式数据:
| 数据 | 说明 | 类型 |
| --- | --- | --- |
| tabsList | 标签页列表 | ComputedRef<Array> |
| currentTab | 当前激活标签页 key | ComputedRef<String> |
| currentTabData | 当前标签页数据 | ComputedRef<Object> |
| breadcrumbList | 面包屑数据 | ComputedRef<Array> |
PageLayout Slots
| 插槽名 | 说明 | 参数 |
| --- | --- | --- |
| default | 主内容区域 | { activeTab, tabData } |
| sidebar | 自定义侧边栏 | - |
| tabs | 自定义标签页 | { tabs, activeTab } |
| breadcrumb | 自定义面包屑 | { items } |
| breadcrumbExtra | 面包屑右侧扩展区域 | - |
菜单数据格式
const menuData = [
{
key: 'dashboard', // 唯一标识
label: '仪表盘', // 菜单名称
title: '仪表盘', // 标签页标题
icon: 'dashboard', // 图标(可选)
path: '/dashboard' // 路由路径
},
{
key: 'user',
label: '用户管理',
title: '用户管理',
children: [ // 子菜单
{
key: 'user-list',
label: '用户列表',
title: '用户列表',
path: '/user/list'
}
]
}
];标签页数据格式
const tab = {
key: 'dashboard', // 唯一标识
title: '仪表盘', // 标签页标题
path: '/dashboard', // 路由路径
closable: true // 是否可关闭(默认 true)
};Tabs 组件
独立的标签页组件,也可单独使用。
Tabs Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| tabs | 标签页数据列表 | Array | [] |
| activeTab | 当前激活标签页 key | String | '' |
Tabs Events
| 事件名 | 说明 | 回调参数 |
| --- | --- | --- |
| update:activeTab | 激活标签页变化 | (key: String) |
| tab-click | 标签页点击事件 | (tab: Tab) |
| tab-close | 标签页关闭事件 | (tab: Tab) |
Tabs Methods
通过 ref 调用:
| 方法名 | 说明 | 参数 |
| --- | --- | --- |
| removeTab | 删除指定标签页 | (key: String) |
使用示例
<template>
<Tabs
ref="tabsRef"
:tabs="tabsList"
:activeTab="activeTab"
@update:activeTab="onTabChange"
@tab-close="onTabClose"
/>
</template>
<script setup>
import { ref } from 'vue';
import { Tabs } from 'shared-layout-components';
const tabsRef = ref();
const tabsList = ref([...]);
const activeTab = ref('');
// 通过 ref 删除指定 tab
const removeTabByKey = (key) => {
tabsRef.value.removeTab(key);
};
</script>状态管理
组件内部使用 Pinia 管理标签页状态,通过 getStores 可访问:
import { getStores } from 'shared-layout-components';
const { useTabsStore } = getStores();
const tabsStore = useTabsStore();
// 获取当前标签页列表
console.log(tabsStore.tabs);
// 获取当前激活标签页
console.log(tabsStore.currentTab);
// 获取当前标签页数据
console.log(tabsStore.currentTabData);本地开发
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build发布
# 构建并发布
npm run prepublishOnly
npm publishLicense
MIT
