File_File_Move 目标为已存在目录时自动移入该目录
此前 pathDest 被固定当作完整目标文件路径, 目标填分类目录时 rename(文件->目录)失败, fallback cp 报 ENOTDIR(cannot overwrite directory with non-directory), 文件整理类流程无法用目录作目标。 现在目标存在且为目录时自动拼接源文件名(移入语义), 目标不存在 仍按完整文件路径处理。 附: 晨间助手全家桶测试流程生成脚本(30节点, 文件整理+记事本 键鼠+Excel加工+OCR/AI)
This commit is contained in:
parent
b59d906161
commit
902ee0cec4
@ -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);
|
||||
|
||||
192
scripts/gen-morning-flow.py
Normal file
192
scripts/gen-morning-flow.py
Normal file
@ -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))
|
||||
Loading…
Reference in New Issue
Block a user