diff --git a/packages/rpa-core/src/capabilities/files.ts b/packages/rpa-core/src/capabilities/files.ts index f594bd2..ecc0414 100644 --- a/packages/rpa-core/src/capabilities/files.ts +++ b/packages/rpa-core/src/capabilities/files.ts @@ -92,6 +92,16 @@ register(["File_File_Move", "File_Move"], async (_ctx, node, args) => { const newName = truthy(args.rename) ? pick(args, ["name"]) : ""; target = requirePath(newName ? path.join(destDir, newName) : destDir, "目的地路径", node.id, node.componentId); } + let targetStat: import("node:fs").Stats | null = null; + try { + targetStat = await fs.stat(target); + } catch { + // 目标不存在,按完整文件路径处理 + } + if (targetStat?.isDirectory()) { + // 目标是已存在的目录:语义为“移入该目录”,自动拼上源文件名 + target = path.join(target, path.basename(source)); + } await fs.mkdir(path.dirname(target), { recursive: true }); try { await fs.rename(source, target); diff --git a/scripts/gen-morning-flow.py b/scripts/gen-morning-flow.py new file mode 100644 index 0000000..1d9887b --- /dev/null +++ b/scripts/gen-morning-flow.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +"""生成「全家桶 · 晨间自动化助手」流程 JSON。""" +import json +import os + +BASE = r"C:\Users\Administrator\Desktop\RPA整理测试" +EXCEL = "C:/Users/Administrator/Desktop/淘宝手机比价.xlsx" +OUT_XLSX = "C:/Users/Administrator/Desktop/低价手机清单.xlsx" +SHOT = BASE + "\\整理完成截图.png" + +nodes = [] +edges = [] + +def node(id, componentId, label, values, output_var=None, timeout=30000, parent=None, slot=None, pos=None, options_extra=None): + options = {"timeoutMs": timeout} + if output_var: + options["outputVar"] = output_var + if options_extra: + options.update(options_extra) + n = { + "id": id, + "componentId": componentId, + "label": label, + "values": values, + "options": options, + } + if parent: + n["parentId"] = parent + n["slot"] = slot + n["position"] = {"x": 18, "y": 0} + else: + n["position"] = pos + nodes.append(n) + return n + +def link(a, b): + edges.append({"id": f"edge_{a}_{b}", "source": a, "target": b}) + +# ============ 主链 ============ +node("node_start", "Trigger.Manual", "立即执行", {"entryName": "立即执行"}, pos={"x": 240, "y": 70}) + +# ---- 板块 A:文件整理 ---- +node("node_scan", "File_GetFileList_V1", "扫描测试文件夹", { + "folder_path": BASE, "key_word": "", "ignore": "", + "recursive": "0", "is_full_path": "1", "sort_type": "1", +}, output_var="dlFiles", pos={"x": 240, "y": 210}) + +node("node_mkdir_zip", "File_MakeDir", "建分类文件夹:压缩包", {"path": BASE + "\\压缩包", "name": "", "overwrite": "False"}, pos={"x": 240, "y": 340}) +node("node_mkdir_img", "File_MakeDir", "建分类文件夹:图片", {"path": BASE + "\\图片", "name": "", "overwrite": "False"}, pos={"x": 240, "y": 460}) +node("node_mkdir_doc", "File_MakeDir", "建分类文件夹:文档", {"path": BASE + "\\文档", "name": "", "overwrite": "False"}, pos={"x": 240, "y": 580}) +node("node_mkdir_other", "File_MakeDir", "建分类文件夹:其他", {"path": BASE + "\\其他", "name": "", "overwrite": "False"}, pos={"x": 240, "y": 700}) + +node("node_loop", "logic_control_array_for_v3", "遍历文件逐个分类", { + "array_name": "dlFiles", "reverse": "0", "start_index": "0", "end_index": "-1", +}, pos={"x": 240, "y": 840}) + +# 循环体内:三层 IF 嵌套分类 +node("node_if_zip", "logic_control_if_else", "是压缩包?", { + "condition": '${contains(lower(item), ".zip") || contains(lower(item), ".rar") || contains(lower(item), ".7z")}', +}, parent="node_loop", slot="array_for_fn") +node("node_move_zip", "File_File_Move", "移入「压缩包」", { + "file_type": "file", "file_path": "${item}", "target_path": BASE + "\\压缩包", "overwrite": "True", +}, parent="node_if_zip", slot="statement_if") +node("node_if_img", "logic_control_if_else", "是图片?", { + "condition": '${contains(lower(item), ".png") || contains(lower(item), ".jpg") || contains(lower(item), ".jpeg") || contains(lower(item), ".gif") || contains(lower(item), ".bmp")}', +}, parent="node_if_zip", slot="statement_else") +node("node_move_img", "File_File_Move", "移入「图片」", { + "file_type": "file", "file_path": "${item}", "target_path": BASE + "\\图片", "overwrite": "True", +}, parent="node_if_img", slot="statement_if") +node("node_if_doc", "logic_control_if_else", "是文档?", { + "condition": '${contains(lower(item), ".doc") || contains(lower(item), ".pdf") || contains(lower(item), ".txt") || contains(lower(item), ".xlsx") || contains(lower(item), ".ppt")}', +}, parent="node_if_img", slot="statement_else") +node("node_move_doc", "File_File_Move", "移入「文档」", { + "file_type": "file", "file_path": "${item}", "target_path": BASE + "\\文档", "overwrite": "True", +}, parent="node_if_doc", slot="statement_if") +node("node_move_other", "File_File_Move", "移入「其他」", { + "file_type": "file", "file_path": "${item}", "target_path": BASE + "\\其他", "overwrite": "True", +}, parent="node_if_doc", slot="statement_else") + +# ---- 板块 B:Excel 数据加工 ---- +node("node_read_excel", "Bivartable_DoOpenExcel", "读取淘宝比价 Excel", { + "filePath": EXCEL, "sheetName": "Sheet1", "indexx": "False", +}, output_var="priceTable", pos={"x": 240, "y": 990}) + +node("node_sort", "Bivartable_SortDf", "按价格升序排序", { + "df": "${priceTable}", "name": "价格", "sort": "True", "showTable": "0", +}, output_var="sortedTable", pos={"x": 240, "y": 1110}) + +node("node_filter", "Bivartable_FilterDf", "筛选 3000 元以下", { + "df": "${sortedTable}", "filter_mode": "ordinary", "col_name": "价格", + "choice": "4", "criteria": "3000", "rule": '["价格"] <= 3000', "showTable": "0", +}, output_var="filteredTable", pos={"x": 240, "y": 1230}) + +node("node_min", "Bivartable_GetMinValue", "算最低价", { + "df": "${priceTable}", "groupby": "", "minvaluecols": "价格", +}, output_var="minPrice", pos={"x": 240, "y": 1350}) + +node("node_max", "Bivartable_GetMaxValue", "算最高价", { + "df": "${priceTable}", "groupby": "", "maxvaluecols": "价格", +}, output_var="maxPrice", pos={"x": 240, "y": 1470}) + +node("node_avg", "Bivartable_GetAvgValue", "算平均价", { + "df": "${priceTable}", "groupby": "", "avgvaluecols": "价格", +}, output_var="avgPrice", pos={"x": 240, "y": 1590}) + +node("node_write_cheap", "Bivartable_DfWriteToFile", "保存低价清单 Excel", { + "df": "${filteredTable}", "suffix": "xlsx", "filePath": OUT_XLSX, "folderPath": "C:/Users/Administrator/Desktop", "sheet": "Sheet1", +}, pos={"x": 240, "y": 1710}) + +# ---- 板块 C:记事本键鼠 + 截图 ---- +node("node_notepad_run", "App_Run", "启动记事本", { + "executeable": "notepad.exe", "params": "", "cwd": "", "is_admin": "0", "create_new_console": "0", +}, output_var="notepadPid", pos={"x": 240, "y": 1830}) + +node("node_delay_notepad", "Basic_Delay_V2", "等记事本就绪", { + "is_random": "0", "second": "1", "min_second": "1", "max_second": "5", +}, pos={"x": 240, "y": 1950}) + +summary_expr = ( + '${"RPA 晨间助手报告 " + today() + ":已整理测试文件 " + len(dlFiles) + " 个;' + '比价表筛出 3000 元以下手机 " + (len(filteredTable) - 1) + " 款;' + '最低 " + minPrice + " 元,最高 " + maxPrice + " 元,平均 " + round(avgPrice, 1) + " 元。"}' +) +node("node_notepad_type", "Keyboard_Send_V1", "记事本输入摘要(键盘模拟)", { + "words": summary_expr, "interval": "10", "fuctype": "0", "window_title": "记事本", +}, pos={"x": 240, "y": 2070}) + +node("node_shot", "Image_Screenshot", "截屏留证", { + "pictureSource": "1", "shotRange": "1", "image_path": SHOT, "position": "", "outImageName": "", +}, pos={"x": 240, "y": 2190}) + +node("node_notepad_kill", "App_Kill", "关闭记事本", { + "pid": "${notepadPid}", +}, pos={"x": 240, "y": 2310}) + +# ---- 板块 D:OCR + AI + 收尾 ---- +node("node_ocr", "OcrOffline_GetOcrTextNew", "OCR 识别截图", { + "image_path": SHOT, +}, output_var="ocrText", timeout=120000, pos={"x": 240, "y": 2430}) + +ai_prompt = ( + "这是一次 RPA 自动化的收尾总结。屏幕 OCR 识别到的内容:${ocrText}。" + "数据统计:整理测试文件 ${len(dlFiles)} 个;低价手机 ${len(filteredTable) - 1} 款;" + "最低价 ${minPrice} 元、最高价 ${maxPrice} 元、平均 ${round(avgPrice, 1)} 元。" + "请用中文生成一段 100 字左右的晨间摘要,说明本次自动化完成了哪些事、结果如何,语气轻松一点。" +) +node("node_ai", "DataService_LargeModel", "AI 生成晨间摘要", { + "hint_word": ai_prompt, +}, output_var="aiSummary", timeout=200000, pos={"x": 240, "y": 2550}) + +node("node_clipboard", "Clipboard_SetText", "摘要复制到剪贴板", { + "text": "${aiSummary}", +}, pos={"x": 240, "y": 2670}) + +node("node_notice", "Dialog_Notice_V2", "弹窗通知完成", { + "text": "晨间摘要已生成并复制到剪贴板:${aiSummary}", "title": "RPA 晨间助手", "time_out": "20", +}, timeout=60000, pos={"x": 240, "y": 2790}) + +# ---- 主链连线 ---- +main_chain = [ + "node_start", "node_scan", + "node_mkdir_zip", "node_mkdir_img", "node_mkdir_doc", "node_mkdir_other", + "node_loop", + "node_read_excel", "node_sort", "node_filter", + "node_min", "node_max", "node_avg", "node_write_cheap", + "node_notepad_run", "node_delay_notepad", "node_notepad_type", "node_shot", "node_notepad_kill", + "node_ocr", "node_ai", "node_clipboard", "node_notice", +] +for a, b in zip(main_chain, main_chain[1:]): + link(a, b) + +flow = { + "schemaVersion": 1, + "id": "flow_morning_assistant", + "name": "全家桶 · 晨间自动化助手", + "description": "测试长流程:文件整理(IF分支+遍历+文件操作) → Excel数据加工(读取/排序/筛选/统计/落盘) → 记事本键鼠模拟+截屏 → OCR识别+AI摘要+剪贴板+弹窗通知。", + "enabled": True, + "trigger": {"label": "手动运行", "type": "manual"}, + "variables": [], + "nodes": nodes, + "edges": edges, + "meta": { + "createdAt": "2026-11-05T07:25:00.000Z", + "updatedAt": "2026-11-05T07:25:00.000Z", + "appVersion": "0.1.0", + }, +} + +out = os.path.join(os.environ["APPDATA"], "rpa-studio", "flows", "flow_morning_assistant.json") +with open(out, "w", encoding="utf-8") as f: + json.dump(flow, f, ensure_ascii=False, indent=2) +print("written:", out, "| nodes:", len(nodes), "| edges:", len(edges))