LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

javascript表格组件

admin
2026年10月19日 9:59 本文热度 152

一、介绍

通过原生 JS + CSS 实现一个简单的表格组件,支持固定表头、左右固定列、三态排序、自定义单元格渲染、空数据与加载状态,零依赖、零构建,引入两个文件即可使用。

二、实现代码

  • javascript代码

class SimpleTable {  constructor(selector, options = {}) {    this.columns = options.columns || [];    this.data = options.data || [];    this.orginalData = [...this.data]; // 备份原始数据,取消排序时还原    this.sortCallback = options.sortCallback; // 传入后完全接管排序(如服务端排序)    this.placeholder = options.placeholder || "-"; // 空值占位符    this.rootEl = document.querySelector(selector);    this.initColumns();    this.init();    this.render();  }  isEmpty(val) {    return val === undefined || val === null || val === "";  }  // 初始化骨架:表头 / 表体拆成两个 table,是固定表头的关键  init() {    this.rootEl.className += " simple-table";    this.rootEl.innerHTML = `      <div class="simple-table-header">        <table style="table-layout: fixed"><colgroup></colgroup><thead></thead></table>      </div>      <div class="simple-table-body">        <table style="table-layout: fixed"><colgroup></colgroup><tbody></tbody></table>        <div class="no-data">暂无数据</div>      </div>      <div class="simple-table-loading" style="display: none">加载中</div>`;    this.headWrapperEl = this.rootEl.querySelector(".simple-table-header");    this.bodyWrapperEl = this.rootEl.querySelector(".simple-table-body");    this.headColgroupEl = this.rootEl.querySelector(      ".simple-table-header colgroup"    );    this.bodyColgroupEl = this.rootEl.querySelector(      ".simple-table-body colgroup"    );    this.headEl = this.rootEl.querySelector("thead");    this.bodyEl = this.rootEl.querySelector("tbody");    this.loadingEl = this.rootEl.querySelector(".simple-table-loading");    this.syncScroll();  }  // 表体滚动时把 scrollLeft 同步给表头 => 表头"固定"  syncScroll() {    this.bodyWrapperEl.onscroll = () => {      this.headWrapperEl.scrollLeft = this.bodyWrapperEl.scrollLeft;    };  }  // 计算固定列的 left / right 偏移,供 sticky 定位使用  initColumns() {    let left = 0;    let right = 0;    const len = this.columns.length;    for (let i = 0; i < len; i++) {      const item = this.columns[i];      item.width = item.width || "auto";      item._className = "";      item._style = "";      if (item.fixed === "left") {        item._style = `left:${left}px;`;        left += parseInt(item.width);        item._className = "fixed";      }      const rightItem = this.columns[len - i - 1];      if (rightItem.fixed === "right") {        rightItem._style = `right:${right}px;`;        right += parseInt(rightItem.width);        rightItem._className = "fixed";      }    }  }  render() {    this.renderHeader();    this.renderData();  }  // 更新数据  setData(data) {    this.data = data;    this.orginalData = [...data];    this.renderData();  }  // 显示 / 隐藏加载遮罩  loading(val) {    this.loadingEl.style.display = val ? "flex" : "none";  }  renderHeader() {    // 两个 table 共用同一份 colgroup,保证列宽严格对齐    const colgroupHtml = this.columns      .map((c) => `<col width="${c.width}">`)      .join("");    const headHtml = `<tr>${this.columns      .map(        (c) => `<th class="${c._className} ${          c.sort ? "simple-table-sort" : ""        }" style="${c._style}"          data-key="${c.key}" ${c.sort ? `data-sort="${c._sort || ""}"` : ""}>${          c.title        }</th>`      )      .join("")}</tr>`;    this.headColgroupEl.innerHTML = colgroupHtml;    this.bodyColgroupEl.innerHTML = colgroupHtml;    this.headEl.innerHTML = headHtml;    // 排序:asc -> desc -> 取消 三态循环    this.headEl.querySelectorAll(".simple-table-sort").forEach((th) => {      th.onclick = (e) => {        const current = e.target.dataset.sort;        const next =          current === "asc" ? "desc" : current === "desc" ? "" : "asc";        const key = e.target.dataset.key;        // 先清空所有列的排序状态        this.headEl.querySelectorAll("th").forEach((t) => {          t.classList.remove("asc", "desc");          t.dataset.sort = "";        });        e.target.dataset.sort = next;        e.target.classList.add(next);        if (typeof this.sortCallback === "function") {          this.sortCallback({ key, sort: next }); // 服务端排序场景        } else {          if (next === "asc") this.data.sort((a, b) => a[key] - b[key]);          else if (next === "desc") this.data.sort((a, b) => b[key] - a[key]);          else this.data = [...this.orginalData]; // 取消排序,还原数据          this.renderData();        }      };    });  }  renderData() {    this.bodyEl.innerHTML = this.data      .map(        (row, index) =>          `<tr>${this.columns            .map((col) => {              // 支持自定义单元格渲染              const content = col.render                ? col.render({                    value: row[col.key],                    key: col.key,                    row,                    index,                    data: this.data,                  })                : this.isEmpty(row[col.key])                ? this.placeholder // 空值占位                : row[col.key];              return `<td class="${col._className}" style="${col._style}">${content}</td>`;            })            .join("")}</tr>`      )      .join("");    // 切换空数据状态    this.rootEl.classList.toggle("simple-table-empty", !this.data.length);  }}   

  • css代码 

/* css部分 *//* 组件整体:flex 纵向布局,表体占满剩余空间并滚动 */.simple-table {  overflow: hidden;  display: flex;  flex-direction: column;  position: relative;}/* 表头容器隐藏溢出,滚动位置由 JS 同步 */.simple-table .simple-table-header {  overflow: hidden;}.simple-table .simple-table-body {  overflow: auto;  flex: 1;}.simple-table table {  table-layout: fixed; /* 固定布局,配合 colgroup 严格控制列宽 */  border-collapse: collapse;  width: 100%;  font-size: 14px;}.simple-table th,.simple-table td {  padding: 14px 16px;  text-align: left;}.simple-table th {  background-color: #f7f8fa;  color: #999;}/* 固定列:sticky 定位,偏移量由 JS 计算后内联到 style 上 */.simple-table th.fixed,.simple-table td.fixed {  position: sticky;  z-index: 1;}/* 可排序表头 */.simple-table-sort {  position: relative;  cursor: pointer;}/* 上/下三角箭头 */.simple-table-sort::before,.simple-table-sort::after {  content: "";  position: absolute;  right: 2px;  border-width: 4px;  border-style: dashed;}.simple-table-sort::before {  top: 50%;  margin-top: -10px;  border-bottom-style: solid;  border-color: transparent transparent #999;}.simple-table-sort::after {  top: 50%;  margin-top: 2px;  border-top-style: solid;  border-color: #999 transparent transparent;}/* 激活态变色 */.simple-table-sort.asc::before {  border-color: transparent transparent #e62b1c;}.simple-table-sort.desc::after {  border-color: #e62b1c transparent transparent;}/* 空数据 */.simple-table .no-data {  display: none;  padding: 30px 0;  text-align: center;  color: #999;}.simple-table.simple-table-empty .no-data {  display: block;}/* 加载遮罩 */.simple-table-loading {  position: absolute;  top: 0;  left: 0;  right: 0;  bottom: 0;  z-index: 100;  display: flex;  align-items: center;  justify-content: center;  background-color: rgba(255, 255, 255, 0.8);  font-size: 14px;}

三、调用示例

<link rel="stylesheet" href="SimpleTable.css" /><script src="SimpleTable.js"></script><!-- 容器宽度决定可视宽度,高度决定表体滚动区域高度,也可以不设置 --><div id="myTable" style="width: 800px; height: 300px"></div>
const table = new SimpleTable("#myTable", {  columns: [    { title: "姓名", key: "name", width: "200px", fixed: "left" }, // 固定在左侧    { title: "分数", key: "score", width: "100px", sort: true }, // 开启排序    { title: "备注", key: "remark", width: "300px" },    {      title: "操作",      key: "op",      width: "120px",      fixed: "right", // 固定在右侧      render: ({ row }) => `<a href="javascript:;">编辑</a>`, // 自定义单元格    },  ],});table.setData([  { name: "张三", score: 90, remark: "优秀" },  { name: "李四", score: 80 },  { name: "", score: 60, remark: null }, // 空值自动显示占位符 "-"]);// table.loading(true); // 显示加载中

四、参数

  • 配置项(options)

属性
说明
默认值
columns
列配置数组,见下表
[]
data
初始数据
[]
minWidth
表格最小宽度
"100%"
placeholder
空值占位符
"-"
emptyContent
空数据自定义内容
暂无数据
loadingContent
加载中提示文案
加载中
sortCallback
自定义排序回调,接收 { key, sort },常用于服务端排序
内置排序
tableClass / trClass / thClass / tdClass / headTrClass / bodyTrClass
各层级元素的自定义 class
""
  • 列配置(column)

属性
说明
默认值
title
表头文案
—
key
对应数据字段名
—
width
列宽(固定列必须为像素值)
"auto"
fixed
固定列:"left" / "right"
—
sort
是否可排序
false
render
自定义渲染函数,参数为 { value, key, row, index, data }
—
class / thClass / tdClass
该列表头及单元格的自定义 class
""
  • 实例方法

方法
说明
setData(data)
更新数据并重新渲染表体
setOptions({ columns, data, sortCallback })
整体更新配置并重新渲染
loading(val)
显示 / 隐藏加载遮罩
setEmptyContent(content)
动态更新空数据提示内容

五、总结

通过双 table 拆分与滚动同步实现固定表头,position: sticky 配合 JS 计算偏移实现固定列,colgroup + table-layout: fixed 保证两套表格列宽严格对齐,再以模板字符串渲染数据、事件绑定实现三态排序。实现这个组件后,对各类表格组件(如 Element UI 的 Table)的底层原理会有更深入的了解。


该文章在 2026/10/19 10:01:12 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-9  粤公网安备44030602007207号