commit 15db2f177ab26e1fd2b3c49f8c6c5c405de84a89
Author: Pi <123@gmail.com>
Date: Fri Jun 6 21:04:04 2025 +0800
init
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..35410ca
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..d843f34
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/script_manager.py b/script_manager.py
new file mode 100644
index 0000000..2a2766b
--- /dev/null
+++ b/script_manager.py
@@ -0,0 +1,657 @@
+import tkinter as tk
+from tkinter import ttk, messagebox, filedialog
+import sqlite3
+import os
+import datetime
+import json
+import importlib.util
+from typing import List, Dict
+import pyperclip
+
+
+class ScriptManager:
+ def __init__(self, db_path="scripts.db", scripts_dir="scripts"):
+ self.db_path = db_path
+ self.scripts_dir = scripts_dir
+ self._init_db()
+ self._init_scripts_dir()
+ self.tag_library = {
+ "文本": ["string", "text", "format", "generate"],
+ "转换": ["convert", "transform", "format"],
+ "处理": ["process", "handle", "manipulate"],
+ "生成": ["create", "generate", "make"],
+ "解析": ["parse", "extract", "analyze"]
+ }
+
+ def _init_db(self):
+ with sqlite3.connect(self.db_path) as conn:
+ cursor = conn.cursor()
+ # 修改 scripts 表,添加 status 和 type 字段,移除 abbreviation
+ cursor.execute("""
+ CREATE TABLE IF NOT EXISTS scripts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ path TEXT NOT NULL,
+ ui_path TEXT NOT NULL,
+ created_at TEXT NOT NULL,
+ tags TEXT,
+ usage_count INTEGER DEFAULT 0,
+ status INTEGER DEFAULT 1,
+ type TEXT DEFAULT 'complex'
+ )
+ """)
+ # 创建 tags 表
+ cursor.execute("""
+ CREATE TABLE IF NOT EXISTS tags (
+ script_id INTEGER,
+ tag TEXT,
+ PRIMARY KEY (script_id, tag),
+ FOREIGN KEY (script_id) REFERENCES scripts(id)
+ )
+ """)
+ # 为 tags 字段创建索引
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_scripts_tags ON scripts(tags)")
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag)")
+ conn.commit()
+
+ def _init_scripts_dir(self):
+ os.makedirs(self.scripts_dir, exist_ok=True)
+
+ def _suggest_tags(self, script_name: str) -> List[str]:
+ name = script_name.lower()
+ suggested_tags = []
+ for category, keywords in self.tag_library.items():
+ if any(keyword in name for keyword in keywords):
+ suggested_tags.append(category)
+ return suggested_tags
+
+ def add_script(self, script_name: str, script_content: str = None, script_path: str = None,
+ ui_content: str = None, script_type: str = "complex") -> Dict:
+ script_name_clean = script_name.replace(" ", "_").replace("/", "_").replace("\\", "_")
+ script_path = os.path.join(self.scripts_dir, f"{script_name_clean}.py")
+ ui_path = os.path.join(self.scripts_dir, f"{script_name_clean}.ui.py")
+
+ if not script_content:
+ script_content = '''def process(input_strings: list) -> list:
+ """处理输入字符串并返回结果"""
+ return [s.upper() for s in input_strings]
+'''
+
+ if not ui_content and script_type == "simple":
+ ui_content = '''import tkinter as tk
+from tkinter import ttk
+import pyperclip
+
+def create_ui(parent, run_callback):
+ """创建简单脚本前端界面:单一输入输出"""
+ frame = ttk.Frame(parent)
+ frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ frame.columnconfigure(1, weight=1)
+
+ ttk.Label(frame, text="输入字符串:").grid(row=0, column=0, sticky=tk.W, pady=2)
+ input_entry = ttk.Entry(frame)
+ input_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ ttk.Label(frame, text="输出结果:").grid(row=1, column=0, sticky=tk.W, pady=2)
+ output_entry = ttk.Entry(frame, state="readonly")
+ output_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ def copy_output(event):
+ output = output_entry.get()
+ if output:
+ pyperclip.copy(output)
+ tk.messagebox.showinfo("提示", "输出已复制到剪贴板")
+
+ output_entry.bind("", copy_output)
+
+ def run():
+ input_text = input_entry.get().strip()
+ if not input_text:
+ tk.messagebox.showerror("错误", "请输入字符串")
+ return
+ result = run_callback([input_text])
+ output_entry.configure(state="normal")
+ output_entry.delete(0, tk.END)
+ output_entry.insert(0, result[0] if result else "")
+ output_entry.configure(state="readonly")
+
+ ttk.Button(frame, text="运行脚本", command=run).grid(row=2, column=0, columnspan=2, pady=5)
+
+ return frame
+'''
+ elif not ui_content:
+ ui_content = '''import tkinter as tk
+from tkinter import ttk
+import pyperclip
+
+def create_ui(parent, run_callback):
+ """创建前端界面"""
+ frame = ttk.Frame(parent)
+ frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ frame.columnconfigure(1, weight=1)
+
+ ttk.Label(frame, text="输入字符串 1:").grid(row=0, column=0, sticky=tk.W, pady=2)
+ input_entry = ttk.Entry(frame)
+ input_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ ttk.Label(frame, text="输出结果 1:").grid(row=1, column=0, sticky=tk.W, pady=2)
+ output_entry = ttk.Entry(frame, state="readonly")
+ output_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ def copy_output(event):
+ output = output_entry.get()
+ if output:
+ pyperclip.copy(output)
+ tk.messagebox.showinfo("提示", "输出已复制到剪贴板")
+
+ output_entry.bind("", copy_output)
+
+ def run():
+ input_text = input_entry.get().strip()
+ if not input_text:
+ tk.messagebox.showerror("错误", "请输入字符串")
+ return
+ result = run_callback([input_text])
+ output_entry.configure(state="normal")
+ output_entry.delete(0, tk.END)
+ output_entry.insert(0, result[0] if result else "")
+ output_entry.configure(state="readonly")
+
+ ttk.Button(frame, text="运行脚本", command=run).grid(row=2, column=0, columnspan=2, pady=5)
+
+ return frame
+'''
+
+ with open(script_path, 'w', encoding='utf-8') as f:
+ f.write(script_content)
+
+ with open(ui_path, 'w', encoding='utf-8') as f:
+ f.write(ui_content)
+
+ suggested_tags = self._suggest_tags(script_name)
+
+ with sqlite3.connect(self.db_path) as conn:
+ cursor = conn.cursor()
+ cursor.execute("""
+ INSERT INTO scripts (name, path, ui_path, created_at, tags, usage_count, status, type)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ """, (
+ script_name,
+ script_path,
+ ui_path,
+ datetime.datetime.now().strftime("%Y年%m月%d日 %H:%M:%S"),
+ json.dumps(suggested_tags),
+ 0,
+ 1,
+ script_type
+ ))
+ script_id = cursor.lastrowid
+ for tag in suggested_tags:
+ cursor.execute("INSERT OR IGNORE INTO tags (script_id, tag) VALUES (?, ?)", (script_id, tag))
+ conn.commit()
+
+ return {
+ "id": script_id,
+ "name": script_name,
+ "path": script_path,
+ "ui_path": ui_path,
+ "suggested_tags": suggested_tags,
+ "type": script_type
+ }
+
+ def update_script(self, script_id: int, new_name: str, new_tags: List[str]) -> None:
+ with sqlite3.connect(self.db_path) as conn:
+ cursor = conn.cursor()
+ cursor.execute("SELECT name, path, ui_path FROM scripts WHERE id = ? AND status = 1", (script_id,))
+ result = cursor.fetchone()
+ if not result:
+ raise ValueError("脚本未找到或已删除")
+
+ old_name, old_path, old_ui_path = result
+ new_name_clean = new_name.replace(" ", "_").replace("/", "_").replace("\\", "_")
+ new_path = os.path.join(self.scripts_dir, f"{new_name_clean}.py")
+ new_ui_path = os.path.join(self.scripts_dir, f"{new_name_clean}.ui.py")
+
+ # 重命名文件
+ if old_name != new_name:
+ os.rename(old_path, new_path)
+ os.rename(old_ui_path, new_ui_path)
+
+ # 更新 scripts 表
+ cursor.execute("""
+ UPDATE scripts SET name = ?, path = ?, ui_path = ?, tags = ?
+ WHERE id = ? AND status = 1
+ """, (new_name, new_path, new_ui_path, json.dumps(new_tags), script_id))
+
+ # 更新 tags 表
+ cursor.execute("DELETE FROM tags WHERE script_id = ?", (script_id,))
+ for tag in new_tags:
+ cursor.execute("INSERT OR IGNORE INTO tags (script_id, tag) VALUES (?, ?)", (script_id, tag))
+ conn.commit()
+
+ def delete_script(self, script_id: int) -> None:
+ with sqlite3.connect(self.db_path) as conn:
+ cursor = conn.cursor()
+ cursor.execute("UPDATE scripts SET status = 0 WHERE id = ? AND status = 1", (script_id,))
+ if cursor.rowcount == 0:
+ raise ValueError("脚本未找到或已删除")
+ conn.commit()
+
+ def list_scripts(self, tag: str = None, name: str = None) -> List[Dict]:
+ with sqlite3.connect(self.db_path) as conn:
+ cursor = conn.cursor()
+ query = "SELECT s.id, s.name, s.path, s.created_at, s.tags, s.usage_count, s.ui_path, s.type " \
+ "FROM scripts s WHERE s.status = 1"
+ params = []
+ conditions = []
+
+ if tag:
+ query += " AND EXISTS (SELECT 1 FROM tags t WHERE t.script_id = s.id AND t.tag LIKE ?)"
+ params.append(f'%{tag}%')
+ if name:
+ conditions.append("s.name LIKE ?")
+ params.append(f'%{name}%')
+
+ if conditions:
+ query += " AND " + " AND ".join(conditions)
+
+ cursor.execute(query, params)
+ scripts = []
+ for row in cursor.fetchall():
+ try:
+ tags = json.loads(row[4]) if row[4] and row[4].strip() else []
+ except json.JSONDecodeError:
+ tags = []
+ scripts.append({
+ "id": row[0],
+ "name": row[1],
+ "path": row[2],
+ "created_at": row[3],
+ "tags": tags,
+ "usage_count": row[5],
+ "ui_path": row[6],
+ "type": row[7]
+ })
+ return scripts
+
+ def run_script(self, script_id: int, input_strings: List[str]) -> List[str]:
+ with sqlite3.connect(self.db_path) as conn:
+ cursor = conn.cursor()
+ cursor.execute("SELECT path FROM scripts WHERE id = ? AND status = 1", (script_id,))
+ result = cursor.fetchone()
+ if not result:
+ raise ValueError("脚本未找到或已删除")
+
+ script_path = result[0]
+ cursor.execute("UPDATE scripts SET usage_count = usage_count + 1 WHERE id = ?", (script_id,))
+ conn.commit()
+
+ spec = importlib.util.spec_from_file_location("script_module", script_path)
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module.process(input_strings)
+
+ def load_ui_module(self, ui_path: str):
+ if not ui_path or not os.path.isfile(ui_path):
+ raise ValueError(f"前端界面文件不存在或路径无效:{ui_path}")
+ try:
+ spec = importlib.util.spec_from_file_location("ui_module", ui_path)
+ if spec is None:
+ raise ValueError(f"无法加载前端界面模块:{ui_path} (模块规格无效)")
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+ except Exception as e:
+ raise ValueError(f"加载前端界面失败:{str(e)}")
+
+
+class ScriptManagerGUI:
+ def __init__(self, root):
+ self.root = root
+ self.root.title("脚本管理器")
+ self.manager = ScriptManager()
+ self.selected_script_id = None
+ self.current_ui_frame = None
+ self.text_editor_content = ""
+ self.create_widgets()
+ self.root.columnconfigure(0, weight=1)
+ self.root.rowconfigure(0, weight=1)
+
+ def create_widgets(self):
+ self.notebook = ttk.Notebook(self.root)
+ self.notebook.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=10, pady=5)
+ self.notebook.columnconfigure(0, weight=1)
+ self.notebook.rowconfigure(0, weight=1)
+
+ # 添加脚本选项卡
+ self.create_add_tab()
+
+ # 修改脚本选项卡
+ self.create_edit_tab()
+
+ # 索引运行脚本选项卡
+ self.create_run_tab()
+
+ # 文本编辑选项卡
+ self.create_text_editor_tab()
+
+ # 绑定选项卡切换事件
+ self.notebook.bind("<>", self.on_tab_changed)
+
+ def create_text_editor_tab(self):
+ """创建文本编辑选项卡"""
+ text_editor_frame = ttk.Frame(self.notebook, padding="10")
+ self.notebook.add(text_editor_frame, text="文本【text】")
+ text_editor_frame.columnconfigure(0, weight=1)
+ text_editor_frame.rowconfigure(0, weight=1)
+
+ self.text_editor = tk.Text(text_editor_frame, height=20, width=50)
+ self.text_editor.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
+ text_scroll = ttk.Scrollbar(text_editor_frame, orient=tk.VERTICAL, command=self.text_editor.yview)
+ text_scroll.grid(row=0, column=1, sticky=(tk.N, tk.S))
+ self.text_editor["yscrollcommand"] = text_scroll.set
+
+ # 恢复之前的内容(如果有)
+ self.text_editor.insert("1.0", self.text_editor_content)
+
+ def on_tab_changed(self, event):
+ """选项卡切换时保存文本编辑内容"""
+ selected_tab = self.notebook.select()
+ tab_name = self.notebook.tab(selected_tab, "text")
+
+ # 如果离开文本编辑选项卡,保存内容
+ if hasattr(self, "text_editor") and tab_name != "文本编辑":
+ self.text_editor_content = self.text_editor.get("1.0", tk.END).strip()
+
+ # 如果切换到文本编辑选项卡,恢复内容
+ if tab_name == "文本编辑" and hasattr(self, "text_editor"):
+ self.text_editor.delete("1.0", tk.END)
+ self.text_editor.insert("1.0", self.text_editor_content)
+
+ def create_add_tab(self):
+ add_frame = ttk.Frame(self.notebook, padding="10")
+ self.notebook.add(add_frame, text="添加【Add】")
+ add_frame.columnconfigure(1, weight=1)
+ add_frame.rowconfigure(3, weight=1)
+
+ ttk.Label(add_frame, text="脚本名称:").grid(row=0, column=0, sticky=tk.W, pady=5)
+ self.script_name_var = tk.StringVar()
+ ttk.Entry(add_frame, textvariable=self.script_name_var).grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
+
+ ttk.Label(add_frame, text="脚本类型:").grid(row=1, column=0, sticky=tk.W, pady=5)
+ self.script_type_var = tk.StringVar(value="simple")
+ ttk.OptionMenu(add_frame, self.script_type_var, "simple", "simple", "complex").grid(row=1, column=1, sticky=tk.W, pady=5)
+
+ ttk.Label(add_frame, text="脚本路径(可选):").grid(row=2, column=0, sticky=tk.W, pady=5)
+ self.script_path_var = tk.StringVar()
+ ttk.Entry(add_frame, textvariable=self.script_path_var).grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5)
+ ttk.Button(add_frame, text="选择文件", command=self.browse_file).grid(row=2, column=2, padx=5)
+
+ ttk.Label(add_frame, text="标签(逗号分隔,可选):").grid(row=3, column=0, sticky=tk.W, pady=5)
+ self.tags_var = tk.StringVar()
+ ttk.Entry(add_frame, textvariable=self.tags_var).grid(row=3, column=1, sticky=(tk.W, tk.E), pady=5)
+
+ ttk.Label(add_frame, text="脚本内容(可选):").grid(row=4, column=0, sticky=tk.W, pady=5)
+ self.script_content_text = tk.Text(add_frame, height=5)
+ self.script_content_text.grid(row=4, column=1, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
+
+ ttk.Label(add_frame, text="前端界面内容(复杂脚本可选):").grid(row=5, column=0, sticky=tk.W, pady=5)
+ self.ui_content_text = tk.Text(add_frame, height=5)
+ self.ui_content_text.grid(row=5, column=1, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
+
+ ttk.Button(add_frame, text="添加脚本", command=self.add_script).grid(row=6, column=0, columnspan=3, pady=5)
+
+ def create_edit_tab(self):
+ edit_frame = ttk.Frame(self.notebook, padding="10")
+ self.notebook.add(edit_frame, text="修改【Modify】")
+ edit_frame.columnconfigure(0, weight=1)
+ edit_frame.rowconfigure(1, weight=1)
+
+ # 搜索和脚本列表
+ ttk.Label(edit_frame, text="搜索(名称或标签):").grid(row=0, column=0, sticky=tk.W, pady=5)
+ self.edit_search_var = tk.StringVar()
+ ttk.Entry(edit_frame, textvariable=self.edit_search_var).grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
+ ttk.Button(edit_frame, text="搜索", command=self.refresh_edit_list).grid(row=0, column=2, padx=5)
+
+ self.edit_tree = ttk.Treeview(edit_frame, columns=("ID", "名称", "标签", "使用次数", "创建时间", "类型"),
+ show="headings")
+ self.edit_tree.heading("ID", text="ID")
+ self.edit_tree.heading("名称", text="名称")
+ self.edit_tree.heading("标签", text="标签")
+ self.edit_tree.heading("使用次数", text="使用次数")
+ self.edit_tree.heading("创建时间", text="创建时间")
+ self.edit_tree.heading("类型", text="类型")
+
+ self.edit_tree.column("ID", width=25, minwidth=25, stretch=False)
+ self.edit_tree.column("名称", width=150, minwidth=100, stretch=True)
+ self.edit_tree.column("标签", width=100, minwidth=80, stretch=True)
+ self.edit_tree.column("使用次数", width=80, minwidth=60, stretch=False)
+ self.edit_tree.column("创建时间", width=120, minwidth=100, stretch=True)
+ self.edit_tree.column("类型", width=80, minwidth=60, stretch=False)
+
+ self.edit_tree.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S))
+ self.edit_tree.bind("<>", self.on_edit_script_select)
+
+ # 修改区域
+ edit_input_frame = ttk.Frame(edit_frame)
+ edit_input_frame.grid(row=2, column=0, columnspan=3, pady=5, sticky=(tk.W, tk.E))
+ edit_input_frame.columnconfigure(1, weight=1)
+
+ ttk.Label(edit_input_frame, text="脚本名称:").grid(row=0, column=0, sticky=tk.W, pady=2)
+ self.edit_name_var = tk.StringVar()
+ ttk.Entry(edit_input_frame, textvariable=self.edit_name_var).grid(row=0, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ ttk.Label(edit_input_frame, text="标签(逗号分隔):").grid(row=1, column=0, sticky=tk.W, pady=2)
+ self.edit_tags_var = tk.StringVar()
+ ttk.Entry(edit_input_frame, textvariable=self.edit_tags_var).grid(row=1, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ ttk.Button(edit_input_frame, text="修改脚本", command=self.update_script).grid(row=2, column=0, pady=5)
+ ttk.Button(edit_input_frame, text="删除脚本", command=self.delete_script).grid(row=2, column=1, sticky=tk.W, pady=5)
+
+ self.refresh_edit_list()
+
+ def create_run_tab(self):
+ run_frame = ttk.Frame(self.notebook, padding="10")
+ self.notebook.add(run_frame, text="运行【Run】")
+ run_frame.columnconfigure(0, weight=1)
+ run_frame.columnconfigure(2, weight=1)
+ run_frame.rowconfigure(0, weight=1)
+
+ left_frame = ttk.Frame(run_frame)
+ left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
+ left_frame.columnconfigure(0, weight=1)
+ left_frame.rowconfigure(1, weight=1)
+
+ ttk.Label(left_frame, text="搜索(名称或标签):").grid(row=0, column=0, sticky=tk.W, pady=5)
+ self.search_var = tk.StringVar()
+ ttk.Entry(left_frame, textvariable=self.search_var).grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
+ ttk.Button(left_frame, text="搜索", command=self.search_scripts).grid(row=0, column=2, padx=5)
+
+ self.tree = ttk.Treeview(left_frame, columns=("ID", "名称", "标签", "使用次数", "创建时间", "类型"),
+ show="headings")
+ self.tree.heading("ID", text="ID")
+ self.tree.heading("名称", text="名称")
+ self.tree.heading("标签", text="标签")
+ self.tree.heading("使用次数", text="使用次数")
+ self.tree.heading("创建时间", text="创建时间")
+ self.tree.heading("类型", text="类型")
+
+ self.tree.column("ID", width=25, minwidth=25, stretch=False)
+ self.tree.column("名称", width=150, minwidth=100, stretch=True)
+ self.tree.column("标签", width=100, minwidth=80, stretch=True)
+ self.tree.column("使用次数", width=80, minwidth=60, stretch=False)
+ self.tree.column("创建时间", width=120, minwidth=100, stretch=True)
+ self.tree.column("类型", width=80, minwidth=60, stretch=False)
+
+ self.tree.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S))
+ self.tree.bind("<>", self.on_script_select)
+
+ ttk.Separator(run_frame, orient=tk.VERTICAL).grid(row=0, column=1, sticky=(tk.N, tk.S), padx=5)
+
+ self.io_frame = ttk.Frame(run_frame)
+ self.io_frame.grid(row=0, column=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 0))
+ self.io_frame.columnconfigure(0, weight=1)
+ self.io_frame.rowconfigure(0, weight=1)
+
+ self.refresh_script_list()
+
+ def browse_file(self):
+ file_path = filedialog.askopenfilename(filetypes=[("Python files", "*.py")])
+ if file_path:
+ self.script_path_var.set(file_path)
+
+ def refresh_script_list(self, search_term: str = None):
+ for item in self.tree.get_children():
+ self.tree.delete(item)
+ scripts = self.manager.list_scripts(tag=search_term, name=search_term)
+ for script in scripts:
+ self.tree.insert("", tk.END, values=(
+ script["id"],
+ script["name"],
+ ", ".join(script["tags"]),
+ script["usage_count"],
+ script["created_at"],
+ script["type"]
+ ))
+
+ def refresh_edit_list(self, search_term: str = None):
+ for item in self.edit_tree.get_children():
+ self.edit_tree.delete(item)
+ scripts = self.manager.list_scripts(tag=search_term, name=search_term)
+ for script in scripts:
+ self.edit_tree.insert("", tk.END, values=(
+ script["id"],
+ script["name"],
+ ", ".join(script["tags"]),
+ script["usage_count"],
+ script["created_at"],
+ script["type"]
+ ))
+
+ def add_script(self):
+ script_name = self.script_name_var.get().strip()
+ script_path = self.script_path_var.get().strip()
+ script_content = self.script_content_text.get("1.0", tk.END).strip()
+ ui_content = self.ui_content_text.get("1.0", tk.END).strip()
+ tags = [t.strip() for t in self.tags_var.get().split(",") if t.strip()]
+ script_type = self.script_type_var.get()
+
+ if not script_name and not script_path:
+ messagebox.showerror("错误", "脚本名称和脚本路径不能同时为空")
+ return
+ if not script_path and not script_content:
+ messagebox.showerror("错误", "脚本路径和脚本内容不能同时为空")
+ return
+ if not script_name and script_path:
+ script_name = os.path.splitext(os.path.basename(script_path))[0]
+
+ try:
+ result = self.manager.add_script(script_name, script_content or None, script_path or None,
+ ui_content or None, script_type)
+ if tags:
+ self.manager.update_script(result["id"], script_name, tags)
+ messagebox.showinfo("成功", f"脚本 '{script_name}' 已添加!\n建议标签:{', '.join(result['suggested_tags'])}")
+ self.script_name_var.set("")
+ self.script_path_var.set("")
+ self.script_content_text.delete("1.0", tk.END)
+ self.ui_content_text.delete("1.0", tk.END)
+ self.tags_var.set(", ".join(result["suggested_tags"]))
+ self.refresh_script_list()
+ self.refresh_edit_list()
+ except Exception as e:
+ messagebox.showerror("错误", f"添加脚本失败:{str(e)}")
+
+ def on_script_select(self, event):
+ selection = self.tree.selection()
+ if selection:
+ item = self.tree.item(selection[0])
+ self.selected_script_id = int(item["values"][0])
+ script = next(s for s in self.manager.list_scripts() if s["id"] == self.selected_script_id)
+ self.refresh_io_frame(script["ui_path"], script["path"])
+ self.refresh_script_list() # 实时更新使用次数
+
+ def on_edit_script_select(self, event):
+ selection = self.edit_tree.selection()
+ if selection:
+ item = self.edit_tree.item(selection[0])
+ self.selected_script_id = int(item["values"][0])
+ script = next(s for s in self.manager.list_scripts() if s["id"] == self.selected_script_id)
+ self.edit_name_var.set(script["name"])
+ self.edit_tags_var.set(", ".join(script["tags"]))
+
+ def update_script(self):
+ if not self.selected_script_id:
+ messagebox.showerror("错误", "请先选择一个脚本")
+ return
+ new_name = self.edit_name_var.get().strip()
+ new_tags = [t.strip() for t in self.edit_tags_var.get().split(",") if t.strip()]
+ if not new_name:
+ messagebox.showerror("错误", "脚本名称不能为空")
+ return
+ try:
+ self.manager.update_script(self.selected_script_id, new_name, new_tags)
+ messagebox.showinfo("成功", f"脚本 '{new_name}' 已更新")
+ self.edit_name_var.set("")
+ self.edit_tags_var.set("")
+ self.refresh_edit_list()
+ self.refresh_script_list()
+ except Exception as e:
+ messagebox.showerror("错误", f"更新脚本失败:{str(e)}")
+
+ def delete_script(self):
+ if not self.selected_script_id:
+ messagebox.showerror("错误", "请先选择一个脚本")
+ return
+ if messagebox.askyesno("确认", "确定要删除此脚本吗?"):
+ try:
+ self.manager.delete_script(self.selected_script_id)
+ messagebox.showinfo("成功", "脚本已删除")
+ self.edit_name_var.set("")
+ self.edit_tags_var.set("")
+ self.refresh_edit_list()
+ self.refresh_script_list()
+ except Exception as e:
+ messagebox.showerror("错误", f"删除脚本失败:{str(e)}")
+
+ def search_scripts(self):
+ search_term = self.search_var.get().strip()
+ self.refresh_script_list(search_term)
+
+ def refresh_io_frame(self, ui_path: str = None, script_path: str = None):
+ if self.current_ui_frame:
+ self.current_ui_frame.destroy()
+
+ if not ui_path or not script_path:
+ self.current_ui_frame = ttk.Label(
+ self.io_frame,
+ text="请选择一个脚本",
+ wraplength=int(self.io_frame.winfo_width() * 0.9) or 300
+ )
+ self.current_ui_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ return
+
+ try:
+ ui_module = self.manager.load_ui_module(ui_path)
+ def run_callback(input_strings):
+ result = self.manager.run_script(self.selected_script_id, input_strings)
+ self.refresh_script_list() # 实时更新使用次数
+ return result
+ self.current_ui_frame = ui_module.create_ui(self.io_frame, run_callback)
+ self.current_ui_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ except Exception as e:
+ frame_width = self.io_frame.winfo_width() or 300
+ self.current_ui_frame = ttk.Label(
+ self.io_frame,
+ text=f"加载前端界面失败:{str(e)}",
+ wraplength=int(frame_width * 0.9),
+ padding=(5, 5),
+ foreground="red"
+ )
+ self.current_ui_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+
+
+if __name__ == "__main__":
+ root = tk.Tk()
+ root.geometry("800x600")
+ app = ScriptManagerGUI(root)
+ root.mainloop()
\ No newline at end of file
diff --git a/scripts.db b/scripts.db
new file mode 100644
index 0000000..ec53e5c
Binary files /dev/null and b/scripts.db differ
diff --git a/scripts/json数组元素字段拼接.py b/scripts/json数组元素字段拼接.py
new file mode 100644
index 0000000..bfe5302
--- /dev/null
+++ b/scripts/json数组元素字段拼接.py
@@ -0,0 +1,34 @@
+import json
+import re
+
+def process(input_strings: list) -> list:
+ """解析 JSON 数组,提取用户指定的字段并按模板拼接"""
+ json_str, template = input_strings[0], input_strings[1]
+ try:
+ # 解析 JSON
+ data = json.loads(json_str)
+ if not isinstance(data, dict) or "data" not in data or not isinstance(data["data"], list):
+ return ["错误:JSON 格式无效,缺少 'data' 数组或不是数组"]
+
+ # 提取模板中的字段名(形如 $field$)
+ fields = re.findall(r'\$([^\$]+)\$', template)
+ if not fields:
+ return ["错误:模板中未找到有效字段(格式如 $field$)"]
+
+ # 拼接结果
+ result = []
+ for item in data["data"]:
+ # 替换模板中的字段
+ formatted = template
+ for field in fields:
+ # 获取字段值,缺失时用 "N/A"
+ value = str(item.get(field, "N/A"))
+ formatted = formatted.replace(f"${field}$", value)
+ result.append(formatted)
+
+ # 返回拼接字符串
+ return [",".join(result)] if result else ["无有效数据"]
+ except json.JSONDecodeError:
+ return ["错误:无效的 JSON 字符串"]
+ except Exception as e:
+ return [f"错误:{str(e)}"]
\ No newline at end of file
diff --git a/scripts/json数组元素字段拼接.ui.py b/scripts/json数组元素字段拼接.ui.py
new file mode 100644
index 0000000..15c128a
--- /dev/null
+++ b/scripts/json数组元素字段拼接.ui.py
@@ -0,0 +1,111 @@
+import tkinter as tk
+from tkinter import ttk
+import pyperclip
+from tkinter import messagebox
+import json
+
+def create_ui(parent, run_callback):
+ """创建前端界面:JSON 输入、字段选择、模板输入、输出显示"""
+ frame = ttk.Frame(parent)
+ frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ frame.columnconfigure(1, weight=1)
+ frame.rowconfigure(1, weight=1)
+ frame.rowconfigure(4, weight=1)
+
+ # 描述
+ ttk.Label(frame, text="解析 JSON 数组,按模板拼接指定字段(如 $field$:OJ:$field2$)").grid(
+ row=0, column=0, columnspan=3, sticky=tk.W, pady=5
+ )
+
+ # JSON 输入
+ ttk.Label(frame, text="输入 JSON:").grid(row=1, column=0, sticky=tk.NW, pady=2)
+ input_text = tk.Text(frame, height=5, width=50)
+ input_text.grid(row=1, column=1, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=2)
+ input_scroll = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=input_text.yview)
+ input_scroll.grid(row=1, column=3, sticky=(tk.N, tk.S))
+ input_text["yscrollcommand"] = input_scroll.set
+
+ # 字段选择
+ ttk.Label(frame, text="可用字段:").grid(row=2, column=0, sticky=tk.NW, pady=2)
+ field_listbox = tk.Listbox(frame, height=5, selectmode=tk.SINGLE)
+ field_listbox.grid(row=2, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), pady=2)
+ field_scroll = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=field_listbox.yview)
+ field_scroll.grid(row=2, column=2, sticky=(tk.N, tk.S))
+ field_listbox["yscrollcommand"] = field_scroll.set
+
+ def detect_fields():
+ """检测 JSON 中的字段"""
+ field_listbox.delete(0, tk.END)
+ json_str = input_text.get("1.0", tk.END).strip()
+ try:
+ data = json.loads(json_str)
+ if isinstance(data, dict) and "data" in data and isinstance(data["data"], list) and data["data"]:
+ fields = set(data["data"][0].keys())
+ for field in sorted(fields):
+ field_listbox.insert(tk.END, field)
+ else:
+ messagebox.showerror("错误", "JSON 格式无效或缺少 'data' 数组")
+ except json.JSONDecodeError:
+ messagebox.showerror("错误", "无效的 JSON 字符串")
+ except Exception as e:
+ messagebox.showerror("错误", f"检测失败:{str(e)}")
+
+ def add_to_template():
+ """将选中的字段添加到模板"""
+ selected = field_listbox.curselection()
+ if not selected:
+ messagebox.showwarning("警告", "请选择一个字段")
+ return
+ field = field_listbox.get(selected[0])
+ template_entry.insert(tk.END, f"${field}$")
+
+ ttk.Button(frame, text="检测字段", command=detect_fields).grid(row=3, column=1, sticky=tk.W, pady=2)
+ ttk.Button(frame, text="添加到模板", command=add_to_template).grid(row=3, column=1, sticky=tk.E, pady=2)
+
+ # 模板输入
+ ttk.Label(frame, text="拼接模板:").grid(row=4, column=0, sticky=tk.NW, pady=2)
+ template_entry = ttk.Entry(frame)
+ template_entry.grid(row=4, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=2)
+
+ # 输出
+ ttk.Label(frame, text="输出结果:").grid(row=5, column=0, sticky=tk.NW, pady=2)
+ output_text = tk.Text(frame, height=5, width=50, state="disabled")
+ output_text.grid(row=5, column=1, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=2)
+ output_scroll = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=output_text.yview)
+ output_scroll.grid(row=5, column=3, sticky=(tk.N, tk.S))
+ output_text["yscrollcommand"] = output_scroll.set
+
+ def copy_output(event):
+ output = output_text.get("1.0", tk.END).strip()
+ if output:
+ pyperclip.copy(output)
+ messagebox.showinfo("提示", "输出已复制到剪贴板")
+
+ output_text.bind("", copy_output)
+
+ # 运行按钮
+ def run():
+ json_str = input_text.get("1.0", tk.END).strip()
+ template = template_entry.get().strip()
+ if not json_str or not template:
+ messagebox.showerror("错误", "请输入 JSON 和模板")
+ return
+ result = run_callback([json_str, template])
+ output_text.configure(state="normal")
+ output_text.delete("1.0", tk.END)
+ output_text.insert("1.0", result[0] if result else "无结果")
+ output_text.configure(state="disabled")
+
+ # 清空按钮
+ def clear():
+ input_text.delete("1.0", tk.END)
+ template_entry.delete(0, tk.END)
+ field_listbox.delete(0, tk.END)
+ output_text.configure(state="normal")
+ output_text.delete("1.0", tk.END)
+ output_text.configure(state="disabled")
+
+ ttk.Button(frame, text="运行脚本", command=run).grid(row=6, column=0, pady=5)
+ ttk.Button(frame, text="清空", command=clear).grid(row=6, column=1, sticky=tk.W, pady=5)
+
+ return frame
\ No newline at end of file
diff --git a/scripts/多个字符串拼接为一个.py b/scripts/多个字符串拼接为一个.py
new file mode 100644
index 0000000..81ea9c2
--- /dev/null
+++ b/scripts/多个字符串拼接为一个.py
@@ -0,0 +1,45 @@
+def process(input_strings: list) -> list:
+ """将多个字符串按位置拼接为 str1:str2:str3,...,支持自定义输入和输出分隔符"""
+ try:
+ if len(input_strings) < 4:
+ return ["错误:请至少输入两个字符串、一个输入分隔符和一个输出分隔符"]
+
+ # 提取分隔符
+ output_separator = input_strings[-1].strip() # 输出分隔符
+ input_separator = input_strings[-2].strip() # 输入分隔符
+ if not input_separator or not output_separator:
+ return ["错误:输入和输出分隔符不能为空"]
+ # 支持换行符
+ if input_separator == "\\n":
+ input_separator = "\n"
+ if output_separator == "\\n":
+ output_separator = "\n"
+
+ # 输入字符串(除去最后两个分隔符)
+ input_strings = input_strings[:-2]
+ if len(input_strings) < 2:
+ return ["错误:请至少输入两个字符串"]
+
+ # 分割每个字符串为列表
+ element_lists = []
+ for s in input_strings:
+ if not s.strip():
+ return ["错误:输入字符串不能为空"]
+ elements = [e.strip() for e in s.split(input_separator) if e.strip()]
+ element_lists.append(elements)
+
+ # 检查元素数量是否一致
+ length = len(element_lists[0])
+ if not all(len(elements) == length for elements in element_lists):
+ return ["错误:所有字符串的元素数量必须相同"]
+
+ # 按位置拼接
+ result = []
+ for i in range(length):
+ concatenated = ":".join(elements[i] for elements in element_lists)
+ result.append(concatenated)
+
+ # 使用指定的输出分隔符连接结果
+ return [output_separator.join(result)]
+ except Exception as e:
+ return [f"错误:{str(e)}"]
\ No newline at end of file
diff --git a/scripts/多个字符串拼接为一个.ui.py b/scripts/多个字符串拼接为一个.ui.py
new file mode 100644
index 0000000..817d81a
--- /dev/null
+++ b/scripts/多个字符串拼接为一个.ui.py
@@ -0,0 +1,144 @@
+import tkinter as tk
+from tkinter import ttk
+import pyperclip
+from tkinter import messagebox
+
+def create_ui(parent, run_callback):
+ """创建前端界面:动态多行输入框、输入/输出分隔符、输出框、运行、清空和添加按钮"""
+ frame = ttk.Frame(parent)
+ frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ frame.columnconfigure(0, weight=1)
+ frame.rowconfigure(1, weight=1)
+
+ # 描述
+ ttk.Label(frame, text="将多个字符串按位置拼接为 str1:str2:str3,...,支持自定义输入分隔符和输出分隔符(如 \\n 表示换行)").grid(
+ row=0, column=0, columnspan=3, sticky=tk.W, pady=5
+ )
+
+ # 可滚动区域,用于输入框
+ canvas = tk.Canvas(frame)
+ canvas.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S))
+ scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=canvas.yview)
+ scrollbar.grid(row=1, column=3, sticky=(tk.N, tk.S))
+ canvas.configure(yscrollcommand=scrollbar.set)
+
+ # 内部框架,包含输入框
+ inner_frame = ttk.Frame(canvas)
+ canvas_frame_id = canvas.create_window((0, 0), window=inner_frame, anchor=tk.NW)
+
+ def update_scrollregion(event=None):
+ """更新滚动区域"""
+ canvas.configure(scrollregion=canvas.bbox("all"))
+ canvas.itemconfig(canvas_frame_id, width=canvas.winfo_width())
+
+ inner_frame.bind("", update_scrollregion)
+
+ # 存储动态控件
+ input_texts = []
+ input_labels = []
+ input_separator_entry = None
+ output_separator_entry = None
+ output_label = ttk.Label(frame, text="输出结果:")
+ output_text = tk.Text(frame, height=4, width=50, state="disabled")
+ output_scroll = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=output_text.yview)
+ add_button = None
+ run_button = None
+ clear_button = None
+
+ def add_input_field():
+ """添加新的多行输入框,无数量限制"""
+ nonlocal output_label, output_text, output_scroll, add_button, run_button, clear_button, input_separator_entry, output_separator_entry
+ row = len(input_texts)
+ label = ttk.Label(inner_frame, text=f"字符串{row + 1}:")
+ label.grid(row=row, column=0, sticky=tk.NW, pady=2)
+ text = tk.Text(inner_frame, height=3, width=50)
+ text.grid(row=row, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=2)
+ input_labels.append(label)
+ input_texts.append(text)
+ inner_frame.columnconfigure(1, weight=1)
+ # 更新滚动区域
+ update_scrollregion()
+
+ # 初始化两个输入框
+ for _ in range(2):
+ add_input_field()
+
+ # 输入分隔符
+ input_separator_label = ttk.Label(frame, text="输入分隔符(\\n 表示换行):")
+ input_separator_label.grid(row=2, column=0, sticky=tk.W, pady=2)
+ input_separator_entry = ttk.Entry(frame, width=5)
+ input_separator_entry.insert(0, ",") # 默认逗号
+ input_separator_entry.grid(row=2, column=1, sticky=tk.W, pady=2)
+
+ # 输出分隔符
+ output_separator_label = ttk.Label(frame, text="输出分隔符(\\n 表示换行):")
+ output_separator_label.grid(row=3, column=0, sticky=tk.W, pady=2)
+ output_separator_entry = ttk.Entry(frame, width=5)
+ output_separator_entry.insert(0, ",") # 默认逗号
+ output_separator_entry.grid(row=3, column=1, sticky=tk.W, pady=2)
+
+ # 输出框配置
+ output_label.grid(row=4, column=0, sticky=tk.NW, pady=2)
+ output_text.grid(row=4, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), pady=2)
+ output_scroll.grid(row=4, column=2, sticky=(tk.N, tk.S))
+ output_text["yscrollcommand"] = output_scroll.set
+
+ def copy_output(event):
+ output = output_text.get("1.0", tk.END).strip()
+ if output:
+ pyperclip.copy(output)
+ messagebox.showinfo("提示", "输出已复制到剪贴板")
+
+ output_text.bind("", copy_output)
+
+ # 添加字符串按钮
+ add_button = ttk.Button(frame, text="添加字符串", command=add_input_field)
+ add_button.grid(row=5, column=0, pady=5)
+
+ # 运行按钮
+ def run():
+ inputs = [text.get("1.0", tk.END).rstrip("\n") for text in input_texts]
+ input_separator = input_separator_entry.get().strip()
+ output_separator = output_separator_entry.get().strip()
+ if len(inputs) < 2 or not all(inputs):
+ messagebox.showerror("错误", "请至少输入两个非空字符串")
+ return
+ if not input_separator or not output_separator:
+ messagebox.showerror("错误", "请输入输入和输出分隔符")
+ return
+ # 传递字符串和分隔符
+ result = run_callback(inputs + [input_separator, output_separator])
+ output_text.configure(state="normal")
+ output_text.delete("1.0", tk.END)
+ output_text.insert("1.0", result[0] if result else "无结果")
+ output_text.configure(state="disabled")
+
+ # 清空按钮
+ def clear():
+ nonlocal output_label, output_text, output_scroll, add_button, run_button, clear_button, input_separator_entry, output_separator_entry
+ # 移除多余输入框,保留两个
+ while len(input_texts) > 2:
+ input_texts.pop().destroy()
+ input_labels.pop().destroy()
+ # 清空输入和输出
+ for text in input_texts:
+ text.delete("1.0", tk.END)
+ input_separator_entry.delete(0, tk.END)
+ input_separator_entry.insert(0, ",") # 重置输入分隔符
+ output_separator_entry.delete(0, tk.END)
+ output_separator_entry.insert(0, ",") # 重置输出分隔符
+ output_text.configure(state="normal")
+ output_text.delete("1.0", tk.END)
+ output_text.configure(state="disabled")
+ # 重置布局
+ for i, (label, text) in enumerate(zip(input_labels, input_texts)):
+ label.grid(row=i, column=0, sticky=tk.NW, pady=2)
+ text.grid(row=i, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=2)
+ update_scrollregion()
+
+ run_button = ttk.Button(frame, text="运行脚本", command=run)
+ run_button.grid(row=6, column=0, pady=5)
+ clear_button = ttk.Button(frame, text="清空", command=clear)
+ clear_button.grid(row=6, column=1, sticky=tk.W, pady=5)
+
+ return frame
\ No newline at end of file
diff --git a/scripts/时间戳.py b/scripts/时间戳.py
new file mode 100644
index 0000000..094bbd5
--- /dev/null
+++ b/scripts/时间戳.py
@@ -0,0 +1,59 @@
+from datetime import datetime
+import pytz
+from typing import List
+
+# 预缓存常用时区
+TIMEZONE_CACHE = {
+ "Asia/Shanghai": pytz.timezone("Asia/Shanghai"),
+ "UTC": pytz.timezone("UTC"),
+ "America/New_York": pytz.timezone("America/New_York"),
+ "Europe/London": pytz.timezone("Europe/London"),
+ "Australia/Sydney": pytz.timezone("Australia/Sydney")
+}
+
+def process(input_strings: List[str]) -> List[str]:
+ """处理时间戳和文本的相互转换"""
+ try:
+ if len(input_strings) != 3:
+ return ["错误:输入格式不正确,期望 [mode, input, timezone]"]
+ mode, input_str, timezone = input_strings
+
+ # 从缓存获取时区
+ tz = TIMEZONE_CACHE.get(timezone)
+ if not tz:
+ return ["错误:无效的时区"]
+
+ if mode == "timestamp_to_text":
+ try:
+ timestamp = int(input_str)
+ dt = datetime.fromtimestamp(timestamp, tz=tz)
+ return [dt.strftime("%Y-%m-%d %H:%M:%S %Z")]
+ except ValueError:
+ return ["错误:请输入有效的时间戳"]
+
+ elif mode == "text_to_timestamp":
+ try:
+ # 检查是否以 UTC 结尾
+ input_str = input_str.strip()
+ use_utc = input_str.endswith(" UTC")
+ if use_utc:
+ input_str = input_str[:-4].strip() # 移除 " UTC"
+ tz = TIMEZONE_CACHE["UTC"] # 强制使用 UTC 时区
+
+ # 尝试解析三种格式
+ try:
+ dt = datetime.strptime(input_str, "%Y-%m-%d %H:%M:%S")
+ except ValueError:
+ try:
+ dt = datetime.strptime(input_str, "%Y年%m月%d日 %H:%M:%S")
+ except ValueError:
+ # 解析新格式:MMM-DD-YYYY HH:MM:SS AM/PM
+ dt = datetime.strptime(input_str, "%b-%d-%Y %I:%M:%S %p")
+ dt = tz.localize(dt)
+ return [str(int(dt.timestamp()))]
+ except ValueError:
+ return ["错误:请输入有效的日期格式(YYYY-MM-DD HH:MM:SS 或 YYYY年MM月DD日 HH:MM:SS 或 MMM-DD-YYYY HH:MM:SS AM/PM)"]
+
+ return ["错误:无效的转换模式"]
+ except Exception as e:
+ return [f"错误:{str(e)}"]
\ No newline at end of file
diff --git a/scripts/时间戳.ui.py b/scripts/时间戳.ui.py
new file mode 100644
index 0000000..ec313d8
--- /dev/null
+++ b/scripts/时间戳.ui.py
@@ -0,0 +1,95 @@
+import tkinter as tk
+from tkinter import ttk
+import pyperclip
+from tkinter import messagebox
+from datetime import datetime
+
+def create_ui(parent, run_callback):
+ """创建时间戳转换前端界面:时间戳转文本和文本转时间戳"""
+ frame = ttk.Frame(parent)
+ frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ frame.columnconfigure(1, weight=1)
+
+ # 时区选项
+ timezones = ["Asia/Shanghai", "UTC", "America/New_York", "Europe/London", "Australia/Sydney"]
+
+ # 上半部分:时间戳转文本
+ timestamp_frame = ttk.LabelFrame(frame, text="时间戳转文本", padding=5)
+ timestamp_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
+ timestamp_frame.columnconfigure(1, weight=1)
+
+ ttk.Label(timestamp_frame, text="时间戳:").grid(row=0, column=0, sticky=tk.W, pady=2)
+ timestamp_entry = ttk.Entry(timestamp_frame)
+ timestamp_entry.insert(0, str(int(datetime.now().timestamp()))) # 默认当前时间戳
+ timestamp_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ ttk.Label(timestamp_frame, text="时区:").grid(row=1, column=0, sticky=tk.W, pady=2)
+ timestamp_tz_var = tk.StringVar(value="Asia/Shanghai")
+ ttk.Combobox(timestamp_frame, textvariable=timestamp_tz_var, values=timezones, state="readonly").grid(row=1, column=1, sticky=tk.W, pady=2)
+
+ ttk.Label(timestamp_frame, text="结果:").grid(row=2, column=0, sticky=tk.W, pady=2)
+ timestamp_result = ttk.Entry(timestamp_frame, state="readonly")
+ timestamp_result.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ def copy_timestamp_result():
+ output = timestamp_result.get()
+ if output:
+ pyperclip.copy(output)
+ messagebox.showinfo("提示", "结果已复制到剪贴板")
+
+ def convert_timestamp():
+ timestamp = timestamp_entry.get().strip()
+ if not timestamp:
+ messagebox.showerror("错误", "请输入时间戳")
+ return
+ result = run_callback(["timestamp_to_text", timestamp, timestamp_tz_var.get()])
+ timestamp_result.configure(state="normal")
+ timestamp_result.delete(0, tk.END)
+ timestamp_result.insert(0, result[0] if result else "无结果")
+ timestamp_result.configure(state="readonly")
+
+ ttk.Button(timestamp_frame, text="转换", command=convert_timestamp).grid(row=3, column=0, columnspan=2, pady=5)
+ timestamp_result.bind("", lambda e: copy_timestamp_result()) # 双击复制
+
+ # 分割线
+ ttk.Separator(frame, orient=tk.HORIZONTAL).grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10)
+
+ # 下半部分:文本转时间戳
+ text_frame = ttk.LabelFrame(frame, text="文本转时间戳", padding=5)
+ text_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
+ text_frame.columnconfigure(1, weight=1)
+
+ ttk.Label(text_frame, text="日期时间:").grid(row=0, column=0, sticky=tk.W, pady=2)
+ text_entry = ttk.Entry(text_frame)
+ text_entry.insert(0, "2025-06-05 16:29:11") # 默认时间
+ text_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ ttk.Label(text_frame, text="时区:").grid(row=1, column=0, sticky=tk.W, pady=2)
+ text_tz_var = tk.StringVar(value="Asia/Shanghai")
+ ttk.Combobox(text_frame, textvariable=text_tz_var, values=timezones, state="readonly").grid(row=1, column=1, sticky=tk.W, pady=2)
+
+ ttk.Label(text_frame, text="结果:").grid(row=2, column=0, sticky=tk.W, pady=2)
+ text_result = ttk.Entry(text_frame, state="readonly")
+ text_result.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ def copy_text_result():
+ output = text_result.get()
+ if output:
+ pyperclip.copy(output)
+ messagebox.showinfo("提示", "结果已复制到剪贴板")
+
+ def convert_text():
+ text = text_entry.get().strip()
+ if not text:
+ messagebox.showerror("错误", "请输入日期时间")
+ return
+ result = run_callback(["text_to_timestamp", text, text_tz_var.get()])
+ text_result.configure(state="normal")
+ text_result.delete(0, tk.END)
+ text_result.insert(0, result[0] if result else "无结果")
+ text_result.configure(state="readonly")
+
+ ttk.Button(text_frame, text="转换", command=convert_text).grid(row=3, column=0, columnspan=2, pady=5)
+ text_result.bind("", lambda e: copy_text_result()) # 双击复制
+
+ return frame
\ No newline at end of file
diff --git a/scripts/比较两个字符串的差异.py b/scripts/比较两个字符串的差异.py
new file mode 100644
index 0000000..15f8d18
--- /dev/null
+++ b/scripts/比较两个字符串的差异.py
@@ -0,0 +1,98 @@
+def process(input_strings: list) -> list:
+ """比较两个字符串的元素差异,支持自定义分隔符、数值比较和忽略微小差异"""
+ try:
+ if len(input_strings) != 5:
+ return ["错误:输入格式不正确,期望 [str1, str2, separator, numeric_compare, ignore_tiny_diff]"]
+ str1, str2, separator, numeric_compare, ignore_tiny_diff = input_strings
+ numeric_compare = numeric_compare.lower() == "true"
+ ignore_tiny_diff = ignore_tiny_diff.lower() == "true"
+
+ if not str1 or not str2:
+ return ["错误:请输入两个字符串"]
+
+ if separator == "\\n":
+ elements1 = [e.strip() for e in str1.splitlines() if e.strip()]
+ elements2 = [e.strip() for e in str2.splitlines() if e.strip()]
+ else:
+ elements1 = [e.strip() for e in str1.split(separator) if e.strip()]
+ elements2 = [e.strip() for e in str2.split(separator) if e.strip()]
+
+ result = []
+ missing_in_1 = []
+ missing_in_2 = []
+
+ if numeric_compare:
+ try:
+ nums1 = [(float(e), i + 1) for i, e in enumerate(elements1) if e.strip()]
+ nums2 = [(float(e), i + 1) for i, e in enumerate(elements2) if e.strip()]
+
+ if ignore_tiny_diff:
+ TOLERANCE = 1e-6
+ set1 = set()
+ set2 = set()
+ for val, idx in nums1:
+ found = False
+ for existing_val in set1:
+ if abs(existing_val - val) < TOLERANCE:
+ found = True
+ break
+ if not found:
+ set1.add(val)
+ for val, idx in nums2:
+ found = False
+ for existing_val in set2:
+ if abs(existing_val - val) < TOLERANCE:
+ found = True
+ break
+ if not found:
+ set2.add(val)
+ for val, idx in nums2:
+ matched = False
+ for v1 in set1:
+ if abs(v1 - val) < TOLERANCE:
+ matched = True
+ break
+ if not matched:
+ missing_in_1.append((idx, f"{val:.6f} (位置 {idx})"))
+ for val, idx in nums1:
+ matched = False
+ for v2 in set2:
+ if abs(v2 - val) < TOLERANCE:
+ matched = True
+ break
+ if not matched:
+ missing_in_2.append((idx, f"{val:.6f} (位置 {idx})"))
+ else:
+ set1 = {n[0] for n in nums1}
+ set2 = {n[0] for n in nums2}
+ for val, idx in nums2:
+ if val not in set1:
+ missing_in_1.append((idx, f"{val:.6f} (位置 {idx})"))
+ for val, idx in nums1:
+ if val not in set2:
+ missing_in_2.append((idx, f"{val:.6f} (位置 {idx})"))
+ except ValueError:
+ return ["错误:输入包含无法转换为数值的元素"]
+ else:
+ set1, set2 = set(elements1), set(elements2)
+ for i, e in enumerate(elements2, 1):
+ if e not in set1:
+ missing_in_1.append((i, f"{e} (位置 {i})"))
+ for i, e in enumerate(elements1, 1):
+ if e not in set2:
+ missing_in_2.append((i, f"{e} (位置 {i})"))
+
+ if missing_in_1:
+ result.append("字符串1相对于字符串2缺少:")
+ result.extend(item[1] for item in sorted(missing_in_1, key=lambda x: x[0]))
+ else:
+ result.append("字符串1相对于字符串2没少")
+ if missing_in_2:
+ result.append("字符串2相对于字符串1缺少:")
+ result.extend(item[1] for item in sorted(missing_in_2, key=lambda x: x[0]))
+ else:
+ result.append("字符串2相对于字符串1没少")
+
+ return ["\n".join(result)]
+ except Exception as e:
+ return [f"错误:{str(e)}"]
\ No newline at end of file
diff --git a/scripts/比较两个字符串的差异.ui.py b/scripts/比较两个字符串的差异.ui.py
new file mode 100644
index 0000000..8f6e936
--- /dev/null
+++ b/scripts/比较两个字符串的差异.ui.py
@@ -0,0 +1,94 @@
+import tkinter as tk
+from tkinter import ttk
+from tkinter import messagebox
+
+def create_ui(parent, run_callback):
+ """创建前端界面:两个输入框、数值比较勾选框、分隔符选择、忽略微小差异选项、输出框、运行和清空按钮"""
+ frame = ttk.Frame(parent)
+ frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ frame.columnconfigure(1, weight=1)
+ frame.rowconfigure(5, weight=1)
+
+ ttk.Label(frame, text="比较两个字符串的元素差异").grid(
+ row=0, column=0, columnspan=2, sticky=tk.W, pady=5
+ )
+
+ ttk.Label(frame, text="字符串1:").grid(row=1, column=0, sticky=tk.W, pady=2)
+ input_entry1 = tk.Text(frame, height=4, width=50)
+ input_entry1.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ ttk.Label(frame, text="字符串2:").grid(row=2, column=0, sticky=tk.W, pady=2)
+ input_entry2 = tk.Text(frame, height=4, width=50)
+ input_entry2.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=2)
+
+ ttk.Label(frame, text="分隔符:").grid(row=3, column=0, sticky=tk.W, pady=2)
+ separator_var = tk.StringVar(value=",")
+ separator_menu = ttk.OptionMenu(
+ frame,
+ separator_var,
+ ",",
+ ",", ";", "\\n", "空格"
+ )
+ separator_menu.grid(row=3, column=1, sticky=tk.W, pady=2)
+
+ numeric_var = tk.BooleanVar(value=False)
+ ttk.Checkbutton(frame, text="启用数值比较", variable=numeric_var).grid(
+ row=3, column=1, sticky=tk.E, pady=2
+ )
+
+ ignore_tiny_diff_var = tk.BooleanVar(value=False)
+ ignore_tiny_diff_check = ttk.Checkbutton(
+ frame, text="忽略微小差异", variable=ignore_tiny_diff_var,
+ command=lambda: numeric_var.set(True) if ignore_tiny_diff_var.get() else None
+ )
+ ignore_tiny_diff_check.grid(row=4, column=1, sticky=tk.E, pady=2)
+
+ ttk.Label(frame, text="比较结果:").grid(row=5, column=0, sticky=tk.NW, pady=2)
+ output_text = tk.Text(frame, height=4, width=50, state="disabled")
+ output_text.grid(row=5, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), pady=2)
+ output_scroll = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=output_text.yview)
+ output_scroll.grid(row=5, column=2, sticky=(tk.N, tk.S))
+ output_text["yscrollcommand"] = output_scroll.set
+
+ def run():
+ input1 = input_entry1.get("1.0", tk.END).strip()
+ input2 = input_entry2.get("1.0", tk.END).strip()
+ if not input1 or not input2:
+ messagebox.showerror("错误", "请输入两个字符串")
+ return
+ separator = separator_var.get()
+ if separator == "空格":
+ separator = " "
+ try:
+ input_list = [
+ input1,
+ input2,
+ separator,
+ str(numeric_var.get()).lower(),
+ str(ignore_tiny_diff_var.get()).lower()
+ ]
+ result = run_callback(input_list)
+ output_text.configure(state="normal")
+ output_text.delete("1.0", tk.END)
+ if result and result[0]:
+ output_text.insert("1.0", result[0])
+ else:
+ output_text.insert("1.0", "无结果")
+ output_text.configure(state="disabled")
+ except Exception as e:
+ messagebox.showerror("错误", f"运行脚本失败:{str(e)}")
+
+ def clear():
+ input_entry1.delete("1.0", tk.END)
+ input_entry2.delete("1.0", tk.END)
+ output_text.configure(state="normal")
+ output_text.delete("1.0", tk.END)
+ output_text.configure(state="disabled")
+ numeric_var.set(False)
+ ignore_tiny_diff_var.set(False)
+ separator_var.set(",")
+
+ ttk.Button(frame, text="运行脚本", command=run).grid(row=6, column=0, pady=5)
+ ttk.Button(frame, text="清空", command=clear).grid(row=6, column=1, sticky=tk.W, pady=5)
+
+ return frame
\ No newline at end of file