feat(grid): add export selected rows and batch restore

## 背景说明
恢复导出选中行的功能,并新增批量恢复行功能。用户现在可以在多行选中时,
通过右键菜单或工具栏导出选中的行,同时支持批量恢复已删除的行。

## 修改内容
- exportCsv/exportJson/exportMarkdown/exportXlsx 支持可选 rowIds 参数
- 新增 rowsToExport 辅助函数,根据 rowIds 筛选要导出的行
- 右键菜单和工具栏新增"导出选中行"选项(CSV/XLSX/JSON/Markdown)
- 新增 restoreRows 函数支持批量恢复已删除行
- 更新上下文菜单中恢复行的文案,支持单数/复数切换
- 补充 en/es/zh-CN 三语言国际化文案

## 测试
- 建议补充:导出函数传入特定 rowIds 的筛选逻辑测试
- 建议补充:批量恢复已删除行的单元测试
- 建议补充:多选时状态栏显示边界测试
This commit is contained in:
runstone
2026-05-13 18:12:01 +08:00
parent 42501cc8fb
commit 2b29780c76
6 changed files with 78 additions and 11 deletions
+42 -3
View File
@@ -927,6 +927,7 @@ const {
requestDeleteRow,
confirmDeleteRow,
restoreRow,
restoreRows,
pendingDeleteRowIds,
requestDeleteRows,
cloneRows,
@@ -1111,6 +1112,22 @@ function affectedRowIds(): number[] {
return [];
}
function exportSelectedRowsCsv() {
return exportCsv(affectedRowIds());
}
function exportSelectedRowsXlsx() {
return exportXlsx(affectedRowIds());
}
function exportSelectedRowsJson() {
return exportJson(affectedRowIds());
}
function exportSelectedRowsMarkdown() {
return exportMarkdown(affectedRowIds());
}
function isRowActive(index: number): boolean {
const item = displayItems.value[index];
if (item && isRowSelected(item.id)) return true;
@@ -2620,8 +2637,12 @@ defineExpose({
<CopyPlus class="w-3.5 h-3.5 mr-2" />
{{ isMultiRow ? t("grid.cloneRows", { count: multiRowCount }) : t("grid.cloneRow") }}
</ContextMenuItem>
<ContextMenuItem v-if="contextRowItem.isDeleted" @click="restoreRow(contextRowItem.id)">
<Undo2 class="w-3.5 h-3.5 mr-2" /> {{ t("grid.restoreRow") }}
<ContextMenuItem
v-if="contextRowItem.isDeleted"
@click="isMultiRow ? restoreRows(affectedRowIds()) : restoreRow(contextRowItem.id)"
>
<Undo2 class="w-3.5 h-3.5 mr-2" />
{{ isMultiRow ? t("grid.restoreRows", { count: multiRowCount }) : t("grid.restoreRow") }}
</ContextMenuItem>
<ContextMenuItem
v-else
@@ -2640,6 +2661,15 @@ defineExpose({
<ContextMenuItem @click="exportXlsx">{{ t("grid.exportXlsx") }}</ContextMenuItem>
<ContextMenuItem @click="exportJson">{{ t("grid.exportJson") }}</ContextMenuItem>
<ContextMenuItem @click="exportMarkdown">{{ t("grid.exportMarkdown") }}</ContextMenuItem>
<template v-if="isMultiRow">
<ContextMenuSeparator />
<ContextMenuItem @click="exportSelectedRowsCsv">{{ t("grid.exportSelectedRowsCsv") }}</ContextMenuItem>
<ContextMenuItem @click="exportSelectedRowsXlsx">{{ t("grid.exportSelectedRowsXlsx") }}</ContextMenuItem>
<ContextMenuItem @click="exportSelectedRowsJson">{{ t("grid.exportSelectedRowsJson") }}</ContextMenuItem>
<ContextMenuItem @click="exportSelectedRowsMarkdown">{{
t("grid.exportSelectedRowsMarkdown")
}}</ContextMenuItem>
</template>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuContent>
@@ -2664,7 +2694,7 @@ defineExpose({
<span v-if="result.truncated" class="text-amber-500 text-xs ml-1">(truncated)</span>
<span v-if="!hasData">{{ t("grid.rowsAffected", { count: result.affected_rows }) }}</span>
<span>{{ result.execution_time_ms }}ms</span>
<span v-if="hasCellSelection" class="text-foreground">{{ selectionSummary }}</span>
<span v-if="selectedRowCount > 0 || hasCellSelection" class="text-foreground">{{ selectionSummary }}</span>
<template v-if="editable && (tableMeta || customSave) && !useTransaction">
<span v-if="hasPendingChanges" class="ml-2 text-foreground">
@@ -2718,6 +2748,15 @@ defineExpose({
<DropdownMenuItem @click="exportXlsx">{{ t("grid.exportXlsx") }}</DropdownMenuItem>
<DropdownMenuItem @click="exportJson">{{ t("grid.exportJson") }}</DropdownMenuItem>
<DropdownMenuItem @click="exportMarkdown">{{ t("grid.exportMarkdown") }}</DropdownMenuItem>
<template v-if="isMultiRow">
<ContextMenuSeparator />
<DropdownMenuItem @click="exportSelectedRowsCsv">{{ t("grid.exportSelectedRowsCsv") }}</DropdownMenuItem>
<DropdownMenuItem @click="exportSelectedRowsXlsx">{{ t("grid.exportSelectedRowsXlsx") }}</DropdownMenuItem>
<DropdownMenuItem @click="exportSelectedRowsJson">{{ t("grid.exportSelectedRowsJson") }}</DropdownMenuItem>
<DropdownMenuItem @click="exportSelectedRowsMarkdown">{{
t("grid.exportSelectedRowsMarkdown")
}}</DropdownMenuItem>
</template>
</DropdownMenuContent>
</DropdownMenu>