/**
* 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;
}