bootstrap-table:
- 1. 数据渲染与分页
- 2. boostrap-table 渲染动态页面
- 3. 列表数据导出
- 后台接口返回数据格式为:
1. 数据渲染与分页
第一次使用bootstrap-table
,感觉就是简洁,真的简洁,接口返回json数据,前端引入对应的js,然后js处理下json数据,页面就自动分页了,根本不用再写额外的分页代码了。
数据格式只要是标准的api接口,然后后面就是前端js处理的事了。
<!-- bootstrap && table -->
<link href="${ctx!}/common/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="${ctx!}/common/plugins/bootstrap-table/bootstrap-table.css" rel="stylesheet" >
<script src="${ctx!}/common/plugins/jquery-3.5.1/jquery-3.5.1.min.js"></script>
<!-- JS定位引擎 点击页面的翻页和下拉框 高版本的bootstrap需要用到 -->
<script src="${ctx!}/common/plugins/popper/popper-1.14.7.js"></script>
<!-- from表单转为json -->
<script src="${ctx!}/common/plugins/jquery-serializejson/jquery.serializejson.js"></script>
<!-- Bootstrap table tableExport new -->
<script src="${ctx!}/common/plugins/bootstrap/js/bootstrap.min.js"></script>
<script src="${ctx!}/common/plugins/bootstrap-table/bootstrap-table.min.js"></script>
<script src="${ctx!}/common/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
页面直接定义一个div,声明一下table的Id
<div class="row row-lg">
<div class="col-sm-12">
<!-- Example Card View -->
<div class="example-wrap">
<div class="example">
<table id="table_list"></table>
</div>
</div>
<!-- End Example Card View -->
</div>
</div>
<script>
$(document).ready(function () {
//初始化表格,动态从服务器加载数据
$("#table_list").bootstrapTable({
//使用get请求到服务器获取数据
method: "POST",
//必须设置,不然request.getParameter获取不到请求参数
contentType: "application/x-www-form-urlencoded",
//获取数据的Servlet地址
url: "${ctx!}/admin/user/queryUserList",
//表格显示条纹
striped: true,
//启动分页
pagination: true,
sortable: true, //是否启用排序
sortOrder: "asc", //排序方式
showColumns: true, //是否显示所有的列
//每页显示的记录数
pageSize1: 10,
//当前第几页
pageNumber1: 1,
//记录数可选列表
pageList: [2, 5, 10, 15, 20, 25],
//是否启用查询
//search: true,
//是否启用详细信息视图 与detailFormatter 结合使用,detailFormatter为显示的具体字段名称
//detailView:true,
//detailFormatter:detailFormatter,
//表示服务端请求
sidePagination: "server",
//设置为undefined可以获取pageNumber,pageSize,searchText,sortName,sortOrder
//设置为limit可以获取limit, offset, search, sort, order
queryParamsType: "undefined",
//得到查询的参数
queryParams : function (params) {
//console.log(params);
var temp = $("#query_form").serializeJSON();
//console.log(temp);
temp["sortName"]= params.sortName,
temp["sortOrder"]= params.sortOrder,
temp["page"]= params.pageNumber,
temp["rows"]= params.pageSize;
console.log(temp);
return temp;
},
//json数据解析
responseHandler: function(data) {
if(data.code == 200){
return {
"rows": data.resultList,
"total": data.count
};
}else{
console.log("接口异常。。。。。。");
}
},
//数据列
columns: [{
title: "ID",
field: "id",
sortable: true
},{
title: "用户名",
field: "userName"
} ,{
title: "昵称",
field: "nickName"
},{
title: "性别",
field: "sex",
formatter: function(value, row, index) {
if (value == '0')
return '<span class="label label-warning">女</span>';
return '<span class="label label-primary">男</span>';
}
},{
title: "出生日期",
field: "birthday",
formatter: function(value, row, index) {
if(value){
return value.substring(0, 10);
}
return value;
}
},{
title: "电话",
field: "telephone"
},{
title: "邮箱",
field: "email"
},{
title: "状态",
sortable: true,
field: "deleteStatus",
formatter: function (value, row, index) {
if (value == '0')
return '<span class="label label-info">未删除</span>';
return '<span class="label label-danger">已删除</span>';
}
},{
title: "锁定",
field: "locked",
formatter: function (value, row, index) {
if (value == '0')
return '<span class="label label-info">未锁定</span>';
return '<span class="label label-danger">已锁定</span>';
}
},{
title: "创建时间",
field: "createTime",
sortable: true
},{
title: "更新时间",
field: "updateTime",
sortable: true
}]
});
});
分页和展示就结束了。
后面的跳转至多少页,可引入`bootstrap-table-page-jump-to.min.jscolumns
<link href="${ctx!}/common/plugins/bootstrap-table/extensions/page-jump-to/bootstrap-table-page-jump-to.min.css" rel="stylesheet" >
<script src="${ctx!}/common/plugins/bootstrap-table/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
然后在表格渲染时,开启页面跳转即可:showJumpTo: true, //是否显示页面跳转至多少页
2. boostrap-table 渲染动态页面
如果做成动态的列,大体思路为:先定义一个模板,然后显示的列可以使用后台返回,然后在responseHandler
里面接收列对应的表头和属性,然后调用自定义函数,进行拼接columns
即可。
这里附上一个用到的动态弹窗页面,其中弹窗页面的列和表头,查询条件,都是动态的。
这里前端页面是用的freemarker,如果前端用的HTML页面,则直接将ftl文件转为html文件即可。同时如果页面里面包含ftl标签,则需要转换下。
首先是模板文件:newSearch.ftl
,
逻辑可能有点乱,大致功能描述是:父页面点击input框,弹出一个模态框,模态框的内容是一个动态列表,表格的内容和查询条件都是动态的。然后对弹窗的表格添加了双击事件,双击某一行,可以将当前行作为一个json对象传入父页面的回调函数里面,可以在父页面进行自定义操作。同时支持多选。
需要用到的文件放在github上面了,地址:https://github.com/linmengmeng-1314/collectionData/tree/main/%E5%89%8D%E7%AB%AF/dynamic_new_search
可以参考下newSearchResult.ftl
文件中$(document).ready(function () {
里面的逻辑,页面加载后,读取父页面js中定义的参数,来动态生成弹窗内容。
其中弹窗页面的js:commonSearch.js
里面定义了多个参数,作为子页面和父页面交互。需要在父页面引入该js。
后端接口的话,参考Controller文件夹里面的controller
类,引用的包在import
文件夹下,只把业务无关的java文件贴出来了。
使用了com.github.pagehelper.PageHelper;
工具包,封装查询分页功能。
<!--分页插件 pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<!-- 特别注意版本问题 -->
<version>1.2.3</version>
</dependency>
3. 列表数据导出
正常导出页面:
在使用bootstrap-table的过程中,由于用到数据导出excel的功能,刚开始用的bootstrap-table的1.11.1版本的js,结果导出图标一直不显示,就很无奈。。。。
显示导出按钮showExport
就够用了,可结合showColumns
,实现前端可自定义导出的列:
showColumns: true, //是否显示所有的列
showExport: true, //工具栏上显示导出按钮
但是呢,我用了之后,一直不显示导出图标,showColumns
可以正常使用,查了半天,没找到什么原因,后来偶然意识到,会不会是js版本的问题,然后试着换个版本。
换新版本的js,建议使用配套的,不然部分js换了新版本,如果其依赖的js还是旧版本,可能会不兼容。这里我直接将整个包复制到静态资源目录下:
导出功能需要用的js
<!-- bootstrap && table -->
<link href="${ctx!}/common/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="${ctx!}/common/plugins/bootstrap-table/bootstrap-table.css" rel="stylesheet" >
<script src="${ctx!}/common/plugins/jquery-3.5.1/jquery-3.5.1.min.js"></script>
<!-- JS定位引擎 点击页面的翻页和下拉框 高版本的bootstrap需要用到 -->
<script src="${ctx!}/common/plugins/popper/popper-1.14.7.js"></script>
<!-- Bootstrap table tableExport new -->
<script src="${ctx!}/common/plugins/bootstrap/js/bootstrap.min.js"></script>
<script src="${ctx!}/common/plugins/bootstrap-table/bootstrap-table.min.js"></script>
<script src="${ctx!}/common/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
<script src="${ctx!}/common/plugins/tableExport/FileSaver/FileSaver.min.js"></script>
<script src="${ctx!}/common/plugins/tableExport/js-xlsx/xlsx.core.min.js"></script>
<script src="${ctx!}/common/plugins/tableExport/tableExport.min.js"></script>
<script src="${ctx!}/common/plugins/bootstrap-table/extensions/export/bootstrap-table-export.min.js"></script>
导出功能可参考:
https://www.freesion.com/article/870763773/
js插件下载地址:
jquery ----- https://www.jq22.com/jquery-info122
popper.js ----- https://popper.js.org/docs/v2/
-------- https://unpkg.com/@popperjs/[email protected]/dist/umd/popper.js
bootstrap ---- https://github.com/twbs/bootstrap
bootstrap-table ---- https://github.com/wenzhixin/bootstrap-table
tableExport ---- https://github.com/hhurz/tableExport.jquery.plugin
其中bootstrap 源码文件夹里的dist即为我们要用的
tableExport 源码libs文件夹下包含了导出excel,PDF等插件,这里导出excel只用fileSaver和js-xlsx即可。
bootstrap-table
引入必要的js之后,只需要在渲染表格时指定显示导出按钮:
showColumns: true, //是否显示所有的列
showExport: true, //工具栏上显示导出按钮
//showToggle:true, //是否显示详细视图和列表视图的切换按钮
exportDataType: "basic", //导出表格方式(默认basic:只导出当前页的表格数据;all:导出所有数据;selected:导出选中的数据)
exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'excel'], //导出文件类型
//exportTypes:['excel','xlsx'],
//exportButton: $('#btn_export'), //为按钮btn_export 绑定导出事件 自定义导出按钮(可以不用)
Icons: 'glyphicon-export icon-share',//导出图标 未显示
exportOptions:{
//ignoreColumn: [0,0], //忽略某一列的索引 当显示 detailView (详细视图时可设置索引,导出时会忽略该列)
fileName: '我是数据导出的文件名', //文件名称设置 生效
worksheetName: 'Sheet1', //表格工作区名称 未生效
tableName: '商品数据表', //未生效
excelstyles: ['background-color', 'color', 'font-size', 'font-weight'],
//onMsoNumberFormat: DoOnMsoNumberFormat //防止js将长数字串 科学计数 未生效 DoOnMsoNumberFormat函数未执行
},
可根据需求,设置导出的数据格式。
其中DoOnMsoNumberFormat
函数如下,但是没有执行,不知道是不是插件版本的原因导致的。
function DoOnMsoNumberFormat(cell, row, col){
console.log("111111111111111111");
console.log(cell);
console.log(row);
console.log(row);
var result = "";
if(row > 0 && col == 6){
result = "\\@";
}
return result;
}
导出文件内容如下:
看了下github里面源码的说明:https://github.com/hhurz/tableExport.jquery.plugin#options
也没看懂这个到底是怎么去除长数字串,被科学计数法了的问题。
$('table').tableExport({type:'excel',
mso: {fileFormat:'xmlss',
worksheetName: ['Table 1','Table 2', 'Table 3']}});
源码里面这里提到自定义文件表格worksheetName,加上之后,worksheetName确实可以自定义了,但是导出的文件名却变成了xml了。
github的Options里面没有提到的参数,是被遗弃了么???? 反正没提到的,配置了也不报错,但是就是没效果。
完整页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>welcome用户列表</title>
<meta name="keywords" content="">
<meta name="description" content="">
<!-- 网页标题前添加一个小图标 -->
<link rel="shortcut icon" href="favicon.ico">
<!-- 矢量图标css -->
<link href="${ctx!}/assets/css/font-awesome.css?v=4.4.0" rel="stylesheet">
<!-- CSS3动画库css -->
<link href="${ctx!}/assets/css/animate.css" rel="stylesheet">
<link href="${ctx!}/assets/css/style.css?v=4.1.0" rel="stylesheet">
<!-- bootstrap && table -->
<link href="${ctx!}/common/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="${ctx!}/common/plugins/bootstrap-table/bootstrap-table.css" rel="stylesheet" >
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox ">
<div class="ibox-title">
<h5>用户管理</h5>
</div>
<!-- 查询参数 form... -->
<div class="form-group" class="col-sm-12">
<form id="query_form">
<div class="col-sm-12">
<label class="col-sm-1 control-label">账户名:</label>
<input class="col-sm-1 control-label" id="userName" name="userName" type="text" value="">
<label class="col-sm-1 control-label">昵称:</label>
<input class="col-sm-1 control-label" id="nickName" name="nickName" type="text" value="">
<button type="button" class="btn btn-success" onclick="search('table_list')" data-icon="search">查询</button>
<div>
</form>
</div>
<div class="ibox-content">
<p>
<@shiro.hasPermission name="system:user:add">
<button class="btn btn-success " type="button" onclick="add();"><i class="fa fa-plus"></i> 添加</button>
</@shiro.hasPermission>
</p>
<hr>
<div class="row row-lg">
<div class="col-sm-12">
<!-- Example Card View -->
<div class="example-wrap">
<div class="example">
<table id="table_list"></table>
</div>
</div>
<!-- End Example Card View -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 全局js -->
<!-- <script src="${ctx!}/assets/js/jquery.min.js?v=2.1.4"></script> -->
<script src="${ctx!}/common/plugins/jquery-3.5.1/jquery-3.5.1.min.js"></script>
<!-- JS定位引擎 点击页面的翻页和下拉框 高版本的bootstrap需要用到 -->
<script src="${ctx!}/common/plugins/popper/popper-1.14.7.js"></script>
<!-- from表单转为json -->
<script src="${ctx!}/common/plugins/jquery-serializejson/jquery.serializejson.js"></script>
<!-- Bootstrap table tableExport new -->
<script src="${ctx!}/common/plugins/bootstrap/js/bootstrap.min.js"></script>
<script src="${ctx!}/common/plugins/bootstrap-table/bootstrap-table.min.js"></script>
<script src="${ctx!}/common/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
<script src="${ctx!}/common/plugins/tableExport/FileSaver/FileSaver.min.js"></script>
<script src="${ctx!}/common/plugins/tableExport/js-xlsx/xlsx.core.min.js"></script>
<script src="${ctx!}/common/plugins/tableExport/tableExport.min.js"></script>
<script src="${ctx!}/common/plugins/bootstrap-table/extensions/export/bootstrap-table-export.min.js"></script>
<!-- Peity -- jQuery简单图表peity.js -->
<script src="${ctx!}/assets/js/plugins/peity/jquery.peity.min.js"></script>
<script src="${ctx!}/assets/js/plugins/layer/layer.min.js"></script>
<!-- 自定义js -->
<script src="${ctx!}/assets/js/content.js?v=1.0.0"></script>
<!-- Page-Level Scripts -->
<script>
$(document).ready(function () {
//初始化表格,动态从服务器加载数据
$("#table_list").bootstrapTable({
//使用get请求到服务器获取数据
method: "POST",
//必须设置,不然request.getParameter获取不到请求参数
contentType: "application/x-www-form-urlencoded",
//获取数据的Servlet地址
url: "${ctx!}/admin/user/queryUserList",
//表格显示条纹
striped: true,
//启动分页
pagination: true,
sortable: true, //是否启用排序
sortOrder: "asc", //排序方式
showColumns: true, //是否显示所有的列
showExport: true, //工具栏上显示导出按钮
//showToggle:true, //是否显示详细视图和列表视图的切换按钮
exportDataType: "basic", //导出表格方式(默认basic:只导出当前页的表格数据;all:导出所有数据;selected:导出选中的数据)
exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'excel'], //导出文件类型
//exportTypes:['excel','xlsx'],
//exportButton: $('#btn_export'), //为按钮btn_export 绑定导出事件 自定义导出按钮(可以不用)
Icons: 'glyphicon-export icon-share',//导出图标 未显示
exportOptions:{
//ignoreColumn: [0,0], //忽略某一列的索引 当显示 detailView (详细视图时可设置索引,导出时会忽略该列)
fileName: '我是数据导出的文件名', //文件名称设置
worksheetName: 'Sheet1', //表格工作区名称 未生效
tableName: '商品数据表', //未生效
excelstyles: ['background-color', 'color', 'font-size', 'font-weight'],
onMsoNumberFormat: DoOnMsoNumberFormat //防止js将长数字串 科学计数
},
//每页显示的记录数
pageSize1: 10,
//当前第几页
pageNumber1: 1,
//记录数可选列表
pageList: [2, 5, 10, 15, 20, 25],
//是否启用查询
//search: true,
//是否启用详细信息视图 与detailFormatter 结合使用,detailFormatter为显示的具体字段名称
//detailView:true,
//detailFormatter:detailFormatter,
//表示服务端请求
sidePagination: "server",
//设置为undefined可以获取pageNumber,pageSize,searchText,sortName,sortOrder
//设置为limit可以获取limit, offset, search, sort, order
queryParamsType: "undefined",
//得到查询的参数
queryParams : function (params) {
//console.log(params);
var temp = $("#query_form").serializeJSON();
//console.log(temp);
temp["sortName"]= params.sortName,
temp["sortOrder"]= params.sortOrder,
temp["page"]= params.pageNumber,
temp["rows"]= params.pageSize;
console.log(temp);
return temp;
},
//json数据解析 处理服务端返回的数据
responseHandler: function(data) {
if(data.code == 200){
return {
"rows": data.resultList,
"total": data.count
};
}else{
console.log("接口异常。。。。。。");
}
},
//数据列
columns: [{
title: "ID",
field: "id",
sortable: true
},{
title: "用户名",
field: "userName"
},{
title: "昵称",
field: "nickName"
},{
title: "性别",
field: "sex",
formatter: function(value, row, index) {
if (value == '0')
return '<span class="label label-warning">女</span>';
return '<span class="label label-primary">男</span>';
}
},{
title: "出生日期",
field: "birthday",
formatter: function(value, row, index) {
if(value){
return value.substring(0, 10);
}
return value;
}
},{
title: "电话",
field: "telephone"
},{
title: "邮箱",
field: "email"
},{
title: "状态",
sortable: true,
field: "deleteStatus",
formatter: function (value, row, index) {
if (value == '0')
return '<span class="label label-info">未删除</span>';
return '<span class="label label-danger">已删除</span>';
}
},{
title: "锁定",
field: "locked",
formatter: function (value, row, index) {
if (value == '0')
return '<span class="label label-info">未锁定</span>';
return '<span class="label label-danger">已锁定</span>';
}
},{
title: "创建时间",
field: "createTime",
sortable: true
},{
title: "更新时间",
field: "updateTime",
sortable: true
},{
title: "操作",
field: "empty",
formatter: function (value, row, index) {
var operateHtml = '<@shiro.hasPermission name="system:user:edit"><button class="btn btn-primary btn-sm" type="button" οnclick="edit(\''+row.id+'\')"><i class="fa fa-edit"></i> 修改</button> </@shiro.hasPermission>';
operateHtml = operateHtml + '<@shiro.hasPermission name="system:user:deleteBatch"><button class="btn btn-danger btn-sm" type="button" οnclick="del('+row.id+',' + row.versionOptimizedLock+')"><i class="fa fa-remove"></i> 删除</button> </@shiro.hasPermission>';
operateHtml = operateHtml + '<@shiro.hasPermission name="system:user:grant1"><button class="btn btn-info btn-sm" type="button" οnclick="grant(\''+row.id+'\')"><i class="fa fa-arrows"></i> 关联角色</button></@shiro.hasPermission>';
return operateHtml;
}
}]
});
});
function DoOnMsoNumberFormat(cell, row, col){
var result = "";
if(row > 0 && col == 6){
result = "\\@";
}
return result;
}
function edit(id){
layer.open({
type: 2,
title: '用户修改',
shadeClose: true,
shade: false,
area: ['893px', '600px'],
content: '${ctx!}/admin/user/edit/' + id,
end: function(index){
$('#table_list').bootstrapTable("refresh");
}
});
}
function add(){
layer.open({
type: 2,
title: '用户添加',
shadeClose: true,
shade: false,
area: ['893px', '600px'],
content: '${ctx!}/admin/user/add',
end: function(index){
$('#table_list').bootstrapTable("refresh");
}
});
}
function grant(id){
layer.open({
type: 2,
title: '关联角色',
shadeClose: true,
shade: false,
area: ['893px', '600px'],
content: '${ctx!}/admin/user/grant/' + id,
end: function(index){
$('#table_list').bootstrapTable("refresh");
}
});
}
function del(id, versionOptimizedLock){
layer.confirm('确定删除吗?', {icon: 3, title:'提示'}, function(index){
$.ajax({
type: "POST",
dataType: "json",
url: "${ctx!}/admin/user/delete",
//url: "${ctx!}/admin/user/delete/" + id,
data: {"id":id, "versionOptimizedLock":versionOptimizedLock},
success: function(msg){
layer.msg(msg.message, {time: 2000},function(){
$('#table_list').bootstrapTable("refresh");
layer.close(index);
});
}
});
});
}
function detailFormatter(index, row) {
var html = [];
html.push('<p><b>描述:</b> ' + row.description + '</p>');
return html.join('');
}
function search(){
console.log("search.....");
$('#table_list').bootstrapTable('refresh');
}
</script>
</body>
</html>
后台接口返回数据格式为:
使用bootstrap table的自动分页功能时,至少需要返回总的条数
,和数据列对象
,其中参数名称可以自定义,使用responseHandler
进行处理一下即可。
{"code":"200","msg":"成功","page":1,"rows":5,"count":9,"resultList":[{"id":1,"userName":"admin","password":"d04d4c356a1031da523d33b81510fec4","address":"成都","birthday":"2020-11-03","createTime":"2020-11-09 14:27:46","deleteStatus":0,"description":"超级管理员","email":"[email protected]","locked":0,"nickName":"admin","sex":0,"telephone":"1592393000111111","updateTime":"2020-11-09 14:27:28","versionOptimizedLock":0},{"id":2,"userName":"test","password":"d04d4c356a1031da523d33b81510fec4","address":"北京","birthday":"2020-11-09","createTime":"2020-11-09 14:50:56","deleteStatus":1,"description":"测试","email":"[email protected]","locked":0,"nickName":"test001","sex":0,"telephone":"15923930123","updateTime":"2020-11-09 14:51:31","versionOptimizedLock":0},{"id":3,"userName":"linmm","password":"d04d4c356a1031da523d33b81510fec4","address":"beijing ","birthday":"2020-11-09","createTime":"2020-11-09 15:44:05","deleteStatus":0,"description":"测试","email":"[email protected]","locked":0,"nickName":"LMMA","sex":1,"telephone":"159239300234","updateTime":"2020-11-09 15:44:00","versionOptimizedLock":1},{"id":4,"userName":"jadl","password":"d04d4c356a1031da523d33b81510fec4","address":"beijing ","birthday":"2020-11-09","createTime":"2020-11-09 15:41:37","deleteStatus":0,"description":"测试","email":"[email protected]","locked":0,"nickName":"LMMA","sex":1,"telephone":"1592393000111111","updateTime":"2020-11-09 15:41:37","versionOptimizedLock":1},{"id":5,"userName":"jadl1","password":null,"address":"beijing ","birthday":"2020-11-09","createTime":"2020-11-09 15:46:41","deleteStatus":0,"description":"测试","email":"[email protected]","locked":0,"nickName":"LMMA1","sex":1,"telephone":"159239300234","updateTime":"2020-11-09 15:46:41","versionOptimizedLock":1}],"totalPage":2}
使用bootstrap的下拉框时,点击下拉框不显示选项
如果感觉写的比较乱,可以直接参考官网的demo,虽然是英文版的,但是简洁易懂:
https://examples.bootstrap-table.com/#extensions/export.html#view-source