user-profile-grid-plugin
v1.1.2
Published
用户画像拖拽布局组件库 - 基于 GridStack 的低耦合可复用 Vue 3 组件。
Readme
user-profile-grid-plugin
用户画像拖拽布局组件库 - 基于 GridStack 的低耦合可复用 Vue 3 组件。
Features
- 支持拖拽和大小调整的网格布局:基于 GridStack,面板可拖拽、可拉伸,支持锁定/解锁与关闭。
- 灵活配置组件类型和布局模板:通过
menus + routes + widgets动态映射可用模块;内置多种布局模板(默认/详细/简约)。 - 低耦合设计,易于集成到任意项目:外部提供布局 JSON(字符串)与保存回调;内部只负责渲染/交互与布局变更同步。
核心组件
- 主组件:
src/components/GridStackCom/KydUserProfileGrid.vue - 网格项包装:
src/components/GridStackCom/GridModel.vue - 面板容器(标题/菜单/更多跳转):
src/components/GridStackCom/ContainerPanel.vue
依赖与环境
- Vue: 3.x
- 构建: Vite(本仓库为 demo/开发环境)
- Grid 引擎:
gridstack - UI:
element-plus(下拉菜单、对话框、表单等)
快速开始(本仓库运行 demo)
npm install
npm run devdemo 入口:src/components/HelloWorld.vue(通过 ref 初始化网格并传入 demo 数据)。
在你的项目中使用
通过 npm 安装
该仓库目前已支持 lib 模式 构建用于发布到 npm(
npm run build:lib)。
npm i user-profile-grid-plugin宿主项目还需要安装(或已安装)以下 peer 依赖:
npm i vue element-plus @element-plus/icons-vue gridstack并在宿主项目入口引入 ElementPlus 样式(至少一次):
import "element-plus/dist/index.css";还需要引入 GridStack 样式 与 本组件库样式(否则网格/占位/拖拽手柄等会错乱):
import "gridstack/dist/gridstack.min.css";
import "user-profile-grid-plugin/style.css";该组件本质上是一个“布局容器”,你需要提供 4 类输入:
- widgets:可渲染业务模块的加载器集合(建议用
import.meta.glob扫描widgets/目录) - routes:路由树(用于从路由表中提取
fullPath,给面板“更多”跳转使用) - menus:可用模块清单(用于生成“添加组件”下拉选项)
- layout:用户布局(JSON 字符串;网格项数组)
1) 准备 widgets(业务模块目录)
示例(与本仓库一致):
// 匹配所有的组件(宿主项目的业务模块)
const widgets = import.meta.glob("./widgets/**/*.vue");要求:
- key 必须包含
widgets/:组件内部用path.split("widgets/")解析模块路径。 menus[i].component与routes[*].component需要能匹配到widgets/下的相对路径(不含.vue),例如:system/user/index。
2) 在页面中渲染并初始化
(参考本仓库 src/components/HelloWorld.vue)
<template>
<kyd-user-profile-grid
ref="gridRef"
v-if="layout"
:proxy="proxy"
:routes="routes"
:layout="layout"
:widgets="widgets"
:menus="menus"
:saveLayout="saveLayout"
:router="router"
/>
</template>
<script setup>
import { getCurrentInstance, nextTick, onMounted, onBeforeUnmount, ref } from "vue";
import { KydUserProfileGrid } from "user-profile-grid-plugin";
const proxy = getCurrentInstance(); // 必传:用于 vNode.appContext 注入
const gridRef = ref(null);
// 你的项目里替换为真实数据
const routes = []; // 路由树
const menus = ref([]); // 模块清单
const widgets = import.meta.glob("@/widgets/**/*.vue");
const router = null; // 如果你希望“更多”跳转生效,传入 router 实例
const layout = ref(""); // 注意:必须是 JSON 字符串
const saveLayout = (items) => {
// items 是网格项数组(包含 w/h/x/y/type/title/...),由组件在拖拽/缩放/增删后回调
// 这里建议你持久化到后端/本地存储,然后重新喂回 layout
};
onMounted(async () => {
layout.value = JSON.stringify([]); // 初次可为空数组
await nextTick();
gridRef.value?.loadGridLayout();
});
onBeforeUnmount(() => {
gridRef.value?.destroyGridLayout();
});
</script>Props(KydUserProfileGrid)
组件使用
defineProps(),以下字段为当前实现所需。
- proxy:
Object(必传)- 来自
getCurrentInstance(),用于给动态渲染的 Grid item vNode 注入appContext。
- 来自
- router:
any(可选)- 透传给内部的
ContainerPanel,用于点击“更多”时执行router.push(skipUrl)。
- 透传给内部的
- routes:
Array(必传)- 路由树结构;内部会递归提取
{ fullPath, component }映射。
- 路由树结构;内部会递归提取
- layout:
string(必传)- 用户布局配置 JSON 字符串。内容是网格项数组(示例见下文“Layout 数据结构”)。
- widgets:
Record<string, Function>(必传)import.meta.glob()的返回值;内部按menus[i].component匹配对应 loader 并defineAsyncComponent。
- menus:
Array(必传)- 可用模块清单(至少需要
menuId/menuName/component)。
- 可用模块清单(至少需要
- saveLayout:
(items: any[]) => void(必传)- 布局持久化回调:拖拽/缩放/新增/删除/套用模板时触发。
Expose(通过 ref 调用)
KydUserProfileGrid 暴露以下方法(defineExpose):
- loadGridLayout()
- 初始化:解析 routes/menus/widgets/layout,并创建 GridStack 实例,然后渲染网格项。
- destroyGridLayout()
- 销毁 GridStack 实例(建议在
onBeforeUnmount调用)。
- 销毁 GridStack 实例(建议在
- adjustGridLayoutSize()
- 重新计算
cellHeight(组件内部也会监听window.resize,你也可以在容器尺寸变化时手动调用)。
- 重新计算
Layout 数据结构
layout 需要是 JSON 字符串,内容是一个数组,每个元素代表一个网格项。典型字段:
- id:
string网格项唯一 id(用于gs-id) - w/h/x/y:
number网格的宽高与坐标(按 12 列计算) - type:
string业务组件标识(例如system/user/index) - title:
string面板标题 - menuId:
number|string关联菜单 id(用于回填) - disableResize:
boolean是否禁用拉伸 - isLocked:
boolean是否锁定(锁定后不可拖拽) - skipUrl:
string“更多”跳转地址(由routes推导得到) - data:
object透传给插槽内容的业务数据(当前实现用于 slot 渲染时展开)
本仓库 demo 数据可参考:src/assets/data/layout.json。
交互说明(面板菜单)
每个网格项由 GridModel -> ContainerPanel 渲染,默认菜单包含:
- 编辑:打开配置弹窗,可修改标题/模块类型/宽高/禁用拉伸/锁定
- 锁定/解锁:切换锁定状态,并更新 GridStack 的
noMove/locked - 关闭:删除网格项并触发
saveLayout
当点击菜单出现非默认 key 时,会以 custom 事件透传(当前实现会在控制台打印)。
常见注意点
- 必须传
proxy:内部动态render(vNode, element)时会用到proxy.appContext,否则会抛错。 - 必须手动调用
loadGridLayout():组件onMounted不会自动初始化,建议nextTick后通过 ref 调用。 - layout 是字符串不是对象:请
JSON.stringify(items)传入;组件内部用JSON.parse(layout)读取。 - widgets 的路径约定:内部通过
path.split("widgets/")[1]匹配模块名,请保持目录约定一致或按需改造匹配逻辑。
项目结构(与集成相关)
src/components/GridStackCom/KydUserProfileGrid.vue:GridStack 初始化与布局管理src/components/GridStackCom/GridModel.vue:网格项包装,统一菜单事件src/components/GridStackCom/ContainerPanel.vue:标题/菜单/更多跳转 UIsrc/assets/data/*.json:demo 的 routes/layout/menus 数据src/utils/random.js:uuid()等工具
