前言
- 最近需要
bootstrapTable转化为DataTables,这两个难道都算老框架了吗,都没多少文讲这个,尤其是DataTables有tfoot后加滚动条,使用scrollX属性没有效果 - 本文主要说下
bootstrapTable与DataTables的区别,怎么去转换,还有如何给有着tfoot的DataTables加滚动条
一、关于bootstrapTable与DataTables的区别(注释用AI润色过,不对的地方请提出)
1.bootstrapTable初始化
直接上一个例子,
javascript
var TableInit = function() {
var oTableInit = new Object();
//初始化Table
oTableInit.Init = function() {
$('#table1').bootstrapTable({
toolbar: '#toolbar', //工具按钮用哪个容器
striped: true, //是否显示行间隔色
cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
pagination: true, //是否显示分页(*)
sortable: true, //是否启用排序
sortOrder: "asc", //排序方式
sortName: "n", //初始化时排序的列名
queryParams: oTableInit.queryParams, //传递参数(*)
sidePagination: "client", //分页方式:client客户端分页,server服务端分页(*)
pageNumber: 1, //初始化加载第一页,默认第一页
pageSize: 20, //每页的记录行数(*)
pageList: [20, 50, 100], //可供选择的每页的行数(*)
search: false, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,所以,个人感觉意义不大
strictSearch: true, //决定搜索时是进行 严格匹配(搜索词必须完全匹配字段值(区分大小写)) 还是 模糊匹配(搜索词可以是字段值的一部分(不区分大小写)),用于search属性产生的搜索框
showColumns: false, //是否显示所有的列
showRefresh: false, //是否显示刷新按钮
minimumCountColumns: 2, //最少允许的列数
clickToSelect: false, //是否启用点击选中行
// height: 565, //行高,如果没有设置height属性,表格自动根据记录条数觉得表格高度
uniqueId: "i", //每一行的唯一标识,一般为主键列
showToggle: false, //是否显示详细视图和列表视图的切换按钮
cardView: false, //是否显示详细视图
detailView: true, //是否显示父子表
/*
exportDataType可以取以下值:
'basic':导出当前页的数据(即页面中当前显示的数据)
'all':导出所有数据(包括所有分页的数据)
'selected':导出选中的数据(需要开启复选框选择功能)
*/
exportDataType: 'all',
showExport: true, //是否显示导出按钮
buttonsAlign: "right", //按钮位置
exportTypes: ['excel'], //导出文件类型
Icons: 'glyphicon-export',
exportOptions: {
ignoreColumn: [0, 1, 9], //忽略某一列的索引
fileName: '列表', //文件名称设置
worksheetName: 'sheet1', //表格工作区名称
tableName: '列表',
excelstyles: ['background-color', 'color', 'font-size', 'font-weight'],
//onMsoNumberFormat: DoOnMsoNumberFormat
},
//注册加载子表的事件。
/**
* 在 Bootstrap Table 中,当启用 detailView 时,点击行前面的展开按钮会触发 onExpandRow 事件。该事件会传入三个参数:
* @param {*} index 当前展开行的索引
* @param {*} row 当前展开行的数据对象
* @param {*} $detail 一个 jQuery 对象,指向用于展示子表格的容器元素
*/
onExpandRow: function(index, row, $detail) {
oTableInit.InitSubTable(index, row, $detail);
},
columns: [{
checkbox: true //显示复选框
}, {
field: 'index', //field是列的字段名
title: '序号', //title是列的标题,也就是表头
/**
* formatter: 用于格式化列的显示内容的函数
* @param {*} value 对应字段(field)的值
* @param {*} row 当前行的数据对象
* @param {*} index 当前行的索引
* @returns
*/
formatter: function(value, row, index) {
return index + 1;
}
}, {
field: 'n',
title: '表名称',
sortable: true, //是否启用排序
}, {
field: 'i',
title: '表号',
sortable: true,
}, {
field: 'groupN',
title: '分组 ',
sortable: true,
/**
* sorter: 用于自定义排序逻辑的函数
* @param {*} a 第一行在 groupN 字段的显示值
* @param {*} b 第二行在 groupN 字段的显示值
* @param {*} ra 第一行的数据对象
* @param {*} rb 第二行的数据对象
* @returns
*/
sorter: function(a, b, ra, rb) {
return groups[ra.d] - groups[rb.d];
},
formatter: function(value, row, index) {
if (row.d == 0)
return "未分组 ";
else return groups[row.d];
}
}, {
field: 'chaozuo',
title: '操作',
//align属性用于设置列内容的对齐方式
align: 'center',
//formatter: 用于格式化列的显示内容的函数
formatter: operateOther
}, {
field: 'd',
title: 'd ',
cellStyle: { //自定义单元格样式
css: {
"display": "none"
}
}
}, ]
});
};
//初始化子表格(无线循环)
oTableInit.InitSubTable = function(index, row, $detail) {
var cur_table = $detail.html('<table ></table>').find('table');
$(cur_table).bootstrapTable({
clickToSelect: false, // 点击行不选中
detailView: false, // 子表格不能再展开(避免无限循环)
uniqueId: "i",
pageSize: 50,
pageList: [10, 50, 100],
columns: [{
field: 'n',
title: '表名称 ',
sortable: true,
}, {
field: 'i',
title: '表号 ',
sortable: true,
}, {
field: 'e',
title: '剩余',
sortable: true,
formatter: function(value, row, index) {
if (row.m == "1") return '-';
else {
return row.e;
}
},
cellStyle: function(value, row, index) {
if (row.m == "1") return {
css: {}
};
if (value <= 0) return {
css: {
"background-color": "red"
}
};
else if (row.w > value) return {
css: {
"background-color": "#ffd9ec"
}
};
else return {
css: {}
};
},
}, {
field: 'chaozuo',
title: '操作',
align: 'center',
}, ],
});
/**
* 加载子表格数据
* 'load':Bootstrap Table 的方法名,表示加载数据
* @param {*} cld[row.i] 传递给 load 方法的数据参数
*/
$(cur_table).bootstrapTable('load', cld[row.i]);
};
return oTableInit;
};
2.DataTables初始化
dom是DataTables中用于控制表格布局结构的配置项:
| 字符 | 组件 | 说明 | 默认位置 |
|---|---|---|---|
| l | length changing | 每页显示条数选择框 | 左上 |
| f | filtering | 搜索过滤框 | 右上 |
| t | table | 表格主体 | 中间 |
| i | info | 表格信息(显示条数) | 左下 |
| p | pagination | 分页控件 | 右下 |
| r | processing | 加载处理提示 | 表格上方 |
| B | buttons | 按钮区域 | 左上 |
| R | ColReorder | 列重新排序 | - |
| S | Scroller | 滚动组件 | - |
| P | serchPanes | 搜索面板 | - |
| Q | searchBuilder | 搜索构建器 | - |
如果未写dom的配置,默认为
javascript
dom: 'lfrtip'
javascript
var TableInit = function () {
var oTableInit = new Object();
//初始化Table
oTableInit.Init = function () {
$('#table1').DataTable({
//引入中文文本版本
language: {
url: 'datetables-zh_cn.json',
},
//DataTables:使用 dom 字符串控制布局
//一个靠左的容器div用于放置表格信息说明il
dom: 'rt<"float-left"il>p',
pageLength: 20, //默认每页显示20条记录
lengthMenu: [20, 50, 100], //每页显示记录数选项
order: [], // 不设置默认排序
autoWidth: false, //禁用自动列宽计算
initComplete: function () { //表格初始化完成后执行,将按钮添加到自定义位置
var btnContainer = this.api().buttons().containers()[0]; //获取按钮容器buttons的第一个元素
$(btnContainer).addClass('float-right'); //添加浮动类使按钮靠右显示
$(btnContainer).appendTo('#toolbar'); //将按钮容器添加到自定义的div中
},
buttons: [ //定义导出按钮
{
extend: 'csvHtml5',
text: '<div class="export"><img src="res/Export.png" alt="导出Excel"/> <span class="export-text">导出</span></div>',
className: 'btn custom_export_btn',
bom: true, //解决中文导出乱码问题
filename: '列表',
title: '列表',
sheetName: 'sheet1',
exportOptions: {
columns: [2, 3, 4] // 要导出的序列
},
customize: function (xlsx) { //可以添加自定义导出内容的操作
return xlsx;
}
}
],
//DataTables:也通过columns数组定义,但使用data属性指定字段,render函数进行格式化
//DataTables: 没有内置的详细视图,但可以通过在每行添加一个按钮,点击时展开一个行来模拟
columns: [
{ //子表格控制列,点击展开子表格
data: null,
orderable: false, //是否启用排序
title: '',
render: function (data, type, row, meta) {
return '<a class="detail-icon" href="#"> <i class="glyphicon glyphicon-plus icon-plus"></i> </a>';
},
className: 'details-control text-center',
},
{ //checkbox列
data: null,
orderable: false,
title: '<div class="custom-control custom-checkbox"><input type="checkbox" class="custom-control-input" id="customCheck"><label class="custom-control-label" for="customCheck"> </label></div>',
render: function (data, type, row, meta) {
return '<div class="custom-control custom-checkbox"><input type="checkbox" class="custom-control-input ccall" id="customCheck' + meta.row + '"><label class="custom-control-label" for="customCheck' + meta.row + '"> </label></div>';
},
},
{
data: null,
title: '序号',
orderable: false,
render: function (data, type, row, meta) {
return meta.row + 1;
}
},
{
data: 'n',
title: '表名称',
orderable: true,
},
{
data: 'i',
title: '表号',
orderable: true,
},
{
data: 't',
title: '备注',
orderable: true,
},
{
data: null,
title: '分组',
orderable: true,
render: function (data, type, row) {
if (type === 'sort') { //dataTables不支持直接比较两行数据的函数格式
// 排序时使用 groups 中的数值
return groups[row.gid] || 0;
} else {
// 显示时使用文本
if (row.gid == 0) return '未分组';
else return groups[row.gid] || '';
}
}
},
{
data: null,
title: '操作',
orderable: false,
render: function (data, type, row, meta) {
return operateOther(null, row, meta.row);
},
},
{
data: null,
title: 'd',
visible: false, //隐藏列
render: function (data, type, row) {
return row.d || '';
}
}
]
});
$('#table1 th:gt(9)').css("display", "none");
};
//初始化子表格(无线循环)
oTableInit.InitSubTable = function (index, row, $detail) {
var subData = cld[row.i] || []; //根据父表的表号i获取对应的子表数据
var cur_table = $detail.html('<table class="table table-hover w-100"></table>').find('table');
$(cur_table).DataTable({
dom: 't',
data: subData,
language: {
url: 'datetables-zh_cn.json',
},
pageLength: 50,
lengthMenu: [10, 50, 100],
columns: [
{
data: 'n',
title: '表名称 ',
orderable: true
},
{
data: 'i',
title: '表号 ',
orderable: true
},
{
data: 'e',
title: '剩余',
orderable: true,
render: function (data, type, row) {
if (row.m == "1" || row.e == null) return '-';
else return row.e;
},
createdCell: function (td, cellData, rowData, row, col) {
if (rowData.m != "1") {
if (cellData <= 0) {
$(td).css('background-color', '#ffd9ec');
} else if (rowData.w > cellData) {
$(td).css('background-color', '#ffd9ec');
}
}
}
},
{
data: 's',
title: '状态 ',
orderable: true,
render: function (data, type, row) {
return editStatus(data, row, index);
}
},
{
data: null,
title: '操作',
orderable: false,
className: 'text-center',
}
]
});
};
return oTableInit;
};
还有处理checkbox在表头的全选功能和关闭展开子表的按钮的功能:
javascript
// 添加表头复选框全选/取消全选功能
// 1. 监听表头复选框的点击事件
// 目标:ID为"customCheck"的表头复选框
// 事件:点击事件
$("#table1").on("click", "#customCheck", function () {
// 2. 判断表头复选框当前是否被选中
// prop("checked")返回布尔值:true表示选中,false表示未选中
if ($("#customCheck").prop("checked")) {
// 3. 如果表头复选框被选中,选中所有行的复选框
// 找到表格中所有类名为"ccall"的复选框(即每行前面的复选框)
// 设置它们的checked属性为"checked"(选中状态)
$("#table1").find(".ccall").prop("checked", "checked");
} else {
// 4. 如果表头复选框取消选中,取消选中所有行的复选框
// 将表格中所有类名为"ccall"的复选框的checked属性设置为false
$("#table1").find(".ccall").prop("checked", false);
}
});
// 监听表格中展开/折叠按钮的点击事件
// 目标:表格中类名为"details-control"的单元格(包含展开图标)
// 事件:点击事件
$('#table1').on('click', 'td.details-control', function () {
// 1. 获取当前点击行和行数据
// tr: 获取包含当前点击单元格的整行元素
var tr = $(this).closest('tr');
// 2. 获取DataTable行对象
// row: 通过DataTable API获取当前行的数据对象和操作方法
var row = $('#table1').DataTable().row(tr);
// 3. 获取当前行的完整数据
// data: 包含该行所有字段值的对象(如n、i、gid等)
var data = row.data();
// 4. 判断子表格当前是否已展开
// isShown(): DataTable API方法,返回布尔值,表示子行是否已显示
if (row.child.isShown()) {
// 5. 如果子表格已展开,执行折叠操作
// a. 隐藏子表格(移除子行DOM)
row.child.hide();
// b. 从当前行移除"shown"类(用于CSS样式控制)
tr.removeClass('shown');
// c. 切换展开图标:从"-"(减号)变为"+"(加号)
// 表示现在可以再次展开
$(this).find('i').removeClass('glyphicon-minus').addClass('glyphicon-plus');
} else {
// 6. 如果子表格未展开,执行展开操作
// a. 创建子表格容器
// 创建一个div元素,用于放置子表格
var childContainer = $('<div class="child-table-container"></div>');
// b. 显示子表格(添加子行DOM)
// child(): 创建子行内容并显示
row.child(childContainer).show();
// c. 调用子表格初始化函数
// 参数1: row.index() - 当前行在DataTable中的索引
// 参数2: data - 当前行的完整数据
// 参数3: childContainer - 子表格容器(相当于Bootstrap Table中的$detail)
oTable.InitSubTable(row.index(), data, childContainer);
// d. 为当前行添加"shown"类(用于CSS样式控制)
tr.addClass('shown');
// e. 切换展开图标:从"+"(加号)变为"-"(减号)
// 表示现在可以折叠
$(this).find('i').removeClass('glyphicon-plus').addClass('glyphicon-minus');
}
});
基本上转换应该就好了,如果有缺漏,希望各位佬告知我补充下去<(^-^)>
二、给DataTables加滚动条
- 方法比较粗暴,用一个
div容器包住表格,自定义滚动条,滚动条轨道宽度与表格宽度一致,容器宽度 ≥ \ge ≥ 表格宽度 → 隐藏滚动条(空间足够),容器宽度 < < < 表格宽度 → 显示滚动条(需要滚动) - 仍旧使用前面使用的例子,虽然没有tfoot,但做法是一样的
- 这一段可通用,建议放在initComplete中
javascript
const tableElement=$('#table1');
const tableDiv = $('<div id="tableDiv" class="w-100"></div>').insertAfter(tableElement);
tableElement.appendTo(tableDiv);
// 设置表格最小宽度,防止收缩过度
tableElement.css('min-width', 900+'px');
const tableWidth=tableElement.innerWidth();
// 创建滚动条结构并放在tableDiv之下,与表格底部对齐
const scrollbar = $('<div class="sticky-scrollbar" id="fakeScrollbar"><div class="scrollbar-track"></div></div>');
scrollbar.insertAfter(tableDiv);
// 使用表格的可见宽度设置滚动条宽度
$('.scrollbar-track',scrollbar).css('width', tableWidth);
// 空间足够时隐藏滚动条,使用容器宽度与表格宽度比较
const containerWidth = tableDiv.innerWidth();
const currentTableWidth = tableWidth;
if (containerWidth >= currentTableWidth) {
scrollbar.attr('hidden', 'hidden');
} else {
scrollbar.removeAttr('hidden');
}
// 6. 实现滚动同步
const fakeScrollbar = scrollbar[0];
const realContent = tableDiv[0];
// 监听真实内容的滚动,同步到假滚动条
realContent.addEventListener('scroll', function() {
// 使用百分比计算进行滚动同步
const scrollPercent = (this.scrollLeft / (this.scrollWidth - this.clientWidth)) * 100;
fakeScrollbar.scrollLeft = (scrollPercent / 100) * (fakeScrollbar.scrollWidth - fakeScrollbar.clientWidth);
});
// 监听假滚动条的滚动,同步到真实内容
fakeScrollbar.addEventListener('scroll', function() {
const scrollPercent = (this.scrollLeft / (this.scrollWidth - this.clientWidth)) * 100;
realContent.scrollLeft = (scrollPercent / 100) * (realContent.scrollWidth - realContent.clientWidth);
});
// 7. 使用Intersection Observer检测可见性,但尊重空间足够时隐藏的条件
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 可见时,根据空间是否足够决定是否显示滚动条
updateScrollbarVisibility();
} else {
// 不可见时隐藏滚动条
fakeScrollbar.style.visibility = 'hidden';
fakeScrollbar.style.opacity = '0';
}
});
}, {
threshold: [0, 0.1, 1], //多点检测
});
observer.observe(realContent);
// 添加窗口大小变化监听,在窗口大小变化时重新检查滚动条显示条件
window.addEventListener('resize', updateScrollbarVisibility);
// 滚动条显示控制逻辑
function updateScrollbarVisibility() {
const containerWidth = tableDiv.innerWidth();
const currentTableWidth = tableElement.innerWidth();
// 更新滚动条宽度
$('.scrollbar-track', scrollbar).css('width', currentTableWidth);
if (containerWidth >= currentTableWidth) {
// 空间足够,隐藏滚动条
scrollbar.attr('hidden', 'hidden');
} else {
// 空间不足,显示滚动条
scrollbar.removeAttr('hidden');
fakeScrollbar.style.visibility = 'visible';
fakeScrollbar.style.opacity = '1';
}
}
还有要加上的css:
css
#tableDiv {
overflow: auto;
scrollbar-width: none;
}
/* 滚动条样式 */
.sticky-scrollbar {
position: sticky;
bottom: 0;
height: 15px;
background: #e0e0e0;
z-index: 10;
overflow-x: auto;
overflow-y: hidden;
}
.scrollbar-track {
height: 10px;
background: #888;
border-radius: 7px;
}