ThinkAdmin V6 官方文档

📊 数据导出

ThinkAdmin 提供完善的数据导出功能,基于 layui.excel 工具实现 Excel 文件的前端导出,支持前端直接生成和下载,无需服务器生成文件。

🚀 主要功能

  • Excel 导出: 支持 Excel 文件的前端导出
  • JSON 数据: 仅需返回 JSON 内容,无需服务器生成文件
  • 前端生成: 由前端 JS 生成并下载到本地
  • 性能优化: 减少服务器负载,提升导出性能
  • 灵活配置: 支持多种导出配置和格式
  • 样式定制: 支持自定义表头和内容样式
  • 用户友好: 提供友好的导出界面和操作

📋 技术特点

基于 layui.excel

  • 官方工具: 基于 layui.excel 官方工具
  • 文档支持: 详细文档参考 http://excel.wj2015.com
  • 功能完整: 支持完整的 Excel 导出功能
  • 兼容性好: 与 LayUI 框架完美兼容

前端处理

  • 无需服务器: 不需要服务器生成 Excel 文件
  • JSON 数据: 仅需返回 JSON 格式的数据
  • 前端生成: 由前端 JavaScript 生成文件
  • 本地下载: 直接下载到用户本地
  • 自动分页: 自动处理分页数据,支持大数据量导出

⚙️ 使用场景

数据报表

  • 业务数据: 导出业务相关的数据报表
  • 统计信息: 导出统计和分析数据
  • 用户数据: 导出用户相关的数据信息
  • 财务数据: 导出财务和交易数据

系统管理

  • 日志导出: 导出系统操作日志
  • 配置导出: 导出系统配置信息
  • 备份数据: 导出重要数据备份
  • 审计数据: 导出审计和合规数据

业务应用

  • 订单导出: 导出订单和交易数据
  • 用户导出: 导出用户信息和数据
  • 内容导出: 导出内容管理数据
  • 营销数据: 导出营销活动数据

🔧 实现方式

方式一:使用 data-form-export 属性(推荐)

这是最简单的方式,只需要在按钮上添加 data-form-export 属性,并绑定 Excel 处理函数。

1. HTML 代码

<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>
 <div class="layui-input-inline">
 <input name="keyword" value="{$get.keyword|default=''}" placeholder="请输入关键词" class="layui-input">
 </div>
 </div>

 <div class="layui-form-item layui-inline">
 <button type="submit" class="layui-btn layui-btn-primary">
 <i class="layui-icon">&#xe615;</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>

2. JavaScript 代码

<script>
require(['excel'], function (excel) {
 // 绑定导出处理函数
 excel.bind(function (data) {
 // data 是从服务器获取的 JSON 数据数组

 // 1. 转换数据格式:将对象数组转换为二维数组
 data.forEach(function (item, index) {
 data[index] = [
 item.id, // ID
 item.username, // 用户名
 item.nickname, // 昵称
 item.phone, // 手机号
 item.email, // 邮箱
 item.status, // 状态
 item.create_at // 创建时间
 ];
 });

 // 2. 添加表头(必须在第一行)
 data.unshift([
 'ID', 
 '用户名', 
 '昵称', 
 '手机号', 
 '邮箱', 
 '状态', 
 '创建时间'
 ]);

 // 3. 应用样式(可选)
 return this.withStyle(data, {
 A: 60, // A列宽度
 B: 100, // B列宽度
 C: 120, // C列宽度
 D: 120, // D列宽度
 E: 150, // E列宽度
 F: 80, // F列宽度
 G: 170 // G列宽度
 });

 }, '用户数据_' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));
});
</script>

3. 后端代码

后端需要返回 JSON 格式的数据:

<?php
declare(strict_types=1);

namespace app\admin\controller;

use think\admin\Controller;
use think\admin\model\SystemUser;

/**
 * 用户管理
 * @class User
 * @package app\admin\controller
 */
class User extends Controller
{
 /**
 * 用户列表(支持导出)
 * @auth true
 */
 public function index()
 {
 // 如果是导出请求,返回 JSON 数据
 if ($this->request->get('output') === 'json') {
 $query = SystemUser::mQuery();
 $query->where(['is_deleted' => 0]);
 $query->like('username,nickname,phone,email');
 $query->equal('status');
 $query->dateBetween('create_at');

 // 导出时不分页,获取所有数据
 $list = $query->order('id desc')->page(true, false);

 $this->success('success', [
 'list' => $list,
 'page' => ['current' => 1, 'pages' => 1, 'total' => count($list)]
 ]);
 }

 // 正常列表显示
 SystemUser::mQuery()->layTable(function () {
 $this->title = '用户管理';
 }, function ($query) {
 $query->where(['is_deleted' => 0]);
 $query->like('username,nickname,phone,email');
 $query->equal('status');
 $query->dateBetween('create_at');
 });
 }
}

方式二:使用 Excel.push 方法(自定义导出)

适用于需要自定义导出逻辑的场景。

<script>
require(['excel'], function (excel) {
 // 自定义导出按钮
 $('#customExportBtn').on('click', function() {
 excel.bind(function (data) {
 // 处理数据
 data.forEach(function (item, index) {
 data[index] = [item.id, item.name, item.value];
 });
 data.unshift(['ID', '名称', '值']);

 // 应用样式
 return this.withStyle(data, {A: 60, B: 120, C: 100});
 }, '自定义导出_' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));

 // 触发导出(需要先绑定 data-form-export 的按钮)
 $('[data-form-export]').trigger('click');
 });
});
</script>

📋 样式设置

使用 withStyle 方法(推荐)

withStyle 是 Excel 插件提供的快捷方法,可以快速设置表格样式。

// 基本用法
return this.withStyle(data, {
 A: 60, // A列宽度
 B: 100, // B列宽度
 C: 120 // C列宽度
});

// 指定默认宽度和高度
return this.withStyle(data, {
 A: 60,
 B: 100
}, 99, // 默认列宽
28); // 默认行高

手动设置样式

如果需要更精细的样式控制,可以手动设置:

require(['excel'], function (excel) {
 excel.bind(function (data) {
 // 转换数据
 data.forEach(function (item, index) {
 data[index] = [item.id, item.username, item.create_at];
 });
 data.unshift(['ID', '用户名', '创建时间']);

 // 计算最后一列
 var lastCol = layui.excel.numToTitle((function (count, idx) {
 for (idx in data[0]) count++;
 return count;
 })(0));

 // 设置表头样式
 layui.excel.setExportCellStyle(data, 'A1:' + lastCol + '1', {
 s: {
 font: {
 sz: 14, // 字体大小
 bold: true, // 粗体
 color: {rgb: "FFFFFF"}, // 字体颜色(白色)
 shadow: true, // 阴影
 name: '微软雅黑' // 字体名称
 },
 fill: {
 bgColor: {indexed: 64},
 fgColor: {rgb: "5FB878"} // 背景颜色(绿色)
 },
 alignment: {
 vertical: 'center', // 垂直居中
 horizontal: 'center' // 水平居中
 }
 }
 });

 // 设置内容样式(交替行颜色)
 var style1 = {
 fill: {bgColor: {indexed: 64}, fgColor: {rgb: "EAEAEA"}}, // 灰色
 alignment: {vertical: 'center', horizontal: 'center'}
 };
 var style2 = {
 fill: {bgColor: {indexed: 64}, fgColor: {rgb: "FFFFFF"}}, // 白色
 alignment: {vertical: 'center', horizontal: 'center'}
 };

 layui.excel.setExportCellStyle(data, 'A2:' + lastCol + data.length, {
 s: style1
 }, function (rawCell, newCell, row, config, currentRow, currentCol, fieldKey) {
 // 判断并转换单元格数据为对象
 typeof rawCell !== 'object' && (rawCell = {v: rawCell});
 rawCell.s = Object.assign({}, style2, rawCell.s || {});
 // 偶数行使用 style1,奇数行使用 style2
 return (currentRow % 2 === 0) ? newCell : rawCell;
 });

 // 设置行高和列宽
 var rowsC = {1: 40}; // 第1行(表头)高度为40
 var colsC = {A: 60, B: 100, C: 170}; // 列宽设置
 rowsC[data.length] = 33; // 最后一行高度为33
 colsC[lastCol] = 160; // 最后一列宽度为160

 this.options.extend = {
 '!rows': layui.excel.makeRowConfig(rowsC, 33), // 默认行高33
 '!cols': layui.excel.makeRowConfig(colsC, 160) // 默认列宽160
 };

 return data;
 }, '导出文件名');
});

📋 完整示例

参考系统日志模块的实现,这是一个完整的导出示例。

1. 前端HTML代码

{extend name="admin@public/container" /}

{block name="content"}
<fieldset>
 <legend>条件搜索</legend>
 <form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">

 <div class="layui-form-item layui-inline">
 <label class="layui-form-label">操作账号</label>
 <div class="layui-input-inline">
 <select name='username' lay-search class="layui-select">
 <option value=''>-- 全部账号 --</option>
 {foreach $users as $user}
 <option value="{$user}" {if $user eq input('get.username')}selected{/if}>{$user}</option>
 {/foreach}
 </select>
 </div>
 </div>

 <div class="layui-form-item layui-inline">
 <label class="layui-form-label">操作节点</label>
 <div class="layui-input-inline">
 <input name="node" value="{$get.node|default=''}" placeholder="请输入操作节点" class="layui-input">
 </div>
 </div>

 <div class="layui-form-item layui-inline">
 <label class="layui-form-label">操作时间</label>
 <div class="layui-input-inline">
 <input data-date-range name="create_at" value="{$get.create_at|default=''}" placeholder="请选择操作时间" class="layui-input">
 </div>
 </div>

 <div class="layui-form-item layui-inline">
 <button type="submit" class="layui-btn layui-btn-primary">
 <i class="layui-icon">&#xe615;</i> 搜 索
 </button>
 <!-- 导出按钮 -->
 <button type="button"
 data-form-export="{:url('index')}?type={$type|default=''}"
 class="layui-btn layui-btn-primary">
 <i class="layui-icon layui-icon-export"></i> 导 出
 </button>
 </div>
 </form>
</fieldset>

<!-- 数据表格 -->
<table id="OplogTable" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
{/block}

{block name='script'}
<script>
$(function () {
 // 初始化表格
 $('#OplogTable').layTable({
 even: true, 
 height: 'full',
 sort: {field: 'id', type: 'desc'},
 cols: [[
 {checkbox: true},
 {field: 'id', title: 'ID', width: 80, sort: true, align: 'center'},
 {field: 'username', title: '操作账号', minWidth: 100, sort: true},
 {field: 'node', title: '操作节点', minWidth: 120},
 {field: 'action', title: '操作行为', minWidth: 120},
 {field: 'content', title: '操作内容', minWidth: 150},
 {field: 'geoip', title: '访问地址', minWidth: 100},
 {field: 'geoisp', title: '网络服务商', minWidth: 100},
 {field: 'create_at', title: '创建时间', minWidth: 170, align: 'center', sort: true}
 ]]
 });
});

// Excel 导出配置
require(['excel'], function (excel) {
 excel.bind(function (data) {
 // 1. 转换数据格式:将对象数组转换为二维数组
 data.forEach(function (item, index) {
 data[index] = [
 item.id, // ID
 item.username, // 操作账号
 item.node, // 操作节点
 item.geoip, // 访问地址
 item.geoisp, // 网络服务商
 item.action, // 操作行为
 item.content, // 操作内容
 item.create_at // 创建时间
 ];
 });

 // 2. 添加表头
 data.unshift([
 'ID', 
 '操作账号', 
 '操作节点', 
 '访问地址', 
 '网络服务商', 
 '操作行为', 
 '操作内容', 
 '创建时间'
 ]);

 // 3. 应用样式(使用 withStyle 快捷方法)
 return this.withStyle(data, {
 A: 60, // ID列
 B: 80, // 操作账号列
 C: 99, // 操作节点列
 D: 120, // 访问地址列
 E: 120, // 网络服务商列
 F: 100, // 操作行为列
 G: 120, // 操作内容列
 H: 170 // 创建时间列
 });

 }, '操作日志_' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));
});
</script>
{/block}

2. 后端PHP代码

<?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
 * @package app\admin\controller
 */
class Oplog extends Controller
{
 /**
 * 系统日志管理
 * @auth true
 * @menu true
 */
 public function index()
 {
 // 如果是导出请求,返回 JSON 数据
 if ($this->request->get('output') === 'json') {
 $query = SystemOplog::mQuery();
 $query->dateBetween('create_at');
 $query->equal('username,action');
 $query->like('content,geoip,node');

 // 导出时不分页,获取所有数据
 $list = $query->order('id desc')->page(true, false);

 // 处理IP地址信息
 $region = new \Ip2Region();
 foreach ($list as &$vo) {
 try {
 $vo['geoisp'] = $region->simple($vo['geoip']);
 } catch (\Exception $exception) {
 $vo['geoisp'] = '';
 }
 }

 $this->success('success', [
 'list' => $list,
 'page' => ['current' => 1, 'pages' => 1, 'total' => count($list)]
 ]);
 }

 // 正常列表显示
 SystemOplog::mQuery()->layTable(function () {
 $this->title = '系统日志管理';
 $columns = SystemOplog::mk()->column('action,username', 'id');
 $this->users = array_unique(array_column($columns, 'username'));
 $this->actions = array_unique(array_column($columns, 'action'));
 }, static function (QueryHelper $query) {
 $query->dateBetween('create_at');
 $query->equal('username,action');
 $query->like('content,geoip,node');
 });
 }
}

📋 数据格式说明

后端返回格式

后端需要返回以下格式的 JSON 数据:

{
 "code": 1,
 "info": "success",
 "data": {
 "list": [
 {
 "id": 1,
 "username": "admin",
 "node": "/admin/user/index",
 "geoip": "127.0.0.1",
 "geoisp": "内网IP",
 "action": "查询",
 "content": "查看用户列表",
 "create_at": "2024年01月01日 12:00:00"
 },
 // ... 更多数据
 ],
 "page": {
 "current": 1,
 "pages": 1,
 "total": 100
 }
 }
}

前端处理流程

  1. 自动分页加载: Excel 插件会自动处理分页,通过 output=json&not_cache_limit=1&limit=100&page=1 等参数获取所有数据
  2. 数据转换: 将对象数组转换为二维数组格式
  3. 添加表头: 在第一行添加表头
  4. 样式设置: 应用表格样式
  5. 生成文件: 生成 Excel 文件并下载

📋 高级用法

自定义导出文件名

require(['excel'], function (excel) {
 excel.bind(function (data) {
 // 处理数据
 // ...
 return data;
 }, '自定义文件名_' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));
});

数据过滤和处理

require(['excel'], function (excel) {
 excel.bind(function (data) {
 // 过滤数据
 data = data.filter(function(item) {
 return item.status === 1; // 只导出状态为1的数据
 });

 // 转换数据
 data.forEach(function (item, index) {
 // 处理状态值
 var statusText = item.status === 1 ? '正常' : '禁用';

 data[index] = [
 item.id,
 item.username,
 statusText, // 转换后的状态
 item.create_at
 ];
 });

 data.unshift(['ID', '用户名', '状态', '创建时间']);
 return this.withStyle(data, {A: 60, B: 100, C: 80, D: 170});
 }, '用户数据导出');
});

多工作表导出

require(['excel'], function (excel) {
 excel.bind(function (data) {
 // 处理数据
 data.forEach(function (item, index) {
 data[index] = [item.id, item.username, item.create_at];
 });
 data.unshift(['ID', '用户名', '创建时间']);

 // 创建多个工作表
 var sheet1 = data;
 var sheet2 = [['统计', '数量'], ['总数', data.length - 1]];

 // 返回工作表对象
 return {
 '用户列表': sheet1,
 '统计信息': sheet2
 };
 }, '多工作表导出');
});

自定义导出选项

require(['excel'], function (excel) {
 // 设置导出选项
 excel.options = {
 writeOpt: {bookSST: false},
 extend: {
 // 自定义选项
 }
 };

 excel.bind(function (data) {
 // 处理数据
 // ...
 return data;
 }, '导出文件名');
});

📋 注意事项

后端注意事项

  1. 返回格式: 必须返回 code: 1data.list 格式的 JSON
  2. 分页处理: 导出时会自动分页获取数据,后端需要支持 output=json&limit=100&page=1 参数
  3. 数据量: 大数据量导出时,建议后端进行数据限制或分批处理
  4. 性能优化: 导出时可以使用缓存或优化查询,提升性能

前端注意事项

  1. 数据格式: 确保数据格式正确,表头和数据行数量匹配
  2. 样式设置: 样式设置是可选的,但建议设置以提高可读性
  3. 文件大小: 大数据量导出可能生成较大的 Excel 文件
  4. 浏览器兼容: 确保浏览器支持文件下载功能

最佳实践

  1. 数据验证: 导出前验证数据是否有效
  2. 进度提示: 大数据量导出时显示进度提示
  3. 错误处理: 完善的错误处理机制
  4. 权限控制: 使用 auth() 函数控制导出权限

📋 常见问题

Q: 导出时如何包含搜索条件? A: data-form-export 会自动获取表单的搜索条件,并附加到导出 URL 中。

Q: 如何导出当前表格显示的数据? A: 使用 data-form-export 时,会自动使用表单的搜索条件,确保导出的数据与表格显示一致。

Q: 大数据量导出如何处理? A: Excel 插件会自动分页获取数据,但建议后端对导出数据进行限制,避免导出过多数据。

Q: 如何自定义导出列? A: 在 excel.bind 的回调函数中,只处理需要导出的字段即可。


提示: 更多详细用法请参考 layui.excel 官方文档 或查看系统日志模块的实际实现。

最近更新:
Contributors: 邹景立, Anyon

AltStyle によって変換されたページ (->オリジナル) /