From 0fe264a8840202a998d8b22e63f2f3b104330ab5 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Wed, 28 Oct 2026 15:04:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B8=85=E7=A9=BA=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=20cellPos=20=E4=B8=8E=20startRow/startColumn?= =?UTF-8?q?=20=E9=94=AE=E5=B9=B6=E6=94=AF=E6=8C=81=E6=B8=85=E9=99=A4?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/capabilities/office-aliases.test.ts | 22 ++++++++++++++++++- src/main/capabilities/office-file-extended.ts | 22 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/main/capabilities/office-aliases.test.ts b/src/main/capabilities/office-aliases.test.ts index 2f5228d..249abda 100644 --- a/src/main/capabilities/office-aliases.test.ts +++ b/src/main/capabilities/office-aliases.test.ts @@ -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"]]); + }); }); diff --git a/src/main/capabilities/office-file-extended.ts b/src/main/capabilities/office-file-extended.ts index d6dcec6..789beb3 100644 --- a/src/main/capabilities/office-file-extended.ts +++ b/src/main/capabilities/office-file-extended.ts @@ -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; });