/** * TcsTable - 通用表格组件 * 提供表格数据生成、渲染、分页和筛选功能 */ class TcsTable { /** * 构造函数 * @param {Object} options - 表格配置选项 * @param {Array} data - 表格数据 */ constructor(options, data) { this.options = Object.assign( { tableId: "data-table", // 表格元素ID paginationInfoId: "pagination-info", // 分页信息元素ID paginationControlsId: "pagination-controls", // 分页控件元素ID pageSizeId: "current-page-size", // 每页显示条数元素ID pageSizeItemClass: "page-size-item", // 每页显示条数选项类名 defaultPageSize: 10, // 默认每页显示条数 maxVisiblePages: 5, // 最大可见页码数 columns: [], // 列配置 }, options ); this.data = data || []; // 原始数据 this.filteredData = [...this.data]; // 筛选后的数据 this.currentPage = 1; // 当前页码 this.itemsPerPage = this.options.defaultPageSize; // 每页显示条数 // 注册内置的表头渲染器 this.headerRenderers = { // 复选框表头渲染器 checkbox: (column, th) => { th.innerHTML = ''; // 绑定全选事件 setTimeout(() => { const selectAll = document.getElementById('select-all'); if (selectAll) { selectAll.addEventListener('change', () => { const checkboxes = document.querySelectorAll('.row-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = selectAll.checked; }); }); // 监听表格体中的复选框变化,更新全选框状态 document.addEventListener('change', (e) => { if (e.target && e.target.classList.contains('row-checkbox')) { this.updateSelectAllCheckboxState(); } }); } }, 0); return th; }, // 可以添加更多内置渲染器... }; // 允许用户注册自定义渲染器 if (options.headerRenderers) { Object.assign(this.headerRenderers, options.headerRenderers); } // 初始化表格 this.init(); } /** * 初始化表格 */ init() { // 初始化表头 this.initTableHeader(); // 渲染表格 this.renderTable(); // 绑定分页事件 this.bindPaginationEvents(); // 绑定每页显示条数事件 this.bindPageSizeEvents(); } /** * 初始化表头 */ initTableHeader() { const tableHead = document.querySelector(`#${this.options.tableId} thead tr`); if (!tableHead) return; // 清空表头 tableHead.innerHTML = ""; // 创建表头单元格 this.options.columns.forEach(column => { const th = document.createElement("th"); // 添加列的自定义类名 if (column.className) { th.className = column.className; } // 使用自定义渲染器(如果有) if (column.headerRenderer && this.headerRenderers[column.headerRenderer]) { this.headerRenderers[column.headerRenderer](column, th); } else { th.textContent = column.title; } tableHead.appendChild(th); }); } /** * 更新全选框状态 */ updateSelectAllCheckboxState() { const selectAllCheckbox = document.getElementById('select-all'); const checkboxes = document.querySelectorAll('.row-checkbox'); const checkedCheckboxes = document.querySelectorAll('.row-checkbox:checked'); if (selectAllCheckbox) { // 如果所有复选框都被选中,则全选框也被选中 selectAllCheckbox.checked = checkboxes.length > 0 && checkboxes.length === checkedCheckboxes.length; // 如果部分复选框被选中,则全选框处于不确定状态 selectAllCheckbox.indeterminate = checkedCheckboxes.length > 0 && checkboxes.length !== checkedCheckboxes.length; } } /** * 获取选中行的ID * @returns {Array} 选中行的ID数组 */ getSelectedRowIds() { const selectedIds = []; const checkedCheckboxes = document.querySelectorAll('.row-checkbox:checked'); checkedCheckboxes.forEach(checkbox => { selectedIds.push(checkbox.getAttribute('data-id')); }); return selectedIds; } /** * 渲染表格 */ renderTable() { const tableBody = document.querySelector(`#${this.options.tableId} tbody`); if (!tableBody) return; tableBody.innerHTML = ""; // 清空表格 const totalItems = this.filteredData.length; const totalPages = Math.ceil(totalItems / this.itemsPerPage); // 计算当前页的数据范围 const startIndex = (this.currentPage - 1) * this.itemsPerPage; const endIndex = Math.min(startIndex + this.itemsPerPage, totalItems); const currentPageData = this.filteredData.slice(startIndex, endIndex); // 填充表格数据 currentPageData.forEach((rowData) => { const row = document.createElement("tr"); // 遍历列配置,生成单元格 this.options.columns.forEach((column) => { const cell = document.createElement("td"); // 添加列的自定义类名 if (column.className) { cell.className = column.className; } // 获取单元格值 let value = rowData[column.field]; // 如果值为空且有默认值,则使用默认值 if ((value === undefined || value === null || value === "") && column.defaultValue !== undefined) { value = column.defaultValue; } // 如果有格式化函数,则使用格式化函数处理值 if (column.formatter && typeof column.formatter === "function") { cell.innerHTML = column.formatter(value, rowData); } else { cell.textContent = value; } row.appendChild(cell); }); tableBody.appendChild(row); }); // 更新分页信息 this.updatePagination(totalItems, totalPages); } /** * 更新分页信息和控件 * @param {Number} totalItems - 总条目数 * @param {Number} totalPages - 总页数 */ updatePagination(totalItems, totalPages) { // 更新分页信息文本 const paginationInfo = document.getElementById(this.options.paginationInfoId); if (paginationInfo) { paginationInfo.textContent = `显示第 ${ totalItems === 0 ? 0 : (this.currentPage - 1) * this.itemsPerPage + 1 } 到第 ${Math.min( this.currentPage * this.itemsPerPage, totalItems )} 条记录,共计 ${totalItems} 条记录`; } // 更新每页显示条数 const currentPageSize = document.getElementById(this.options.pageSizeId); if (currentPageSize) { currentPageSize.textContent = this.itemsPerPage; } // 更新分页控件 const paginationControls = document.getElementById( this.options.paginationControlsId ); if (!paginationControls) return; paginationControls.innerHTML = ""; // 上一页按钮 const prevDisabled = this.currentPage === 1 || totalItems === 0; const prevItem = document.createElement("li"); prevItem.className = `page-item ${prevDisabled ? "disabled" : ""}`; prevItem.innerHTML = ` `; paginationControls.appendChild(prevItem); // 页码按钮 const maxVisiblePages = this.options.maxVisiblePages; let startPage = Math.max( 1, this.currentPage - Math.floor(maxVisiblePages / 2) ); let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); if (endPage - startPage < maxVisiblePages - 1 && startPage > 1) { startPage = Math.max(1, endPage - maxVisiblePages + 1); } // 添加页码 for (let i = 1; i <= totalPages; i++) { // 显示第一页、最后一页和当前页附近的页码 if ( i === 1 || i === totalPages || (i >= startPage && i <= endPage) ) { const pageItem = document.createElement("li"); pageItem.className = `page-item ${ i === this.currentPage ? "active" : "" }`; pageItem.innerHTML = `${i}`; paginationControls.appendChild(pageItem); } // 添加省略号 else if (i === startPage - 1 || i === endPage + 1) { const ellipsisItem = document.createElement("li"); ellipsisItem.className = "page-item disabled"; ellipsisItem.innerHTML = '...'; paginationControls.appendChild(ellipsisItem); } } // 下一页按钮 const nextDisabled = this.currentPage === totalPages || totalItems === 0; const nextItem = document.createElement("li"); nextItem.className = `page-item ${nextDisabled ? "disabled" : ""}`; nextItem.innerHTML = ` `; paginationControls.appendChild(nextItem); } /** * 绑定分页事件 */ bindPaginationEvents() { const paginationControls = document.getElementById( this.options.paginationControlsId ); if (!paginationControls) return; paginationControls.addEventListener("click", (e) => { e.preventDefault(); // 检查点击的是否是分页链接 const pageLink = e.target.closest(".page-link"); if (!pageLink) return; // 检查链接是否被禁用 if (pageLink.getAttribute("aria-disabled") === "true") return; // 获取目标页码 const targetPage = parseInt(pageLink.getAttribute("data-page")); if (isNaN(targetPage)) return; // 更新当前页码 this.currentPage = targetPage; // 重新渲染表格 this.renderTable(); }); } /** * 绑定每页显示条数事件 */ bindPageSizeEvents() { const pageSizeItems = document.querySelectorAll(`.${this.options.pageSizeItemClass}`); pageSizeItems.forEach((item) => { item.addEventListener("click", (e) => { e.preventDefault(); // 获取新的每页显示条数 const newItemsPerPage = parseInt(item.getAttribute("data-size")); if (isNaN(newItemsPerPage)) return; // 更新每页显示条数 this.itemsPerPage = newItemsPerPage; this.currentPage = 1; // 重置为第一页 // 重新渲染表格 this.renderTable(); }); }); } /** * 筛选数据 * @param {Function} filterFn - 筛选函数 */ filter(filterFn) { if (typeof filterFn !== "function") { this.filteredData = [...this.data]; } else { this.filteredData = this.data.filter(filterFn); } this.currentPage = 1; // 重置为第一页 this.renderTable(); // 重新渲染表格 } /** * 搜索数据 * @param {String} keyword - 搜索关键字 * @param {Array} fields - 要搜索的字段 */ search(keyword, fields) { if (!keyword) { this.filteredData = [...this.data]; } else { const lowerKeyword = keyword.toLowerCase(); this.filteredData = this.data.filter((item) => { return fields.some((field) => { const value = item[field]; if (value === undefined || value === null) return false; // 处理对象类型的值 if (typeof value === "object" && value.text) { return value.text.toLowerCase().includes(lowerKeyword); } return String(value).toLowerCase().includes(lowerKeyword); }); }); } this.currentPage = 1; // 重置为第一页 this.renderTable(); // 重新渲染表格 } /** * 重置筛选 */ resetFilter() { this.filteredData = [...this.data]; this.currentPage = 1; // 重置为第一页 this.renderTable(); // 重新渲染表格 } /** * 更新数据 * @param {Array} data - 新数据 */ updateData(data) { this.data = data || []; this.filteredData = [...this.data]; this.currentPage = 1; // 重置为第一页 this.renderTable(); // 重新渲染表格 } /** * 添加数据 * @param {Object|Array} data - 要添加的数据 */ addData(data) { if (Array.isArray(data)) { this.data = [...this.data, ...data]; } else { this.data.push(data); } this.filteredData = [...this.data]; this.renderTable(); // 重新渲染表格 } /** * 删除数据 * @param {Function} predicate - 判断函数 */ removeData(predicate) { if (typeof predicate !== "function") return; this.data = this.data.filter((item) => !predicate(item)); this.filteredData = [...this.data]; this.renderTable(); // 重新渲染表格 } /** * 生成模拟订单数据 * @param {Number} count - 数据条数 * @returns {Array} - 生成的数据 */ static generateOrderData(count = 100) { const statusOptions = [ { text: "进行中", class: "bg-success" }, { text: "待处理", class: "bg-warning" }, { text: "已完成", class: "bg-info" }, { text: "已取消", class: "bg-danger" }, { text: "已超时", class: "bg-secondary" }, ]; const locations = ["A区", "B区", "C区", "D区", "E区", "F区"]; const goods = [ "原材料", "半成品", "成品", "包装材料", "零部件", "工具", ]; const orders = []; for (let i = 1; i <= count; i++) { // 生成订单ID const id = `ORD-${String(i).padStart(3, "0")}`; // 生成订单时间(过去30天内的随机时间) const now = new Date(); const randomDays = Math.floor(Math.random() * 30); const randomHours = Math.floor(Math.random() * 24); const randomMinutes = Math.floor(Math.random() * 60); const orderDate = new Date(now); orderDate.setDate(now.getDate() - randomDays); orderDate.setHours(now.getHours() - randomHours); orderDate.setMinutes(now.getMinutes() - randomMinutes); const orderTime = `${orderDate.getFullYear()}-${String( orderDate.getMonth() + 1 ).padStart(2, "0")}-${String(orderDate.getDate()).padStart( 2, "0" )} ${String(orderDate.getHours()).padStart(2, "0")}:${String( orderDate.getMinutes() ).padStart(2, "0")}`; // 随机选择起点和终点 const from = `${ locations[Math.floor(Math.random() * locations.length)] }-${Math.floor(Math.random() * 20) + 1}号仓位`; let to; do { to = `${ locations[Math.floor(Math.random() * locations.length)] }-${Math.floor(Math.random() * 20) + 1}号仓位`; } while (to === from); // 确保起点和终点不同 // 随机选择货物 const good = goods[Math.floor(Math.random() * goods.length)]; // 生成期限(订单时间后的1-24小时) const deadlineDate = new Date(orderDate); deadlineDate.setHours( orderDate.getHours() + Math.floor(Math.random() * 24) + 1 ); const deadline = `${deadlineDate.getFullYear()}-${String( deadlineDate.getMonth() + 1 ).padStart(2, "0")}-${String(deadlineDate.getDate()).padStart( 2, "0" )} ${String(deadlineDate.getHours()).padStart(2, "0")}:${String( deadlineDate.getMinutes() ).padStart(2, "0")}`; // 随机选择状态 const status = statusOptions[Math.floor(Math.random() * statusOptions.length)]; // 生成开始和结束时间 let startTime = ""; let endTime = ""; if (status.text !== "待处理") { // 如果不是待处理状态,则有开始时间 const startDate = new Date(orderDate); startDate.setMinutes( startDate.getMinutes() + Math.floor(Math.random() * 60) ); startTime = `${startDate.getFullYear()}-${String( startDate.getMonth() + 1 ).padStart(2, "0")}-${String(startDate.getDate()).padStart( 2, "0" )} ${String(startDate.getHours()).padStart(2, "0")}:${String( startDate.getMinutes() ).padStart(2, "0")}`; // 如果是已完成或已取消或已超时状态,则有结束时间 if ( status.text === "已完成" || status.text === "已取消" || status.text === "已超时" ) { const endDate = new Date(startDate); endDate.setMinutes( endDate.getMinutes() + Math.floor(Math.random() * 120) ); endTime = `${endDate.getFullYear()}-${String( endDate.getMonth() + 1 ).padStart(2, "0")}-${String(endDate.getDate()).padStart( 2, "0" )} ${String(endDate.getHours()).padStart(2, "0")}:${String( endDate.getMinutes() ).padStart(2, "0")}`; } } // 创建订单对象 const order = { id, orderTime, from, to, good, deadline, startTime, endTime, status, }; orders.push(order); } return orders; } /** * 生成模拟车辆数据 * @param {Number} count - 数据条数 * @returns {Array} - 生成的数据 */ static generateVehicleData(count = 100) { const statusOptions = [ { text: "空闲", class: "bg-success" }, { text: "任务中", class: "bg-warning" }, { text: "充电中", class: "bg-info" }, { text: "维修中", class: "bg-danger" }, { text: "离线", class: "bg-secondary" }, ]; const vehicleTypes = ["AGV-小车", "AGV-叉车", "AGV-拖车", "AGV-搬运车", "AGV-牵引车"]; const locations = ["A区", "B区", "C区", "D区", "E区", "F区"]; const vehicles = []; for (let i = 1; i <= count; i++) { // 生成车辆ID const id = `AGV-${String(i).padStart(3, "0")}`; // 随机选择车辆类型 const type = vehicleTypes[Math.floor(Math.random() * vehicleTypes.length)]; // 随机生成电量 (0-100) const battery = Math.floor(Math.random() * 101); // 随机选择位置 const location = `${ locations[Math.floor(Math.random() * locations.length)] }-${Math.floor(Math.random() * 20) + 1}号位置`; // 随机选择状态 const status = statusOptions[Math.floor(Math.random() * statusOptions.length)]; // 随机生成运行时间 (0-10000小时) const runtime = Math.floor(Math.random() * 10000); // 创建车辆对象 const vehicle = { id, type, battery, location, status, runtime: `${runtime}小时`, lastMaintenance: `${Math.floor(Math.random() * 365)}天前`, }; vehicles.push(vehicle); } return vehicles; } } // 如果在Node.js环境中,导出模块 if (typeof module !== 'undefined' && module.exports) { module.exports = TcsTable; }