mirror of
https://github.com/liubinfighter/csv-lite.git
synced 2026-07-22 05:43:52 +00:00
feat: 添加行列插入和删除功能,优化表格操作体验 [FR] Insert column/row before or after #16
This commit is contained in:
parent
0adb0ce7d3
commit
441716e45e
3 changed files with 124 additions and 33 deletions
58
src/view.ts
58
src/view.ts
|
|
@ -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" });
|
||||
|
||||
|
|
|
|||
|
|
@ -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` },
|
||||
|
|
|
|||
58
styles.css
58
styles.css
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue