mirror of
https://github.com/liubinfighter/csv-lite.git
synced 2026-07-22 12:20:28 +00:00
185 lines
6.5 KiB
TypeScript
185 lines
6.5 KiB
TypeScript
import { TableUtils } from "../utils/table-utils";
|
|
import { CSVUtils } from "../utils/csv-utils";
|
|
import { i18n } from "../i18n";
|
|
|
|
export interface TableRenderOptions {
|
|
tableData: string[][];
|
|
columnWidths: number[];
|
|
autoResize: boolean;
|
|
tableEl: HTMLElement;
|
|
editInput: HTMLInputElement;
|
|
activeCellEl: HTMLInputElement | null;
|
|
activeRowIndex: number;
|
|
activeColIndex: number;
|
|
setActiveCell: (row: number, col: number, cellEl: HTMLInputElement) => void;
|
|
saveSnapshot: () => void;
|
|
requestSave: () => void;
|
|
setupAutoResize: (input: HTMLInputElement) => void;
|
|
adjustInputHeight: (input: HTMLInputElement) => void;
|
|
selectRow: (rowIndex: number) => void;
|
|
selectColumn: (colIndex: number) => void;
|
|
getColumnLabel: (index: number) => string;
|
|
setupColumnResize: (handle: HTMLElement, columnIndex: number) => void;
|
|
insertRowAt: (rowIndex: number, after?: boolean) => void;
|
|
deleteRowAt: (rowIndex: number) => void;
|
|
insertColAt: (colIndex: number, after?: boolean) => void;
|
|
deleteColAt: (colIndex: number) => void;
|
|
}
|
|
|
|
export function renderTable(options: TableRenderOptions) {
|
|
const {
|
|
tableData,
|
|
columnWidths,
|
|
autoResize,
|
|
tableEl,
|
|
editInput,
|
|
activeCellEl,
|
|
activeRowIndex,
|
|
activeColIndex,
|
|
setActiveCell,
|
|
saveSnapshot,
|
|
requestSave,
|
|
setupAutoResize,
|
|
adjustInputHeight,
|
|
selectRow,
|
|
selectColumn,
|
|
getColumnLabel,
|
|
setupColumnResize,
|
|
insertRowAt,
|
|
deleteRowAt,
|
|
insertColAt,
|
|
deleteColAt,
|
|
} = options;
|
|
|
|
tableEl.empty();
|
|
|
|
// 创建表头行(包含列号)
|
|
const headerRow = tableEl.createEl("thead").createEl("tr");
|
|
|
|
// 计算初始列宽(如果未设置)
|
|
if (columnWidths.length === 0 && tableData[0]) {
|
|
const widths = TableUtils.calculateColumnWidths(tableData);
|
|
columnWidths.splice(0, columnWidths.length, ...widths);
|
|
}
|
|
|
|
// 添加左上角单元格
|
|
const cornerTh = headerRow.createEl("th", { cls: "csv-corner-cell" });
|
|
// 可选:在左上角添加插入列/行按钮
|
|
|
|
// 创建列号行
|
|
if (tableData[0]) {
|
|
tableData[0].forEach((headerCell, index) => {
|
|
const th = headerRow.createEl("th", {
|
|
cls: "csv-col-number",
|
|
attr: {
|
|
style: `width: ${columnWidths[index] || 100}px`,
|
|
},
|
|
});
|
|
th.textContent = getColumnLabel(index);
|
|
th.onclick = (e) => {
|
|
e.stopPropagation();
|
|
selectColumn(index);
|
|
};
|
|
// 插入列操作按钮
|
|
const insertLeft = th.createEl("button", { cls: "csv-insert-col-btn left" });
|
|
insertLeft.innerText = "+";
|
|
insertLeft.title = i18n.t("buttons.insertColBefore") || "Insert column before";
|
|
insertLeft.onclick = (e) => { e.stopPropagation(); options.insertColAt(index, false); };
|
|
const insertRight = th.createEl("button", { cls: "csv-insert-col-btn right" });
|
|
insertRight.innerText = "+";
|
|
insertRight.title = i18n.t("buttons.insertColAfter") || "Insert column after";
|
|
insertRight.onclick = (e) => { e.stopPropagation(); options.insertColAt(index, true); };
|
|
const delCol = th.createEl("button", { cls: "csv-del-col-btn" });
|
|
delCol.innerText = "-";
|
|
delCol.title = i18n.t("buttons.deleteColumn") || "Delete column";
|
|
delCol.onclick = (e) => { e.stopPropagation(); options.deleteColAt(index); };
|
|
});
|
|
}
|
|
|
|
// 创建表头数据行
|
|
const dataHeaderRow = tableEl.createEl("thead").createEl("tr");
|
|
const headerRowNumber = dataHeaderRow.createEl("th", { cls: "csv-row-number" });
|
|
headerRowNumber.textContent = "0";
|
|
|
|
if (tableData[0]) {
|
|
tableData[0].forEach((headerCell, index) => {
|
|
const th = dataHeaderRow.createEl("th", {
|
|
cls: "csv-th",
|
|
attr: {
|
|
style: `width: ${columnWidths[index] || 100}px`,
|
|
},
|
|
});
|
|
const headerInput = th.createEl("input", {
|
|
cls: "csv-cell-input",
|
|
attr: { value: headerCell },
|
|
});
|
|
headerInput.oninput = (ev) => {
|
|
if (ev.currentTarget instanceof HTMLInputElement) {
|
|
saveSnapshot();
|
|
tableData[0][index] = ev.currentTarget.value;
|
|
requestSave();
|
|
}
|
|
};
|
|
headerInput.onfocus = (ev) => {
|
|
setActiveCell(0, index, ev.currentTarget as HTMLInputElement);
|
|
};
|
|
const resizeHandle = th.createEl("div", { cls: "resize-handle" });
|
|
setupColumnResize(resizeHandle, index);
|
|
});
|
|
}
|
|
|
|
// 创建表格主体
|
|
const tableBody = tableEl.createEl("tbody");
|
|
const startRowIndex = tableData.length > 1 ? 1 : 0;
|
|
for (let i = startRowIndex; i < tableData.length; i++) {
|
|
const row = tableData[i];
|
|
const tableRow = tableBody.createEl("tr");
|
|
const rowNumberCell = tableRow.createEl("td", { cls: "csv-row-number" });
|
|
rowNumberCell.textContent = i.toString();
|
|
rowNumberCell.onclick = (e) => {
|
|
e.stopPropagation();
|
|
selectRow(i);
|
|
};
|
|
// 插入行操作按钮
|
|
const insertAbove = rowNumberCell.createEl("button", { cls: "csv-insert-row-btn above" });
|
|
insertAbove.innerText = "+";
|
|
insertAbove.title = i18n.t("buttons.insertRowBefore") || "Insert row before";
|
|
insertAbove.onclick = (e) => { e.stopPropagation(); options.insertRowAt(i, false); };
|
|
const insertBelow = rowNumberCell.createEl("button", { cls: "csv-insert-row-btn below" });
|
|
insertBelow.innerText = "+";
|
|
insertBelow.title = i18n.t("buttons.insertRowAfter") || "Insert row after";
|
|
insertBelow.onclick = (e) => { e.stopPropagation(); options.insertRowAt(i, true); };
|
|
const delRow = rowNumberCell.createEl("button", { cls: "csv-del-row-btn" });
|
|
delRow.innerText = "-";
|
|
delRow.title = i18n.t("buttons.deleteRow") || "Delete row";
|
|
delRow.onclick = (e) => { e.stopPropagation(); options.deleteRowAt(i); };
|
|
row.forEach((cell, j) => {
|
|
const td = tableRow.createEl("td", {
|
|
attr: { style: `width: ${columnWidths[j] || 100}px` },
|
|
});
|
|
const input = td.createEl("input", {
|
|
cls: "csv-cell-input",
|
|
attr: { value: cell },
|
|
});
|
|
setupAutoResize(input);
|
|
input.oninput = (ev) => {
|
|
if (ev.currentTarget instanceof HTMLInputElement) {
|
|
saveSnapshot();
|
|
tableData[i][j] = ev.currentTarget.value;
|
|
if (activeCellEl === ev.currentTarget && editInput) {
|
|
editInput.value = ev.currentTarget.value;
|
|
}
|
|
requestSave();
|
|
if (autoResize) {
|
|
adjustInputHeight(ev.currentTarget);
|
|
}
|
|
}
|
|
};
|
|
input.onfocus = (ev) => {
|
|
setActiveCell(i, j, ev.currentTarget as HTMLInputElement);
|
|
};
|
|
});
|
|
}
|
|
|
|
// 滚动条容器宽度同步逻辑建议由主类处理
|
|
}
|