Pinia 用于管理面板布局状态而非实现拖拽,仅持久化可序列化的结构化数据(如 id、type、x/y/w/h、visible 等),通过 store actions 更新并配合插件自动同步到 localStorage。

Pinia 本身不直接处理拖拽逻辑,但它非常适合管理用户自定义面板布局的“状态快照”——即拖拽后每个面板的位置、大小、顺序、显隐等元数据。真正实现拖拽行为靠的是 UI 库(如 vuedraggable、vue3-draggable-resizable),而 Pinia 负责把用户操作后的最终布局结构持久化地存下来,并在页面加载时还原。
布局状态该存什么?
别存 DOM 或组件实例,只存可序列化的结构化数据。典型字段包括:
-
id:唯一标识一个面板(如
"chart-sales-2024") -
type:组件类型(如
"line-chart"、"table-widget") - x / y / w / h:位置与尺寸(适用于绝对定位或 grid 布局)
- zIndex 或 order:控制叠放顺序或 tab 标签页顺序
- props:该面板特有的配置项(如图表时间范围、表格默认排序字段)
- visible:是否启用显示(支持动态开关面板)
用 Pinia 定义布局 Store
在 src/stores/layout.ts 中定义一个 store,明确声明哪些字段需要持久化:
import { defineStore } from 'pinia'
import { usePersistedState } from 'pinia-plugin-persistedstate'
export const useLayoutStore = defineStore('layout', {
state: () => ({
// 所有面板的布局数据,按 id 索引
panels: {} as Record<string, PanelSchema>,
// 当前激活的面板 id(用于高亮/聚焦)
activePanelId: '',
// 是否处于编辑模式(影响拖拽/缩放开关)
isEditing: false,
}),
persist: {
enabled: true,
// 只持久化核心布局结构,避免存大对象或临时状态
paths: ['panels', 'activePanelId']
},
actions: {
// 添加新面板(例如从组件库拖入)
addPanel(panel: PanelSchema) {
this.panels[panel.id] = panel
},
// 更新某面板的位置尺寸
updatePanel(id: string, updates: Partial<PanelSchema>) {
if (this.panels[id]) {
Object.assign(this.panels[id], updates)
}
},
// 删除面板
removePanel(id: string) {
delete this.panels[id]
if (this.activePanelId === id) this.activePanelId = ''
},
// 批量导入整个布局(如从后端拉取或模板应用)
setLayout(panels: Record<string, PanelSchema>) {
this.panels = panels
}
}
})
与拖拽组件联动的关键时机
拖拽结束不是终点,而是触发 Pinia 状态更新的信号:
- 监听
@dragend或@resize:end(取决于你用的库),拿到目标元素的新x/y/w/h - 通过
useLayoutStore()调用updatePanel(id, { x, y, w, h }) - 如果用了
pinia-plugin-persistedstate,这次修改会自动写入localStorage;刷新后 store 初始化时自动读回 - 若需同步到后端,可在 action 内部加 API 调用(建议节流防频繁请求)
进阶:支持多套布局方案
用户可能想保存“监控模式”“分析模式”“汇报模式”等不同布局。这时可扩展 store 结构:
- 把
panels改为layouts: Record<string, Record<string, PanelSchema>> - 新增
currentLayoutKey: string = 'default' - persist 配置中 paths 改为
['layouts', 'currentLayoutKey'] - 切换布局只需改
currentLayoutKey,视图层根据它读取对应layouts[key]


















