jQuery EasyUI 教程

jQuery EasyUI 是一组基于 jQuery 的 UI 插件集合,帮助开发者快速搭建后台管理界面。它提供了数据网格(datagrid)、树形菜单(tree)、窗口(window)、对话框(dialog)、表单(form)等几十个开箱即用的组件。

使用 EasyUI 几乎不需要编写复杂的 JavaScript 交互代码:多数组件只需在 HTML 标签上声明 class="easyui-xxx" 或用 data-options 配置属性,即可完成初始化。

本教程结构

模块内容
基础入门环境搭建、第一个组件、CRUD 应用
布局组件layout、accordion、tabs、menus
数据网格datagrid 的列、分页、编辑、格式化
窗口组件window、dialog
树形组件tree、treegrid
表单组件form、validatebox、combobox
✅ 建议
EasyUI 上手快,适合管理系统 / 后台界面;学习时先跑通一个 CRUD 示例,再逐步掌握各组件细节。

jQuery EasyUI 简介

jQuery EasyUI 是一个用于创建富交互 Web 界面的 jQuery 插件框架。它的设计理念是「用 HTML 声明 UI,用 JavaScript 增强交互」,内置组件覆盖了后台系统几乎全部常用场景。

主要特点

  • 上手简单:只需引入 CSS 与 JS,用 class 声明即可使用
  • 组件丰富:datagrid、tree、tabs、accordion、dialog、menu 等 30+ 组件
  • 主题可换:内置多种皮肤,支持自定义配色
  • 中文文档友好:国内使用广泛,社区资料多

环境准备

下载 EasyUI 包后,在页面中引入以下文件:

<link rel="stylesheet" href="themes/default/easyui.css">
<link rel="stylesheet" href="themes/icon.css">
<script src="jquery.min.js"></script>
<script src="jquery.easyui.min.js"></script>

第一个组件

给任意 <div> 加上 easyui-panel 类,就得到了一个面板:

<div class="easyui-panel" title="我的面板" style="width:300px;height:150px;">
  欢迎使用 jQuery EasyUI!
</div>
💡 提示
所有组件都支持两种初始化方式:HTML 声明(class + data-options)或 JavaScript 调用(如 $('#id').panel({...}))。

jEasyUI 创建 CRUD 应用

CRUD 指 Create(增)/ Read(查)/ Update(改)/ Delete(删),是后台管理系统最常见的需求。本教程的示例将演示:用 datagrid 展示数据、用 dialog + form 完成新增与编辑、用 linkbutton 触发操作。

整体思路

页面布局:
┌────────────────────────────┐
│ 工具栏:新增 | 编辑 | 删除    │
├────────────────────────────┤
│  数据网格(datagrid)         │
│  ┌──────────────────────┐  │
│  │ 对话框(dialog)        │  │
│  │ 表单:名称/价格/分类...  │  │
│  └──────────────────────┘  │
└────────────────────────────┘

页面骨架

<table id="dg" class="easyui-datagrid" title="商品列表"
       url="data.json" toolbar="#tb"
       rownumbers="true" pagination="true">
  <thead>
    <tr>
      <th data-options="field:'id',width:60">ID</th>
      <th data-options="field:'name',width:200">名称</th>
      <th data-options="field:'price',width:100">价格</th>
    </tr>
  </thead>
</table>

<div id="tb">
  <a class="easyui-linkbutton" data-options="iconCls:'icon-add'">新增</a>
  <a class="easyui-linkbutton" data-options="iconCls:'icon-edit'">编辑</a>
  <a class="easyui-linkbutton" data-options="iconCls:'icon-remove'">删除</a>
</div>
✅ 最佳实践
  • 数据接口统一返回 {total: n, rows: [...]} 结构,datagrid 自动分页
  • 增删改操作后调用 $('#dg').datagrid('reload') 刷新列表
  • 对话框点击「保存」后先做表单校验,再提交

jEasyUI 创建 CRUD 数据网格

数据网格(datagrid)是 EasyUI 最核心的组件,负责表格展示与交互。本教程实现一个完整的 CRUD 数据网格:展示商品列表、支持分页、行选中、增删改。

HTML 定义表格

<table id="dg" class="easyui-datagrid" style="width:700px;height:300px;"
       url="get_data.php"
       toolbar="#tb" rownumbers="true" singleSelect="true" pagination="true">
  <thead>
    <tr>
      <th data-options="field:'id',width:60">ID</th>
      <th data-options="field:'name',width:200">商品名</th>
      <th data-options="field:'price',width:100,formatter:fmtPrice">价格</th>
      <th data-options="field:'stock',width:80">库存</th>
    </tr>
  </thead>
</table>

工具栏与操作函数

<div id="tb" style="padding:4px;">
  <a href="#" class="easyui-linkbutton" iconCls="icon-add" plain="true">新增</a>
  <a href="#" class="easyui-linkbutton" iconCls="icon-remove" plain="true">删除</a>
</div>
function fmtPrice(value) {
  return "¥" + value;
}

function delRow() {
  var row = $("#dg").datagrid("getSelected");
  if (!row) { $.messager.alert("提示", "请先选择一行"); return; }
  $.post("delete.php", { id: row.id }, function (res) {
    if (res.success) { $("#dg").datagrid("reload"); }
  }, "json");
}

function addRow() {
  // 追加一行空数据(可配合行内编辑)
  $("#dg").datagrid("appendRow", { id: 0, name: "", price: 0, stock: 0 });
}
⚠️ 注意
分页模式下,删除后刷新会回到第一页;如需停留在当前页,可用 load 方法指定页码参数。

jEasyUI 表单的 CRUD 应用

纯数据网格只解决了「展示」,新增与编辑需要表单 + 对话框配合。本教程演示如何用 dialog 弹出表单,实现完整的增改流程。

对话框 + 表单

<div id="dlg" class="easyui-dialog" title="新增商品" style="width:360px;height:220px;"
     data-options="closed:true,modal:true,buttons:'#dlg-buttons'">
  <form id="fm" method="post">
    <table style="margin:10px;">
      <tr>
        <td>名称:</td>
        <td><input name="name" class="easyui-textbox" required="true"></td>
      </tr>
      <tr>
        <td>价格:</td>
        <td><input name="price" class="easyui-numberbox" required="true"></td>
      </tr>
    </table>
  </form>
</div>

<div id="dlg-buttons">
  <a href="#" class="easyui-linkbutton" iconCls="icon-ok">保存</a>
  <a href="#" class="easyui-linkbutton" iconCls="icon-cancel">取消</a>
</div>

新增 / 编辑 / 保存

function addItem() {
  $("#fm").form("clear");          // 清空表单
  $("#dlg").dialog("open").dialog("setTitle", "新增商品");
}

function editItem() {
  var row = $("#dg").datagrid("getSelected");
  if (!row) return;
  $("#fm").form("load", row);      // 回填数据
  $("#dlg").dialog("open").dialog("setTitle", "编辑商品");
}

function save() {
  if (!$("#fm").form("validate")) return;  // 校验必填
  $.post("save.php", $("#fm").serialize(), function (res) {
    if (res.success) {
      $("#dlg").dialog("close");
      $("#dg").datagrid("reload");
    }
  }, "json");
}
✅ 技巧
form('load', row) 可以自动把对象字段映射到同名表单项,省去手动赋值。

jEasyUI 创建 RSS 阅读器

本教程用 datagrid 做一个简单的 RSS 阅读器:加载 RSS 源、展示标题列表、点击展开摘要,体验「数据驱动组件」的用法。

RSS 数据结构

服务端把 RSS 解析成 JSON 返回,字段与表格列一一对应:

{
  "total": 20,
  "rows": [
    {
      "title": "如何高效学习 jQuery",
      "link": "https://example.com/1",
      "pubDate": "2026-08-01",
      "description": "一篇关于学习方法论的文章……"
    }
  ]
}

页面与组件

<table id="rss" class="easyui-datagrid" title="我的订阅"
       url="rss_feed.php" pagination="true" rownumbers="true"
      >
  <thead>
    <tr>
      <th data-options="field:'title',width:300">标题</th>
      <th data-options="field:'pubDate',width:120">发布时间</th>
    </tr>
  </thead>
</table>

<div id="detail" style="margin-top:8px;padding:8px;border:1px solid #ccc;height:150px;">
  选中文章后在此显示摘要
</div>

选中展示摘要

function showDetail(index, row) {
  $("#detail").html(
    "<strong>" + row.title + "</strong><br>" +
    "<a href='" + row.link + "' target='_blank'>阅读原文</a><br>" +
    row.description
  );
}
💡 提示
跨域获取 RSS 时浏览器会拦截,建议通过服务端代理转发(如 PHP 的 file_get_contents 或 Node 的 axios)。

jEasyUI 基本的拖动和放置

draggable 让元素可以自由拖动,droppable 让元素可以接收被拖放的目标。二者组合可以实现拖拽排序、拖拽到回收站等交互。

使元素可拖动

<div id="box" style="width:120px;height:80px;background:#87ceeb;cursor:move;">
  拖动我
</div>
$("#box").draggable({
  revert: true,          // 拖放后回到原位
  proxy: "clone",        // 拖动时显示克隆占位
  onStartDrag: function () {
    $(this).addClass("dragging");
  },
  onStopDrag: function () {
    $(this).removeClass("dragging");
  }
});

放置目标

<div id="drop" style="width:200px;height:150px;border:2px dashed #999;">
  放这里
</div>
$("#drop").droppable({
  accept: "#box",        // 只接受 id 为 box 的元素
  onDragEnter: function () {
    $(this).css("border-color", "#2e8b57");
  },
  onDrop: function (e, source) {
    $(this).html("已收到:" + $(source).text());
    $(this).css("border-color", "#999");
  }
});
⚠️ 注意
proxy:'clone' 拖动时用的是克隆体,原元素位置不变;拖放回调里通过 source 参数拿到被拖动的原始元素。

jEasyUI 创建拖放的购物车

综合运用 draggable + droppable 实现一个迷你购物车:商品可拖入购物车,购物车显示数量与总价。

商品区与购物车

<div style="width:640px;">
  <div id="goods">
    <div class="item" data-price="99">📱 手机 ¥99</div>
    <div class="item" data-price="199">🎧 耳机 ¥199</div>
    <div class="item" data-price="49">⌨️ 键盘 ¥49</div>
  </div>
  <div id="cart" style="margin-top:10px;min-height:100px;border:2px dashed #f60;">
    <div style="padding:8px;">把商品拖进来</div>
  </div>
  <p>共 <span id="count">0</span> 件,合计 ¥<span id="total">0</span></p>
</div>

初始化拖放

$(function () {
  $(".item").draggable({
    revert: true,
    proxy: "clone",
    cursor: "move"
  });

  $("#cart").droppable({
    accept: ".item",
    onDrop: function (e, source) {
      var price = parseInt($(source).attr("data-price"));
      var count = parseInt($("#count").text()) + 1;
      var total = parseInt($("#total").text()) + price;
      $("#count").text(count);
      $("#total").text(total);
      $("#cart").append("<p>" + $(source).text() + "</p>");
    }
  });
});
✅ 技巧
数量与总价直接读取 data-price 属性累加,逻辑清晰且易于扩展成真实购物车数据。

jEasyUI 创建学校课程表

用 layout 布局 + draggable 做一个可视化的学校课程表:左侧为课程列表,右侧为周时间网格,课程可拖到对应格子。

布局骨架

<div class="easyui-layout" style="width:700px;height:400px;">
  <div data-options="region:'west',title:'课程列表',width:180,split:true">
    <div class="course">语文</div>
    <div class="course">数学</div>
    <div class="course">英语</div>
  </div>
  <div data-options="region:'center',title:'课程表'">
    <table id="timetable"></table>
  </div>
</div>

生成时间网格

var days = ["周一", "周二", "周三", "周四", "周五"];
var cells = "<tr><th>节次</th>";
$.each(days, function (i, d) { cells += "<th>" + d + "</th>"; });
cells += "</tr>";
for (var h = 1; h <= 6; h++) {
  cells += "<tr><td>第" + h + "节</td>";
  for (var j = 0; j < 5; j++) {
    cells += "<td class='slot' data-day='" + j + "' data-hour='" + h + "'></td>";
  }
  cells += "</tr>";
}
$("#timetable").html(cells);

课程可拖、格子可收

$(".course").draggable({ revert: true, proxy: "clone" });
$(".slot").droppable({
  accept: ".course",
  onDrop: function (e, source) {
    $(this).text($(source).text()).css("background", "#ffe4b5");
  }
});
💡 提示
这种「左侧列表 + 右侧网格」的拖放模式,也可复用到排班、会议安排、任务分配等场景。

jEasyUI 创建简单的菜单

menu 组件用于创建右键菜单、下拉菜单。配合 menubutton 可以做成工具栏下拉,配合 onContextMenu 可以实现右键菜单。

定义菜单

<div id="mm" class="easyui-menu" style="width:150px;">
  <div data-options="iconCls:'icon-new'">新建</div>
  <div data-options="iconCls:'icon-edit'">编辑</div>
  <div class="menu-sep"></div>
  <div>保存</div>
  <div data-options="iconCls:'icon-remove'">删除</div>
</div>

显示菜单

// 点击按钮弹出菜单
$("#btn").menubutton({
  menu: "#mm"
});

// 或右键表格弹出
$("#dg").datagrid({
  onRowContextMenu: function (e, index, row) {
    e.preventDefault();
    $(this).datagrid("selectRow", index);
    $("#mm").menu("show", {
      left: e.pageX,
      top: e.pageY
    });
  }
});

菜单项处理函数

function doNew()  { $.messager.alert("菜单", "点击了新建"); }
function doEdit() { $.messager.alert("菜单", "点击了编辑"); }
function doDelete() {
  var row = $("#dg").datagrid("getSelected");
  $.messager.confirm("确认", "确定删除选中行吗?", function (ok) {
    if (ok) $("#dg").datagrid("reload");
  });
}
⚠️ 注意
右键菜单记得 e.preventDefault(),否则浏览器默认菜单也会弹出。

jEasyUI 创建链接按钮

linkbutton 把普通的 <a>、<button>、<div> 变成带图标、带样式的按钮,是后台界面最常用的组件之一。

基本用法

<!-- 纯文本按钮 -->
<a class="easyui-linkbutton">确定</a>

<!-- 带图标 -->
<a class="easyui-linkbutton" iconCls="icon-add">新增</a>

<!-- 扁平按钮 -->
<a class="easyui-linkbutton" plain="true" iconCls="icon-edit">编辑</a>

<!-- 禁用按钮 -->
<a class="easyui-linkbutton" disabled="true">已禁用</a>

JS 方式创建

$("#myBtn").linkbutton({
  iconCls: "icon-ok",
  text: "保存",
  onClick: function () {
    $.messager.show({ title: "提示", msg: "点击了保存" });
  }
});

动态启用 / 禁用

function toggleBtn(disabled) {
  $("#myBtn").linkbutton(disabled ? "disable" : "enable");
}

// 未选中行时禁用「编辑」按钮
function onSelectRow() {
  $("#editBtn").linkbutton("enable");
}
方法作用
disable禁用按钮
enable启用按钮
resize调整按钮尺寸
✅ 建议
按钮图标统一使用内置的 icon-xxx 类;自定义图标可把图片放到 themes/icons 目录后注册。

jEasyUI 创建菜单按钮

menubutton 是「按钮 + 下拉菜单」的组合:点击按钮触发默认动作,点击箭头弹出菜单项。

基本定义

<div id="mb" class="easyui-menubutton" data-options="menu:'#mm',iconCls:'icon-edit'">
  编辑
</div>

<div id="mm" style="width:150px;">
  <div>复制</div>
  <div>剪切</div>
  <div class="menu-sep"></div>
  <div>粘贴</div>
</div>

菜单项带子菜单

<div id="mm2" style="width:150px;">
  <div>
    <span>导出</span>
    <div style="width:120px;">
      <div>导出 Excel</div>
      <div>导出 CSV</div>
    </div>
  </div>
</div>

JS 方式创建

$("#mb2").menubutton({
  text: "更多操作",
  iconCls: "icon-more",
  menu: "#mm2",
  duration: 200
});
💡 提示
菜单项里的 onclick 可以直接写函数名;复杂逻辑建议统一在函数内通过「选中行」等上下文处理,避免在 HTML 中堆业务代码。

jEasyUI 创建分割按钮

splitbutton 与 menubutton 类似,但点击按钮主体就触发主动作,点击右侧小箭头才弹出菜单,适合「默认执行 + 可选扩展」的场景。

基本定义

<div id="sb" class="easyui-splitbutton" data-options="menu:'#mm',iconCls:'icon-save'">
  保存
</div>

<div id="mm" style="width:150px;">
  <div>另存为</div>
  <div>保存副本</div>
  <div class="menu-sep"></div>
  <div>打印</div>
</div>

JS 方式创建

$("#sb2").splitbutton({
  text: "发布",
  iconCls: "icon-publish",
  menu: "#mm",
  duration: 100,
  onClick: function () {
    // 点击按钮主体触发
    $.messager.alert("发布", "即将发布当前内容");
  }
});

常用属性

属性说明
menu关联的菜单选择器
duration菜单展开动画时长(ms)
plain是否扁平样式
disabled是否禁用
✅ 用法
「保存 / 另存为」「发布 / 预览」「导出 / 打印」这类主次动作很适合用 splitbutton 承载。

jEasyUI 创建边框布局

layout 是后台界面的「骨架」组件,把页面分成 north / south / east / west / center 五个区域,配合 split 可拖动调整宽度。

经典后台布局

<div class="easyui-layout" style="width:800px;height:500px;">
  <div data-options="region:'north',title:'顶部导航',height:60,split:false">
    网站 Logo 与全局菜单
  </div>
  <div data-options="region:'west',title:'侧边栏',width:180,split:true">
    功能列表
  </div>
  <div data-options="region:'center',title:'内容区'">
    主体内容
  </div>
  <div data-options="region:'south',title:'底部',height:40,split:false">
    版权信息
  </div>
</div>

区域属性速查

属性作用
region区域位置:north/south/east/west/center
split是否可拖动调整大小
collapsible是否可折叠
minWidth / maxWidth宽度限制
border是否显示边框

JS 动态操作

$("#layout").layout("collapse", "west");   // 折叠左侧
$("#layout").layout("expand", "west");      // 展开左侧
$("#layout").layout("resize");              // 整体重算尺寸
⚠️ 注意
center 区域必填,其他区域可以省略;布局容器必须显式设置宽高,否则无法撑开。

jEasyUI 创建复杂布局

真实系统中往往需要嵌套布局:外层 layout 划分左右,右侧内容区再嵌套一个 layout 或 tabs,形成「侧边栏 + 多标签页」的常见后台形态。

嵌套布局示例

<div class="easyui-layout" style="width:900px;height:550px;">
  <div data-options="region:'west',title:'系统菜单',width:200,split:true">
    <div class="easyui-accordion" style="height:100%;">
      <div title="用户管理">用户增删改查</div>
      <div title="商品管理">商品列表与分类</div>
      <div title="系统设置">参数与权限</div>
    </div>
  </div>

  <div data-options="region:'center',title:'工作区'">
    <div class="easyui-tabs" style="height:100%;">
      <div title="概览">今日数据看板</div>
      <div title="明细">数据明细表格</div>
    </div>
  </div>
</div>

动态添加标签页

function openTab(title, url) {
  var tabs = $("#centerTabs");
  if (tabs.tabs("exists", title)) {
    tabs.tabs("select", title);          // 已存在则激活
    return;
  }
  tabs.tabs("add", {
    title: title,
    href: url,
    closable: true,
    tools: [{
      iconCls: "icon-refresh",
      handler: function () {
        var tab = tabs.tabs("getSelected");
        tabs.tabs("update", { tab: tab, options: { href: url } });
      }
    }]
  });
}
✅ 最佳实践
「左侧 accordion 菜单 + 右侧 tabs 工作区」是后台管理系统的标准范式,每个菜单项对应一个可关闭的标签页,体验接近桌面应用。

jEasyUI 创建折叠面板

accordion(折叠面板)一次只展开一个面板,适合做侧边栏菜单或分区设置界面,节省垂直空间。

基本用法

<div class="easyui-accordion" style="width:300px;height:250px;">
  <div title="面板一" iconCls="icon-home">
    第一个面板的内容
  </div>
  <div title="面板二">
    第二个面板的内容
  </div>
  <div title="面板三">
    第三个面板的内容
  </div>
</div>

常用配置

$("#acc").accordion({
  multiple: false,          // 是否允许多个面板同时展开
  animate: true,            // 展开动画
  selected: 0,              // 默认选中第几个
  onSelect: function (title, index) {
    console.log("选中:", title, index);
  }
});

JS 控制

$("#acc").accordion("select", 2);    // 展开第3个面板
$("#acc").accordion("getSelected");   // 获取当前面板
$("#acc").accordion("add", {
  title: "新增面板",
  content: "<p>动态添加的内容</p>"
});
💡 提示
作侧边栏时建议每个面板里放一个 <ul> 菜单列表,点击项再通过 JS 打开对应的 tabs 标签页。

jEasyUI 创建标签页

tabs 组件把内容分成多个标签页,适合「一个页面多个视图」的场景,如详情/编辑/日志分开展示。

基本定义

<div id="tt" class="easyui-tabs" style="width:700px;height:300px;">
  <div title="基本信息">
    名称、分类、描述等基础字段
  </div>
  <div title="价格库存">
    价格、成本、库存数量
  </div>
  <div title="操作日志">
    修改记录与审核记录
  </div>
</div>

常用属性与事件

$("#tt").tabs({
  tabPosition: "top",        // top / bottom / left / right
  border: true,
  onSelect: function (title, index) {
    // 切换到某个标签时触发
  },
  onClose: function (title, index) {
    // 关闭标签时触发
  }
});

JS 操作

$("#tt").tabs("add", { title: "新页", content: "内容" });   // 添加
$("#tt").tabs("close", "基本信息");                          // 关闭
$("#tt").tabs("select", 1);                                  // 激活
$("#tt").tabs("getTab", "日志").panel("refresh");            // 刷新指定页
✅ 技巧
用 href 属性可以让标签页内容从独立 URL 异步加载,适合大页面拆分为多个模板。

jEasyUI 动态添加标签页

把 tabs 与菜单联动:点击左侧菜单项,动态在右侧工作区新增或激活对应标签页——这是后台系统最经典的交互。

菜单点击触发

<div class="easyui-accordion" style="width:200px;height:400px;">
  <div title="商品管理">
    <ul class="menu-list">
      <li>商品列表</li>
      <li>商品分类</li>
    </ul>
  </div>
</div>

动态开标签页

function openTab(title, url) {
  var tabs = $("#tabs");
  // 已打开则直接激活
  if (tabs.tabs("exists", title)) {
    tabs.tabs("select", title);
    return;
  }
  // 未打开则新增
  tabs.tabs("add", {
    title: title,
    href: url,
    closable: true,
    onLoad: function () {
      // 内容加载完成后执行初始化
    }
  });
}

限制最大标签数

var MAX = 8;
function openTabSafe(title, url) {
  var tabs = $("#tabs");
  var count = tabs.tabs("tabs").length;
  if (count >= MAX) {
    $.messager.alert("提示", "最多同时打开 " + MAX + " 个标签页");
    return;
  }
  openTab(title, url);
}
⚠️ 注意
用 href 加载的标签页,页面内引用的 JS 需要在子页面自身执行;父页面可通过 onLoad 事件做联动初始化。

jEasyUI 添加自动播放标签页

实现自动轮播的标签页:每隔几秒自动切换到下一个标签,适合展示轮播图、大屏数据面板等场景。

基础结构

<div id="tt" class="easyui-tabs" style="width:700px;height:300px;">
  <div title="页面一">第一屏内容</div>
  <div title="页面二">第二屏内容</div>
  <div title="页面三">第三屏内容</div>
</div>
<a id="play" class="easyui-linkbutton">开始轮播</a>
<a id="stop" class="easyui-linkbutton">停止轮播</a>

轮播逻辑

var timer = null;
var INTERVAL = 3000;   // 3 秒切换一次

function startPlay() {
  if (timer) return;                 // 已在播放
  timer = setInterval(function () {
    var tabs = $("#tt");
    var count = tabs.tabs("tabs").length;
    var cur = tabs.tabs("getTabIndex", tabs.tabs("getSelected"));
    var next = (cur + 1) % count;    // 循环到第一个
    tabs.tabs("select", next);
  }, INTERVAL);
}

function stopPlay() {
  clearInterval(timer);
  timer = null;
}

$("#play").click(startPlay);
$("#stop").click(stopPlay);

// 用户手动切换时暂停 5 秒再继续(体验优化)
$("#tt").tabs({
  onSelect: function () {
    stopPlay();
    setTimeout(startPlay, 5000);
  }
});
✅ 技巧
轮播用 setInterval + 取模实现循环切换;记得在页面卸载时 clearInterval,避免定时器泄漏。

jEasyUI 创建 XP 风格左侧面板

模仿经典 Windows XP 的左侧导航样式:把 layout 的 west 区域做成深色标题栏 + 分组链接列表的「资源管理器」风格。

样式定义

.xp-panel { background: #f0f0f0; height: 100%; }
.xp-title {
  background: linear-gradient(#4a90d9, #2f6fb2);
  color: #fff; padding: 8px 12px; font-weight: bold;
}
.xp-group { margin: 10px 8px; }
.xp-group h4 {
  font-size: 12px; color: #666; border-bottom: 1px solid #ddd;
  padding-bottom: 4px;
}
.xp-group ul { list-style: none; padding: 4px 0 0 8px; }
.xp-group li { padding: 4px 6px; cursor: pointer; }
.xp-group li:hover { background: #dbe9fb; border-radius: 3px; }

HTML 结构

<div class="easyui-layout" style="width:800px;height:500px;">
  <div data-options="region:'west',split:true" style="width:200px;">
    <div class="xp-panel">
      <div class="xp-title">我的电脑</div>
      <div class="xp-group">
        <h4>文件夹</h4>
        <ul>
          <li>📁 文档</li>
          <li>🖼️ 图片</li>
        </ul>
      </div>
      <div class="xp-group">
        <h4>系统</h4>
        <ul>
          <li>⚙️ 设置</li>
        </ul>
      </div>
    </div>
  </div>
  <div data-options="region:'center',title:'内容区'">
    <div id="folder-content">请选择左侧文件夹</div>
  </div>
</div>

交互函数

function openFolder(name) {
  $("#folder-content").html("<h2>" + name + "</h2><p>这里是 " + name + " 的内容……</p>");
}
💡 提示
这种「分组链接列表」只需纯 CSS + 少量 JS 就能实现,是低成本高辨识度的导航风格。

jEasyUI 转换 HTML 表格为数据网格

已有静态 HTML 表格时,无需重写数据,用 datagrid 的 data 属性或 loadData 方法即可把表格内容直接转成数据网格,立刻获得排序、分页、选择等能力。

静态表格转网格

<table id="dg"></table>

<script>
$(function () {
  $("#dg").datagrid({
    title: "学生列表",
    rownumbers: true,
    columns: [[
      { field: "no", title: "学号", width: 100 },
      { field: "name", title: "姓名", width: 150 },
      { field: "score", title: "成绩", width: 100 }
    ]],
    data: [
      { no: "001", name: "张三", score: 92 },
      { no: "002", name: "李四", score: 85 },
      { no: "003", name: "王五", score: 78 }
    ]
  });
});
</script>

从已有 table 结构转换

<table id="oldTable">
  <thead>
    <tr><th>ID</th><th>城市</th><th>人口(万)</th></tr>
  </thead>
  <tbody>
    <tr><td>1</td><td>北京</td><td>2189</td></tr>
    <tr><td>2</td><td>上海</td><td>2487</td></tr>
  </tbody>
</table>
// 读取 tbody 数据并转换为 datagrid 数据
function tableToRows($table) {
  var rows = [];
  $table.find("tbody tr").each(function () {
    var cells = $(this).find("td").map(function () {
      return $(this).text();
    }).get();
    rows.push({ id: cells[0], city: cells[1], pop: cells[2] });
  });
  return rows;
}

$("#oldTable").hide();   // 隐藏原表格
$("#dg").datagrid({
  columns: [[
    { field: "id", title: "ID", width: 60 },
    { field: "city", title: "城市", width: 150 },
    { field: "pop", title: "人口(万)", width: 120 }
  ]],
  data: tableToRows($("#oldTable"))
});
✅ 适用场景
旧系统改造、静态数据展示、临时报表——都能用这种方式快速「升级」成可排序分页的网格。

jEasyUI 取得选中行数据

操作数据网格前,必须先拿到选中的行数据。本教程梳理 datagrid 的选中 API:单选、多选、按索引取、以及选中事件。

单选模式

// singleSelect=true 时
var row = $("#dg").datagrid("getSelected");
if (row) {
  console.log(row.id, row.name);
} else {
  $.messager.alert("提示", "请先选择一行");
}

多选模式

// singleSelect=false(默认多选,配合 checkbox 列)
var rows = $("#dg").datagrid("getSelections");
console.log("选中了 " + rows.length + " 行");
var ids = $.map(rows, function (r) { return r.id; });
$.post("batch_delete.php", { ids: ids.join(",") });

按索引取 / 选中事件

// 取第 3 行数据
var row3 = $("#dg").datagrid("getRows")[2];

// 选中事件
$("#dg").datagrid({
  onSelect: function (index, row) {
    console.log("选中第", index, "行:", row.name);
    $("#detail").text(row.name + " / " + row.price);
  },
  onUnselect: function (index, row) {
    console.log("取消选中", row.name);
  },
  onSelectAll: function (rows) {
    console.log("全选", rows.length, "行");
  }
});

批量操作前的安全校验

function getChecked() {
  var rows = $("#dg").datagrid("getSelections");
  if (rows.length === 0) {
    $.messager.alert("提示", "请至少选择一行");
    return null;
  }
  return rows;
}

function batchDelete() {
  var rows = getChecked();
  if (!rows) return;
  $.messager.confirm("确认", "确定删除选中的 " + rows.length + " 条吗?", function (ok) {
    if (ok) {
      // 执行删除并刷新
      $("#dg").datagrid("reload");
    }
  });
}
⚠️ 注意
分页模式下 getSelections 只包含当前页选中的行;跨页选择需要自行用 Map 维护已选 id。

jEasyUI 添加查询功能

给 datagrid 加一个搜索工具栏:输入关键字 + 选择条件,把参数传给后端重新加载数据。

查询工具栏

<table id="dg" class="easyui-datagrid" style="width:700px;height:300px;"
       url="search.php" toolbar="#tb" pagination="true">
  <thead>
    <tr>
      <th data-options="field:'name',width:200">商品名</th>
      <th data-options="field:'price',width:100">价格</th>
    </tr>
  </thead>
</table>

<div id="tb" style="padding:4px;">
  <input id="kw" class="easyui-textbox" prompt="商品名" style="width:150px;">
  <input id="minP" class="easyui-numberbox" prompt="最低价" style="width:80px;">
  <a href="#" class="easyui-linkbutton" iconCls="icon-search">查询</a>
  <a href="#" class="easyui-linkbutton" iconCls="icon-clear">重置</a>
</div>

查询与重置

function doSearch() {
  $("#dg").datagrid("load", {
    keyword: $("#kw").textbox("getValue"),
    minPrice: $("#minP").numberbox("getValue")
  });
}

function clearSearch() {
  $("#kw").textbox("setValue", "");
  $("#minP").numberbox("setValue", "");
  $("#dg").datagrid("load", {});
}

// 回车即搜索
$("#kw").textbox("textbox").keydown(function (e) {
  if (e.keyCode === 13) doSearch();
});

后端接收(伪代码)

$keyword = $_GET['keyword'] ?? '';
$minPrice = $_GET['minPrice'] ?? 0;

$where = '1=1';
if ($keyword !== '')  $where .= " AND name LIKE '%$keyword%'";
if ($minPrice > 0)   $where .= " AND price >= $minPrice";

// 拼接分页 LIMIT 并返回 {total, rows}
✅ 最佳实践
查询参数统一通过 load 方法传递,datagrid 会自动合并到分页请求中;重置时传空对象即可回到全量数据。

jEasyUI 添加工具栏

工具栏(toolbar)是 datagrid 顶部的一组操作按钮,承载新增、编辑、删除、导出等常用动作。本教程演示「内置工具栏」与「自定义工具栏」两种方式。

方式一:内置工具栏

$("#dg").datagrid({
  toolbar: [{
    text: "新增",
    iconCls: "icon-add",
    handler: function () { addItem(); }
  }, {
    text: "编辑",
    iconCls: "icon-edit",
    handler: function () { editItem(); }
  }, "-",           // 分隔线
  {
    text: "删除",
    iconCls: "icon-remove",
    handler: function () { delItem(); }
  }]
});

方式二:HTML 工具栏(推荐)

<div id="tb">
  <a class="easyui-linkbutton" data-options="iconCls:'icon-add',plain:true"
    >新增</a>
  <a class="easyui-linkbutton" data-options="iconCls:'icon-edit',plain:true"
    >编辑</a>
  <a class="easyui-linkbutton" data-options="iconCls:'icon-remove',plain:true"
    >删除</a>
  <span style="margin-left:12px;">
    <input id="kw" class="easyui-searchbox" data-options="prompt:'搜索',searcher:doSearch">
  </span>
</div>

<table id="dg" class="easyui-datagrid" toolbar="#tb" url="list.php">
  <thead><tr><th data-options="field:'name'">名称</th></tr></thead>
</table>

工具栏按钮状态联动

$("#dg").datagrid({
  onSelect: function () {
    $("#editBtn").linkbutton("enable");
    $("#delBtn").linkbutton("enable");
  },
  onUnselect: function () {
    $("#editBtn").linkbutton("disable");
    $("#delBtn").linkbutton("disable");
  }
});
💡 提示
HTML 工具栏更直观、易维护,也方便塞入搜索框等复合控件;建议项目统一采用这种方式。

jEasyUI 创建复杂工具栏

复杂工具栏在基础按钮之上加入按钮组、下拉菜单、搜索框、状态文本等元素,形成完整操作区。本教程演示一个综合示例。

完整工具栏示例

<div id="tb" style="padding:4px;">
  <!-- 主操作组 -->
  <a class="easyui-linkbutton" data-options="iconCls:'icon-add',plain:true">新增</a>
  <a class="easyui-linkbutton" data-options="iconCls:'icon-edit',plain:true">编辑</a>
  <a class="easyui-linkbutton" data-options="iconCls:'icon-remove',plain:true">删除</a>

  <span class="tb-sep"></span>

  <!-- 下拉操作 -->
  <div id="moreMenu" style="width:130px;">
    <div>导出 Excel</div>
    <div>导出 CSV</div>
  </div>
  <a class="easyui-menubutton" data-options="menu:'#moreMenu',iconCls:'icon-export',plain:true">导出</a>

  <span class="tb-sep"></span>

  <!-- 搜索区 -->
  <input id="kw" class="easyui-textbox" data-options="prompt:'关键字'" style="width:140px;">
  <a class="easyui-linkbutton" data-options="iconCls:'icon-search'">查询</a>

  <span style="float:right;line-height:26px;">
    共 <span id="totalInfo">0</span> 条记录
  </span>
</div>

工具栏样式

.tb-sep {
  display: inline-block; width: 1px; height: 16px;
  background: #ddd; margin: 0 8px; vertical-align: middle;
}

联动更新统计文本

$("#dg").datagrid({
  onLoadSuccess: function (data) {
    $("#totalInfo").text(data.total);
  }
});
✅ 设计建议
工具栏元素分组用 tb-sep 分隔线视觉分组;高频操作放左边,低频/导出类用下拉,右侧放全局统计信息。

jEasyUI 设置冻结列

冻结列(frozenColumns)让某些列在水平滚动时保持固定,常用于「序号 / 操作列 / 主键」等需要始终可见的列。

基本用法

$("#dg").datagrid({
  title: "冻结列示例",
  url: "list.php",
  frozenColumns: [[
    { field: "id", title: "ID", width: 60 },
    { field: "name", title: "姓名", width: 120 }
  ]],
  columns: [[
    { field: "math", title: "数学", width: 100 },
    { field: "english", title: "英语", width: 100 },
    { field: "physics", title: "物理", width: 100 },
    { field: "chemistry", title: "化学", width: 100 },
    { field: "biology", title: "生物", width: 100 }
  ]]
});

在 HTML 中定义

<table id="dg" class="easyui-datagrid" title="冻结列(HTML)" style="width:700px;height:250px;">
  <thead>
    <tr>
      <th data-options="field:'id',width:60,frozen:true">ID</th>
      <th data-options="field:'name',width:120,frozen:true">姓名</th>
      <th data-options="field:'score',width:200">总分</th>
    </tr>
  </thead>
</table>
💡 提示
冻结列会渲染为独立的表格层,因此样式和排序需要在冻结列配置里同样声明;推荐用 JS 方式配置,便于管理。
⚠️ 注意
冻结列过多会占用大量横向空间,一般冻结 1-3 列即可。

jEasyUI 动态改变列

根据用户需求动态显示/隐藏列、增删列,让表格更灵活。常用于「列设置」功能——用户自选要显示的字段。

显示/隐藏列

// 隐藏某列
$("#dg").datagrid("hideColumn", "price");

// 显示某列
$("#dg").datagrid("showColumn", "price");

// 判断列是否可见
var col = $("#dg").datagrid("getColumnOption", "price");
console.log(col.hidden ? "已隐藏" : "可见");

列设置面板

<div id="colSetting" style="padding:8px;">
  <label><input type="checkbox" value="id" checked> ID</label><br>
  <label><input type="checkbox" value="name" checked> 名称</label><br>
  <label><input type="checkbox" value="price" checked> 价格</label><br>
  <label><input type="checkbox" value="stock"> 库存</label><br>
</div>
function applyColumns() {
  $("#colSetting input[type=checkbox]").each(function () {
    var field = $(this).val();
    if ($(this).is(":checked")) {
      $("#dg").datagrid("showColumn", field);
    } else {
      $("#dg").datagrid("hideColumn", field);
    }
  });
}

// 用 checkboxlist 打开设置
$("#colBtn").linkbutton({
  onClick: function () {
    $("#colSetting").dialog({
      title: "列设置",
      buttons: [{
        text: "应用",
        iconCls: "icon-ok",
        handler: function () {
          applyColumns();
          $("#colSetting").dialog("close");
        }
      }]
    });
  }
});

动态增加列

// 追加一列
$("#dg").datagrid("appendColumn", {
  field: "remark",
  title: "备注",
  width: 150
});
✅ 最佳实践
用户自定义列配置可以存到 localStorage,下次进入自动恢复,体验更完整。

jEasyUI 格式化列

formatter 是列的回调函数,用来把原始值渲染成友好内容:金额、状态标签、日期、图片、操作按钮等。

状态格式化

function fmtStatus(value) {
  if (value === 1) return "<span class='badge badge-on'>启用</span>";
  if (value === 0) return "<span class='badge badge-off'>停用</span>";
  return "未知";
}

$("#dg").datagrid({
  columns: [[
    { field: "status", title: "状态", width: 80, formatter: fmtStatus }
  ]]
});

金额与日期格式化

function fmtMoney(value, row, index) {
  return "<span style='color:#e4393c;font-weight:bold;'>¥" + Number(value).toFixed(2) + "</span>";
}

function fmtDate(value) {
  if (!value) return "-";
  var d = new Date(value.replace(/-/g, "/"));
  var pad = function (n) { return n < 10 ? "0" + n : n; };
  return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
}

$("#dg").datagrid({
  columns: [[
    { field: "price", title: "单价", width: 100, formatter: fmtMoney },
    { field: "created", title: "创建时间", width: 120, formatter: fmtDate }
  ]]
});

行内操作按钮

function fmtAction(value, row, index) {
  return "<a href='#'>查看</a> " +
         "<a href='#'>删除</a>";
}

function view(index) {
  var row = $("#dg").datagrid("getRows")[index];
  $.messager.alert("详情", row.name + " / " + row.price);
}

$("#dg").datagrid({
  columns: [[
    { field: "op", title: "操作", width: 120, formatter: fmtAction }
  ]]
});
⚠️ 注意
formatter 返回 HTML 时注意防注入:展示用户输入的内容前先转义 <、>。

jEasyUI 设置排序

datagrid 支持列头点击排序:前端排序(本地数据)或后端排序(远程数据)两种模式。

前端排序(本地数据)

$("#dg").datagrid({
  sortName: "score",        // 默认排序列
  sortOrder: "desc",        // 默认排序方向
  data: [
    { name: "张三", score: 92 },
    { name: "李四", score: 85 },
    { name: "王五", score: 78 }
  ]
});

后端排序(远程数据)

$("#dg").datagrid({
  url: "list.php",
  remoteSort: true,          // 默认即 true
  sortName: "price",
  sortOrder: "asc"
});

// 点击列头时,datagrid 自动把 sort/order/page 参数发给后端
// 后端接收:sort=price&order=asc&page=1&rows=20
$sort = $_GET['sort'] ?? 'id';
$order = $_GET['order'] ?? 'asc';
$page = (int)($_GET['page'] ?? 1);
$rows = (int)($_GET['rows'] ?? 20);

// 白名单校验排序字段,防注入
$allowed = ['id', 'name', 'price', 'stock'];
if (!in_array($sort, $allowed)) $sort = 'id';

$sql = "SELECT * FROM goods ORDER BY $sort $order LIMIT " .
       (($page - 1) * $rows) . ", $rows";

指定某列不可排序

// 操作列不需要排序
{ field: "op", title: "操作", width: 100, sortable: false }
💡 提示
列头排序箭头只在 sortable: true 的列上显示;本地排序模式无需后端参与,适合数据量小的场景。

jEasyUI 自定义排序

当默认排序(字典序/数值序)不满足需求时,用 sorter 自定义排序逻辑:日期字符串、中文拼音、特殊业务规则等。

日期字符串排序

function dateSorter(a, b) {
  var da = new Date(a.replace(/-/g, "/")).getTime();
  var db = new Date(b.replace(/-/g, "/")).getTime();
  return da < db ? -1 : (da > db ? 1 : 0);
}

$("#dg").datagrid({
  columns: [[
    { field: "date", title: "日期", width: 120, sortable: true, sorter: dateSorter }
  ]]
});

混合数据排序(数字+文字)

function smartSorter(a, b) {
  // 把 "10 元" 这类文本拆出数字比较
  var na = parseFloat(a) || 0;
  var nb = parseFloat(b) || 0;
  return na - nb;
}

$("#dg").datagrid({
  columns: [[
    { field: "priceText", title: "价格", width: 100, sortable: true, sorter: smartSorter }
  ]]
});

按业务权重排序

var LEVEL_WEIGHT = { "高": 3, "中": 2, "低": 1 };

function levelSorter(a, b) {
  return (LEVEL_WEIGHT[a] || 0) - (LEVEL_WEIGHT[b] || 0);
}

$("#dg").datagrid({
  columns: [[
    { field: "level", title: "优先级", width: 80, sortable: true, sorter: levelSorter }
  ]]
});
✅ 适用场景
sorter 只对本地数据生效;远程数据排序时应在服务端实现同样逻辑。

jEasyUI 创建列组合

列组合(colspan)把多列合并为一个表头,形成分组,例如「成绩」下细分「语文 / 数学 / 英语」。

JS 方式配置列组

$("#dg").datagrid({
  columns: [[
    { title: "基本信息", colspan: 2 },
    { title: "成绩", colspan: 3 }
  ], [
    { field: "name", title: "姓名", width: 120 },
    { field: "class", title: "班级", width: 80 },
    { field: "chinese", title: "语文", width: 80 },
    { field: "math", title: "数学", width: 80 },
    { field: "english", title: "英语", width: 80 }
  ]]
});

HTML 方式

<table id="dg" class="easyui-datagrid" style="width:600px;height:250px;">
  <thead>
    <tr>
      <th colspan="2">基本信息</th>
      <th colspan="3">成绩</th>
    </tr>
    <tr>
      <th data-options="field:'name',width:120">姓名</th>
      <th data-options="field:'class',width:80">班级</th>
      <th data-options="field:'chinese',width:80">语文</th>
      <th data-options="field:'math',width:80">数学</th>
      <th data-options="field:'english',width:80">英语</th>
    </tr>
  </thead>
</table>
💡 提示
列组合可以嵌套多层(每层一个数组)。注意每层列宽之和应一致,否则表头会错位。
⚠️ 注意
列组合与冻结列同时使用时,冻结区域也需要相同的分组结构,否则列对不齐。

jEasyUI 添加复选框

在数据网格最前面加一列复选框,实现多选 + 批量操作,是后台管理列表的标准配置。

启用复选框

$("#dg").datagrid({
  // checkbox 列由系统自动生成,无需声明 field
  columns: [[
    { field: "ck", checkbox: true },
    { field: "id", title: "ID", width: 60 },
    { field: "name", title: "名称", width: 200 }
  ]]
});

获取选中行

function batchOp() {
  var rows = $("#dg").datagrid("getChecked");   // 推荐:只返回勾选的行
  if (rows.length === 0) {
    $.messager.alert("提示", "请先勾选数据");
    return;
  }
  var ids = $.map(rows, function (r) { return r.id; }).join(",");
  $.messager.alert("已选", "ID: " + ids);
}

// 对比:
// getChecked()   —— 只包含 checkbox 勾选的行
// getSelections()—— 包含 click 选中的行 + checkbox 勾选的行

勾选事件

$("#dg").datagrid({
  onCheck: function (index, row) {
    console.log("勾选:", row.name);
    updateBtnState();
  },
  onUncheck: function (index, row) {
    console.log("取消勾选:", row.name);
    updateBtnState();
  },
  onCheckAll: function (rows) {
    updateBtnState();
  }
});

function updateBtnState() {
  var n = $("#dg").datagrid("getChecked").length;
  $("#batchBtn").linkbutton(n > 0 ? "enable" : "disable");
}
✅ 技巧
表头复选框只作用于当前页;需要跨页记忆勾选状态时,自行用 Map 记录 id 并在 onLoadSuccess 中回填。

jEasyUI 自定义分页

分页组件(pagination)默认显示页码、每页条数。通过配置可自定义:显示总数、跳页输入框、按钮布局等。

分页属性

$("#dg").datagrid({
  pagination: true,
  pageSize: 20,              // 每页条数
  pageList: [10, 20, 50, 100],
  pageNumber: 1,
  loadMsg: "加载中,请稍候..."
});

自定义分页布局

<div id="pp" class="easyui-pagination"
     style="width:700px;"
     data-options="total:114,pageSize:20,pageList:[10,20,50],
                   layout:['first','prev','manual','next','last','refresh','sep','info']">
</div>
$("#pp").pagination({
  layout: ["first", "prev", "manual", "next", "last", "refresh", "sep", "info"],
  pageSize: 20,
  total: 114,
  onSelectPage: function (pageNumber, pageSize) {
    $("#dg").datagrid("load", { page: pageNumber, rows: pageSize });
  }
});

分页事件

$("#dg").datagrid({
  onLoadSuccess: function (data) {
    // 数据加载完成后可更新自定义统计
    $("#total").text(data.total);
  }
});

// 分页器事件(独立 pagination 时)
$("#pp").pagination({
  onRefresh: function (pageNumber, pageSize) {
    $("#dg").datagrid("reload");
  },
  onChangePageSize: function (pageSize) {
    // 每页条数改变
  }
});
💡 提示
layout 属性支持: first / prev / manual / next / last / refresh / sep / info,可按需组合顺序。

jEasyUI 启用行内编辑

行内编辑(editRow)让用户在表格里直接修改数据,无需弹窗,体验更流畅。

开始编辑

function startEdit() {
  var row = $("#dg").datagrid("getSelected");
  if (!row) return;
  var index = $("#dg").datagrid("getRowIndex", row);
  $("#dg").datagrid("beginEdit", index);   // 进入编辑态
}

function endEdit() {
  var rows = $("#dg").datagrid("getRows");
  $.each(rows, function (i) {
    var editing = $("#dg").datagrid("getEditors", i);
    if (editing.length) {
      $("#dg").datagrid("endEdit", i);     // 提交编辑
    }
  });
}

列级编辑器配置

$("#dg").datagrid({
  columns: [[
    { field: "name", title: "名称", width: 150,
      editor: { type: "textbox", options: { required: true } } },
    { field: "price", title: "价格", width: 100,
      editor: { type: "numberbox", options: { min: 0, precision: 2 } } },
    { field: "cat", title: "分类", width: 100,
      editor: { type: "combobox", options: {
        data: [{id:1,text:"图书"},{id:2,text:"数码"}],
        valueField: "id", textField: "text"
      } } },
    { field: "stock", title: "库存", width: 80,
      editor: { type: "numberspinner", options: { min: 0 } } }
  ]],
  onClickRow: function (index) {
    // 点击行直接进入编辑
    var editing = $("#dg").datagrid("getEditors", index);
    if (!editing.length) $("#dg").datagrid("beginEdit", index);
  }
});

保存编辑结果

function saveEdit() {
  endEdit();
  var rows = $("#dg").datagrid("getChanges");   // 获取变更行
  if (rows.length === 0) {
    $.messager.alert("提示", "没有修改");
    return;
  }
  $.post("save_all.php", JSON.stringify(rows), function (res) {
    if (res.success) $("#dg").datagrid("reload");
  }, "json");
}
✅ 最佳实践
编辑结束后用 getChanges() 只提交变更过的行,减少请求数据量;配合 onEndEdit 事件做行级校验。

jEasyUI 扩展编辑器

内置编辑器不够用时,可以注册自定义编辑器。本教程演示如何把「日期选择、下拉树」等组件变成 datagrid 的编辑器。

注册日期编辑器

$.extend($.fn.datagrid.defaults.editors, {
  datebox: {
    init: function (container, options) {
      var input = $("<input type='text'>").appendTo(container);
      input.datebox(options);
      return input;
    },
    destroy: function (target) { $(target).datebox("destroy"); },
    getValue: function (target) { return $(target).datebox("getValue"); },
    setValue: function (target, value) { $(target).datebox("setValue", value); },
    resize: function (target, width) { $(target).datebox("resize", width); }
  }
});

使用自定义编辑器

$("#dg").datagrid({
  columns: [[
    { field: "name", title: "名称", width: 120 },
    { field: "birth", title: "出生日期", width: 140,
      editor: { type: "datebox", options: { required: true } } }
  ]]
});

注册下拉树编辑器

$.extend($.fn.datagrid.defaults.editors, {
  combotree: {
    init: function (container, options) {
      var input = $("<input type='text'>").appendTo(container);
      input.combotree($.extend({ url: "tree_data.php" }, options));
      return input;
    },
    destroy: function (target) { $(target).combotree("destroy"); },
    getValue: function (target) { return $(target).combotree("getValue"); },
    setValue: function (target, value) { $(target).combotree("setValue", value); },
    resize: function (target, width) { $(target).combotree("resize", width); }
  }
});
⚠️ 注意
编辑器对象必须实现 init / destroy / getValue / setValue / resize 五个方法,缺一不可,否则编辑时会报错。

jEasyUI 列运算

数据网格底部显示汇总运算(合计/平均/最大值等)是报表类页面常见需求,本教程演示「页脚行 + 列运算」。

数据带 footer 结构

{
  "total": 3,
  "rows": [
    { "name": "手机", "price": 1999, "qty": 2 },
    { "name": "耳机", "price": 399,  "qty": 3 },
    { "name": "键盘", "price": 249,  "qty": 1 }
  ],
  "footer": [
    { "name": "合计", "price": 2647, "qty": 6 }
  ]
}

前端自行运算

$("#dg").datagrid({
  url: "list.php",
  showFooter: true,          // 显示页脚
  onLoadSuccess: function (data) {
    // 计算合计
    var totalPrice = 0, totalQty = 0;
    $.each(data.rows, function (i, r) {
      totalPrice += r.price * r.qty;
      totalQty += r.qty;
    });
    // 更新页脚(需要在列配置中声明 footer 列)
    $("#dg").datagrid("reloadFooter", [{
      name: "合计",
      price: totalPrice,
      qty: totalQty
    }]);
  }
});

列配置支持 footer

$("#dg").datagrid({
  columns: [[
    { field: "name", title: "商品", width: 150 },
    { field: "price", title: "单价", width: 100 },
    { field: "qty", title: "数量", width: 80 },
    { field: "amount", title: "金额", width: 120,
      formatter: function (v, r) { return r.price * r.qty; } }
  ]]
});

// 用 formatter 计算金额列,footer 中对应的 amount 也会自动算

平均 / 最大最小值

function buildFooter(rows) {
  var prices = $.map(rows, function (r) { return r.price; });
  var avg = prices.reduce(function (a, b) { return a + b; }, 0) / prices.length;
  return [{
    name: "均价",
    price: Math.round(avg * 100) / 100
  }];
}
💡 提示
大数据量场景建议在服务端完成汇总随 footer 返回,前端只负责渲染,避免把全量数据拉下来算。

jEasyUI 合并单元格

合并单元格(mergeCells)把多个相邻单元格合并,用于呈现分组数据或报表结构。

基本合并

function mergeCells() {
  // 合并第 0 行的 0~1 列
  $("#dg").datagrid("mergeCells", {
    index: 0,          // 起始行
    field: "name",     // 起始列(field)
    colspan: 2,        // 横向合并数量
    rowspan: 1         // 纵向合并数量
  });
}

$(function () {
  $("#dg").datagrid({...});
  mergeCells();
});

按分组自动合并

// 数据按 dept 分组,自动把同组的第一列合并
function mergeByField(rows, field) {
  var i = 0;
  while (i < rows.length) {
    var j = i + 1;
    while (j < rows.length && rows[j][field] === rows[i][field]) j++;
    if (j - i > 1) {
      $("#dg").datagrid("mergeCells", {
        index: i, field: field, rowspan: j - i
      });
    }
    i = j;
  }
}

$("#dg").datagrid({
  onLoadSuccess: function (data) {
    mergeByField(data.rows, "dept");
  }
});

合并后取数

// 注意:mergeCells 只是视觉合并,getRows 仍返回每行独立数据
function getMergedValue(index) {
  var row = $("#dg").datagrid("getRows")[index];
  return row.dept;   // 合并列只在该组第一行有值
}
⚠️ 注意
合并后排序/分页刷新会破坏合并状态,需要在 onLoadSuccess 中重新合并;数据应预先按分组字段排序。

jEasyUI 创建自定义视图

默认视图是「横排表格」,通过自定义视图可以把数据渲染成卡片、列表、图表等形态。

切换到卡片视图

var view_cards = $.extend({}, $.fn.datagrid.defaults.view, {
  render: function (target, container, frozen) {
    var opts = $.data(target, "datagrid").options;
    var rows = $.data(target, "datagrid").data.rows;
    if (frozen) return;   // 冻结部分不渲染
    var html = [];
    $.each(rows, function (i, row) {
      html.push(
        "<div class='card'>" +
          "<div class='card-title'>" + row.name + "</div>" +
          "<div class='card-price'>¥" + row.price + "</div>" +
          "<div class='card-desc'>" + (row.desc || "") + "</div>" +
        "</div>"
      );
    });
    $(container).html(html.join(""));
  }
});

$("#dg").datagrid({
  view: view_cards,
  url: "list.php"
});

卡片样式

.card {
  display: inline-block; width: 180px; margin: 8px;
  padding: 12px; border: 1px solid #e5e5e5;
  border-radius: 8px; background: #fff; cursor: pointer;
}
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.12); }
.card-title { font-weight: bold; }
.card-price { color: #e4393c; margin: 6px 0; }
.card-desc { color: #888; font-size: 12px; }

切换视图按钮

function switchView(viewName) {
  if (viewName === "table") {
    $("#dg").datagrid("options").view = $.fn.datagrid.defaults.view;
  } else {
    $("#dg").datagrid("options").view = view_cards;
  }
  $("#dg").datagrid("reload");
}
✅ 场景
商品橱窗、图片素材库、成员列表等以展示为主的页面非常适合卡片视图;切换按钮给用户多一种查看方式。

jEasyUI 创建页脚摘要

页脚摘要(footer)在表格底部显示合计、平均、计数等信息。本教程演示「服务端返回 footer」与「前端计算 footer」两种方式。

方式一:服务端返回 footer

{
  "total": 5,
  "rows": [
    {"name":"手机","price":1999,"qty":2},
    {"name":"耳机","price":399,"qty":3}
  ],
  "footer": [
    {"name":"合计","price":2398,"qty":5}
  ]
}
$("#dg").datagrid({
  url: "list.php",
  showFooter: true,
  columns: [[
    { field: "name", title: "商品", width: 150 },
    { field: "price", title: "价格", width: 100 },
    { field: "qty", title: "数量", width: 80 }
  ]]
});
// 后端返回 footer 字段后,datagrid 自动渲染页脚行

方式二:前端计算

function buildFooter(rows) {
  var sumPrice = 0, sumQty = 0;
  $.each(rows, function (i, r) {
    sumPrice += r.price;
    sumQty += r.qty;
  });
  return [{
    name: "合计",
    price: sumPrice,
    qty: sumQty
  }];
}

$("#dg").datagrid({
  url: "list.php",
  showFooter: true,
  onLoadSuccess: function (data) {
    if (data.rows) {
      $("#dg").datagrid("reloadFooter", buildFooter(data.rows));
    }
  }
});

页脚列格式化

columns: [[
  { field: "price", title: "价格", width: 100,
    formatter: function (v, r, i) {
      // 判断是否为 footer 行(footer 行没有 id 等业务字段)
      return r.id === undefined ? "¥" + v + " (合计)" : "¥" + v;
    } }
]]
✅ 建议
数据量大时优先用服务端计算;数据量小(< 100 行)前端算更省事,响应也快。

jEasyUI 条件设置行背景颜色

根据行数据条件给整行设置背景色,突出异常数据,例如库存不足标红、VIP 客户高亮。

用 rowStyler 设置

$("#dg").datagrid({
  url: "list.php",
  rowStyler: function (index, row) {
    if (row.stock < 10) {
      return "background:#ffe4e1;color:#c00;";   // 库存不足标红
    }
    if (row.vip) {
      return "background:#fff7e6;color:#b8860b;"; // VIP 高亮
    }
    return "";
  }
});

按数值区间高亮

$("#dg").datagrid({
  rowStyler: function (index, row) {
    if (row.score >= 90) return "background:#e8f5e9;";
    if (row.score >= 60) return "background:#fffde7;";
    return "background:#ffebee;color:#d32f2f;";
  }
});

动态刷新样式

// 修改数据后重新渲染行样式
function refreshRow(row) {
  var index = $("#dg").datagrid("getRowIndex", row);
  // 先删再插入,触发重新渲染
  $("#dg").datagrid("deleteRow", index);
  $("#dg").datagrid("insertRow", { index: index, row: row });
}

自定义徽标列

function fmtLevel(value) {
  var cls = value === "high" ? "bg-red" :
            value === "mid"  ? "bg-yellow" : "bg-green";
  var text = value === "high" ? "高" :
             value === "mid"  ? "中" : "低";
  return "<span class='" + cls + "' style='color:#fff;padding:2px 8px;border-radius:3px;'>" + text + "</span>";
}

columns: [[
  { field: "level", title: "优先级", width: 80, formatter: fmtLevel }
]]
⚠️ 注意
rowStyler 返回的是行内联样式;与列 formatter 配合使用效果最好——一个管行底色,一个管单元格内容。

jEasyUI 创建属性网格

propertygrid 是专门展示「键值对属性」的网格:左侧属性名,右侧属性值,支持行内编辑。适合做配置项、对象属性查看器。

基本用法

$("#pg").propertygrid({
  url: "props.php",
  showGroup: true,          // 按 group 分组显示
  scrollbarSize: 0
});

数据格式

[
  { "name": "host", "value": "127.0.0.1", "group": "连接", "editor": "text" },
  { "name": "port", "value": "3306", "group": "连接", "editor": "numberbox" },
  { "name": "username", "value": "root", "group": "认证", "editor": "text" },
  { "name": "password", "value": "******", "group": "认证",
    "editor": { "type": "passwordbox" } }
]

直接加载数据

$("#pg").propertygrid({
  data: [
    { name: "宽度", value: "800px", group: "尺寸", editor: "text" },
    { name: "高度", value: "600px", group: "尺寸", editor: "text" },
    { name: "背景色", value: "#ffffff", group: "样式",
      editor: { type: "textbox" } }
  ],
  columns: [[
    { field: "name", title: "属性", width: 120 },
    { field: "value", title: "值", width: 200,
      editor: "text" }
  ]]
});

获取修改后的值

function saveProps() {
  // 结束所有编辑
  var rows = $("#pg").propertygrid("getRows");
  $.each(rows, function (i) {
    $("#pg").propertygrid("endEdit", i);
  });
  // 收集所有属性
  var result = {};
  $.each(rows, function (i, r) {
    result[r.name] = r.value;
  });
  console.log(result);
  // $.post("save.php", result);
}
✅ 场景
系统配置页、连接参数编辑、对象属性查看——propertygrid 比手工拼表单高效得多。

jEasyUI 扩展行显示细节

expandRow 让选中行展开一个详情区域,显示该行的附加信息,类似「主从表」效果。

HTML 定义

<table id="dg" class="easyui-datagrid" title="订单列表" style="width:700px;height:300px;"
       data-options="url:'orders.php',onClickRow:onClickRow">
  <thead>
    <tr>
      <th data-options="field:'id',width:80">订单号</th>
      <th data-options="field:'name',width:150">客户</th>
      <th data-options="field:'amount',width:100">金额</th>
    </tr>
  </thead>
</table>

点击展开详情

function onClickRow(index) {
  if ($("#dg").datagrid("getRows").length <= 0) return;
  if ($("#dg").datagrid("getRowIndex", $("#dg").datagrid("getSelected")) === -1) return;

  var row = $("#dg").datagrid("getSelected");
  if ($("#dg").datagrid("isExpanded", index)) {
    $("#dg").datagrid("collapseRow", index);   // 已展开则收起
  } else {
    $("#dg").datagrid("expandRow", index);     // 展开显示 detail
  }
}

detail 渲染模板

$("#dg").datagrid({
  detailFormatter: function (index, row) {
    // 返回 HTML,会被渲染进展开区域
    return "<table style='width:90%;margin:8px auto;'>" +
      "<tr><td>收货地址</td><td>" + (row.address || "-") + "</td></tr>" +
      "<tr><td>下单时间</td><td>" + (row.time || "-") + "</td></tr>" +
      "</table>";
  }
});

异步加载详情

detailFormatter: function (index, row) {
  return "<div id='detail-" + row.id + "'>加载中...</div>";
},
onExpandRow: function (index, row) {
  $.getJSON("order_detail.php?id=" + row.id, function (data) {
    $("#detail-" + row.id).html(
      "<p>商品:" + data.goods + "<br>状态:" + data.status + "</p>"
    );
  });
}
✅ 适用
订单明细、用户详情、任务日志——「主行 + 可展开详情」比弹窗更直观,不打断浏览。

jEasyUI 创建子网格

在 datagrid 中嵌套第二个数据网格(子网格),实现「主从结构」:左侧主列表,选中后在子网格显示对应明细。

方案一:布局 + 主从网格

<div class="easyui-layout" style="width:800px;height:500px;">
  <div data-options="region:'west',title:'订单',width:380,split:true">
    <table id="dg1" class="easyui-datagrid"
           url="orders.php" singleSelect="true"
          >
      <thead><tr>
        <th data-options="field:'id',width:60">单号</th>
        <th data-options="field:'name',width:180">客户</th>
      </tr></thead>
    </table>
  </div>
  <div data-options="region:'center',title:'明细'">
    <table id="dg2" class="easyui-datagrid">
      <thead><tr>
        <th data-options="field:'goods',width:200">商品</th>
        <th data-options="field:'price',width:100">单价</th>
      </tr></thead>
    </table>
  </div>
</div>

联动加载明细

function loadDetail(index, row) {
  // 重新加载子网格数据
  $("#dg2").datagrid({
    url: "order_items.php?order_id=" + row.id,
    onLoadSuccess: function (data) {
      // 可顺带更新合计
    }
  });
  // 或使用 load 方法
  // $("#dg2").datagrid("load", { order_id: row.id });
}

方案二:expandRow 内嵌子网格

detailFormatter: function (index, row) {
  return "<table id='sub-" + row.id + "'></table>";
},
onExpandRow: function (index, row) {
  $("#sub-" + row.id).datagrid({
    url: "order_items.php?order_id=" + row.id,
    singleSelect: true,
    height: 120,
    columns: [[
      { field: "goods", title: "商品", width: 200 },
      { field: "price", title: "单价", width: 100 }
    ]]
  });
}
⚠️ 注意
方案二中子网格表格 id 必须唯一(用行 id 拼接);数据量大时建议只在展开时初始化,收起时销毁以省内存。

jEasyUI 显示海量数据

一次渲染几千行会导致页面卡顿。EasyUI 提供了渲染优化手段:分页、虚拟滚动、按需加载。

方案一:服务端分页(推荐)

$("#dg").datagrid({
  url: "big_list.php",
  pagination: true,
  pageSize: 50,
  pageList: [50, 100, 200],
  rownumbers: true,
  // 大数据量时关闭不必要的交互
  singleSelect: true
});
// 后端按 page/rows 分页返回 {total, rows}

方案二:前端分页

$("#dg").datagrid({
  data: bigArray,          // 全部数据(如 5000 行)
  pagination: true,
  pageSize: 100,
  // 关键:前端分页
  loadFilter: function (data) {
    return data;           // 数据已在本地,直接返回
  }
});

方案三:懒加载 + 滚动加载

var page = 1, loading = false;

$("#dg").datagrid({
  url: "lazy_list.php?page=" + page,
  onBeforeLoad: function () { return false; },   // 阻止默认加载
  onLoadSuccess: function (data) {
    if (data.rows.length) {
      $("#dg").datagrid("appendRow", data.rows);
      page++;
    } else {
      // 没有更多数据
      $("#loadMore").linkbutton("disable");
    }
  }
});

// 点击「加载更多」
function loadMore() {
  if (loading) return;
  loading = true;
  $.getJSON("lazy_list.php?page=" + page, function (data) {
    $("#dg").datagrid("appendRow", data.rows);
    page++;
    loading = false;
  });
}

性能小贴士

手段说明
分页最常用,单页 ≤ 100 行
减少列数列越多渲染越慢,隐藏不常用列
精简 formatter避免每行做昂贵 DOM 操作
关闭 fitColumns大数据量下自适应列宽开销大
✅ 建议
万行级数据务必走服务端分页;前端一次渲染超过 2000 行即使分页也会卡顿,优先减少单页条数。

jEasyUI 添加分页组件

独立的pagination组件可以脱离 datagrid 使用,自定义页脚的分页控件,如底部统计条、跳页、每页条数。

基础分页组件

<div id="pp" class="easyui-pagination" style="width:600px;height:32px;"
     data-options="total:114,pageSize:20,pageList:[10,20,50]"
    >
</div>

与列表联动

function onSelectPage(pageNumber, pageSize) {
  $("#dg").datagrid("load", {
    page: pageNumber,
    rows: pageSize
  });
}

$("#dg").datagrid({
  url: "list.php",
  onLoadSuccess: function (data) {
    // 同步分页组件的总条数与当前页
    $("#pp").pagination("refresh", {
      total: data.total,
      pageNumber: $("#dg").datagrid("options").pageNumber,
      pageSize: $("#dg").datagrid("options").pageSize
    });
  }
});

自定义分页布局

$("#pp").pagination({
  total: 0,
  layout: ["first", "prev", "manual", "next", "last", "refresh", "info"],
  showPageList: true,
  showRefresh: false,
  beforePageText: "第",
  afterPageText: "页,共 {pages} 页",
  displayMsg: "显示 {from} 到 {to} 条,共 {total} 条"
});
💡 提示
displayMsg 支持占位符: {from} {to} {total} {pages};用 layout 可自由组合按钮顺序。

jEasyUI 创建简单窗口

window 是最基础的浮动容器:支持拖动、缩放、最大化最小化、模态遮罩。对话框(dialog)就是它的子类。

HTML 定义窗口

<div id="win" class="easyui-window" title="简单窗口"
     data-options="modal:true,closed:true,width:400,height:250,
                   minimizable:true,maximizable:true,collapsible:true,
                   draggable:true,resizable:true,iconCls:'icon-tip'">
  <p>这是一个可拖动、可缩放、可最大化的窗口</p>
  <p>内容可以放任意 HTML 或 iframe</p>
</div>

JS 方式创建

$("#win").window({
  title: "动态窗口",
  width: 400,
  height: 250,
  modal: true,           // 模态遮罩
  closed: true,          // 初始关闭
  onClose: function () {
    // 关闭时清理
  },
  onOpen: function () {
    // 打开时初始化
  }
});

打开 / 关闭 / 定位

$("#win").window("open");            // 打开
$("#win").window("close");           // 关闭
$("#win").window("center");          // 居中
$("#win").window("maximize");        // 最大化
$("#win").window("restore");         // 还原

// 移动到指定位置
$("#win").window("move", { left: 200, top: 100 });

嵌入 iframe

$("#win").window({
  content: "<iframe src='detail.html' style='width:100%;height:100%;border:0;'></iframe>"
});
⚠️ 注意
窗口必须设置宽高;内容为动态加载时,先 open 再填充内容,避免尺寸计算错误。

jEasyUI 自定义窗口工具栏

给 window 添加自定义工具栏/按钮区:右上角自定义按钮、底部操作按钮,让窗口更像一个完整的功能模块。

底部按钮(buttons)

<div id="win" class="easyui-window" title="编辑用户" style="width:420px;height:300px;"
     data-options="modal:true,closed:true,buttons:'#win-buttons'">
  <form id="userForm">
    姓名:<input name="name" class="easyui-textbox" style="width:180px;">
  </form>
</div>

<div id="win-buttons">
  <a class="easyui-linkbutton" data-options="iconCls:'icon-ok'">保存</a>
  <a class="easyui-linkbutton" data-options="iconCls:'icon-cancel'">取消</a>
</div>

右上角自定义按钮(tools)

$("#win").window({
  title: "任务详情",
  tools: [
    {
      iconCls: "icon-refresh",
      handler: function () {
        $("#win").window("refresh");   // 刷新内容
      }
    }, {
      iconCls: "icon-print",
      handler: function () { printWin(); }
    }
  ]
});

JS 方式配置 buttons

$("#win").window({
  buttons: [{
    text: "保存",
    iconCls: "icon-save",
    handler: function () {
      if ($("#userForm").form("validate")) {
        $.post("save_user.php", $("#userForm").serialize());
        $("#win").window("close");
      }
    }
  }, {
    text: "关闭",
    handler: function () { $("#win").window("close"); }
  }]
});
✅ 最佳实践
表单类窗口统一用 buttons 放「保存/取消」;操作类窗口用 tools 放快捷图标,保持标题栏简洁。

jEasyUI 窗口与布局

window 与 layout 结合,可以做出「弹窗内嵌复杂布局」的效果:弹窗里再分栏、放 tabs,适用于复杂的编辑页。

窗口内嵌布局

$("#win").window({
  title: "复杂编辑",
  width: 700,
  height: 450,
  modal: true,
  content: "<div id='innerLayout' class='easyui-layout' style='width:100%;height:100%;'>" +
    "<div data-options="region:'west',title:'导航',width:150,split:true">" +
    "  左侧菜单</div>" +
    "<div data-options="region:'center',title:'内容'">" +
    "  编辑区域</div>" +
    "</div>",
  onOpen: function () {
    // 窗口打开后,布局需要重新渲染一次
    $("#innerLayout").layout("resize");
  }
});

窗口内嵌 tabs

$("#win").window({
  title: "商品编辑",
  width: 650,
  height: 400,
  content:
    "<div id='innerTabs' class='easyui-tabs' style='width:100%;height:100%;'>" +
    "  <div title='基本信息'>名称、分类、描述</div>" +
    "  <div title='价格库存'>价格与库存</div>" +
    "  <div title='图片'>图片上传</div>" +
    "</div>"
});

父子窗口传值

// 打开子窗口并传值
function openChild(row) {
  $("#childWin").window({
    onOpen: function () {
      $("#childForm").form("load", row);   // 回填数据
    }
  }).window("open");
}

// 子窗口保存后刷新父列表
function childSaved() {
  $("#childWin").window("close");
  $("#dg").datagrid("reload");
}
⚠️ 注意
动态注入的布局/tabs HTML,必须在窗口 open 之后再触发组件初始化(或直接给元素加 class="easyui-xxx" 让 EasyUI 自动渲染)。

jEasyUI 创建对话框

dialog 是 window 的扩展,自带标题栏、关闭按钮、底部按钮区,是「确认、表单、提示」类弹窗的首选。

基本对话框

<div id="dlg" class="easyui-dialog" title="新增" style="width:380px;height:220px;"
     data-options="modal:true,closed:true,
                   buttons:[{text:'保存',iconCls:'icon-ok',handler:save},
                            {text:'取消',handler:function(){$(this).dialog('close')}}]">
  <form id="fm">
    <input name="name" class="easyui-textbox" label="名称:" style="width:200px;">
  </form>
</div>

快捷方法

// 打开/关闭
$("#dlg").dialog("open");
$("#dlg").dialog("close");

// 修改标题
$("#dlg").dialog("setTitle", "编辑");

// 调整大小
$("#dlg").dialog("resize", { width: 500, height: 300 });

// 判断是否打开
if ($("#dlg").dialog("options").closed === false) { ... }

加载远程内容

$("#dlg").dialog({
  href: "edit_form.php?id=5",     // 从远程加载内容
  onLoad: function () {
    // 内容加载完成后初始化组件
  }
});

确认对话框($.messager)

$.messager.confirm("确认删除", "确定删除这条记录吗?", function (ok) {
  if (ok) {
    // 执行删除
    $("#dg").datagrid("reload");
  }
});

$.messager.alert("提示", "操作成功", "info");
$.messager.show({
  title: "通知",
  msg: "保存成功",
  timeout: 3000,
  showType: "slide"
});
✅ 建议
轻量提示用 $.messager;需要表单交互用 dialog;需要自由布局用 window——三者按复杂度递增选择。

jEasyUI 自定义对话框

自定义对话框在 dialog 基础上扩展:自定义底部按钮、右上角工具、皮肤样式,让弹窗与业务完全匹配。

自定义底部按钮

$("#dlg").dialog({
  title: "审核",
  width: 400,
  height: 240,
  modal: true,
  closed: true,
  buttons: [{
    text: "通过",
    iconCls: "icon-ok",
    handler: function () { audit(1); }
  }, {
    text: "驳回",
    iconCls: "icon-no",
    handler: function () { audit(0); }
  }, {
    text: "取消",
    handler: function () { $("#dlg").dialog("close"); }
  }]
});

右上角自定义工具

$("#dlg").dialog({
  tools: [{
    iconCls: "icon-help",
    handler: function () {
      $.messager.alert("帮助", "填写必填项后点击保存");
    }
  }]
});

自定义样式

.custom-dialog .panel-title { color: #fff; }
.custom-dialog .panel-header {
  background: linear-gradient(90deg, #4a90d9, #2f6fb2);
  color: #fff;
}
.custom-dialog .panel-body { background: #f7f9fc; }
$("#dlg").dialog({
  cls: "custom-dialog"        // 附加自定义 class
});

对话框里嵌套表单校验

function saveFromDialog() {
  if (!$("#dlgForm").form("validate")) {
    $.messager.alert("提示", "请完善必填项");
    return;
  }
  $.post("save.php", $("#dlgForm").serialize(), function (res) {
    if (res.success) {
      $("#dlg").dialog("close");
      $("#dg").datagrid("reload");
    }
  }, "json");
}
💡 提示
dialog 的所有选项都支持 cls 属性叠加自定义样式;业务相关弹窗建议封装成函数,统一管理打开/保存/关闭逻辑。

jEasyUI 使用标记创建树形菜单

tree 组件用来展示层级数据。用 HTML 标记(ul/li)创建树是最直观的方式,适合静态菜单。

HTML 树形菜单

<ul id="tt" class="easyui-tree">
  <li>
    <span>前端开发</span>
    <ul>
      <li><span>HTML</span></li>
      <li><span>CSS</span></li>
      <li>
        <span>JavaScript</span>
        <ul>
          <li><span>ES6</span></li>
          <li><span>DOM</span></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <span>后端开发</span>
    <ul>
      <li><span>Python</span></li>
      <li><span>Go</span></li>
    </ul>
  </li>
</ul>

带图标与状态

<ul id="tt" class="easyui-tree" data-options="animate:true">
  <li data-options="iconCls:'icon-folder'">
    <span>文档</span>
    <ul>
      <li data-options="iconCls:'icon-file'"><span>需求.md</span></li>
      <li data-options="iconCls:'icon-file'"><span>设计.md</span></li>
    </ul>
  </li>
</ul>

常用事件

$("#tt").tree({
  onClick: function (node) {
    console.log("点击:", node.text, "id:", node.id);
    // 加载对应内容
    $("#content").load(node.url || "about:blank");
  },
  onExpand: function (node) {
    console.log("展开:", node.text);
  }
});
✅ 适用
静态分类菜单、目录树、固定导航——标记方式零 JS 依赖,语义清晰;动态数据则用 JSON 方式(见下章)。

jEasyUI 创建异步树形菜单

大型菜单数据量庞大,使用异步加载:节点展开时才向后端请求子节点,减少首屏加载量。

异步加载配置

$("#tt").tree({
  url: "tree_data.php",
  method: "get",
  // 展开节点时自动请求:url?id=父节点id
  onBeforeExpand: function (node) {
    $("#tt").tree("options").url = "tree_data.php?id=" + node.id;
  }
});

后端返回格式

// 顶级节点
[
  { "id": 1, "text": "用户管理", "state": "closed", "children": [] }
]

// 子节点(展开时请求 id=1 返回)
[
  { "id": 11, "text": "用户列表" },
  { "id": 12, "text": "角色管理" }
]

状态说明

state含义
open节点展开,children 可直接返回
closed节点收起,展开时才异步加载子节点

完整示例

$("#tt").tree({
  url: "menu.php",
  loadFilter: function (data) {
    // 数据清洗:确保每个节点有 state
    $.each(data, function (i, n) {
      if (n.children && n.children.length === 0) {
        n.state = "closed";   // 有子级但未加载
      }
    });
    return data;
  },
  onSelect: function (node) {
    if (node.attributes && node.attributes.url) {
      window.open(node.attributes.url);
    }
  }
});
⚠️ 注意
异步加载时,子节点请求必须带上父节点 id,否则后端无法定位;首次加载只请求顶级(不带 id)。

jEasyUI 树形菜单添加节点

通过 JS API 向树形菜单动态添加/更新/删除节点,实现菜单编辑、分类管理等功能。

添加节点

// 在选中节点下追加子节点
function addChild() {
  var node = $("#tt").tree("getSelected");
  if (!node) {
    $.messager.alert("提示", "请先选中父节点");
    return;
  }
  $("#tt").tree("append", {
    parent: node,
    data: [{
      id: Date.now(),          // 生成唯一 id
      text: "新节点 " + Math.floor(Math.random() * 100),
      state: "open"
    }]
  });
}

// 在根节点追加
function addRoot() {
  $("#tt").tree("append", {
    data: [{ id: Date.now(), text: "新根节点" }]
  });
}

更新节点文本

function renameNode() {
  var node = $("#tt").tree("getSelected");
  if (!node) return;
  $.messager.prompt("重命名", "输入新名称:", function (val) {
    if (val) {
      $("#tt").tree("update", {
        target: node.target,
        text: val
      });
    }
  });
}

删除节点

function removeNode() {
  var node = $("#tt").tree("getSelected");
  if (!node) return;
  $.messager.confirm("确认", "删除节点「" + node.text + "」及其子节点?", function (ok) {
    if (ok) {
      $("#tt").tree("remove", node.target);
    }
  });
}

插入到指定位置

// 在选中节点之前/之后插入
$("#tt").tree("insert", {
  before: node.target,
  data: { id: 99, text: "插入节点" }
});
✅ 最佳实践
本地增删改只是视图操作;真实系统中应同步提交后端,刷新时从服务端重新加载,保证数据一致。

jEasyUI 创建带复选框的树形菜单

给树节点加复选框,实现权限勾选、多选分类——后台系统权限管理的标配。

启用复选框

$("#tt").tree({
  url: "tree_data.php",
  checkbox: true,          // 关键:显示复选框
  cascadeCheck: true       // 父子联动(默认 true)
});

获取勾选节点

function getCheckedIds() {
  var nodes = $("#tt").tree("getChecked");   // 勾选的节点
  var ids = $.map(nodes, function (n) { return n.id; });
  return ids;
}

// 只取「叶节点」(父节点勾选通常不单独处理)
function getLeafChecked() {
  var nodes = $("#tt").tree("getChecked");
  var leaves = $.map(nodes, function (n) {
    var children = $("#tt").tree("getChildren", n.target);
    return children.length === 0 ? n.id : null;
  });
  return leaves;
}

设置默认勾选

// 数据加载后,按权限回填勾选
$("#tt").tree({
  onLoadSuccess: function () {
    var permIds = [1, 5, 9];    // 用户已有权限
    $.each(permIds, function (i, id) {
      var node = $("#tt").tree("find", id);
      if (node) $("#tt").tree("check", node.target);
    });
  }
});

父子联动与事件

$("#tt").tree({
  checkbox: true,
  cascadeCheck: true,   // 父勾选→子全勾;取消父→子全取消
  onCheck: function (node, checked) {
    // 勾选/取消时触发,可实时统计
    var ids = getCheckedIds();
    $("#permInfo").text("已选权限数:" + ids.length);
  }
});
⚠️ 注意
权限场景建议只保存叶节点 id或单独处理父节点,避免父子联动造成数据冗余;编辑时用 getChecked + 手动递归处理半选状态。

jEasyUI 树形菜单拖放控制

允许拖拽节点改变树结构,实现分类排序、层级调整。EasyUI 树内置拖放支持,只需开启 dnd 并校验规则。

开启拖放

$("#tt").tree({
  url: "tree_data.php",
  dnd: true               // 开启节点拖放
});

拖放规则校验

$("#tt").tree({
  dnd: true,
  // 判断能否把节点拖到目标
  onDragEnter: function (target, source) {
    // 不允许拖到自己的子节点下
    if ($(target).parents("#tt").find(source).length) {
      return false;
    }
    return true;
  },
  onDrop: function (target, source, point) {
    // point: 'append' | 'top' | 'bottom'
    var targetNode = $(target).data("tree-node");
    var sourceNode = $(source).data("tree-node");
    console.log(sourceNode.text, "->", targetNode.text, point);

    // 把新结构提交给后端
    saveTree();
  }
});

保存新结构

function saveTree() {
  var roots = $("#tt").tree("getRoots");
  var data = [];
  $.each(roots, function (i, root) {
    data.push(buildNode(root));
  });
  $.post("save_tree.php", { tree: JSON.stringify(data) }, function (res) {
    $.messager.show({ title: "提示", msg: "保存成功" });
  }, "json");
}

function buildNode(node) {
  var item = { id: node.id, children: [] };
  var children = $("#tt").tree("getChildren", node.target);
  $.each(children, function (i, c) {
    item.children.push(buildNode(c));
  });
  return item;
}
⚠️ 注意
防止循环拖放(把节点拖进自己的子树)会导致数据错乱;onDragEnter 返回 false 可以禁止非法目标。

jEasyUI 树形菜单加载父/子节点

默认展开深度有限,通过预加载父节点 + 惰性加载子节点的组合,兼顾首屏速度与完整导航。

预加载两层

// 后端一次性返回两级结构
// [
//   { id:1, text:'A', state:'closed',
//     children:[ { id:11, text:'A1', state:'closed' } ] }
// ]
$("#tt").tree({
  url: "tree_two_levels.php"
});

节点状态决定加载方式

// state='closed' 的节点展开时才异步请求
$("#tt").tree({
  url: "tree_async.php",
  onBeforeExpand: function (node) {
    // 有 children 说明已加载,不再请求
    if (node.children && node.children.length > 0) return;
    $("#tt").tree("options").url = "tree_async.php?id=" + node.id;
  }
});

手动加载子节点

function loadChildren(nodeId) {
  $.getJSON("tree_async.php?id=" + nodeId, function (data) {
    var node = $("#tt").tree("find", nodeId);
    if (node) {
      $("#tt").tree("append", { parent: node, data: data });
    }
  });
}

全部展开 / 收起

function expandAll() {
  var roots = $("#tt").tree("getRoots");
  $.each(roots, function (i, root) {
    $("#tt").tree("expandAll", root.target);
  });
}

function collapseAll() {
  var roots = $("#tt").tree("getRoots");
  $.each(roots, function (i, root) {
    $("#tt").tree("collapseAll", root.target);
  });
}
💡 提示
异步树中「展开全部」会逐个请求子节点,层级深时可能请求较多;建议限制展开层级或使用一次性加载。

jEasyUI 创建基础树形网格

treegrid 是「树 + 表格」的结合体:行有层级关系,同时具备多列、分页、行内编辑等表格能力。

基本用法

$("#tg").treegrid({
  url: "treegrid_data.php",
  idField: "id",              // 行 id 字段
  treeField: "name",          // 显示树结构的列
  columns: [[
    { field: "name", title: "部门", width: 220 },
    { field: "manager", title: "负责人", width: 100 },
    { field: "member", title: "人数", width: 80 }
  ]]
});

数据格式

[
  {
    "id": 1, "name": "总公司",
    "manager": "张总", "member": 100,
    "children": [
      { "id": 11, "name": "技术部", "manager": "李工", "member": 40,
        "children": [
          { "id": 111, "name": "前端组", "manager": "王工", "member": 15 }
        ]
      },
      { "id": 12, "name": "市场部", "manager": "赵总", "member": 60 }
    ]
  }
]

常用操作

// 展开/收起
$("#tg").treegrid("expand", 1);
$("#tg").treegrid("collapse", 1);

// 选中行
var row = $("#tg").treegrid("getSelected");

// 获取子节点
var children = $("#tg").treegrid("getChildren", 1);

// 重新加载
$("#tg").treegrid("reload");
✅ 适用
组织架构、商品分类(多级)、权限树+表格属性等「既有层级又有多列」的场景。

jEasyUI 创建复杂树形网格

在基础 treegrid 上加入复选框、排序、格式化、行操作按钮,构建完整的复杂树形网格。

复选框 + 操作列

$("#tg").treegrid({
  url: "data.php",
  idField: "id",
  treeField: "name",
  checkbox: true,               // 复选框
  columns: [[
    { field: "name", title: "分类", width: 220 },
    { field: "sort", title: "排序", width: 60, sortable: true },
    { field: "status", title: "状态", width: 80,
      formatter: function (v) {
        return v === 1 ? "<span style='color:green'>启用</span>"
                       : "<span style='color:gray'>停用</span>";
      } },
    { field: "op", title: "操作", width: 120,
      formatter: function (v, r) {
        return "<a href='#' + r.id + "')">加子级</a> " +
               "<a href='#' + r.id + "')">编辑</a>";
      } }
  ]],
  onLoadSuccess: function () {
    // 加载后展开前两级
    var rows = $("#tg").treegrid("getRoots");
    $.each(rows, function (i, r) {
      $("#tg").treegrid("expand", r.id);
    });
  }
});

获取勾选的节点

function getChecked() {
  var rows = $("#tg").treegrid("getChecked");
  return $.map(rows, function (r) { return r.id; });
}

行操作函数

function addChild(pid) {
  $("#dlg").dialog("open").dialog("setTitle", "新增子分类");
  $("#fm input[name=pid]").val(pid);
}

function editRow(id) {
  var row = $("#tg").treegrid("find", id);
  $("#fm").form("load", row);
  $("#dlg").dialog("open").dialog("setTitle", "编辑分类");
}

function save() {
  if (!$("#fm").form("validate")) return;
  $.post("save.php", $("#fm").serialize(), function (res) {
    if (res.success) {
      $("#dlg").dialog("close");
      $("#tg").treegrid("reload");
    }
  }, "json");
}
⚠️ 注意
treegrid 的 idField 值必须全局唯一(父子层级间也不能重复),否则展开/选中会错乱。

jEasyUI 树形网格动态加载

treegrid 也支持异步加载:展开父节点时才请求子级数据,适合层级深、数据量大的分类树。

异步加载配置

$("#tg").treegrid({
  url: "treegrid_data.php",
  idField: "id",
  treeField: "name",
  // 展开前动态设置 url,带上父节点 id
  onBeforeExpand: function (row) {
    $("#tg").treegrid("options").url =
      "treegrid_data.php?parent_id=" + row.id;
  },
  columns: [[
    { field: "name", title: "分类", width: 240 },
    { field: "count", title: "子类数", width: 80 }
  ]]
});

后端响应

// 带 children 表示有子级(展开图标显示),前端展开时才请求
[
  { "id": 1, "name": "电脑", "count": 2, "children": [
    { "id": 11, "name": "笔记本", "count": 5, "children": [] }
  ] }
]

惰性加载标记

// 后端返回的节点若无 children 字段,前端会视为叶节点
// 若「可能有子级但未加载」,应返回 children:[] 或 state 标记
// 前端 loadFilter 统一处理:
$("#tg").treegrid({
  loadFilter: function (data) {
    $.each(data, function (i, d) {
      if (d.has_child && (!d.children || !d.children.length)) {
        d.state = "closed";      // 标记为可展开,异步加载
      }
    });
    return data;
  }
});

手动刷新子级

function refreshNode(id) {
  $("#tg").treegrid("remove", id);          // 删除旧节点
  $("#tg").treegrid("reload");              // 整体重载
  // 或精确:append 新数据
  $.getJSON("treegrid_data.php?parent_id=" + id, function (data) {
    $("#tg").treegrid("append", { parent: id, data: data });
  });
}
✅ 建议
异步 treegrid 中,展开后再次展开同一节点不会再请求;如需强制刷新,先 remove 再重新加载该分支。

jEasyUI 树形网格添加分页

treegrid 支持分页,但层级结构会打散到多页。常见做法:分页只作用于根节点,子级在展开时异步加载。

启用分页

$("#tg").treegrid({
  url: "treegrid_page.php",
  idField: "id",
  treeField: "name",
  pagination: true,           // 启用分页
  pageSize: 10,
  pageList: [10, 20, 50],
  columns: [[
    { field: "name", title: "部门", width: 220 },
    { field: "count", title: "人数", width: 80 }
  ]]
});

服务端分页逻辑

// 根节点分页
$page = (int)($_GET['page'] ?? 1);
$rows = (int)($_GET['rows'] ?? 10);

$total = $db->query("SELECT COUNT(*) FROM dept WHERE parent_id=0")->value;
$list  = $db->query(
  "SELECT * FROM dept WHERE parent_id=0 LIMIT " .
  (($page - 1) * $rows) . ", $rows"
);

echo json_encode([
  "total" => $total,
  "rows"  => $list          // 每行含 children 供展开
]);

分页后子级异步加载

$("#tg").treegrid({
  pagination: true,
  onBeforeExpand: function (row) {
    $("#tg").treegrid("options").url =
      "treegrid_children.php?parent_id=" + row.id;
  },
  onLoadSuccess: function (data) {
    // 更新分页信息
    var opts = $("#tg").treegrid("options");
    opts.total = data.total;
    $("#tg").treegrid("resize");
  }
});

前端分页(小数据)

// 数据量小(几千行)时用前端分页
$("#tg").treegrid({
  data: allData,       // 完整层级数据
  pagination: true,
  fitColumns: true
});
⚠️ 注意
treegrid 分页的 total 指的是根节点总数;子节点不计入分页计数。展开后子级变化不会影响分页状态。

jEasyUI 树形网格惰性加载节点

惰性加载(Lazy Load):初始只加载根节点,展开时才请求子节点。相比一次全量加载,首屏更快、请求更少。

惰性加载示例

$("#tg").treegrid({
  url: "lazy_root.php",        // 初始只请求根
  idField: "id",
  treeField: "name",
  loadFilter: function (data) {
    // 关键:把「有子级的节点」标记为未展开
    $.each(data, function (i, d) {
      if (d.has_children) d.state = "closed";
    });
    return data;
  },
  onBeforeExpand: function (row) {
    // 展开时请求该节点子级
    $("#tg").treegrid("options").url =
      "lazy_children.php?parent_id=" + row.id;
  },
  columns: [[
    { field: "name", title: "节点", width: 240 },
    { field: "leaf", title: "类型", width: 100,
      formatter: function (v) {
        return v === 1 ? "叶节点" : "分支";
      } }
  ]]
});

后端两套接口

// lazy_root.php 返回根节点
[
  { "id": 1, "name": "华东区", "has_children": true },
  { "id": 2, "name": "华北区", "has_children": true }
]

// lazy_children.php?parent_id=1 返回子节点
[
  { "id": 11, "name": "上海分公司", "has_children": false },
  { "id": 12, "name": "杭州分公司", "has_children": true }
]

展开状态记忆

// 记录已展开的节点,刷新后恢复
var expanded = {};
$("#tg").treegrid({
  onExpand: function (row) { expanded[row.id] = true; },
  onCollapse: function (row) { delete expanded[row.id]; },
  onLoadSuccess: function () {
    $.each(expanded, function (id) {
      var row = $("#tg").treegrid("find", id);
      if (row) $("#tg").treegrid("expand", id);
    });
  }
});
✅ 最佳实践
惰性加载 + 展开状态记忆,是组织架构树、商品分类树在大数据量下的标准方案,用户体验接近本地应用。

jEasyUI 创建异步提交表单

默认表单提交会整页刷新,使用 EasyUI form 组件的 ajax 提交可以无刷新保存,配合成功回调刷新列表。

表单 HTML

<form id="fm" method="post">
  <input name="name" class="easyui-textbox" label="名称:" style="width:200px;" required="true">
  <br>
  <input name="price" class="easyui-numberbox" label="价格:" style="width:200px;" min="0">
</form>

异步提交

function submitForm() {
  if (!$("#fm").form("validate")) {
    $.messager.alert("提示", "请完善表单");
    return;
  }
  // 方式一:ajax 提交到 url
  $("#fm").form("submit", {
    url: "save.php",
    onSubmit: function () {
      return $("#fm").form("validate");   // 提交前校验
    },
    success: function (res) {
      var data = $.parseJSON(res);
      if (data.success) {
        $.messager.show({ title: "成功", msg: "保存成功" });
        $("#dlg").dialog("close");
        $("#dg").datagrid("reload");
      } else {
        $.messager.alert("错误", data.msg || "保存失败");
      }
    }
  });
}

方式二:$.post 手动提交(更灵活)

function submitManual() {
  if (!$("#fm").form("validate")) return;
  $.post("save.php", $("#fm").serialize(), function (res) {
    if (res.success) {
      $("#dlg").dialog("close");
      $("#dg").datagrid("reload");
    } else {
      $.messager.alert("错误", res.msg);
    }
  }, "json");
}

上传文件(带文件域的表单)

// 表单中包含 <input type="file"> 时,必须用 form 的 ajax 提交
$("#fm").form("submit", {
  url: "upload.php",
  success: function (res) {
    var data = $.parseJSON(res);
    if (data.success) $("#imgPreview").attr("src", data.url);
  }
});
✅ 建议
简单字段用 form('submit');需要额外加工数据(如转 JSON、追加参数)用 $.post + serialize(),控制力更强。

jEasyUI 表单验证

EasyUI 表单内置校验规则(required、email、url、length 等),还支持自定义规则与实时提示。

内置校验

<form id="fm">
  <input class="easyui-validatebox" data-options="required:true,validType:'length[2,20]'"
         style="width:200px;">  用户名(2-20位)
  <br>
  <input class="easyui-validatebox" data-options="required:true,validType:'email'"
         style="width:200px;">  邮箱
  <br>
  <input class="easyui-validatebox" data-options="required:true,validType:'url'"
         style="width:200px;">  网址
</form>

校验规则速查

validType说明
email邮箱格式
url网址格式
length[2,20]长度 2-20
min[5]最小长度 5
max[10]最大长度 10

自定义校验规则

// 注册自定义规则:手机号
$.extend($.fn.validatebox.defaults.rules, {
  phone: {
    validator: function (value, param) {
      return /^1[3-9]\d{9}$/.test(value);
    },
    message: "请输入正确的手机号"
  },
  // 确认密码
  equalTo: {
    validator: function (value, param) {
      return $(param[0]).val() === value;
    },
    message: "两次输入的密码不一致"
  }
});

// 使用
// <input class="easyui-validatebox" data-options="required:true,validType:'phone'">
// <input class="easyui-validatebox" data-options="required:true,validType:'equalTo['#pwd']'">

表单级校验与提交

function checkAndSubmit() {
  if (!$("#fm").form("validate")) {
    // 定位第一个错误项
    $.messager.alert("提示", "请检查表单填写");
    return;
  }
  // 校验通过,提交
  $("#fm").form("submit", { url: "save.php" });
}

// 实时校验
$("#fm input").each(function () {
  $(this).validatebox({
    onValidate: function (valid) {
      $(this).parent().find(".tip").text(valid ? "" : "有误");
    }
  });
});
⚠️ 注意
前端校验只是体验优化,后端必须重复校验;自定义规则中正则里的反斜杠在 JS 字符串中需要转义。

jEasyUI 创建树形下拉框

combotree 是「下拉框 + 树」的组合:点击展开树形选项,选择后回填所选节点文本。常用于分类选择、上级部门选择。

基本用法

<input id="cc" class="easyui-combotree" style="width:220px;"
       data-options="url:'tree_data.php',required:true">

JS 方式创建

$("#cc").combotree({
  url: "tree_data.php",
  valueField: "id",        // 值字段
  textField: "text",       // 文本字段
  method: "get",
  panelHeight: "auto",
  onSelect: function (node) {
    console.log("选中节点:", node);
  }
});

取值与赋值

// 取值(返回 id)
var selectedId = $("#cc").combotree("getValue");

// 取文本
var text = $("#cc").combotree("getText");

// 赋值(按 id 回显)
$("#cc").combotree("setValue", 5);

// 清空
$("#cc").combotree("clear");

只允许选择叶节点

$("#cc").combotree({
  url: "tree_data.php",
  onBeforeSelect: function (node) {
    // 有子节点则禁止选择(只能选叶子)
    if ($(node.target).find("ul").length > 0) {
      $.messager.alert("提示", "请选择具体分类");
      return false;
    }
    return true;
  }
});

多选模式

$("#cc").combotree({
  url: "tree_data.php",
  multiple: true,          // 多选
  cascadeCheck: true
});

// 多选取值(返回 id 数组)
var ids = $("#cc").combotree("getValues");
✅ 场景
商品分类、上级部门、权限范围——combotree 一框解决「层级 + 选择」,比两级联动下拉更简洁。

jEasyUI 格式化下拉框

combobox 的 formatter 可以自定义选项的显示:加图标、加副标题、多字段组合展示。

基础格式化

$("#cc").combobox({
  url: "users.php",
  valueField: "id",
  textField: "name",
  formatter: function (row) {
    // 返回 HTML,作为选项显示内容
    return "<span class='user-opt'>" +
           "<span class='avatar'>" + (row.avatar || "👤") + "</span> " +
           row.name +
           " <span style='color:#999;'>(" + row.dept + ")</span>" +
           "</span>";
  }
});

带图标选项

$("#cc").combobox({
  url: "icons.php",
  valueField: "name",
  textField: "name",
  formatter: function (row) {
    return "<span style='display:inline-block;width:24px;'>" + row.icon + "</span> " + row.name;
  }
});

显示样式 CSS

.user-opt { display: flex; align-items: center; gap: 6px; }
.user-opt .avatar { font-size: 18px; }

分组显示(optgroup)

$("#cc").combobox({
  valueField: "id",
  textField: "text",
  data: [
    { id: 1, text: "手机", group: "数码" },
    { id: 2, text: "电脑", group: "数码" },
    { id: 3, text: "衬衫", group: "服饰" }
  ],
  groupField: "group"       // 按 group 字段分组显示
});

选中后回填自定义文本

$("#cc").combobox({
  onSelect: function (row) {
    // 在别处显示完整描述
    $("#desc").text(row.name + " - " + row.dept);
  }
});
✅ 技巧
formatter 返回 HTML 时配合 panelHeight:'auto' 让面板自适应高度;复杂选项慎用,影响下拉性能。

jEasyUI 过滤下拉数据网格

让下拉框与数据网格联动:选中下拉值后,数据网格按条件过滤重新加载——列表页最常见的交互。

下拉 + 网格布局

<div style="padding:8px;background:#f5f5f5;">
  分类:
  <input id="catFilter" class="easyui-combobox" style="width:180px;"
         data-options="url:'cats.php',valueField:'id',textField:'name',
                       onSelect:filterGrid">
  <a class="easyui-linkbutton" data-options="iconCls:'icon-clear'"
    >清除筛选</a>
</div>

<table id="dg" class="easyui-datagrid" style="width:100%;height:350px;"
       url="goods.php">
  <thead><tr>
    <th data-options="field:'name',width:200">商品</th>
    <th data-options="field:'price',width:100">价格</th>
  </tr></thead>
</table>

过滤逻辑

function filterGrid(record) {
  // 把分类 id 作为参数传给网格接口
  $("#dg").datagrid("load", {
    cat_id: record.id
  });
}

function clearFilter() {
  $("#catFilter").combobox("clear");     // 清空下拉
  $("#dg").datagrid("load", {});         // 加载全部
}

多条件组合过滤

function doFilter() {
  $("#dg").datagrid("load", {
    cat_id: $("#catFilter").combobox("getValue"),
    keyword: $("#kw").textbox("getValue"),
    in_stock: $("#stockOnly").switchbutton("options").checked ? 1 : 0
  });
}

// 任一控件变化都触发
$("#catFilter").combobox({ onChange: doFilter });
$("#kw").textbox({ onChange: doFilter });

记住筛选状态

// 刷新页面后恢复筛选条件
$(function () {
  var saved = $.cookie ? $.cookie("filter") : null;
  if (saved) {
    var f = JSON.parse(saved);
    $("#catFilter").combobox("setValue", f.cat_id);
    $("#dg").datagrid("load", f);
  }
});
⚠️ 注意
过滤参数会与分页参数 page/rows 一起提交;后端记得把 total 按过滤后的结果统计,否则页码会错。

jQuery EasyUI 插件

EasyUI 提供了 searchbox、numberbox、switchbutton、progressbar、slider 等丰富的表单与交互插件,覆盖各种输入场景。

常用插件速览

插件用途
searchbox搜索框(带放大镜图标)
numberbox数字输入框(范围/精度)
numberspinner数字微调器(上下箭头)
switchbutton开关按钮(开/关)
progressbar进度条
slider滑块(区间选择)
datetimebox日期时间选择器

searchbox 搜索框

$("#sb").searchbox({
  prompt: "搜索商品...",
  searcher: function (value, name) {
    // 回车或点击搜索按钮触发
    $("#dg").datagrid("load", { q: value });
  }
});

switchbutton 开关

<input id="sw" class="easyui-switchbutton" data-options="onText:'启用',offText:'停用'">

<script>
$("#sw").switchbutton({
  onChange: function (checked) {
    // checked: true/false
    $.post("toggle.php", { enabled: checked ? 1 : 0 });
  }
});
</script>

progressbar 进度条

$("#pb").progressbar({ value: 0 });
var p = 0;
var timer = setInterval(function () {
  p += 10;
  $("#pb").progressbar("setValue", p);
  if (p >= 100) clearInterval(timer);
}, 300);

slider 滑块

$("#sl").slider({
  min: 0, max: 100, value: 30,
  showTip: true,
  onChange: function (v) {
    $("#priceRange").text("¥" + v);
  }
});
💡 提示
所有插件都遵循相同的 API 风格:$('#id').插件名('方法', 参数),学会一个就能举一反三。

jQuery EasyUI 扩展

EasyUI 支持通过 $.extend 扩展默认行为:自定义验证规则、新组件、主题变量。本教程演示最常用的几种扩展方式。

1. 扩展验证规则

$.extend($.fn.validatebox.defaults.rules, {
  idcard: {
    validator: function (value) {
      return /^\d{15}$|^\d{17}[0-9Xx]$/.test(value);
    },
    message: "身份证号格式不正确"
  }
});

2. 扩展 datagrid 列格式化公共函数

$.extend($.fn.datagrid.defaults, {
  formatters: {
    yesno: function (v) {
      return v ? "<span class='badge-ok'>是</span>"
               : "<span class='badge-no'>否</span>";
    },
    money: function (v) {
      return v == null ? "-" : "¥" + Number(v).toFixed(2);
    }
  }
});

// 使用
// { field: "paid", title: "已付款", formatter: $.fn.datagrid.defaults.formatters.yesno }

3. 自定义主题变量

/* 在引入 easyui.css 后覆盖主题色 */
.panel-header, .tabs-header {
  background: #2f6fb2;
}
a.l-btn:hover {
  background: #e8f0fb;
}

4. 注册自定义组件(以「星级评分」为例)

$.fn.stars = function (options) {
  var defaults = { max: 5, value: 0, readonly: false };
  var opts = $.extend({}, defaults, options);
  return this.each(function () {
    var $el = $(this), html = [];
    for (var i = 1; i <= opts.max; i++) {
      html.push("<span class='star" + (i <= opts.value ? " on" : "") + "' data-v='" + i + "'>★</span>");
    }
    $el.html(html.join(""));
    if (!opts.readonly) {
      $el.on("click", ".star", function () {
        var v = $(this).data("v");
        $el.find(".star").removeClass("on")
            .filter(function (i) { return i < v; }).addClass("on");
      });
    }
  });
};

// 使用:$("#score").stars({ value: 3 });
✅ 建议
扩展遵循「小步复用」:把项目中重复的规则/格式化/样式抽成公共扩展文件,团队统一引用,维护成本大降。
第 1 / 68 节