fix: 清空区域兼容 cellPos 与 startRow/startColumn 键并支持清除格式模式

This commit is contained in:
yu 2026-10-28 15:04:53 +08:00
parent 753508b500
commit 0fe264a884
2 changed files with 41 additions and 3 deletions

View File

@ -1,6 +1,7 @@
import { readFile, mkdtemp, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import type { Catalog, CatalogComponent, FlowNode } from "../../shared/types";
import type { RunContext } from "../engine/types";
@ -19,7 +20,7 @@ afterEach(async () => Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursiv
describe("common Excel aliases", () => {
it("registers only real catalog aliases", async () => {
const catalog = JSON.parse(await readFile("resources/catalog.json", "utf8")) as Catalog;
const catalog = JSON.parse(await readFile(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../resources/catalog.json"), "utf8")) as Catalog;
const ids = new Set(catalog.components.map((item) => item.id));
expect(EXCEL_COMMON_ALIASES.length).toBeGreaterThanOrEqual(25);
for (const id of EXCEL_COMMON_ALIASES) { expect(ids.has(id), id).toBe(true); expect(getHandler(id), id).toBeTruthy(); }
@ -82,4 +83,23 @@ describe("common Excel aliases", () => {
const savedAs = await run("Excel_SaveAs", { objExcel: created, filename: "rel-target.xlsx", isAbsolutePath: "False" }) as { path: string };
expect(savedAs.path).toBe(path.resolve(directory, "rel-target.xlsx"));
});
it("clears a range through the Excel_ClearRange cellPos key", async () => {
const directory = await mkdtemp(path.join(os.tmpdir(), "rpa-excel-clear-")); dirs.push(directory);
const created = await run("Excel_NewExcel", { filePath: directory, name: "clear.xlsx" }) as { path: string };
await run("Excel_WriteRange", { objExcel: created, sheet: "Sheet1", cellPos: "A1", data: [["甲", "乙"], ["丙", "丁"]] });
await run("Excel_ClearRange", { objExcel: created, sheet: "Sheet1", cellPos: "A1:B2", isFormat: "False", isSave: "True" });
const reopened = await run("Excel_OpenExcel", { filePath: created.path }) as { id: string };
await expect(run("Excel_Cs_ReadContentV2", { workbook: reopened, sheet: "Sheet1", excelRng: "A1:B2" })).resolves.toEqual([["", ""], ["", ""]]);
});
it("clears through the Excel_Cs_ClearRange startRow/startColumn keys", async () => {
const directory = await mkdtemp(path.join(os.tmpdir(), "rpa-excel-clear-cs-")); dirs.push(directory);
const created = await run("Excel_NewExcel", { filePath: directory, name: "clear-cs.xlsx" }) as { path: string };
await run("Excel_WriteRange", { objExcel: created, sheet: "Sheet1", cellPos: "A1", data: [["1", "2"], ["3", "4"]] });
await run("Excel_Cs_ClearRange", { objExcel: created, sheet: "Sheet1", startRow: "2", startColumn: "A", lastRow: "2", lastColumn: "B", mode: "内容", save: "True" });
const reopened = await run("Excel_OpenExcel", { filePath: created.path }) as { id: string };
await expect(run("Excel_Cs_ReadContentV2", { workbook: reopened, sheet: "Sheet1", excelRng: "A2:B2" })).resolves.toEqual([["", ""]]);
await expect(run("Excel_Cs_ReadContentV2", { workbook: reopened, sheet: "Sheet1", excelRng: "A1:B1" })).resolves.toEqual([["1", "2"]]);
});
});

View File

@ -164,12 +164,30 @@ register("Excel_Cs_DeleteColumn", async (_ctx, node, args) => {
register("Excel_Cs_ClearRangeV4", async (_ctx, node, args) => {
const source = await loadWorkbook(args, node.id, node.componentId);
const sheet = worksheetOf(source.workbook, args);
const range = text(args, ["excelRng", "range"]);
// 兼容各别名版本的键:V4=excelRng、V2/V3=range、Legacy Excel_ClearRange=cellPos、V1=startRow/startColumn/lastRow/lastColumn
let range = text(args, ["excelRng", "range", "cellPos"]);
if (!range) {
const startRow = text(args, ["startRow"]);
const startColumn = text(args, ["startColumn"]);
if (startRow && startColumn) {
const lastRow = text(args, ["lastRow"], startRow);
const lastColumn = text(args, ["lastColumn"], startColumn);
range = `${startColumn}${startRow}:${lastColumn}${lastRow}`;
}
}
if (!range) throw new RuntimeError("validation_error", "Range is empty", { nodeId: node.id, componentId: node.componentId });
const [start, end = start] = range.split(":");
const first = sheet.getCell(start);
const last = sheet.getCell(end);
for (let row = first.row; row <= last.row; row += 1) for (let column = first.col; column <= last.col; column += 1) sheet.getCell(row, column).value = null;
// 清除模式:全部=内容+格式,格式=仅格式,其余(内容/默认)=仅内容;Legacy isFormat=True 同样清格式
const mode = text(args, ["clearType", "mode"], "内容");
const clearFormat = mode.includes("全部") || mode.includes("格式") || truthy(value(args, ["isFormat"]));
const clearValue = !mode.includes("格式");
for (let row = first.row; row <= last.row; row += 1) for (let column = first.col; column <= last.col; column += 1) {
const cell = sheet.getCell(row, column);
if (clearValue) cell.value = null;
if (clearFormat) cell.style = {};
}
await saveWhenRequested(source, args);
return range;
});