主要是在数据报表这块, 做了好几年发现, 其实用户最终想要看的并不是酷炫的BI大屏, 而是最基础也是最复杂的 中国式报表. 更多就是倾向于从表格中去获取数据信息, 最简单的就是最好的, 于是还是来总结一下表格这块的东西.

基础表格

先来实现一个最基础的表格, 用 table 标签, 为更好语义话, 用 thead, tbody 将表格分为表头和表数据,

同时给表格行添加一个悬浮的的效果, 表头也进行加粗这样的, 稍微好看一点.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>基础表格</title>
<style>
/* 单元格边框合并, 宽度 100% */
table {
border-collapse: collapse;
width: 100%;
}
/* 每个格子都加上边框 */
th, td {
border: 1px solid #ccc;
text-align: center;
height: 2em;
}
/* 表头加个背景 */
th {
background-color: #f8f8f9;
}
/* 行加一个悬停 */
tr:hover {
background-color: #f8f8f9;
} </style>
</head>
<body>
<div class="tableCon">
<table>
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
</tr>
</thead>
<tbody>
<tr>
<td>小王</td>
<td>28</td>
<td>男</td>
</tr>
<tr>
<td>小红</td>
<td>24</td>
<td>女</td>
</tr>
<tr>
<td>小张</td>
<td>18</td>
<td>男</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

表格字段可拖拽列宽

这个功能很重要, 尤其是对用户的一些交互上

// 拖拽列宽
var table = document.querySelector('table')
var resizableColumn = null;
var startX = 0;
var startWidth = 0; table.addEventListener("mousedown", function (event) {
if (event.target.tagName === "TH" && event.offsetX > event.target.offsetWidth - 10) {
resizableColumn = event.target;
startX = event.pageX;
startWidth = resizableColumn.offsetWidth;
}
}); document.addEventListener("mousemove", function (event) {
if (resizableColumn) {
var newWidth = startWidth + (event.pageX - startX);
resizableColumn.style.width = newWidth + "px";
}
}); document.addEventListener("mouseup", function () {
resizableColumn = null;
});

固定首行首列

很多推荐的方式, 包括一些UI框架都是通过三至四个表格组合,然后js处理同步滚动来实现,这样的好处是容易实现,PC端也不会出现什么问题。

但是在手机端时,会有严重的不同步滚动现象,处理的不好时,甚至会出现错位等,体验不好. 其实我们用纯 css 实现即可.

  • table-layout: fiexd
  • position: sticky

为了让表格呈现滚动效果, 需给表格容器设置宽度, 同时给表格布局为 table-layout: fixed

表格的 fiexd 布局下, 单元格宽度根据第一行进行设定, 提前确定下来而不用再动态去根据内容计算, 可以加快渲染速度, 即我们可以事先固定表格宽度, 内容撑大则隐藏啥的操作 (针对数据指标)

固定行列则给需设置的行/列 单元格进行 position: sticky. 它的表现类似于 relative + fixed 的合体, 当超过目标区域时, 会固定于目标的位置.

    <style>
.table-con {
width: 200px;
height: 300px;
overflow: auto;
border: 1px solid gray;
border-right: 0;
border-bottom: 0;
} table {
table-layout: fixed;
border-collapse: separate;
border-spacing: 0;
width: 100%;
} td,
th {
/* 设置td,th宽度高度 */
width: 100px;
height: 2em; /* 超出宽度显示为... */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
/* 只设置右, 下的边框线 */
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
th {
background-color: #f8f8f8;
} /* 列首永远固定在头部 */
thead tr th {
position: sticky;
top: 0;
}
/* 首行永远固定在左侧 */
td:first-child,
th:first-child {
position: sticky;
left: 0;
z-index: 1;
background-color: #f8f8f8;
} th:first-child {
z-index: 2;
background-color: #f8f8f8;
}
</style>

动态数据生成表格

就模拟一下后端传过来的 JSON 数据而已, 然后这里用原生 js 来创建表格和渲染数据而已.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态表格</title>
<style> /* 同上面一样的内容 */ </head>
<body>
<div id="table-con"></div> <script>
// 模拟后端传过来的数据
var tableData = [
{ name: 'youge', age: 28, sex: 'M'},
{ name: 'yaya', age: 18, sex: 'F'},
{ name: 'jiezai', age: 30, sex: 'M'},
] // 当行数到 10w, 浏览器就有点卡了
// for (var i = 1; i < 100000; i++) {
// tableData.push({ name: 'test', age: 30, sex: 'F'})
// } var tableCon = document.getElementById('table-con')
var table = document.createElement('table')
var thead = document.createElement('thead')
var tbody = document.createElement('tbody') // 创建表头, 并获取第一个对象的键作为表头
headRow = document.createElement('tr')
headCells = Object.keys(tableData[0]) for (var i = 0; i < headCells.length; i++) {
var th = document.createElement('th')
th.innerHTML = headCells[i]
headRow.appendChild(th)
}
// 将表头挂载到 thead中
thead.appendChild(headRow) // 创建表格数据行, tr, td 根据数据
for (var i = 0; i < tableData.length; i++) {
var tr = document.createElement('tr')
for (var j = 0; j < headCells.length; j++) {
const td = document.createElement('td')
td.innerHTML = tableData[i][headCells[j]]
tr.appendChild(td)
}
// 将每行数据挂载到 tbody 下
tbody.appendChild(tr)
} // 表格树: tableCon -> table -> thead/tbody -> tr -> td
table.appendChild(thead)
table.appendChild(tbody)
tableCon.appendChild(table)
</script>
</body>
</html>

这里的模拟数据也可以这样做一个 fake 数据生成, 方便后续的测试.

// 模拟后端传过来的数据
function genData(rows, columns) {
var arr = []
for (var i = 0; i < rows; i++) {
var obj = {i}
for (var j = 0; j < columns; j++) {
obj[j] = j
}
arr.push(obj)
}
return arr
} // 生成一个 1000行, 30列的 对象数据 [ {}, {}, {}...]
var tableData = genData(10000, 30

表格分页

如果表格内容过多,我们可以添加分页功能,使用户能够浏览大量数据。

这要结合后端接口来实现, 这里我们可用通过数据行隐藏的方式来模拟分页的实现:

前端

  • pageNum: 前端传给后端, 要访问的页码
  • pageSise: 前端传给后端, 每页显示的数量

后端

  • totalCount: 假设数据是按照自增id存储的, 先算出总的数据量 totalCount , 假设是 101
  • totalPages: 根据前端传过来的每页数量 pageSize, 假设是 5, 则算出一共要分 ceil( 101 / 5 ) = 21 页
  • currentPageData: 从总表总截取当前页条数返回, 偏移位置为: pageSize * pageNum, 偏移量是 pageSize
select * from tb limit [offset], rows

-- 假设要获取第1页, 每页5行, 则:(1-1) * 5, 5; id 为 1, 2, 3, 4, 5
select * from tb limit 0, 5 -- 假设要获取第4页, 每页4行, 则:(4-1) * 5, 5; id 为 16, 17, 18, 19
select * from tb limit 15, 5

数据的开始索引 startIndex = (pageNum -1) * pageSize,

数据的结束索引 endIndex = startIndex + pageSize

<div id="table-con"></div>
<div id="paginationContainer"></div>
// 表格数据渲染部分 ...

// 表格分页
var pageSize = 10 // 每页显示行数
var currentPage = 1 // 当前页码 // 渲染某个页码的数据 (模拟接口就筛选出来和隐藏其他行)
function displayOnePage(page) {
// var table = document.getElementsByTagName('table')
var rows = table.rows
var totalRows = rows.length - 1 // 减去表头行 // 计算起始行索引 和 结束行索引
var startIndex = (page - 1) * pageSize + 1
var endIndex = Math.min(startIndex + pageSize - 1, totalRows) // 遍历表格行, 只显示当前页, 其余的隐藏 (真实环境是后端直接返回当前页哦)
for (var i = 1; i <= totalRows; i++) {
if ( i >= startIndex && i <= endIndex) {
console.log(i);
rows[i].style.display = ''
} else {
rows[i].style.display = 'none'
}
}
} // 分页器
function renderPagination() {
// var table = document.getElementsByTagName('table')
var rows = table.rows
var totalRows = rows.length -1 var paginationContainer = document.getElementById('paginationContainer')
paginationContainer.innerHTML = '' // 分页器页码数, 比如有 100行, 每页显示 10行, 则一共有10页
var totalPages = Math.ceil(totalRows / pageSize)
// 根据计算的页码数, 生成分页的按钮
for (var i = 1; i <= totalPages; i++) {
var btn = document.createElement('button')
btn.textContent = i
// 点击哪页, 就用上面的 displayOnePage() 来显示页面数据
btn.onclick = function () {
currentPage = parseInt(this.textContent)
displayOnePage(currentPage)
}
// 将创建的页码按钮进行挂载
paginationContainer.appendChild(btn)
}
} // 监听加载事件
document.addEventListener('DOMContentLoaded', function() {
displayOnePage(currentPage)
}) renderPagination()

对于页码可以进行样式美化一下:

/* 分页器-按钮美化 */
#paginationContainer button {
display: inline-block;
margin-right: 10px;
margin-top: 10px;
padding: 10 20px;
width: 40px;
border: none;
font-size: 16px;
color: #fff;
text-align: center;
background-color: #008CBA;
}

表格字段点击排序

原理就是获取表格数据所有行组成的类数组 rows, 通过对数组的排序 sort (fn), 里面的函数根据用户点击的是哪一列来作为排序判断的依据.

  • 获取表格数据行数组 rows
  • 根据点击的哪个列进行 sort(function( td1, td2) )
  • tbody 的内容进行替换 (追加一样效果) tbody.appendChild(rows[i])
<script>
var table = document.getElementById('myTable')
// 获取表格字段列表
var columnList = []
var headers = table.getElementsByTagName('th')
for (var i = 0; i < headers.length; i++) {
columnList.push(headers[i].textContent)
} function sortTable(colIndex) {
// 获取表格数据行, 并将其copy转为一个数组, 主要是为了调用 sort 方法
var tbody = table.tBodies[0]
var rows = tbody.getElementsByTagName('tr')
rows = Array.prototype.slice.call(rows) // 根据点击表头的 字段索引 进行比较 sort
var flag = false
rows.sort(function (row1, row2) {
var a = row1.getElementsByTagName('td')[colIndex].textContent
var b = row2.getElementsByTagName('td')[colIndex].textContent
return a > b ? 1 : -1
}) for (var i = 0; i < rows.length; i++) {
tbody.appendChild(rows[i])
}
} table.querySelector('thead').addEventListener('click', function(e) {
var column = e.target.textContent
var colIndex = columnList.indexOf(column)
sortTable(colIndex)
})
</script>

表格模糊筛选

就是遍历单元格的的每行, 每列, 找到匹配项, 然后显示该行而已. 当然是模拟哈.

htmlCopy code
<input type="text" id="searchInput" onkeyup="filterTable()" placeholder="搜索..."> <script>
// 关键字筛选
function filterTable() {
var input = document.getElementById('searchInput')
var keyword = input.value.toLowerCase() // 英文的话统一大小写 var table = document.querySelector('table')
var rows = table.getElementsByTagName("tr") // 所有的表格行 // 遍历每个单元格, 先行后列
for (var i = 0; i < rows.length; i++) {
// 获取一行内的所有列 cells
var cells = rows[i].getElementsByTagName('td') var found = false
// 遍历每个格子进行查找
for (var j = 0; j < cells.length; j++) {
var cell = cells[j]
if (cell) {
// 获取当前单元格的值 和 输入的 keyword 进行比对
var cellValue = cell.textContent || cell.innerText
if (cellValue.toLocaleLowerCase().indexOf(keyword) > -1) {
found = true
break;
}
}
}
// 如果找到就显示这些行呗
if (found) {
rows[i].style.display = ""
} else {
rows[i].style.display = "none"
}
}
}
</script>

数据响应式和打印样式

让我们使表格在不同屏幕尺寸下具有良好的显示效果,通过CSS媒体查询来实现响应式设计:

例如, 当屏幕宽度小于600px时,表格单元格的内边距和字体大小会变小,以适应小屏幕设备

为了让用户能够打印表格时获得最佳打印效果,我们可以添加打印样式, ctr + P 可以预览。

<style>
/* 鼠标悬停效果 */
tr:hover {
background-color: #f5f5f5;
} /* 响应式设计 */
@media screen and (max-width: 600px) {
th, td {
padding: 6px;
font-size: 12px;
}
} @media print {
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #000;
padding: 8px;
// 字体变红
color: red;
}
caption {
font-weight: bold;
}
}
</style>

在这个步骤中,我们使用了@media print媒体查询,定义了打印时表格的样式,使得打印出来的表格具有清晰的边框和适当的间距

导出CSV

对于简单的导出需求,如导出少量数据到CSV或JSON格式,前端可以实现。这通常通过创建一个隐藏的<a>标签,设置其href为包含数据的Blob URL,然后模拟点击这个标签来实现下载。

对于更复杂或需要特定格式的导出(如Excel、PDF等),后端通常更合适。后端可以处理数据的查询、转换和格式化,然后生成相应的文件供前端下载。

当数据量很大时,前端处理可能会导致性能问题或浏览器崩溃。在这种情况下,后端可以处理数据的分页、筛选和导出。后端还可以处理权限和安全性问题,确保只有授权的用户可以导出数据。

因此最佳的方案是, 前端传查询条件, 后端来导出.

  • 前端负责触发导出操作,并将必要的参数(如筛选条件、排序等)传递给后端。
  • 后端根据这些参数查询数据库,处理数据,并生成导出文件。然后,后端将文件发送给前端,前端负责显示下载链接或触发下载操作。
  • 解决中文乱码问题: blob = new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type });
// 前端导出csv
function exportTableToCSV() {
var csv = '';
var rows = document.querySelectorAll('table tr'); for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var rowArray = []; for (var j = 0; j < row.cells.length; j++) {
var cell = row.cells[j];
rowArray.push(cell.textContent.replace(/(\r\n|\n|\r)/gm, ''));
}
csv += rowArray.join(',') + '\n';
} var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
// 专门来处理中文问题, 强制编码为 unicode
blob = new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type }); var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.setAttribute('download', 'table.csv');
a.click();
}

基本常用的一些功能就这些了, 对于表格, 主要我是搞数据为主, 因此核心是能显示数据, 数据排序, 检索数据 和下载, 基本就行了. 其实根本就可以不用各类 ui 组件, 原生的就行啦.

原生JS表格数据常用总结的更多相关文章

  1. 原生js实现数据双向绑定

    最近接触了vue,在谈到vue等等的mvvm框架之前,先了解什么是数据双向绑定以及如何利用原生JS实现数据双向绑定 单向数据绑定 指先把模板写好,然后把模板和数据(数据可能来自后台)整合到一起形成HT ...

  2. 原生js实现数据的双向绑定

    原生js实现数据的双向绑定 需要了解的属性是原色js的Object.definePrototype(obj,pop,descript); obj:被构造的对象 pop:被构造的对象的属性,创建对象或修 ...

  3. 通过原生js实现数据的双向绑定

    通过js实现数据的双向绑定 : Object.defineProperty了解 语法: Object.defineProperty(obj, prop, descriptor) obj 要定义属性的对 ...

  4. 原生JS的对象常用操作总结

       前端时间写了篇怎么判断js对象相等的文章,一直在期待大神给点消息,无奈一直杳无音讯,还是自己写个函数来进行判断,下面总结一些常用的对象操作的方法.    咋们来个先抑后扬的方式,先放出几个基本的 ...

  5. 原生js实现数据单向绑定

    Object.defineProperty()方法直接在对象上定义一个新属性,或修改对象上的现有属性,并返回该对象. Object.defineProperty(obj, prop, descript ...

  6. 原生JS表格行拖动排序,添加了回调功能

    function tableDnD(el, callback) { if (typeof (el) == "string") { el = document.getElementB ...

  7. 二.jquery.datatables.js表格数据添加

    1.后台php public function addtable(){ $data = $_POST; if(M('yanfa_project')->add($data)){ $this-> ...

  8. 原生js实现双向数据绑定

    一.两个model之间的双向绑定 var o = { a: 0 } o.b = o.a + 1; console.log(o.a); // "0" console.log(o.b) ...

  9. Ajax进阶之原生js与跨域jsonp

    什么是Ajax? 两个数求和: 用Jquery和数据用json格式 viws函数: from django.shortcuts import render,HttpResponse # Create ...

  10. 用原生 JS 实现双向绑定及应用实例

    写在前面: 所谓的双向绑定,无非是从界面的操作能实时反映到数据,数据的变更也能实时展现到界面.angular封装了双向绑定的方法,使双向绑定变得十分简单.但是在有些场景下(比如下面那个场景),不能使用 ...

随机推荐

  1. WPF 事件实现MVVM中的Command绑定

    1. 在ViewModel中弹出消息提示框,需要添加下面的代码块: <dxmvvm:Interaction.Behaviors> <dx:DXMessageBoxService /& ...

  2. linux的使用(2)

    1,覆盖 > cat 文件名a>文件名b:将文档a覆盖文档b 2,追加 >> cat 文档名a>>文档名b:将文档a追加到文档b后 追加错误 上图所示:尽量使用字母 ...

  3. FANUC机器人M-410iB/700电机断轴维修方法

    发那科(FANUC)作为电机领域的领袖品牌,其伺服电机广泛应用于各种工业设备中,特别是在机床.自动化控制.机器人等领域.然而,即使是如此高品质的伺服电机,也难免会出现FANUC工业机械手电机故障,其中 ...

  4. 音乐在线刮削容器部署(Music Tag Web)

    『音乐标签』Web版是一款可以编辑歌曲的标题,专辑,艺术家,歌词,封面等信息的音乐标签编辑器程序, 支持FLAC, APE, WAV, AIFF, WV, TTA, MP3, M4A, OGG, MP ...

  5. TVbox蜂蜜影视_v3.1.6:智能电视观影新选择,简洁界面与强大功能兼具

    蜂蜜影视是一款基于猫影视开源项目 CatVodTVJarLoader 开发的智能电视软件,专为追求简洁与高效观影体验的用户设计.该软件从零开始编写,界面清爽,操作流畅,特别适合在智能电视上使用.其最大 ...

  6. Echarts与Vue3中获取DOM节点可能出现的异常错误

    useTemplateRef 的简单介绍 官方:返回一个浅层 ref,其值将与模板中的具有匹配 ref attribute 的元素或组件同步. 参数匹配机制‌:useTemplateRe的参数需与模板 ...

  7. MyCat分库分表-安装

    准备3台虚拟机CentOS7,一台MyCat,两台MySQL 一.安装MySQL 打开MySQL官网mysql.com根据提示安装 1.yum仓库 https://dev.mysql.com/down ...

  8. python3 报错ModuleNotFoundError: No module named 'apt_pkg'

    前言 apt update无法执行,python3 报错 ModuleNotFoundError: No module named 'apt_pkg' 这是因为将 python 版本升级后的问题 正确 ...

  9. linux php重启

    1.停止命令 你可以先查看自己的php进程有没有启动 ps -ef | grep php [root@iZ6we4yxap93y2r0clg3g8Z ~]# ps -ef | grep php roo ...

  10. nginx配置2个不同端口的应用

    如何配置nginx,在同一台服务器上,部署2个不同端口的web应用? 1,利用Django框架搭建的web应用,默认端口是8000: 2,利用Flask框架搭建的web应用,默认端口是5000: 第一 ...