feat: Excel 高阶接入——格式/筛选/表格样式/隐藏/尾空单元格/列标识/选中区域/关闭/加载/写列(16 id)
This commit is contained in:
parent
fc1207cddd
commit
26c682d2f9
@ -611,3 +611,191 @@ registerMany(["Excel_Cs_AutoFill", "Excel_Cs_AutoFillV2"], autoFill);
|
||||
registerMany(["Excel_Cs_ColumnSplit", "Excel_Cs_ColumnSplitV2"], splitColumn);
|
||||
registerMany(["Excel_Close", "Excel_Cs_Close"], closeWorkbook);
|
||||
registerMany(["Excel_DeleteRange", "Excel_Cs_DeleteRange", "Excel_Cs_DeleteRangeV2", "Excel_Cs_DeleteRangeV3"], deleteRange);
|
||||
|
||||
|
||||
// ===== Excel 高阶(批四) =====
|
||||
function cellAddress(addr: string): { row: number; column: number } {
|
||||
const m = addr.toUpperCase().match(/^([A-Z]+)(\d+)$/);
|
||||
if (!m) throw new Error("Invalid cell address: " + addr);
|
||||
return { row: Number(m[2]), column: columnNumber(m[1]) };
|
||||
}
|
||||
|
||||
function toArgb(color: string): string {
|
||||
const hex = color.replace(/^#/, "").toUpperCase();
|
||||
return hex.length === 6 ? "FF" + hex : hex.padStart(8, "F");
|
||||
}
|
||||
|
||||
function parseFormatSpec(raw: unknown): { bold?: boolean; italic?: boolean; underline?: boolean; fontSize?: number; color?: string; bgColor?: string; numFmt?: string } {
|
||||
if (typeof raw === "object" && raw !== null) return raw as ReturnType<typeof parseFormatSpec>;
|
||||
const text = String(raw ?? "").trim();
|
||||
if (!text) return {};
|
||||
try {
|
||||
const parsed = JSON.parse(text) as Record<string, unknown>;
|
||||
if (typeof parsed === "object" && parsed) return parsed as ReturnType<typeof parseFormatSpec>;
|
||||
} catch { /* fall through to keywords */ }
|
||||
const spec: ReturnType<typeof parseFormatSpec> = {};
|
||||
const lower = text.toLowerCase();
|
||||
if (/加粗|bold/.test(lower)) spec.bold = true;
|
||||
if (/斜体|italic/.test(lower)) spec.italic = true;
|
||||
if (/下划线|underline/.test(lower)) spec.underline = true;
|
||||
const sizeMatch = text.match(/(\d+)号|font-size[::]\s*(\d+)/i);
|
||||
if (sizeMatch) spec.fontSize = Number(sizeMatch[1] || sizeMatch[2]);
|
||||
return spec;
|
||||
}
|
||||
|
||||
registerMany(["Excel_Cs_SetCellFormat", "Excel_Cs_SetCellFormat_V2", "Excel_Cs_SetCellFormat_V3"], 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", "cellPos", "rng"], "A1");
|
||||
const spec = parseFormatSpec(args.format);
|
||||
const addresses: string[] = [];
|
||||
const parts = range.toUpperCase().split(":");
|
||||
if (parts.length === 2 && parts[0] !== parts[1]) {
|
||||
const from = cellAddress(parts[0]);
|
||||
const to = cellAddress(parts[1]);
|
||||
for (let r = from.row; r <= to.row; r += 1) {
|
||||
for (let cIdx = from.column; cIdx <= to.column; cIdx += 1) {
|
||||
addresses.push(columnLetter(cIdx) + r);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addresses.push(parts[0]);
|
||||
}
|
||||
for (const addr of addresses) {
|
||||
const cell = sheet.getCell(addr);
|
||||
if (spec.bold !== undefined || spec.italic !== undefined || spec.underline !== undefined || spec.fontSize !== undefined || spec.color) {
|
||||
cell.font = {
|
||||
...cell.font,
|
||||
bold: spec.bold,
|
||||
italic: spec.italic,
|
||||
underline: spec.underline,
|
||||
size: spec.fontSize,
|
||||
color: spec.color ? { argb: toArgb(spec.color) } : undefined
|
||||
};
|
||||
}
|
||||
if (spec.numFmt) cell.numFmt = spec.numFmt;
|
||||
if (spec.bgColor) cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: toArgb(spec.bgColor) } };
|
||||
}
|
||||
await saveWhenRequested(source, args);
|
||||
ctx.log("已设置 " + addresses.length + " 个单元格格式", "info", node.id);
|
||||
return { formatted: addresses.length, range };
|
||||
});
|
||||
|
||||
register("Excel_Cs_ExcelFilter", async (ctx, node, args) => {
|
||||
const source = await loadWorkbook(args, node.id, node.componentId);
|
||||
const sheet = worksheetOf(source.workbook, args);
|
||||
const rule = text(args, ["rule"]);
|
||||
const startRow = numberValue(args.startRow, 1);
|
||||
const endRow = numberValue(args.endRow, sheet.rowCount || 1);
|
||||
const range = rule && rule.includes(":") ? rule.toUpperCase() : "A" + startRow + ":" + columnLetter(sheet.columnCount || 1) + endRow;
|
||||
sheet.autoFilter = range;
|
||||
await saveWhenRequested(source, args);
|
||||
ctx.log("已设置自动筛选:" + range, "info", node.id);
|
||||
return { filter: range };
|
||||
});
|
||||
|
||||
register("Excel_Cs_ShowTableStyle", async (ctx, node, args) => {
|
||||
const source = await loadWorkbook(args, node.id, node.componentId);
|
||||
const sheet = worksheetOf(source.workbook, args);
|
||||
const headerRow = sheet.getRow(1);
|
||||
headerRow.font = { ...headerRow.font, bold: true };
|
||||
headerRow.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFD9E8F5" } };
|
||||
headerRow.commit();
|
||||
sheet.autoFilter = { from: { row: 1, column: 1 }, to: { row: Math.max(1, sheet.rowCount), column: Math.max(1, sheet.columnCount) } };
|
||||
await saveWhenRequested(source, args, true);
|
||||
ctx.log("已应用表格样式(表头加粗 + 筛选)", "info", node.id);
|
||||
return { styled: true };
|
||||
});
|
||||
|
||||
register("Excel_Cs_Hide", async (ctx, node, args) => {
|
||||
const source = await loadWorkbook(args, node.id, node.componentId);
|
||||
const sheet = worksheetOf(source.workbook, args);
|
||||
const mode = String(value(args, ["mode"], "row")).toLowerCase();
|
||||
const range = text(args, ["range"]);
|
||||
const hide = !/取消|取消隐藏|unhide|show/i.test(mode + range);
|
||||
if (mode.includes("col") || mode.includes("列")) {
|
||||
const targets = range ? range.split(",") : [text(args, ["column"])];
|
||||
for (const t of targets) {
|
||||
if (!t.trim()) continue;
|
||||
const letter = t.trim().replace(/^\d+:?/, "").toUpperCase() || t.trim().toUpperCase();
|
||||
sheet.getColumn(columnNumber(letter)).hidden = hide;
|
||||
}
|
||||
} else {
|
||||
const targets = range ? range.split(":") : [text(args, ["row"])];
|
||||
if (targets.length === 2) {
|
||||
const from = Number(targets[0]); const to = Number(targets[1]);
|
||||
for (let r = Math.min(from, to); r <= Math.max(from, to); r += 1) sheet.getRow(r).hidden = hide;
|
||||
} else {
|
||||
const r = Number(targets[0]);
|
||||
if (Number.isFinite(r) && r > 0) sheet.getRow(r).hidden = hide;
|
||||
}
|
||||
}
|
||||
await saveWhenRequested(source, args);
|
||||
ctx.log((hide ? "已隐藏" : "已取消隐藏") + "(" + mode + " " + range + ")", "info", node.id);
|
||||
return { hidden: hide };
|
||||
});
|
||||
|
||||
register("Excel_Cs_GetLastEmptyCell", async (ctx, node, args) => {
|
||||
const source = await loadWorkbook(args, node.id, node.componentId);
|
||||
const sheet = worksheetOf(source.workbook, args);
|
||||
const column = columnLetter(numberValue(args.column, 1));
|
||||
let lastFilled = 0;
|
||||
for (let r = 1; r <= sheet.rowCount; r += 1) {
|
||||
const v = sheet.getRow(r).getCell(numberValue(args.column, 1)).value;
|
||||
if (v !== null && v !== undefined && v !== "") lastFilled = r;
|
||||
}
|
||||
const emptyRow = lastFilled + 1;
|
||||
return { address: column + emptyRow, row: emptyRow, column: numberValue(args.column, 1) };
|
||||
});
|
||||
|
||||
register("Excel_GetColumnId", async (_ctx, _node, args) => columnLetter(numberValue(args.num, 1)));
|
||||
|
||||
register(["Excel_Cs_SelectRange", "Excel_Cs_SelectRangeV2", "Excel_Cs_SelectRangeV3", "Excel_SelectRange"], async (ctx, node, args) => {
|
||||
const source = await loadWorkbook(args, node.id, node.componentId);
|
||||
const sheet = worksheetOf(source.workbook, args);
|
||||
const cellPos = text(args, ["cellPos", "range", "Start"], "A1");
|
||||
const parts = cellPos.toUpperCase().split(":");
|
||||
const from = cellAddress(parts[0]);
|
||||
const to = parts[1] ? cellAddress(parts[1]) : from;
|
||||
const rows: unknown[][] = [];
|
||||
for (let r = from.row; r <= to.row; r += 1) {
|
||||
const row: unknown[] = [];
|
||||
for (let cIdx = from.column; cIdx <= to.column; cIdx += 1) {
|
||||
row.push(sheet.getRow(r).getCell(cIdx).value ?? "");
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
ctx.log("已读取区域 " + cellPos + "(" + rows.length + " 行)", "info", node.id);
|
||||
return rows;
|
||||
});
|
||||
|
||||
register("Excel_Cs_CloseV2", async (ctx, node, args) => {
|
||||
const source = await loadWorkbook(args, node.id, node.componentId);
|
||||
const save = !/不保存|discard|false/i.test(text(args, ["closeAction"]));
|
||||
if (save) await source.workbook.xlsx.writeFile(source.filePath);
|
||||
releaseWorkbook(source.filePath);
|
||||
ctx.log("已关闭工作簿" + (save ? "(已保存)" : "(未保存)"), "info", node.id);
|
||||
return { closed: true, saved: save };
|
||||
});
|
||||
|
||||
register(["Excel_Cs_Load", "Excel_Cs_LoadV2"], async (ctx, node, args) => {
|
||||
const source = await loadWorkbook(args, node.id, node.componentId);
|
||||
const sheets = source.workbook.worksheets.map(w => w.name);
|
||||
ctx.log("已加载工作簿:" + source.filePath + "(" + sheets.length + " 个 Sheet)", "info", node.id);
|
||||
return { filePath: source.filePath, sheets };
|
||||
});
|
||||
|
||||
register("Excel_WriteColumn", async (ctx, node, args) => {
|
||||
const source = await loadWorkbook(args, node.id, node.componentId);
|
||||
const sheet = worksheetOf(source.workbook, args);
|
||||
const start = cellAddress(text(args, ["cellPos"], "A1").toUpperCase());
|
||||
const valuesRaw = value(args, ["value"]);
|
||||
const values = Array.isArray(valuesRaw) ? valuesRaw : [valuesRaw];
|
||||
const flat = Array.isArray(values) ? values.flat() : [values];
|
||||
flat.forEach((item, offset) => {
|
||||
sheet.getRow(start.row + offset).getCell(start.column).value = item as ExcelJS.CellValue;
|
||||
});
|
||||
await saveWhenRequested(source, args, true);
|
||||
ctx.log("已写入 " + flat.length + " 行(" + columnLetter(start.column) + start.row + " 起)", "info", node.id);
|
||||
return { written: flat.length };
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user