📊 Excel 导出
ThinkAdmin 后台的 Excel 导出由前端静态模块 plugs/admin/excel.js 实现。它会在浏览器端分页请求 JSON 数据,合并结果后调用 layui.excel.exportExcel() 生成并下载 .xlsx 文件,服务端不需要生成 Excel 文件。
think\admin\extend\ExcelExtend 是已废弃的 CSV 导出辅助类,不是当前后台页面使用的 XLSX 导出路径。
基本流程
- 页面按钮通过
data-form-export或data-excel指定导出地址。 excel.bind()绑定点击事件。- 点击导出时读取按钮所在表单的序列化数据。
- 前端自动追加
output=json¬_cache_limit=1&limit=100&page=N分页请求数据。 - 后端返回
code=1,并在data.list与data.page中提供当前页数据和分页信息。 - 前端把每页
data.list合并为一个数组,交给excel.bind()的回调转换成 Excel 数据。 - 回调返回二维数组或多工作表对象后,前端下载
.xlsx文件。
按钮绑定
最常用写法是在搜索表单中放置 data-form-export 按钮:
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" method="get">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">关键词</label>
<label class="layui-input-inline">
<input name="keyword" value="{$get.keyword|default=''}" placeholder="请输入关键词" class="layui-input">
</label>
</div>
<div class="layui-form-item layui-inline">
<button type="submit" class="layui-btn layui-btn-primary">
<i class="layui-icon"></i> 搜 索
</button>
<button type="button" data-form-export="{:url('index')}" class="layui-btn layui-btn-primary">
<i class="layui-icon layui-icon-export"></i> 导 出
</button>
</div>
</form>data-form-export 的取值规则:
| 写法 | 说明 |
|---|---|
data-form-export | 使用按钮所在表单的 action 作为请求地址 |
data-form-export="{:url('index')}" | 使用指定地址请求导出数据 |
data-method="post" | 覆盖表单 method,默认取表单 method,再默认 get |
data-filename="用户数据" | 覆盖 excel.bind() 传入的文件名 |
如果列表使用 layTable 并发生排序,admin.js 会把排序字段写到导出按钮的 data-sort-field 和 data-sort-type,导出请求会追加 _field_ 与 _order_。
前端示例
实际页面通常只需要把后端对象数组转换成二维数组,并在第一行插入表头:
<script>
require(['excel'], function (excel) {
excel.bind(function (data) {
data.forEach(function (item, index) {
data[index] = [
item.id || 0,
item.username || '',
item.node || '',
item.geoip || '',
item.geoisp || '',
item.action || '',
item.content || '',
item.create_at || ''
];
});
data.unshift(['ID', '操作账号', '操作节点', '访问地址', '网络服务商', '操作行为', '操作内容', '创建时间']);
return this.withStyle(data, {A: 60, B: 80, C: 99, E: 120, G: 120});
}, '操作日志' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));
});
</script>excel.bind(done, filename, selector, options) 参数说明:
| 参数 | 说明 |
|---|---|
done | 数据转换回调,第 1 个参数是合并后的 data.list 数组,第 2 个参数是源码传入的空数组 |
filename | 默认文件名,源码当前会追加 .xlsx 后缀 |
selector | 自定义触发选择器,默认 [data-form-export] |
options | 合并到导出配置,默认包含 writeOpt: {bookSST: false} |
自定义触发
自定义导出按钮使用 data-excel 指定请求地址,再把选择器传给 excel.bind():
<button type="button" id="exportOrderBtn" data-excel="{:url('index')}?type=order" class="layui-btn layui-btn-primary">
<i class="layui-icon layui-icon-export"></i> 导出订单
</button>
<script>
require(['excel'], function (excel) {
excel.bind(function (data) {
data.forEach(function (item, index) {
data[index] = [
item.id || 0,
item.order_no || '',
item.order_amount || '',
item.create_time || ''
];
});
data.unshift(['ID', '订单编号', '订单金额', '创建时间']);
return this.withStyle(data, {C: 120, D: 170});
}, '订单数据' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'), '#exportOrderBtn');
});
</script>Excel.push() 属于导入流程:它会读取本地 Excel 文件并逐行 POST 到后端,不能用于导出。
直接导出
如果数据已经在前端准备好,可以直接调用 excel.export(data, filename, options):
require(['excel'], function (excel) {
var data = [
['名称', '数量'],
['已支付订单', 128],
['待支付订单', 16]
];
excel.export(data, '订单统计');
});源码当前的后缀判断使用 name.substring(0, -5),实际会为文件名追加 .xlsx 后缀。建议传入不带扩展名的文件名,避免出现 订单统计.xlsx.xlsx。
后端返回
导出请求使用 output=json,后端需要返回根级 code=1,并在 data 中包含 list 与 page。使用 QueryHelper::layTable() 时,这个结构由 PageHelper 自动处理:
<?php
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemOplog;
class Oplog extends Controller
{
/**
* 系统日志管理
* @auth true
* @menu true
*/
public function index()
{
SystemOplog::mQuery()->layTable(function () {
$this->title = '系统日志管理';
}, static function (QueryHelper $query) {
$query->dateBetween('create_at')->equal('username,action')->like('content,geoip,node');
});
}
/**
* 列表数据处理
*/
protected function _index_page_filter(array &$data)
{
foreach ($data as &$vo) {
$vo['geoisp'] = $vo['geoisp'] ?? '';
}
}
}返回结构示例:
{
"code": 1,
"info": "JSON-DATA",
"data": {
"page": {
"limit": 100,
"total": 120,
"pages": 2,
"current": 1
},
"list": [
{
"id": 1,
"username": "admin",
"node": "/admin/oplog/index",
"geoip": "127.0.0.1",
"geoisp": "内网IP",
"action": "查询",
"content": "查看系统日志",
"create_at": "2026-01-01 12:00:00"
}
]
}
}若不使用 layTable(),也可以自行调用分页并返回相同结构:
if ($this->request->get('output') === 'json') {
$result = SystemOplog::mQuery()
->dateBetween('create_at')
->equal('username,action')
->like('content,geoip,node')
->order('id desc')
->page(true, false);
$this->success('JSON-DATA', $result);
}前端 excel.load() 只在收到 ret.data.page 后继续调度下一页。正常导出接口必须返回 data.page.current 和 data.page.pages。
样式设置
withStyle(data, colsWidth, defaultWidth, defaultHeight) 是当前封装提供的快捷样式方法:
return this.withStyle(data, {
A: 60,
B: 100,
C: 170
}, 99, 28);实际效果:
- 根据
data[0]计算列范围。 - 表头行使用绿色背景、白色加粗字体和居中对齐。
- 从第 2 行开始设置交替行背景色。
- 默认列宽为
defaultWidth || 99,A 列默认 60,最后一列默认 160。 - 第 1 行高度为 33,最后一行高度为
defaultHeight || 28。 - 通过
this.options.extend写入layui.excel的!cols与!rows配置。
需要更复杂样式时,可以直接调用 layui.excel.setExportCellStyle()、layui.excel.makeColConfig() 和 layui.excel.makeRowConfig(),再把配置合并到 this.options.extend。
多工作表
layui.excel.exportExcel() 支持对象形式的多工作表数据,excel.bind() 的回调可以直接返回对象:
require(['excel'], function (excel) {
excel.bind(function (data) {
var rows = [['ID', '账号', '创建时间']];
data.forEach(function (item) {
rows.push([item.id || 0, item.username || '', item.create_at || '']);
});
return {
'用户列表': rows,
'统计信息': [
['项目', '数量'],
['导出数量', rows.length - 1]
]
};
}, '用户数据');
});注意事项
- 导出是浏览器端生成文件,大数据量会占用浏览器内存。
- 前端固定按
limit=100分页拉取数据,后端应支持page和limit参数。 not_cache_limit=1用于避免本次limit=100写入后台分页条数 Cookie。- 失败响应只会提示
数据加载异常或数据加载失败,复杂错误信息应在后端日志中记录。 data-form-export会序列化按钮所在表单;导出按钮应放在搜索表单内,或使用自定义选择器自行组织请求。
常见问题
为什么导出没有开始下载?
优先检查后端是否返回了 code=1、data.list 和 data.page。当前实现收到 data.page 后才会继续分页并最终完成导出。
搜索条件会一起导出吗?
会。点击按钮时会读取按钮所在表单,并通过 form.serialize() 把搜索条件一起提交给导出接口。
表格排序会影响导出吗?
使用 layTable 且导出按钮能被表格关联到时,会同步当前排序字段。请求中会包含 _field_ 和 _order_。
能否在 PHP 中直接生成 Excel?
当前后台封装没有内置 PHP XLSX 导出器。需要服务端生成文件时,应自行接入 phpoffice/phpspreadsheet 等库,或改用业务自定义下载接口。
