diff --git a/src/view.ts b/src/view.ts index c7d263c..7612da8 100644 --- a/src/view.ts +++ b/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" }); diff --git a/src/view/table-render.ts b/src/view/table-render.ts index 3356926..0821663 100644 --- a/src/view/table-render.ts +++ b/src/view/table-render.ts @@ -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` }, diff --git a/styles.css b/styles.css index 460c2ce..3c7f88a 100644 --- a/styles.css +++ b/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; +}