
企业工作台/首页设计可拖拽磁贴 待办聚合 数据卡片实现演示地址http://ruoyioffice.com | 源码1·GitHubruoyi-office | 源码2·GitCoderuoyi-office | 源码3·Giteeruoyi-office | 微信17156169080备注「RuoYi Office」员工每天打开系统的第一个页面决定了他对整套 ERP/OA 的第一印象。钉钉工作台、飞书多维表格首页——头部产品都在做可拖拽磁贴 待办聚合 数据卡片。本文不重复「千人千面数据模型」的理论详见 custom-homepage-personalized-experience.md而是聚焦RuoYi Office 工作台的前端实操grid-layout-plus怎么拖、registry.ts怎么注册 11 个磁贴、workbench-task-list怎么聚合四路 BPM 待办、workbench-app-center怎么用vuedraggable排应用。▲ 工作台全景设计器/渲染器分层、grid-layout-plus 24 列栅格、11 个内置磁贴组件、四 Tab 待办聚合与 vuedraggable 应用中心一图看懂引言工作台设计到底难在哪「首页」和「工作台」在企业系统里常被混用但工程上它们要解决不同问题问题一布局不可写死。不同角色需要不同磁贴组合——欢迎区、待办、公告、日程、统计卡片——硬编码div布局无法支撑千人千面。问题二拖拽与渲染要分离。设计器需要拖拽/缩放员工日常使用时不能误触改变布局——同一套GridLayout组件两种模式参数截然不同。问题三待办来源分散。待办任务、我的单据、已办、抄送——四路 BPM APITab 切换、Badge 统计、跳转审批详情逻辑集中在一个磁贴里。问题四应用中心与权限菜单联动。「常用应用」必须从 RBAC 菜单树提取叶子节点拖拽排序后持久化到用户维度——不能展示无权限的入口。问题五组件扩展不能改框架。新增「合同到期提醒」磁贴 ideally 只写 Vue 组件 一行registerComponent不动index.vue。痛点不处理的后果布局写死每改一次首页发版拖拽不分离员工误拖布局体验灾难待办分散4 个菜单才能办完一件事应用无权限过滤点击 403信任崩塌组件不注册设计器选不到新磁贴RuoYi Office 的解法是grid-layout-plus 栅格 组件注册制 设计/渲染双模式。代码路径ruoyi-office-vben/apps/web-antd/src/views/dashboard/home/。一、目录结构与页面分层▲ 员工登录后的工作台首页欢迎语天气磁贴、应用中心发起流程/用印申请/请假销假等高频入口、待办任务/我的单据/已办/抄送多 Tab 聚合、通知公告与日程待办——全部由磁贴拼装而成1.1 前端目录地图dashboard/home/ ├── index.vue # 员工工作台入口加载我的首页 ├── renderer/ │ └── layout-renderer.vue # 只读渲染grid-layout-plus static ├── designer/ │ ├── index.vue # 可视化设计器 │ └── components/ │ ├── designer-canvas.vue # 可拖拽画布 │ ├── component-panel.vue # 左侧组件库 │ └── config-panel.vue # 右侧属性配置 ├── components/ │ ├── registry.ts # ★ 11 个磁贴注册表 │ ├── wrapper/component-wrapper.vue # 动态组件加载 │ ├── welcome/workbench-welcome.vue │ ├── app-center/workbench-app-center.vue # ★ vuedraggable │ ├── taskLists/workbench-task-list.vue # ★ 四 Tab 待办 │ ├── notice/workbench-notice.vue │ ├── schedule/workbench-schedule.vue │ ├── navigation/workbench-quick-nav.vue │ ├── lists/workbench-project.vue │ ├── lists/workbench-trends.vue │ ├── statistics/analytics-visits.vue │ ├── statistics/analytics-visits-data.vue │ └── charts/analytics-visits-source.vue ├── manage/ # 首页模板管理管理员 └── types/layout.ts # GridLayoutItem 类型定义1.2 运行时分层层级文件职责入口index.vue调用getMyHomePage()获取当前用户首页 ID渲染layout-renderer.vue加载布局 JSON只读展示磁贴设计designer-canvas.vue拖拽/缩放/删除保存到后端调度component-wrapper.vue按componentCode从 registry 取 Vue 组件磁贴workbench-*.vue各业务 UI 实现index.vue │ getMyHomePage() ▼ LayoutRenderer (pageId) │ getHomePageLayoutList(pageId) ▼ GridLayout GridItem[] │ componentCode ▼ ComponentWrapper → registry.getComponent(code)二、grid-layout-plus24 列磁贴布局▲ 首页设计器左侧「组件库」按统计卡片/图表/列表/快捷导航分组中间是 24 列栅格画布从组件库拖拽磁贴到画布即可布局右侧「属性配置」编辑选中磁贴参数顶部「预览 / 保存」实时生效2.1 为什么选 grid-layout-plusgrid-layout-plus是 Vue3 版 react-grid-layout支持拖拽、缩放、碰撞检测、响应式栅格。RuoYi Office 采用24 列设计与 Ant Design 栅格习惯一致rowHeight60px磁贴宽高以「格」为单位存储。参数设计模式渲染模式colNum2424rowHeight6060isDraggabletruefalseisResizabletruefalsestaticfalsetruemargin[10, 10][10, 10]结论前置设计器与渲染器共用GridLayout组件通过isDraggable/isResizable/static三个开关区分模式避免维护两套布局引擎。2.2 layout-renderer.vue只读渲染核心员工打开工作台时layout-renderer.vue从后端拉取布局列表转换为GridLayoutItem[]import{GridItem,GridLayout}fromgrid-layout-plus;import{getHomePageLayoutList}from#/api/system/home;constlayoutConfigref({colNum:24,rowHeight:60,isDraggable:false,// 渲染模式不可拖拽isResizable:false,margin:[10,10],containerPadding:[10,10],verticalCompact:false,});asyncfunctionloadLayout(){constlayoutItemsawaitgetHomePageLayoutList(props.pageId);layout.valuelayoutItems.map((item)({i:item-${item.id},x:item.positionX,y:item.positionY,w:item.width,h:item.height,componentCode:item.componentCode,config:item.config?JSON.parse(item.config):{},isDraggable:false,isResizable:false,static:true,}));}模板中每个GridItem包裹ComponentWrapper把componentCode和config传给具体磁贴GridItem v-foritem in layout :keyitem.i :xitem.x :yitem.y :witem.w :hitem.h :iitem.i :staticitem.static div classlayout-item-content h-full w-full overflow-hidden rounded bg-white shadow-sm ComponentWrapper :component-codeitem.componentCode :configitem.config / /div /GridItem2.3 designer-canvas.vue设计模式差异设计器打开isDraggabletrue、isResizabletrue布局变更通过emit(update:layout)回传父组件保存functionhandleLayoutUpdated(newLayout:any[]){constupdatedLayoutnewLayout.map((item){constexistingItemlocalLayout.value.find((l)l.iitem.i);return{...existingItem,x:item.x,y:item.y,w:item.w,h:item.h};});emit(update:layout,updatedLayout);}2.4 GridLayoutItem 数据结构exportinterfaceGridLayoutItem{i:string;// 唯一标识 item-{id}x:number;// 列位置 0-23y:number;// 行位置w:number;// 宽度格数h:number;// 高度格数componentCode:string;// 磁贴编码对应 registryconfig?:Recordstring,any;// 磁贴私有配置 JSONstatic?:boolean;}后端表system_home_page_layout存储position_x/y、width/height、component_code、config。三、11 个内置磁贴registry.ts 注册制3.1 注册表设计所有可拖拽到工作台的磁贴在components/registry.ts统一注册exportinterfaceComponentRegistryItem{code:string;component:Component;name:string;description?:string;}constcomponentRegistrynewMapstring,ComponentRegistryItem();exportfunctionregisterComponent(item:ComponentRegistryItem){componentRegistry.set(item.code,item);}exportfunctiongetComponent(code:string):Component|undefined{returncomponentRegistry.get(code)?.component;}3.2 完整 11 组件清单code组件文件名称功能workbench_welcomeworkbench-welcome.vue欢迎组件用户名、问候语、天气workbench_app_centerworkbench-app-center.vue应用中心常用应用 vuedraggableworkbench_task_listworkbench-task-list.vue任务列表四 Tab BPM 待办聚合workbench_noticeworkbench-notice.vue通知公告系统公告列表workbench_scheduleworkbench-schedule.vue我的日程日历 待办事项workbench_quick_navworkbench-quick-nav.vue快捷导航自定义快捷入口workbench_projectworkbench-project.vue项目列表项目卡片workbench_trendsworkbench-trends.vue动态列表最新动态analytics_visitsanalytics-visits.vue访问统计访问量 KPI 卡片analytics_visits_dataanalytics-visits-data.vue数据统计访问详情analytics_visits_sourceanalytics-visits-source.vue访问来源饼图分析注册示例registerComponent({code:workbench_task_list,component:WorkbenchTaskList,name:任务列表,description:展示我的单据、待办任务、已办任务、抄送我的,});3.3 ComponentWrapper 动态加载script setup langts import { computed } from vue; import { getComponent } from ../registry; const props defineProps{ componentCode: string; config?: Recordstring, any; }(); const DynamicComponent computed(() getComponent(props.componentCode)); /script template component :isDynamicComponent v-ifDynamicComponent v-bindconfig / div v-else组件 {{ componentCode }} 未注册/div /template3.4 新增磁贴四步在components/下新建workbench-xxx.vue在registry.ts中registerComponent({ code: workbench_xxx, ... })后端system_home_component表录入组件元数据设计器组件库展示在设计器中拖入画布调整 w/h保存布局四、待办聚合workbench-task-list.vue4.1 四 Tab 设计workbench-task-list是企业工作台的核心磁贴——把 BPM 四条线合并为一个 Tab 面板Tab Key标签API说明todo待办任务getTaskTodoPage当前用户待审批myBill我的单据getProcessInstanceMyPage我发起的流程done已办任务getTaskDonePage我已处理的copy抄送我的getProcessInstanceCopyPage抄送通知typeTabKeytodo|done|myBill|copy;consttabscomputed(()[{key:todo,label:待办任务,count:statistics.value.todo},{key:myBill,label:我的单据,count:statistics.value.myBill},{key:done,label:已办任务,count:statistics.value.done},{key:copy,label:抄送我的,count:statistics.value.copy},]);4.2 列定义与 Tab 差异不同 Tab 返回的数据结构略有差异Task vs ProcessInstance vs Copy组件用customRender统一列展示列todo/donemyBillcopy单据类型processInstance.namenameprocessInstanceName单据编号processInstance.billCodebillCodebillCode审批状态DictTagDictTag 流程图—摘要summary JSONsummarysummary特殊列任务节点/审批时间发起时间抄送节点/抄送时间4.3 统计 Badge 与跳转组件挂载时并行请求四路 API 的total填充 Badge点击行跳转 BPM 审批详情functionhandleViewDetail(record:any){consttabactiveTab.value;letpath;if(tabtodo||tabdone){path/bpm/task/detail?id${record.id};}elseif(tabmyBill){path/bpm/process-instance/detail?id${record.id};}elseif(tabcopy){path/bpm/process-instance/detail?id${record.processInstanceId};}if(path)router.push(path);}4.4 可配置 propsinterfaceProps{maxRecordNum?:number;// 默认 10控制表格最大行数}设计器 config JSON 可覆盖maxRecordNum小磁贴显示 5 条大磁贴显示 20 条。五、应用中心workbench-app-center.vue vuedraggable5.1 功能概述应用中心磁贴展示用户「常用应用」图标墙支持从 RBAC 权限菜单选择叶子节点添加vuedraggable拖拽排序删除应用Popconfirm排序持久化updateUserAppSort5.2 从权限菜单提取可选应用constmenuOptionscomputed((){constmenusaccessStore.accessMenus||[];constoptions[];constprocessMenus(menuList:any[],parents:any[][]){for(constmenuofmenuList){constchildrenArray.isArray(menu.children)?menu.children:[];constisLeafchildren.length0;if(isLeafmenu.path){constidmenu.id??menu.menuId??menu.meta?.id;if(!id)return;options.push({id:Number(id),name:menu.meta?.title??menu.name,path:menu.path,icon:menu.icon??menu.meta?.icon??carbon:application,});}if(children.length0)processMenus(children,[...parents,menu]);}};processMenus(menus);returnoptions;});设计要点只取有path的叶子菜单天然过滤目录节点无 ID 的菜单跳过并 warn避免后端保存失败。5.3 vuedraggable 排序持久化Draggable v-modelappList item-keyid :disabled!enableDrag ghost-classapp-ghost startisDragging true endhandleDragEnd template #item{ element } div classapp-item clickhandleAppClick(element) IconifyIcon :iconelement.icon / span{{ element.name }}/span /div /template /DraggableasyncfunctionhandleDragEnd(){isDragging.valuefalse;constsortListappList.value.map((app,index)({id:app.id,sort:index1,}));awaitupdateUserAppSort(sortList);message.success(排序已保存);}5.4 首次初始化用户首次进入且无应用数据时调用initUserApp()从系统默认应用模板初始化避免空白磁贴。Props默认说明maxAppCount12最大展示应用数gridCols4图标网格列数enableDragtrue是否允许拖拽排序六、index.vue 入口与预览模式consthomePageInforefany(null);asyncfunctionloadHomePage(){homePageInfo.valueawaitgetMyHomePage();}constpreviewPageIdcomputed((){constpageIdroute.query.preview;returnpageId?Number(pageId):null;});constcurrentPageIdcomputed((){returnpreviewPageId.value||homePageInfo.value?.id;});预览模式URL?preview{pageId}设计器保存前预览其余磁贴welcome、notice、schedule、analytics 数据卡片按同一ComponentWrapper模式加载。▲ 首页管理列表支持维护多套首页模板默认工作台、自定义首页…每套可「设计 / 设置为首页 / 启用」——配合按角色下发不同默认首页即可实现「千人千面」的工作台与 custom-homepage-personalized-experience.md 的分工该文讲数据表与后端服务本文讲 grid-layout 拖拽、registry 注册、待办聚合、vuedraggable 应用中心前端实操。七、技术亮点总结设计要点实现方式价值磁贴布局grid-layout-plus拖拽/缩放/栅格化双模式isDraggable/static 开关设计灵活、使用稳定组件扩展registry.ts新增磁贴不改框架动态加载ComponentWrapper按 code 懒渲染待办聚合4 Tab 4 API一屏办完审批应用中心vuedraggable用户自定义排序权限联动accessStore.accessMenus不展示无权限入口预览?previewpageId设计器即时验证八、快速体验在线演示 地址http://ruoyioffice.com/web/ 账号admin/admin123 登录后默认进入工作台首页推荐体验流程登录后观察默认工作台磁贴布局欢迎 待办 应用中心点击待办 Tab 切换「待办任务 / 我的单据 / 已办 / 抄送」在应用中心拖拽图标调整顺序刷新页面验证持久化进入工作台 → 首页管理或/workspace/home/manage打开设计器从左侧组件库拖入「我的日程」磁贴调整大小位置保存后在员工首页查看效果阅读registry.ts理解组件注册机制本地启动cd W:\ruoyi-office\ruoyi-office-vben pnpm dev:antd# 访问 http://localhost:5800源码路径ruoyi-office-vben/apps/web-antd/src/views/dashboard/home/常见问题FAQRuoYi Office 工作台支持拖拽布局吗支持。基于grid-layout-plus24 列栅格管理员在设计器中拖拽/缩放磁贴员工端只读渲染。内置有哪些磁贴组件共11 个welcome、app_center、task_list、notice、schedule、quick_nav、project、trends以及 3 个 analytics 数据卡片。见components/registry.ts。如何新增自定义磁贴新建 Vue 组件 →registerComponent注册 → 后端组件库录入 → 设计器拖入。无需修改index.vue。待办聚合包含哪些类型四 Tab待办任务、我的单据、已办任务、抄送我的分别对接 BPM Task 和 ProcessInstance API。结语企业工作台不是「把几个 Chart 拼到首页」——它是员工每天工作的操作系统桌面。RuoYi Office 用 grid-layout-plus 解决布局、用 registry 解决扩展、用四 Tab 聚合解决待办分散、用 vuedraggable 解决应用排序——四个齿轮咬合才能支撑 14 大模块、不同角色的千人千面。如果你正在做 OA/ERP 首页改造或基于 RuoYi Office 二开新磁贴如「合同到期提醒」「库存预警」这套dashboard/home/目录就是最好的起点。欢迎 Star也欢迎微信17156169080备注「RuoYi Office」交流工作台定制经验。你的团队首页有哪些「离不开的磁贴」待办、日程还是数据看板评论区聊聊。想要体验 RuoYi Office 的工作台在线演示http://ruoyioffice.com/web/账号 admin / admin123源码仓库GitCode | GitHub技术咨询添加微信17156169080备注「RuoYi Office」⭐如果觉得不错请给个 Star 支持一下