feat: 添加行列插入和删除功能,优化表格操作体验 [FR] Insert column/row before or after #16

This commit is contained in:
JayBridge 2025-06-19 16:42:39 +08:00
parent 0adb0ce7d3
commit 441716e45e
3 changed files with 124 additions and 33 deletions

View file

@ -219,6 +219,34 @@ export class CSVView extends TextFileView {
selectColumn: (colIndex) => this.highlightManager.selectColumn(colIndex),
getColumnLabel: (index) => this.getColumnLabel(index),
setupColumnResize: (handle, columnIndex) => this.setupColumnResize(handle, columnIndex),
insertRowAt: (rowIndex, after = false) => {
this.saveSnapshot();
const idx = after ? rowIndex + 1 : rowIndex;
this.tableData.splice(idx, 0, Array(this.tableData[0].length).fill(""));
this.refresh();
this.requestSave();
},
deleteRowAt: (rowIndex) => {
if (this.tableData.length <= 1) return;
this.saveSnapshot();
this.tableData.splice(rowIndex, 1);
this.refresh();
this.requestSave();
},
insertColAt: (colIndex, after = false) => {
this.saveSnapshot();
const idx = after ? colIndex + 1 : colIndex;
this.tableData.forEach(row => row.splice(idx, 0, ""));
this.refresh();
this.requestSave();
},
deleteColAt: (colIndex) => {
if (this.tableData[0].length <= 1) return;
this.saveSnapshot();
this.tableData.forEach(row => row.splice(colIndex, 1));
this.refresh();
this.requestSave();
},
});
// 在完成表格渲染后,更新滚动条容器的宽度
@ -500,26 +528,6 @@ export class CSVView extends TextFileView {
.setIcon("redo")
.onClick(() => this.redo());
// 添加行按钮
new ButtonComponent(buttonContainer)
.setButtonText(i18n.t("buttons.addRow"))
.onClick(() => this.addRow());
// 删除行按钮
new ButtonComponent(buttonContainer)
.setButtonText(i18n.t("buttons.deleteRow"))
.onClick(() => this.deleteRow());
// 添加列按钮
new ButtonComponent(buttonContainer)
.setButtonText(i18n.t("buttons.addColumn"))
.onClick(() => this.addColumn());
// 删除列按钮
new ButtonComponent(buttonContainer)
.setButtonText(i18n.t("buttons.deleteColumn"))
.onClick(() => this.deleteColumn());
// 重置列宽按钮
new ButtonComponent(buttonContainer)
.setButtonText(i18n.t("buttons.resetColumnWidth"))
@ -529,16 +537,6 @@ export class CSVView extends TextFileView {
this.refresh();
});
// 新增:在操作按钮容器中创建搜索栏
this.searchBar = new SearchBar(buttonContainer, {
getTableData: () => this.tableData,
tableEl: this.tableEl,
getColumnLabel: (index) => this.getColumnLabel(index),
getCellAddress: (row, col) => this.getCellAddress(row, col),
jumpToCell: (row, col) => this.jumpToCell(row, col),
clearSearchHighlights: () => this.clearSearchHighlights(),
});
// CSV导入导出选项
this.operationEl.createEl("div", { cls: "csv-export-import" });

View file

@ -20,6 +20,10 @@ export interface TableRenderOptions {
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) {
@ -41,6 +45,10 @@ export function renderTable(options: TableRenderOptions) {
selectColumn,
getColumnLabel,
setupColumnResize,
insertRowAt,
deleteRowAt,
insertColAt,
deleteColAt,
} = options;
tableEl.empty();
@ -55,7 +63,8 @@ export function renderTable(options: TableRenderOptions) {
}
// 添加左上角单元格
headerRow.createEl("th", { cls: "csv-corner-cell" });
const cornerTh = headerRow.createEl("th", { cls: "csv-corner-cell" });
// 可选:在左上角添加插入列/行按钮
// 创建列号行
if (tableData[0]) {
@ -68,9 +77,22 @@ export function renderTable(options: TableRenderOptions) {
});
th.textContent = getColumnLabel(index);
th.onclick = (e) => {
e.stopPropagation(); // 防止事件冒泡导致高亮被清除
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); };
});
}
@ -115,9 +137,22 @@ export function renderTable(options: TableRenderOptions) {
const rowNumberCell = tableRow.createEl("td", { cls: "csv-row-number" });
rowNumberCell.textContent = i.toString();
rowNumberCell.onclick = (e) => {
e.stopPropagation(); // 防止事件冒泡导致高亮被清除
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` },

View file

@ -453,3 +453,61 @@ td input:focus {
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
}
/* 插入/删除行列按钮样式 */
.csv-insert-row-btn,
.csv-insert-col-btn,
.csv-del-row-btn,
.csv-del-col-btn {
display: none;
position: absolute;
z-index: 20;
width: 18px;
height: 18px;
padding: 0;
margin: 0 2px;
border: none;
border-radius: 50%;
background: var(--background-secondary-alt, #f0f0f0);
color: var(--text-muted);
font-size: 14px;
cursor: pointer;
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.csv-insert-row-btn:hover,
.csv-insert-col-btn:hover,
.csv-del-row-btn:hover,
.csv-del-col-btn:hover {
background: var(--interactive-accent);
color: var(--text-on-accent);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
/* 行按钮定位 */
.csv-row-number { position: relative; }
.csv-insert-row-btn.above { top: -10px; left: 50%; transform: translateX(-50%); }
.csv-insert-row-btn.below { bottom: -10px; left: 50%; transform: translateX(-50%); }
.csv-del-row-btn { top: 50%; right: -20px; transform: translateY(-50%); background: #ffeaea; color: #d00; }
.csv-del-row-btn:hover { background: #ffb3b3; color: #fff; }
/* 列按钮定位 */
.csv-col-number { position: relative; }
.csv-insert-col-btn.left { left: -10px; top: 50%; transform: translateY(-50%); }
.csv-insert-col-btn.right { right: -10px; top: 50%; transform: translateY(-50%); }
.csv-del-col-btn { top: -20px; left: 50%; transform: translateX(-50%); background: #ffeaea; color: #d00; }
.csv-del-col-btn:hover { background: #ffb3b3; color: #fff; }
/* 仅在悬浮或选中时显示按钮 */
.csv-row-number:hover .csv-insert-row-btn,
.csv-row-number:hover .csv-del-row-btn,
.csv-row-selected .csv-row-number .csv-insert-row-btn,
.csv-row-selected .csv-row-number .csv-del-row-btn {
display: inline-block;
}
.csv-col-number:hover .csv-insert-col-btn,
.csv-col-number:hover .csv-del-col-btn,
.csv-col-selected.csv-col-number .csv-insert-col-btn,
.csv-col-selected.csv-col-number .csv-del-col-btn {
display: inline-block;
}