From 877dab703f6ede005373c37142e57707b822b479 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Wed, 28 Oct 2026 10:55:47 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E5=AF=B9=E9=BD=90=20Excel=20?= =?UTF-8?q?=E6=96=B0=E5=BB=BA/=E6=89=93=E5=BC=80=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E7=9B=AE=E5=BD=95=E5=AD=97=E6=AE=B5=E9=94=AE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - newExcel 兼容 Excel_NewExcel 的 filePath 键(此前只读 path/folder, 导致已填写文件路径仍报「Excel目录为空」) - newExcel 支持 isMakedirs(是否创建该路径):False 且目录不存在时 给出明确报错与建议;缺省时保持自动创建目录 - openExcel 兼容 Excel_OpenExcel / Excel_OpenExcelNew 的 filePath 键 - bool() 大小写不敏感,正确识别目录下拉框的 True/False - 补充别名键名回归测试(4 例) --- src/main/capabilities/office-aliases.test.ts | 18 ++++++++++++++++++ src/main/capabilities/office.ts | 13 +++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/capabilities/office-aliases.test.ts b/src/main/capabilities/office-aliases.test.ts index 348f333..3e8f33c 100644 --- a/src/main/capabilities/office-aliases.test.ts +++ b/src/main/capabilities/office-aliases.test.ts @@ -31,4 +31,22 @@ describe("common Excel aliases", () => { await run("Excel_WriteCellNew", { workbook: handle, sheet: "Sheet1", row: 1, column: "A", content: "alias", save: "1" }); await expect(run("Excel_ReadCellNew", { workbook: handle, sheet: "Sheet1", row: 1, column: "A" })).resolves.toBe("alias"); }); + + it("accepts the Excel_NewExcel catalog keys (filePath / isMakedirs)", async () => { + const directory = await mkdtemp(path.join(os.tmpdir(), "rpa-excel-new-")); dirs.push(directory); + const handle = await run("Excel_NewExcel", { filePath: directory, name: "catalog-keys.xlsx", isMakedirs: "True" }) as { path: string }; + expect(handle.path).toBe(path.resolve(directory, "catalog-keys.xlsx")); + }); + + it("rejects a missing directory when isMakedirs is False", async () => { + const directory = path.join(await mkdtemp(path.join(os.tmpdir(), "rpa-excel-new-")), "missing"); + dirs.push(directory); + await expect(run("Excel_NewExcel", { filePath: directory, name: "none.xlsx", isMakedirs: "False" })).rejects.toThrow("目录不存在"); + }); + + it("opens a workbook through the Excel_OpenExcel filePath key", async () => { + const directory = await mkdtemp(path.join(os.tmpdir(), "rpa-excel-open-")); dirs.push(directory); + const created = await run("Excel_NewExcel", { filePath: directory, name: "open-me.xlsx" }) as { path: string }; + await expect(run("Excel_OpenExcel", { filePath: created.path })).resolves.toMatchObject({ path: created.path }); + }); }); diff --git a/src/main/capabilities/office.ts b/src/main/capabilities/office.ts index ae70e90..388308d 100644 --- a/src/main/capabilities/office.ts +++ b/src/main/capabilities/office.ts @@ -24,6 +24,7 @@ function text(args: Record, keys: string[], fallback = ""): str function bool(valueToRead: unknown, fallback = false): boolean { if (valueToRead === undefined || valueToRead === null || valueToRead === "") return fallback; + if (typeof valueToRead === "string") return ["true", "1", "yes", "是", "开启"].includes(valueToRead.trim().toLowerCase()); return [true, "true", "1", 1, "是", "开启"].includes(valueToRead as never); } @@ -101,9 +102,17 @@ function rowsFrom(valueToRead: unknown): unknown[][] { } const newExcel: CapabilityHandler = async (_ctx, node, args) => { - const folder = required(value(args, ["path", "folder"]), "Excel目录", node.id, node.componentId); + const folder = required(value(args, ["filePath", "path", "folder"]), "文件路径", node.id, node.componentId); const name = text(args, ["name"], "新建工作簿.xlsx"); const filePath = path.resolve(folder, path.extname(name) ? name : `${name}.xlsx`); + const makedirs = bool(value(args, ["isMakedirs"]), true); + if (!makedirs) { + try { + await fs.access(path.dirname(filePath)); + } catch { + throw new RuntimeError("validation_error", `目录不存在:${path.dirname(filePath)}`, { nodeId: node.id, componentId: node.componentId, recommendation: "将「是否创建该路径」设为 True,或先创建目录" }); + } + } if (!bool(value(args, ["override"]), true)) { try { await fs.access(filePath); @@ -120,7 +129,7 @@ const newExcel: CapabilityHandler = async (_ctx, node, args) => { }; const openExcel: CapabilityHandler = async (_ctx, node, args) => { - const filePath = required(value(args, ["file", "path"]), "Excel文件", node.id, node.componentId); + const filePath = required(value(args, ["file", "filePath", "path"]), "Excel文件", node.id, node.componentId); const workbook = new ExcelJS.Workbook(); await workbook.xlsx.readFile(path.resolve(filePath)); return newHandle(path.resolve(filePath), workbook); From c5ac0ebc319d84fba399f3d81c90fba16728ef69 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Wed, 28 Oct 2026 11:17:16 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20Excel=5FWriteRange=20=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=E7=9B=AE=E5=BD=95=E9=94=AE=E5=90=8D=20cellPos/data/is?= =?UTF-8?q?Save?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持 cellPos(如 B2)定位起始单元格,与 column/row 双兼容 - 数据字段兼容 data 键(目录定义),保存字段兼容 isSave 键 - 新增 Excel_WriteRange 目录键名回归测试 --- src/main/capabilities/office-aliases.test.ts | 8 ++++++++ src/main/capabilities/office.ts | 10 +++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/capabilities/office-aliases.test.ts b/src/main/capabilities/office-aliases.test.ts index 3e8f33c..36d7444 100644 --- a/src/main/capabilities/office-aliases.test.ts +++ b/src/main/capabilities/office-aliases.test.ts @@ -49,4 +49,12 @@ describe("common Excel aliases", () => { const created = await run("Excel_NewExcel", { filePath: directory, name: "open-me.xlsx" }) as { path: string }; await expect(run("Excel_OpenExcel", { filePath: created.path })).resolves.toMatchObject({ path: created.path }); }); + + it("writes a range through the Excel_WriteRange catalog keys (objExcel/cellPos/data/isSave)", async () => { + const directory = await mkdtemp(path.join(os.tmpdir(), "rpa-excel-range-")); dirs.push(directory); + const created = await run("Excel_NewExcel", { filePath: directory, name: "range.xlsx" }) as { path: string }; + await run("Excel_WriteRange", { objExcel: created, sheet: "Sheet1", cellPos: "B2", data: [[1, "壹"], [2, "贰"]], 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: "B2:C3" })).resolves.toEqual([[1, "壹"], [2, "贰"]]); + }); }); diff --git a/src/main/capabilities/office.ts b/src/main/capabilities/office.ts index 388308d..5a8ed11 100644 --- a/src/main/capabilities/office.ts +++ b/src/main/capabilities/office.ts @@ -153,14 +153,18 @@ const writeCell: CapabilityHandler = async (_ctx, node, args) => { const writeRange: CapabilityHandler = async (_ctx, node, args) => { const { handle, workbook } = await resolveWorkbook(args, node.id, node.componentId); const sheet = sheetOf(workbook, text(args, ["sheet", "sheetName"])); - const start = `${text(args, ["column"], "A")}${text(args, ["row"], "1")}`; + const cellPos = text(args, ["cellPos"]); + const cellMatch = cellPos.match(/^([A-Za-z]+)(\d+)$/); + const column = cellMatch ? cellMatch[1] : text(args, ["column"], "A"); + const row = cellMatch ? cellMatch[2] : text(args, ["row"], "1"); + const start = `${column}${row}`; const startCell = sheet.getCell(start); const startRow = startCell.row; const startColumn = startCell.col; - for (const [rowIndex, row] of rowsFrom(value(args, ["contents", "table"])).entries()) { + for (const [rowIndex, row] of rowsFrom(value(args, ["contents", "data", "table"])).entries()) { for (const [columnIndex, cell] of row.entries()) sheet.getCell(startRow + rowIndex, startColumn + columnIndex).value = cell as ExcelJS.CellValue; } - if (bool(value(args, ["save"]), true)) await workbook.xlsx.writeFile(handle.path); + if (bool(value(args, ["save", "isSave"]), true)) await workbook.xlsx.writeFile(handle.path); return handle; };