必须使用原生 drag API 实现跨容器模块拖拽,因Sortable.js仅支持同级元素排序;需设置draggable/droppable属性、preventDefault防拦截、移动端降级为touch模拟,并通过localStorage按容器ID存模块ID数组实现持久化布局。

拖拽模块互换位置必须用原生 drag API,Sortable.js 不适用
因为模块是页面内独立区块(比如 .module-card),不是同级列表项,Sortable.js 默认只处理同级 li 或 tr 的顺序重排,无法跨容器移动。真正能实现「A 区块拖进 B 区域」的,只有浏览器原生 dragstart/dragover/drop 三件套。
关键点在于:drop 目标必须是带 droppable="true" 的容器,且 dragover 中必须调用 e.preventDefault(),否则事件直接被浏览器拦截——你拖着松手,啥也没发生,连控制台都不报错。
- 每个可拖拽模块加
draggable="true",并在dragstart中通过e.dataTransfer.setData('text', e.target.id)记录来源 ID - 每个目标区域(如
#sidebar、#main-content)设droppable="true",并监听dragover和drop -
drop里用appendChild或insertBefore移动 DOM,别用innerHTML重写,会丢事件绑定和状态 - 移动端完全不支持 drag 系列事件,必须检测
'ontouchstart' in window,走 touch 模拟逻辑,否则 iOS 上功能直接消失
保存排版不能只存 DOM 结构,要存「容器 → 模块 ID 列表」映射
用户拖完模块,页面布局变了,但刷新就回退——因为没持久化。localStorage 只能存字符串,所以得把结构扁平化成键值对:key 是容器 ID,value 是该容器下模块 ID 的有序数组,例如:localStorage.setItem('layout-sidebar', '["mod-user-info","mod-notices"]')。
千万别存 HTML 字符串或 JSON 对象嵌套,一来体积大,二来解析失败就整个排版崩掉;也别用 document.body.innerHTML 快照,模块内部可能有动态渲染内容(如 ECharts 图表),快照后还原会白屏。
- 每次
drop完成后,遍历所有目标容器,收集其子模块的id属性,生成数组并存入 localStorage - 页面初始化时,在
layui.use加载完成、DOM 就绪后,立即读取各容器对应 key,按顺序appendChild还原模块 - 如果某个模块 ID 在 localStorage 里存在但当前页面没加载(比如权限控制隐藏了该模块),跳过它,不要报错中断流程
- 避免用
window.location.href当 key 前缀——带查询参数的 URL 会导致重复 key,比如/dashboard?tab=1和/dashboard?tab=2存两份
模块拖入后样式错乱?多半是 CSS 选择器没适配动态插入
模块从 A 容器拖到 B 容器,DOM 节点移动了,但它的样式规则可能依赖父级类名,比如 #sidebar .module-card { width: 240px; },而 #main-content .module-card { width: 100%; }。拖过去后,元素还在,但样式没自动切换。
最稳妥的做法是:所有模块自身带完整内联样式或独立 class,不依赖父容器上下文。例如给每个模块加 class="module-card module-card--sidebar" 或 module-card--main,然后在 CSS 里分别定义,而不是靠后代选择器。
- 避免使用
.container > .module-card:nth-child(2)这类强位置依赖的样式,拖拽后 DOM 顺序变,样式就失效 - 模块内部如果有 iframe 或 canvas,拖拽后需手动触发 resize 或 reinit(比如 ECharts 的
resize()) - 如果模块用了 layui 的
form.render()或element.init(),移动后要重新调用,否则开关、标签页等组件会失活
为什么 reload 页面后模块顺序恢复默认?时机没卡准
很多人把排版还原逻辑写在 layui.use 回调里,但这时 DOM 可能还没挂载完;或者写在 $(document).ready,但 layui 的模块加载是异步的,DOM 已存在,模块却还没初始化完毕——结果就是:还原代码执行了,模块被 append 进去,但紧接着 layui 自己又按原始 HTML 渲染了一遍,覆盖掉了你的操作。
正确时机只有一个:等所有 layui 模块(table、form、element)都 use 完,并确认页面上所有模块容器已真实存在(可用 document.getElementById('sidebar') 拿到非 null 结果),再执行还原。
- 推荐做法:用
setTimeout延迟 10ms 执行还原逻辑,确保渲染队列清空 - 更健壮的方式:监听
DOMContentLoaded+layui.use([...], callback)双保险,callback 内再检查容器是否存在 - 绝对不要在
table.done或form.render回调里还原模块顺序——那是表格/表单专属逻辑,跟布局无关,混在一起容易互相干扰


















