SPA 的架构思维——页面片段化、全局状态隔离、DOM 事件委托

Pear Admin(基于 Layui)有 两个主流版本

版本 模式 特点
iframe 版(经典) 多页应用 左侧菜单 + 右侧 iframe,每个页面是独立 HTML,互不影响
single 版(单页) SPA 无 iframe,通过路由把页面片段(如你的 favorites.html)注入到主容器,所有页面共享同一个 document

单页版的核心问题是:所有页面片段共享同一个 document 和同一个 JS 全局作用域。

1. DOM ID 必须不同(最关键)

<!-- 页面A -->
<input id="name">

<!-- 页面B -->
<input id="name">

单页版中,如果先打开页面A,再切换到页面B,旧的 DOM 不会被自动清空(取决于你的路由切换逻辑)。此时 document.getElementById('name') 永远只返回第一个,页面B就废了。

✅ 建议:加页面前缀

<!-- favorites.html -->
<input id="fav_name">
<input id="fav_id">

<!-- storage.html -->
<input id="sto_name">
<input id="sto_id">

2. lay-filter 必须不同

// 页面A
form.on('submit(formDemo)', function(){ ... });

// 页面B  
form.on('submit(formDemo)', function(){ ... });

在单页版里,这两个都会注册到同一个 layui.form 实例上。后注册的不会覆盖先注册的,而是叠加,导致点提交时两个回调都可能触发,或者行为异常。

✅ 建议:每个页面用独立的 filter

// favorites.html
form.on('submit(formDemo_fav)', function(){ ... });

// storage.html
form.on('submit(formDemo_sto)', function(){ ... });

3. 表单提交的 name 属性 —— 后端字段名

<!-- 页面A -->
<input name="name" id="fav_name">

<!-- 页面B -->
<input name="name" id="sto_name">

这个可以相同。 后端接收的是 HTTP 请求体里的参数名,name 相同完全没问题,因为请求是独立的。但建议也加前缀,方便前端调试时一眼看出来源。


4. 隐藏弹窗容器的 ID

你上传的代码里有这个:

<div style="display: none;" id='sto_layer'>

如果另一个页面也有 id="sto_layer",layer 打开时会找错容器。

✅ 建议:弹窗容器 ID 也要加前缀

<div style="display: none;" id="sto_layer">   <!-- storage 页面 -->
<div style="display: none;" id="fav_layer">   <!-- favorites 页面 -->

5. 全局 JS 变量/函数名

// 页面A
var currentId = null;

// 页面B
var currentId = null;  // 后加载的会覆盖先加载的!

✅ 建议:用 IIFE 包裹,或挂在页面命名空间下

// favorites.html
var FAV = {
    currentId: null,
    init: function(){ ... }
};
FAV.init();

// storage.html  
var STO = {
    currentId: null,
    init: function(){ ... }
};
STO.init();

总结

类型 是否需要不同 原因
id="xxx" ✅ 必须不同 全局 DOM,getElementById 唯一
lay-filter="xxx" ✅ 必须不同 layui 事件会叠加绑定
弹窗容器 id ✅ 必须不同 layer 通过 ID 查找内容
name="xxx" ❌ 可以相同 每次请求独立,后端无感知
JS 全局变量 ✅ 建议不同 后加载会覆盖先加载

一句话:单页版里,凡是全局唯一的标识符(ID、filter、全局变量)都必须做隔离,加页面前缀是最省事的办法。