[Python] 纯文本查看 复制代码
#!/usr/bin/env python3# -*- coding: utf-8 -*-
"""
超级工具箱 v4.0 - 增强版
功能涵盖:文件操作、编解码、二维码、哈希加密、时间、网络、转换、文本、其他工具
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog, colorchooser
import base64, hashlib, os, qrcode, webbrowser, time, random, json, requests
import threading, subprocess, socket, string, re, zipfile, binascii, hmac, urllib.parse, struct
from PIL import Image, ImageTk, ImageDraw, ImageGrab
from datetime import datetime, timezone, timedelta
from tkinter import scrolledtext
import pyperclip
import zhconv
# ======================== 全局样式与主题 ========================
BG_COLOR = "#EEF1F5"
NAV_COLOR = "#1A2332"
NAV_HOVER = "#2C3E50"
NAV_ACTIVE = "#E74C3C"
CARD_BG = "#FFFFFF"
TEXT_WHITE = "#FFFFFF"
TEXT_DARK = "#2C3E50"
TEXT_GRAY = "#95A5A6"
ACCENT = "#E74C3C"
ACCENT2 = "#3498DB"
SUCCESS = "#27AE60"
WARNING = "#F39C12"
FONT = "Microsoft YaHei UI"
F12 = 12
F14 = 14
F16 = 16
F18 = 18
F20 = 20
CODE_FONT = ("Consolas", 12)
BODY = (FONT, F12)
BODY_B = (FONT, F12, "bold")
TITLE = (FONT, F16, "bold")
TITLE_SM = (FONT, F14, "bold")
TITLE_LG = (FONT, F20, "bold")
PAD = 12
class ModernButton(tk.Frame):
"""自定义现代扁平化导航按钮"""
def __init__(self, parent, text, command, bg=NAV_COLOR, hover_bg=NAV_HOVER,
active_bg=NAV_ACTIVE, **kw):
super().__init__(parent, bg=bg, cursor="hand2", height=44)
self.command = command
self.bg = bg
self.hover_bg = hover_bg
self.active_bg = active_bg
self.is_active = False
self.label = tk.Label(self, text=text, bg=bg, fg=TEXT_WHITE,
font=(FONT, F12), anchor="w", padx=24, pady=10)
self.label.pack(fill="x", expand=True)
for w in (self, self.label):
w.bind("<Enter>", self._enter)
w.bind("<Leave>", self._leave)
w.bind("<Button-1>", self._click)
def _enter(self, e):
if not self.is_active:
c = self.hover_bg
self.config(bg=c); self.label.config(bg=c)
def _leave(self, e):
if not self.is_active:
c = self.bg
self.config(bg=c); self.label.config(bg=c)
def _click(self, e):
self.command()
def set_active(self, active):
self.is_active = active
c = self.active_bg if active else self.bg
self.config(bg=c); self.label.config(bg=c)
class SectionFrame(tk.LabelFrame):
"""统一区块容器"""
def __init__(self, parent, title, **kw):
super().__init__(parent, text=f" {title} ", bg=CARD_BG, fg=TEXT_DARK,
font=TITLE_SM, padx=PAD, pady=PAD,
relief="groove", bd=1, **kw)
# ======================== 主应用 ========================
class ToolBox:
def __init__(self, root):
self.root = root
self.root.title("超级工具箱 v4.0 - 增强版")
self.root.geometry("1500x950")
self.root.configure(bg=BG_COLOR)
self.root.minsize(1200, 800)
try:
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except Exception:
pass
self.style = ttk.Style()
self.style.theme_use("clam")
self.style.configure("TFrame", background=BG_COLOR)
self.style.configure("TLabel", font=BODY, background=BG_COLOR)
self.style.configure("Card.TLabel", font=BODY, background=CARD_BG)
self.style.configure("TButton", font=BODY, padding=6)
self.style.configure("Header.TLabel", font=TITLE, background=BG_COLOR)
self.style.configure("Treeview", font=(FONT, 11), rowheight=28)
self.style.configure("Treeview.Heading", font=(FONT, 11, "bold"))
self.style.configure("TNotebook", background=BG_COLOR)
self.style.configure("TNotebook.Tab", font=(FONT, F12), padding=[16, 8])
self.style.configure("Accent.TButton", font=BODY_B)
self.files_to_compress = []
self.current_frame = None
self.nav_buttons = []
self.chat_server_running = False
self.chat_clients = []
self.clip_history = []
self.center_img_path = None
# ---- 左侧导航 ----
self.nav_frame = tk.Frame(self.root, bg=NAV_COLOR, width=240)
self.nav_frame.pack(side="left", fill="y")
self.nav_frame.pack_propagate(False)
# 导航头部
hdr = tk.Frame(self.nav_frame, bg=NAV_COLOR)
hdr.pack(fill="x", pady=(28, 20))
tk.Label(hdr, text="🛠", bg=NAV_COLOR, fg=TEXT_WHITE,
font=(FONT, 28)).pack(side="left", padx=(20, 8))
tk.Label(hdr, text="超级工具箱", bg=NAV_COLOR, fg=TEXT_WHITE,
font=(FONT, F18, "bold")).pack(side="left")
# 分割线
tk.Frame(self.nav_frame, bg="#2C3E50", height=1).pack(fill="x", padx=16, pady=4)
# 右侧内容
self.content_frame = tk.Frame(self.root, bg=BG_COLOR)
self.content_frame.pack(side="right", fill="both", expand=True)
# 状态栏
self.status_var = tk.StringVar(value="就绪")
tk.Label(self.root, textvariable=self.status_var, bg="#D5DBDB",
fg=TEXT_DARK, anchor="w", padx=16, font=(FONT, 11)).pack(side="bottom", fill="x")
# 初始化页面
self.frames = {}
self._init_frames()
self._create_navigation()
self._create_context_menu()
self.show_frame("dashboard")
self.root.protocol("WM_DELETE_WINDOW", self._on_closing)
# -------------------- 框架管理 --------------------
def _init_frames(self):
container = tk.Frame(self.content_frame, bg=BG_COLOR)
container.pack(fill="both", expand=True, padx=16, pady=16)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
pages = ["dashboard", "file_tools", "encoder_tools", "qrcode_tools",
"hash_tools", "time_tools", "network_tools",
"conversion_tools", "text_tools", "other_tools"]
for p in pages:
f = tk.Frame(container, bg=BG_COLOR)
f.grid(row=0, column=0, sticky="nsew")
self.frames[p] = f
self._build_dashboard()
self._build_file_tools()
self._build_encoder_tools()
self._build_qrcode_tools()
self._build_hash_tools()
self._build_time_tools()
self._build_network_tools()
self._build_conversion_tools()
self._build_text_tools()
self._build_other_tools()
def _create_navigation(self):
items = [
("🏠 仪表盘", "dashboard"),
("📁 文件工具", "file_tools"),
("🔐 编解码工具", "encoder_tools"),
("📱 二维码工具", "qrcode_tools"),
("🧬 哈希工具", "hash_tools"),
("⏳ 时间工具", "time_tools"),
("🌐 网络工具", "network_tools"),
("⚖️ 转换工具", "conversion_tools"),
("📝 文本工具", "text_tools"),
("🛠 其他工具", "other_tools"),
]
for text, target in items:
btn = ModernButton(self.nav_frame, text, lambda t=target: self.show_frame(t))
btn.pack(fill="x", pady=2, padx=10)
self.nav_buttons.append((btn, target))
def show_frame(self, name):
if self.current_frame:
self.frames[self.current_frame].grid_remove()
self.frames[name].grid()
self.current_frame = name
for btn, t in self.nav_buttons:
btn.set_active(t == name)
labels = {"dashboard": "仪表盘", "file_tools": "文件工具",
"encoder_tools": "编解码工具", "qrcode_tools": "二维码工具",
"hash_tools": "哈希工具", "time_tools": "时间工具",
"network_tools": "网络工具", "conversion_tools": "转换工具",
"text_tools": "文本工具", "other_tools": "其他工具"}
self.status_var.set(f"当前模块:{labels.get(name, '未知')}")
# -------------------- 右键菜单 --------------------
def _create_context_menu(self):
self.ctx = tk.Menu(self.root, tearoff=0, font=BODY)
self.ctx.add_command(label="复制", command=self._copy_text)
self.ctx.add_command(label="粘贴", command=self._paste_text)
self.root.bind("<Button-3>", lambda e: self.ctx.post(e.x_root, e.y_root))
def _copy_text(self):
w = self.root.focus_get()
if isinstance(w, (tk.Entry, tk.Text, scrolledtext.ScrolledText)):
w.event_generate("<<Copy>>")
def _paste_text(self):
w = self.root.focus_get()
if isinstance(w, (tk.Entry, tk.Text, scrolledtext.ScrolledText)):
w.event_generate("<<Paste>>")
# ==================== 仪表盘 ====================
def _build_dashboard(self):
f = self.frames["dashboard"]
tk.Label(f, text="功能鸟瞰 · 一键直达", bg=BG_COLOR, fg=TEXT_DARK,
font=TITLE_LG).pack(anchor="w", pady=(0, 20))
cats = [
("📁 文件操作", [("搜索文件", "file_tools"), ("批量重命名", "file_tools"),
("压缩 / 解压", "file_tools")]),
("🔐 编解码", [("Base64/32", "encoder_tools"), ("URL编解码", "encoder_tools"),
("Hex / ROT13 / Morse", "encoder_tools"), ("JWT解码", "encoder_tools")]),
("📱 二维码", [("生成二维码", "qrcode_tools"), ("带Logo二维码", "qrcode_tools")]),
("🧬 哈希加密", [("字符串哈希", "hash_tools"), ("文件哈希", "hash_tools"), ("HMAC", "hash_tools")]),
("⏳ 时间", [("时间戳转换", "time_tools"), ("时区转换", "time_tools")]),
("🌐 网络", [("IP查询", "network_tools"), ("端口扫描", "network_tools"),
("HTTP请求", "network_tools"), ("汇率转换", "network_tools")]),
("⚖️ 转换", [("单位 / 温度", "conversion_tools"), ("进制转换", "conversion_tools"),
("颜色转换", "conversion_tools"), ("简繁转换", "conversion_tools")]),
("📝 文本", [("JSON格式化", "text_tools"), ("正则测试", "text_tools"),
("文本统计", "text_tools"), ("大小写转换", "text_tools")]),
("🛠 其他", [("密码生成", "other_tools"), ("剪贴板历史", "other_tools"),
("颜色选择器", "other_tools")]),
]
container = tk.Frame(f, bg=BG_COLOR)
container.pack(fill="both", expand=True)
for i, (cat, funcs) in enumerate(cats):
cf = tk.Frame(container, bg=CARD_BG, bd=0, highlightthickness=1,
highlightbackground="#D5DBDB")
cf.grid(row=i // 3, column=i % 3, sticky="nsew", padx=8, pady=8)
cf.columnconfigure(0, weight=1)
tk.Label(cf, text=cat, bg=CARD_BG, fg=TEXT_DARK,
font=TITLE_SM, anchor="w").pack(fill="x", padx=16, pady=(14, 8))
for fn, tgt in funcs:
b = tk.Button(cf, text=f" → {fn}", bg="#F4F6F9", fg=TEXT_DARK,
relief="flat", font=BODY, cursor="hand2", anchor="w",
padx=16, pady=7, activebackground=ACCENT, activeforeground=TEXT_WHITE,
command=lambda t=tgt: self.show_frame(t))
b.pack(fill="x", padx=16, pady=3)
tk.Frame(cf, bg=CARD_BG, height=8).pack()
# ==================== 文件工具 ====================
def _build_file_tools(self):
f = self.frames["file_tools"]
f.columnconfigure(0, weight=1)
# -- 搜索 --
sf = SectionFrame(f, "文件搜索"); sf.grid(row=0, column=0, sticky="nsew", pady=6)
sf.columnconfigure(1, weight=1)
r = 0
for lbl, attr in [("搜索目录:", "path_entry"), ("关键词(支持正则):", "keyword_entry"),
("扩展名(如 .txt):", "ext_entry"), ("最小大小(KB):", "minsize_entry")]:
tk.Label(sf, text=lbl, bg=CARD_BG, font=BODY).grid(row=r, column=0, sticky="w", pady=5)
e = ttk.Entry(sf, width=50, font=BODY); e.grid(row=r, column=1, padx=6, pady=5, sticky="ew")
setattr(self, attr, e); r += 1
ttk.Button(sf, text="选择目录", command=self._browse_dir).grid(row=0, column=2, padx=6)
ttk.Button(sf, text="🔍 开始搜索", command=self._search_files).grid(row=r, column=1, sticky="w", pady=8)
cols = ("文件名", "大小(KB)", "修改时间")
self.file_tree = ttk.Treeview(sf, columns=cols, show="headings", height=8)
for c in cols:
self.file_tree.heading(c, text=c)
self.file_tree.column(c, anchor="w", width=250 if c == "文件名" else 120)
self.file_tree.grid(row=r + 1, column=0, columnspan=3, padx=4, pady=4, sticky="nsew")
self.file_tree.bind("<Double-1>", self._open_file)
self.search_prog = ttk.Progressbar(sf, mode="determinate")
self.search_prog.grid(row=r + 2, column=0, columnspan=3, sticky="ew", padx=4, pady=4)
# -- 重命名 & 压缩 --
of = SectionFrame(f, "批量重命名 / 压缩 / 解压"); of.grid(row=1, column=0, sticky="nsew", pady=6)
of.columnconfigure(1, weight=1); of.columnconfigure(3, weight=1)
tk.Label(of, text="原字符串:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=4, pady=5)
self.orig_name_e = ttk.Entry(of, width=24, font=BODY); self.orig_name_e.grid(row=0, column=1, padx=4, sticky="ew")
tk.Label(of, text="新字符串:", bg=CARD_BG, font=BODY).grid(row=0, column=2, padx=4, pady=5)
self.new_name_e = ttk.Entry(of, width=24, font=BODY); self.new_name_e.grid(row=0, column=3, padx=4, sticky="ew")
ttk.Button(of, text="执行重命名", command=self._batch_rename).grid(row=0, column=4, padx=8)
ttk.Separator(of).grid(row=1, column=0, columnspan=5, sticky="ew", pady=12)
ttk.Button(of, text="选择文件压缩", command=self._sel_compress).grid(row=2, column=0, padx=4)
self.comp_lbl = ttk.Label(of, text="未选择文件"); self.comp_lbl.grid(row=2, column=1, columnspan=2, sticky="w", padx=4)
ttk.Button(of, text="压缩为ZIP", command=self._compress).grid(row=2, column=3, padx=4)
ttk.Separator(of).grid(row=3, column=0, columnspan=5, sticky="ew", pady=12)
ttk.Button(of, text="选择ZIP解压", command=self._decompress).grid(row=4, column=0, padx=4)
self.decomp_lbl = ttk.Label(of, text="未选择文件"); self.decomp_lbl.grid(row=4, column=1, columnspan=2, sticky="w", padx=4)
ttk.Button(of, text="解压到目录", command=self._do_decompress).grid(row=4, column=3, padx=4)
self.decompress_src = None
def _browse_dir(self):
p = filedialog.askdirectory()
if p: self.path_entry.delete(0, tk.END); self.path_entry.insert(0, p)
def _search_files(self):
d = self.path_entry.get().strip()
kw = self.keyword_entry.get().strip()
ext = self.ext_entry.get().strip().lower()
try: minsz = float(self.minsize_entry.get()) * 1024 if self.minsize_entry.get() else 0
except: minsz = 0
if not d:
messagebox.showwarning("提示", "请先选择目录"); return
for it in self.file_tree.get_children(): self.file_tree.delete(it)
self.search_prog["value"] = 0; self.status_var.set("正在搜索...")
def run():
all_files = []
for root_d, _, files in os.walk(d):
for fn in files:
all_files.append(os.path.join(root_d, fn))
total = len(all_files)
self.root.after(0, lambda: self.search_prog.configure(maximum=max(total, 1)))
for i, fp in enumerate(all_files):
fn = os.path.basename(fp)
if kw and not re.search(kw, fn, re.IGNORECASE):
self.root.after(0, lambda v=i+1: self.search_prog.configure(value=v))
continue
if ext and not fn.lower().endswith(ext):
self.root.after(0, lambda v=i+1: self.search_prog.configure(value=v))
continue
try: st = os.stat(fp)
except:
self.root.after(0, lambda v=i+1: self.search_prog.configure(value=v))
continue
sz = st.st_size / 1024
if sz < minsz:
self.root.after(0, lambda v=i+1: self.search_prog.configure(value=v))
continue
mt = datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
self.root.after(0, lambda p=fp, s=sz, m=mt, v=i+1: (
self.file_tree.insert("", tk.END, values=(p, f"{s:.1f}", m)),
self.search_prog.configure(value=v)))
self.root.after(0, lambda: self.status_var.set("搜索完成"))
threading.Thread(target=run, daemon=True).start()
def _open_file(self, _):
it = self.file_tree.focus()
if it:
fp = self.file_tree.item(it, "values")[0]
if os.path.isfile(fp):
try:
os.startfile(fp) if os.name == "nt" else webbrowser.open(fp)
except Exception as e:
messagebox.showerror("错误", str(e))
def _batch_rename(self):
orig = self.orig_name_e.get(); new = self.new_name_e.get()
d = self.path_entry.get().strip()
if not d: messagebox.showwarning("提示", "请先填写搜索目录"); return
cnt = 0
for fn in os.listdir(d):
if orig in fn:
try: os.rename(os.path.join(d, fn), os.path.join(d, fn.replace(orig, new))); cnt += 1
except: pass
messagebox.showinfo("完成", f"成功重命名 {cnt} 个文件")
def _sel_compress(self):
fs = filedialog.askopenfilenames(title="选择要压缩的文件")
if fs: self.files_to_compress = fs; self.comp_lbl.config(text=f"{len(fs)} 个文件已选")
else: self.files_to_compress = []; self.comp_lbl.config(text="未选择文件")
def _compress(self):
if not self.files_to_compress: messagebox.showwarning("提示", "请先选择文件"); return
zp = filedialog.asksaveasfilename(defaultextension=".zip", filetypes=[("ZIP", "*.zip")])
if zp:
try:
with zipfile.ZipFile(zp, "w", zipfile.ZIP_DEFLATED) as zf:
for fp in self.files_to_compress:
zf.write(fp, os.path.basename(fp))
messagebox.showinfo("完成", f"成功压缩为 {zp}")
except Exception as e:
messagebox.showerror("错误", str(e))
def _decompress(self):
fp = filedialog.askopenfilename(filetypes=[("ZIP", "*.zip")])
if fp: self.decompress_src = fp; self.decomp_lbl.config(text=os.path.basename(fp))
def _do_decompress(self):
if not self.decompress_src: messagebox.showwarning("提示", "请先选择ZIP文件"); return
dp = filedialog.askdirectory(title="选择解压目录")
if dp:
try:
with zipfile.ZipFile(self.decompress_src, "r") as zf: zf.extractall(dp)
messagebox.showinfo("完成", f"已解压到 {dp}")
except Exception as e:
messagebox.showerror("错误", str(e))
# ==================== 编解码工具 ====================
def _build_encoder_tools(self):
f = self.frames["encoder_tools"]
# IO区
iof = SectionFrame(f, "输入 / 输出"); iof.pack(fill="both", expand=True, pady=6)
iof.columnconfigure(1, weight=1)
tk.Label(iof, text="原始内容:", bg=CARD_BG, font=BODY).grid(row=0, column=0, sticky="nw", pady=6)
self.enc_in = scrolledtext.ScrolledText(iof, height=7, font=CODE_FONT, wrap="word")
self.enc_in.grid(row=0, column=1, padx=6, pady=6, sticky="nsew")
tk.Label(iof, text="转换结果:", bg=CARD_BG, font=BODY).grid(row=1, column=0, sticky="nw", pady=6)
self.enc_out = scrolledtext.ScrolledText(iof, height=7, font=CODE_FONT, wrap="word")
self.enc_out.grid(row=1, column=1, padx=6, pady=6, sticky="nsew")
# 按钮区
bf = SectionFrame(f, "操作"); bf.pack(fill="x", pady=6)
buttons = [
("Base64编码", lambda: self._enc_dec("b64e")), ("Base64解码", lambda: self._enc_dec("b64d")),
("Base32编码", lambda: self._enc_dec("b32e")), ("Base32解码", lambda: self._enc_dec("b32d")),
("Hex编码", lambda: self._enc_dec("hexe")), ("Hex解码", lambda: self._enc_dec("hexd")),
("URL编码", lambda: self._enc_dec("urle")), ("URL解码", lambda: self._enc_dec("urld")),
("Unicode→中文", lambda: self._enc_dec("uned")), ("中文→Unicode", lambda: self._enc_dec("unen")),
("HTML实体编码", lambda: self._enc_dec("htmle")), ("HTML实体解码", lambda: self._enc_dec("htmld")),
("ROT13", lambda: self._enc_dec("rot13")),
("Morse编码", lambda: self._enc_dec("morse_e")), ("Morse解码", lambda: self._enc_dec("morse_d")),
("JWT解码", lambda: self._enc_dec("jwt")),
]
for i, (txt, cmd) in enumerate(buttons):
ttk.Button(bf, text=txt, command=cmd).grid(row=i // 5, column=i % 5, padx=6, pady=5, sticky="ew")
for c in range(5): bf.columnconfigure(c, weight=1)
ttk.Button(bf, text="清空", command=self._clear_enc).grid(row=3, column=4, padx=6, pady=5, sticky="e")
MORSE_MAP = {
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.',
'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.',
'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
'Y': '-.--', 'Z': '--..', '0': '-----', '1': '.----', '2': '..---',
'3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...',
'8': '---..', '9': '----.', ' ': '/'
}
MORSE_REV = {v: k for k, v in MORSE_MAP.items()}
def _enc_output(self, result):
self.enc_out.delete("1.0", tk.END); self.enc_out.insert(tk.END, result)
try: pyperclip.copy(result)
except: pass
self.status_var.set("结果已复制到剪贴板")
def _enc_dec(self, mode):
text = self.enc_in.get("1.0", tk.END).strip()
if not text and mode != "jwt":
messagebox.showwarning("提示", "请输入内容"); return
try:
if mode == "b64e": r = base64.b64encode(text.encode()).decode()
elif mode == "b64d": r = base64.b64decode(text).decode()
elif mode == "b32e": r = base64.b32encode(text.encode()).decode()
elif mode == "b32d": r = base64.b32decode(text).decode()
elif mode == "hexe": r = text.encode().hex()
elif mode == "hexd": r = bytes.fromhex(text.strip()).decode()
elif mode == "urle": r = urllib.parse.quote(text)
elif mode == "urld": r = urllib.parse.unquote(text)
elif mode == "uned": r = text.encode().decode("unicode_escape")
elif mode == "unen": r = text.encode("unicode_escape").decode()
elif mode == "htmle":
r = text.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace("'", "'")
elif mode == "htmld":
import html as _html; r = _html.unescape(text)
elif mode == "rot13":
r = text.translate(str.maketrans(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
"NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"))
elif mode == "morse_e":
r = " ".join(self.MORSE_MAP.get(c.upper(), "?") for c in text)
elif mode == "morse_d":
r = "".join(self.MORSE_REV.get(w, "?") for w in text.split(" "))
elif mode == "jwt":
parts = text.split(".")
if len(parts) != 3: raise ValueError("JWT格式错误,需3段")
header = json.loads(base64.urlsafe_b64decode(parts[0] + "=="))
payload = json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
r = f"== HEADER ==\n{json.dumps(header, indent=2, ensure_ascii=False)}\n\n== PAYLOAD ==\n{json.dumps(payload, indent=2, ensure_ascii=False)}"
else: r = text
self._enc_output(r)
except Exception as e:
messagebox.showerror("错误", f"操作失败:{e}")
def _clear_enc(self):
self.enc_in.delete("1.0", tk.END); self.enc_out.delete("1.0", tk.END)
# ==================== 二维码工具 ====================
def _build_qrcode_tools(self):
f = self.frames["qrcode_tools"]; f.columnconfigure(1, weight=1)
lf = SectionFrame(f, "参数设置", width=380); lf.grid(row=0, column=0, sticky="nsew", padx=6, pady=6)
rf = SectionFrame(f, "预览"); rf.grid(row=0, column=1, sticky="nsew", padx=6, pady=6)
lf.columnconfigure(1, weight=1); r = 0
for lbl, attr, w in [("输入内容:", "qr_content", 50), ("容错级别:", "qr_error", 6),
("中心图比例(0.1~0.4):", "qr_ratio", 6)]:
tk.Label(lf, text=lbl, bg=CARD_BG, font=BODY).grid(row=r, column=0, sticky="w", pady=5)
if attr == "qr_error":
e = ttk.Combobox(lf, values=["L", "M", "Q", "H"], state="readonly", width=w, font=BODY)
e.set("H")
elif attr == "qr_ratio":
e = ttk.Spinbox(lf, from_=0.1, to=0.4, increment=0.05, width=w, font=BODY)
e.set("0.25")
else:
e = ttk.Entry(lf, width=w, font=BODY)
e.grid(row=r, column=1, padx=6, pady=5, sticky="ew"); setattr(self, attr, e); r += 1
ttk.Button(lf, text="选择中心图片", command=self._choose_center).grid(row=r, column=0, padx=4, pady=6)
self.center_lbl = ttk.Label(lf, text="无"); self.center_lbl.grid(row=r, column=1, sticky="w", padx=6)
r += 1
bf = tk.Frame(lf, bg=CARD_BG); bf.grid(row=r, column=0, columnspan=2, pady=18)
ttk.Button(bf, text="生成二维码", command=self._gen_qr).pack(side="left", padx=10)
ttk.Button(bf, text="保存二维码", command=self._save_qr).pack(side="left", padx=10)
self.qr_label = tk.Label(rf, bg=CARD_BG, width=350, height=350)
self.qr_label.pack(padx=20, pady=20, expand=True)
def _choose_center(self):
p = filedialog.askopenfilename(filetypes=[("图片", "*.png;*.jpg;*.jpeg;*.bmp")])
if p: self.center_img_path = p; self.center_lbl.config(text=os.path.basename(p))
def _gen_qr(self):
data = self.qr_content.get()
if not data: messagebox.showwarning("提示", "请输入内容"); return
try:
err = {"L": qrcode.constants.ERROR_CORRECT_L, "M": qrcode.constants.ERROR_CORRECT_M,
"Q": qrcode.constants.ERROR_CORRECT_Q, "H": qrcode.constants.ERROR_CORRECT_H
}.get(self.qr_error.get(), qrcode.constants.ERROR_CORRECT_H)
qr = qrcode.QRCode(error_correction=err, box_size=10, border=4)
qr.add_data(data); qr.make(fit=True)
self._qr_img = qr.make_image(fill_color="black", back_color="white").convert("RGB")
if self.center_img_path:
ci = Image.open(self.center_img_path)
qs = self._qr_img.size[0]; ratio = float(self.qr_ratio.get()); sz = int(qs * ratio)
resample = getattr(Image, "Resampling", Image).LANCZOS
ci = ci.resize((sz, sz), resample)
self._qr_img.paste(ci, ((qs - sz) // 2, (qs - sz) // 2))
img = self._qr_img.copy(); img.thumbnail((350, 350))
self._qr_photo = ImageTk.PhotoImage(img)
self.qr_label.config(image=self._qr_photo, width=350, height=350)
except Exception as e:
messagebox.showerror("错误", str(e))
def _save_qr(self):
if hasattr(self, "_qr_img"):
fn = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG", "*.png")])
if fn: self._qr_img.save(fn); messagebox.showinfo("成功", "已保存")
else:
messagebox.showwarning("提示", "请先生成二维码")
# ==================== 哈希工具 ====================
def _build_hash_tools(self):
f = self.frames["hash_tools"]
sf = SectionFrame(f, "字符串哈希"); sf.pack(fill="x", pady=6)
sf.columnconfigure(1, weight=1)
tk.Label(sf, text="输入字符串:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=4, pady=6)
self.hash_str_e = ttk.Entry(sf, width=60, font=BODY); self.hash_str_e.grid(row=0, column=1, padx=4, sticky="ew")
ttk.Button(sf, text="计算", command=self._calc_str_hash).grid(row=0, column=2, padx=6)
self.hash_result = scrolledtext.ScrolledText(sf, height=6, font=CODE_FONT)
self.hash_result.grid(row=1, column=0, columnspan=3, padx=4, pady=4, sticky="nsew")
ff = SectionFrame(f, "文件哈希"); ff.pack(fill="x", pady=6)
ff.columnconfigure(1, weight=1)
tk.Label(ff, text="选择文件:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=4, pady=6)
self.fhash_e = ttk.Entry(ff, width=60, font=BODY); self.fhash_e.grid(row=0, column=1, padx=4, sticky="ew")
ttk.Button(ff, text="浏览", command=self._browse_fhash).grid(row=0, column=2, padx=6)
tk.Label(ff, text="算法:", bg=CARD_BG, font=BODY).grid(row=1, column=0, padx=4, pady=6)
self.fhash_algo = ttk.Combobox(ff, values=["md5", "sha1", "sha256", "sha512", "crc32"],
state="readonly", font=BODY); self.fhash_algo.set("md5")
self.fhash_algo.grid(row=1, column=1, sticky="w", padx=4)
ttk.Button(ff, text="计算", command=self._calc_file_hash).grid(row=1, column=2, padx=6)
hf = SectionFrame(f, "HMAC签名"); hf.pack(fill="x", pady=6)
hf.columnconfigure(1, weight=1)
tk.Label(hf, text="消息:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=4, pady=6)
self.hmac_msg = ttk.Entry(hf, width=40, font=BODY); self.hmac_msg.grid(row=0, column=1, padx=4, sticky="ew")
tk.Label(hf, text="密钥:", bg=CARD_BG, font=BODY).grid(row=1, column=0, padx=4, pady=6)
self.hmac_key = ttk.Entry(hf, width=40, font=BODY); self.hmac_key.grid(row=1, column=1, padx=4, sticky="ew")
tk.Label(hf, text="算法:", bg=CARD_BG, font=BODY).grid(row=2, column=0, padx=4, pady=6)
self.hmac_algo = ttk.Combobox(hf, values=["md5", "sha1", "sha256", "sha512"],
state="readonly", font=BODY); self.hmac_algo.set("sha256")
self.hmac_algo.grid(row=2, column=1, sticky="w", padx=4)
ttk.Button(hf, text="计算HMAC", command=self._calc_hmac).grid(row=2, column=2, padx=6)
def _calc_str_hash(self):
t = self.hash_str_e.get(); algos = ["md5", "sha1", "sha256", "sha512"]
lines = []
for a in algos:
h = hashlib.new(a); h.update(t.encode()); lines.append(f"{a.upper():8s}: {h.hexdigest()}")
crc = binascii.crc32(t.encode()) & 0xffffffff
lines.append(f"{'CRC32':8s}: {crc:08x}")
self.hash_result.delete("1.0", tk.END); self.hash_result.insert(tk.END, "\n".join(lines))
def _browse_fhash(self):
fn = filedialog.askopenfilename()
if fn: self.fhash_e.delete(0, tk.END); self.fhash_e.insert(0, fn)
def _calc_file_hash(self):
fn = self.fhash_e.get()
if not os.path.isfile(fn): messagebox.showerror("错误", "文件不存在"); return
algo = self.fhash_algo.get()
try:
if algo == "crc32":
crc = 0
with open(fn, "rb") as fh:
while True:
chunk = fh.read(65536)
if not chunk: break
crc = binascii.crc32(chunk, crc)
r = f"CRC32: {crc & 0xffffffff:08x}"
else:
h = hashlib.new(algo)
with open(fn, "rb") as fh:
for chunk in iter(lambda: fh.read(65536), b""): h.update(chunk)
r = f"{algo.upper()}: {h.hexdigest()}"
self.hash_result.delete("1.0", tk.END); self.hash_result.insert(tk.END, r)
except Exception as e:
messagebox.showerror("错误", str(e))
def _calc_hmac(self):
msg = self.hmac_msg.get(); key = self.hmac_key.get(); algo = self.hmac_algo.get()
try:
h = hmac.new(key.encode(), msg.encode(), algo)
self.hash_result.delete("1.0", tk.END)
self.hash_result.insert(tk.END, f"HMAC-{algo.upper()}: {h.hexdigest()}")
except Exception as e:
messagebox.showerror("错误", str(e))
# ==================== 时间工具 ====================
def _build_time_tools(self):
f = self.frames["time_tools"]
tf = SectionFrame(f, "时间戳与日期转换"); tf.pack(fill="x", pady=6)
tf.columnconfigure(1, weight=1)
tk.Label(tf, text="时间戳:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=4, pady=6)
self.ts_e = ttk.Entry(tf, width=30, font=BODY); self.ts_e.grid(row=0, column=1, padx=4, sticky="w")
ttk.Button(tf, text="→ 日期", command=self._ts2dt).grid(row=0, column=2, padx=6)
tk.Label(tf, text="日期时间:", bg=CARD_BG, font=BODY).grid(row=1, column=0, padx=4, pady=6)
self.dt_e = ttk.Entry(tf, width=30, font=BODY); self.dt_e.grid(row=1, column=1, padx=4, sticky="w")
ttk.Button(tf, text="→ 时间戳", command=self._dt2ts).grid(row=1, column=2, padx=6)
ttk.Button(tf, text="获取当前时间", command=self._now).grid(row=2, column=1, sticky="w", pady=8)
self.now_label = tk.Label(tf, text="", bg=CARD_BG, font=(FONT, F18, "bold"), fg=ACCENT)
self.now_label.grid(row=3, column=0, columnspan=3, pady=10)
self._tick()
tzf = SectionFrame(f, "时区转换"); tzf.pack(fill="x", pady=6)
tk.Label(tzf, text="目标时区(如 +8, -5):", bg=CARD_BG, font=BODY).pack(side="left", padx=4)
self.tz_e = ttk.Entry(tzf, width=8, font=BODY); self.tz_e.pack(side="left", padx=4)
ttk.Button(tzf, text="转换", command=self._convert_tz).pack(side="left", padx=6)
self.tz_result = ttk.Label(tzf, text="结果:", font=(FONT, F14)); self.tz_result.pack(side="left", padx=10)
def _tick(self):
now = datetime.now()
self.now_label.config(text=now.strftime("%Y-%m-%d %H:%M:%S"))
self.root.after(1000, self._tick)
def _ts2dt(self):
try:
ts = int(self.ts_e.get())
self.dt_e.delete(0, tk.END); self.dt_e.insert(0, datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S"))
except: messagebox.showerror("错误", "无效的时间戳")
def _dt2ts(self):
try:
dt = datetime.strptime(self.dt_e.get(), "%Y-%m-%d %H:%M:%S")
self.ts_e.delete(0, tk.END); self.ts_e.insert(0, str(int(dt.timestamp())))
except: messagebox.showerror("错误", "格式需为 YYYY-MM-DD HH:MM:SS")
def _now(self):
self.ts_e.delete(0, tk.END); self.ts_e.insert(0, str(int(time.time())))
self.dt_e.delete(0, tk.END); self.dt_e.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
def _convert_tz(self):
try:
off = int(self.tz_e.get())
t = datetime.now(timezone.utc) + timedelta(hours=off)
self.tz_result.config(text=f"结果: {t.strftime('%Y-%m-%d %H:%M:%S')}")
except Exception as e:
messagebox.showerror("错误", str(e))
# ==================== 网络工具 ====================
def _build_network_tools(self):
f = self.frames["network_tools"]
nb = ttk.Notebook(f); nb.pack(fill="both", expand=True)
# IP
ip_t = tk.Frame(nb, bg=BG_COLOR); nb.add(ip_t, text=" IP查询 ")
tk.Label(ip_t, text="输入IP(留空查本机):", bg=BG_COLOR, font=BODY).pack(pady=8)
self.ip_e = ttk.Entry(ip_t, width=36, font=BODY); self.ip_e.pack(pady=4)
ttk.Button(ip_t, text="查询", command=self._ip_lookup).pack(pady=6)
self.ip_res = scrolledtext.ScrolledText(ip_t, height=10, font=CODE_FONT)
self.ip_res.pack(fill="both", expand=True, padx=20, pady=8)
# 端口扫描
pt_t = tk.Frame(nb, bg=BG_COLOR); nb.add(pt_t, text=" 端口扫描 ")
pc = tk.Frame(pt_t, bg=BG_COLOR); pc.pack(fill="x", padx=16, pady=10)
tk.Label(pc, text="目标IP:", bg=BG_COLOR, font=BODY).pack(side="left")
self.scan_ip_e = ttk.Entry(pc, width=18, font=BODY); self.scan_ip_e.pack(side="left", padx=4)
self.scan_ip_e.insert(0, "127.0.0.1")
tk.Label(pc, text="起始端口:", bg=BG_COLOR, font=BODY).pack(side="left", padx=(10, 0))
self.scan_sp_e = ttk.Entry(pc, width=7, font=BODY); self.scan_sp_e.pack(side="left", padx=4)
self.scan_sp_e.insert(0, "1")
tk.Label(pc, text="结束端口:", bg=BG_COLOR, font=BODY).pack(side="left", padx=(10, 0))
self.scan_ep_e = ttk.Entry(pc, width=7, font=BODY); self.scan_ep_e.pack(side="left", padx=4)
self.scan_ep_e.insert(0, "1024")
ttk.Button(pc, text="扫描", command=self._scan_ports).pack(side="left", padx=10)
cols = ("端口", "状态", "服务")
self.port_tree = ttk.Treeview(pt_t, columns=cols, show="headings", height=12)
for c in cols: self.port_tree.heading(c, text=c); self.port_tree.column(c, width=120)
self.port_tree.pack(fill="both", expand=True, padx=16, pady=4)
self.port_prog = ttk.Progressbar(pt_t, mode="determinate"); self.port_prog.pack(fill="x", padx=16, pady=6)
# HTTP
http_t = tk.Frame(nb, bg=BG_COLOR); nb.add(http_t, text=" HTTP请求 ")
hc = tk.Frame(http_t, bg=BG_COLOR); hc.pack(fill="x", padx=16, pady=10)
self.http_method = ttk.Combobox(hc, values=["GET", "POST", "PUT", "DELETE"],
state="readonly", width=8, font=BODY)
self.http_method.set("GET"); self.http_method.pack(side="left", padx=4)
self.http_url = ttk.Entry(hc, width=50, font=BODY); self.http_url.pack(side="left", padx=4, fill="x", expand=True)
self.http_url.insert(0, "https://httpbin.org/get")
ttk.Button(hc, text="发送", command=self._http_req).pack(side="left", padx=6)
tk.Label(http_t, text="Body (POST/PUT):", bg=BG_COLOR, font=BODY).pack(anchor="w", padx=16)
self.http_body = scrolledtext.ScrolledText(http_t, height=4, font=CODE_FONT)
self.http_body.pack(fill="x", padx=16, pady=4)
self.http_res = scrolledtext.ScrolledText(http_t, height=12, font=CODE_FONT)
self.http_res.pack(fill="both", expand=True, padx=16, pady=4)
# 汇率
rate_t = tk.Frame(nb, bg=BG_COLOR); nb.add(rate_t, text=" 汇率转换 ")
rc = tk.Frame(rate_t, bg=BG_COLOR); rc.pack(pady=30)
tk.Label(rc, text="金额:", bg=BG_COLOR, font=BODY).grid(row=0, column=0, padx=6)
self.amt_e = ttk.Entry(rc, width=12, font=BODY); self.amt_e.grid(row=0, column=1, padx=6)
self.from_cur = ttk.Combobox(rc, values=["USD", "CNY", "EUR", "JPY", "GBP", "KRW", "HKD", "TWD"],
state="readonly", width=6, font=BODY); self.from_cur.set("USD")
self.from_cur.grid(row=0, column=2, padx=6)
tk.Label(rc, text="→", bg=BG_COLOR, font=TITLE).grid(row=0, column=3)
self.to_cur = ttk.Combobox(rc, values=["USD", "CNY", "EUR", "JPY", "GBP", "KRW", "HKD", "TWD"],
state="readonly", width=6, font=BODY); self.to_cur.set("CNY")
self.to_cur.grid(row=0, column=4, padx=6)
ttk.Button(rc, text="转换", command=self._convert_currency).grid(row=0, column=5, padx=10)
self.rate_res = tk.Label(rate_t, text="", bg=BG_COLOR, font=(FONT, F18, "bold"), fg=ACCENT)
self.rate_res.pack(pady=20)
# DNS
dns_t = tk.Frame(nb, bg=BG_COLOR); nb.add(dns_t, text=" DNS查询 ")
dc = tk.Frame(dns_t, bg=BG_COLOR); dc.pack(pady=20)
tk.Label(dc, text="域名:", bg=BG_COLOR, font=BODY).pack(side="left", padx=6)
self.dns_e = ttk.Entry(dc, width=36, font=BODY); self.dns_e.pack(side="left", padx=6)
self.dns_e.insert(0, "www.baidu.com")
ttk.Button(dc, text="查询", command=self._dns_lookup).pack(side="left", padx=6)
self.dns_res = scrolledtext.ScrolledText(dns_t, height=10, font=CODE_FONT)
self.dns_res.pack(fill="both", expand=True, padx=20, pady=10)
def _ip_lookup(self):
ip = self.ip_e.get().strip()
try:
r = requests.get(f"http://ip-api.com/json/{ip}", timeout=8).json()
t = (f"国家: {r.get('country','未知')}\n地区: {r.get('regionName','未知')}\n"
f"城市: {r.get('city','未知')}\nISP: {r.get('isp','未知')}\n"
f"IP: {r.get('query','未知')}\n经度: {r.get('lon','未知')}\n纬度: {r.get('lat','未知')}")
self.ip_res.delete("1.0", tk.END); self.ip_res.insert(tk.END, t)
except Exception as e:
messagebox.showerror("错误", str(e))
def _scan_ports(self):
ip = self.scan_ip_e.get().strip()
try: sp = int(self.scan_sp_e.get()); ep = int(self.scan_ep_e.get())
except: messagebox.showerror("错误", "端口范围无效"); return
if sp < 1 or ep > 65535 or sp > ep: messagebox.showerror("错误", "端口范围1~65535"); return
for it in self.port_tree.get_children(): self.port_tree.delete(it)
total = ep - sp + 1; self.port_prog["value"] = 0; self.port_prog["maximum"] = total
self.status_var.set("正在扫描端口...")
COMMON_PORTS = {20: "FTP-DATA", 21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP",
53: "DNS", 80: "HTTP", 110: "POP3", 143: "IMAP", 443: "HTTPS",
465: "SMTPS", 993: "IMAPS", 995: "POP3S", 3306: "MySQL",
3389: "RDP", 5432: "PostgreSQL", 6379: "Redis", 8080: "HTTP-Proxy",
27017: "MongoDB"}
def chk(port):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
if s.connect_ex((ip, port)) == 0:
svc = COMMON_PORTS.get(port, "未知")
self.root.after(0, lambda p=port, sv=svc: self.port_tree.insert("", tk.END, values=(p, "开放", sv)))
s.close()
except: pass
finally:
self.root.after(0, lambda: self.port_prog.configure(value=self.port_prog["value"] + 1))
for p in range(sp, ep + 1):
threading.Thread(target=chk, args=(p,), daemon=True).start()
def _http_req(self):
url = self.http_url.get().strip()
if not url: messagebox.showwarning("提示", "请输入URL"); return
method = self.http_method.get()
body = self.http_body.get("1.0", tk.END).strip() if method in ("POST", "PUT") else None
self.status_var.set(f"正在发送 {method} 请求...")
def run():
try:
r = requests.request(method, url, data=body, timeout=10,
headers={"User-Agent": "SuperToolBox/4.0"})
t = (f"状态码: {r.status_code}\n耗时: {r.elapsed.total_seconds():.2f}s\n"
f"Content-Type: {r.headers.get('Content-Type','未知')}\n"
f"{'─'*50}\n")
try: t += json.dumps(r.json(), indent=2, ensure_ascii=False)
except: t += r.text[:5000]
self.root.after(0, lambda: (self.http_res.delete("1.0", tk.END),
self.http_res.insert(tk.END, t),
self.status_var.set("请求完成")))
except Exception as e:
self.root.after(0, lambda: messagebox.showerror("错误", str(e)))
threading.Thread(target=run, daemon=True).start()
def _convert_currency(self):
amt = self.amt_e.get(); fc = self.from_cur.get(); tc = self.to_cur.get()
try: a = float(amt)
except: messagebox.showerror("错误", "请输入正确的金额"); return
self.status_var.set("正在获取汇率...")
def run():
try:
r = requests.get(f"https://open.er-api.com/v6/latest/{fc}", timeout=8).json()
if r.get("result") == "success":
rate = r.get("rates", {}).get(tc, 0)
res = a * rate
self.root.after(0, lambda: self.rate_res.config(
text=f"{a} {fc} = {res:.4f} {tc}\n汇率: 1 {fc} = {rate:.6f} {tc}"))
else:
self.root.after(0, lambda: messagebox.showerror("错误", "汇率获取失败"))
except Exception as e:
self.root.after(0, lambda: messagebox.showerror("错误", str(e)))
self.root.after(0, lambda: self.status_var.set("就绪"))
threading.Thread(target=run, daemon=True).start()
def _dns_lookup(self):
domain = self.dns_e.get().strip()
if not domain: messagebox.showwarning("提示", "请输入域名"); return
try:
infos = socket.getaddrinfo(domain, None)
seen = set(); lines = []
for fam, _, _, _, addr in infos:
ip = addr[0]
if ip not in seen:
seen.add(ip)
fam_name = "IPv4" if fam == socket.AF_INET else "IPv6"
lines.append(f"{fam_name}: {ip}")
self.dns_res.delete("1.0", tk.END); self.dns_res.insert(tk.END, "\n".join(lines))
except Exception as e:
messagebox.showerror("错误", str(e))
# ==================== 转换工具 ====================
def _build_conversion_tools(self):
f = self.frames["conversion_tools"]
nb = ttk.Notebook(f); nb.pack(fill="both", expand=True)
# 单位
u_t = tk.Frame(nb, bg=BG_COLOR); nb.add(u_t, text=" 单位转换 ")
uf = SectionFrame(u_t, "基础单位"); uf.pack(fill="x", padx=16, pady=16)
units = ["米", "千米", "厘米", "英尺", "英寸", "英里", "公斤", "克", "磅", "盎司"]
tk.Label(uf, text="数值:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=6, pady=8)
self.unit_val = ttk.Entry(uf, width=14, font=BODY); self.unit_val.grid(row=0, column=1, padx=6)
self.unit_from = ttk.Combobox(uf, values=units, state="readonly", width=8, font=BODY)
self.unit_from.set("米"); self.unit_from.grid(row=0, column=2, padx=6)
tk.Label(uf, text="→", bg=CARD_BG, font=TITLE).grid(row=0, column=3)
self.unit_to = ttk.Combobox(uf, values=units, state="readonly", width=8, font=BODY)
self.unit_to.set("千米"); self.unit_to.grid(row=0, column=4, padx=6)
ttk.Button(uf, text="转换", command=self._unit_conv).grid(row=0, column=5, padx=10)
self.unit_res = ttk.Label(uf, text="结果:", font=(FONT, F16, "bold"))
self.unit_res.grid(row=1, column=0, columnspan=6, pady=10)
# 温度
t_t = tk.Frame(nb, bg=BG_COLOR); nb.add(t_t, text=" 温度转换 ")
tf = SectionFrame(t_t, "温度"); tf.pack(fill="x", padx=16, pady=16)
temps = ["摄氏度", "华氏度", "开氏度"]
tk.Label(tf, text="温度:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=6, pady=8)
self.temp_val = ttk.Entry(tf, width=14, font=BODY); self.temp_val.grid(row=0, column=1, padx=6)
self.temp_from = ttk.Combobox(tf, values=temps, state="readonly", width=8, font=BODY)
self.temp_from.set("摄氏度"); self.temp_from.grid(row=0, column=2, padx=6)
tk.Label(tf, text="→", bg=CARD_BG, font=TITLE).grid(row=0, column=3)
self.temp_to = ttk.Combobox(tf, values=temps, state="readonly", width=8, font=BODY)
self.temp_to.set("华氏度"); self.temp_to.grid(row=0, column=4, padx=6)
ttk.Button(tf, text="转换", command=self._temp_conv).grid(row=0, column=5, padx=10)
self.temp_res = ttk.Label(tf, text="结果:", font=(FONT, F16, "bold"))
self.temp_res.grid(row=1, column=0, columnspan=6, pady=10)
# 进制
b_t = tk.Frame(nb, bg=BG_COLOR); nb.add(b_t, text=" 进制转换 ")
bf = SectionFrame(b_t, "进制"); bf.pack(fill="x", padx=16, pady=16)
bf.columnconfigure(1, weight=1)
tk.Label(bf, text="输入数值:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=6, pady=8)
self.base_val = ttk.Entry(bf, width=30, font=BODY); self.base_val.grid(row=0, column=1, padx=6, sticky="ew")
tk.Label(bf, text="源进制:", bg=CARD_BG, font=BODY).grid(row=1, column=0, padx=6, pady=8)
self.base_from = ttk.Combobox(bf, values=["2", "8", "10", "16"], state="readonly", width=6, font=BODY)
self.base_from.set("10"); self.base_from.grid(row=1, column=1, sticky="w", padx=6)
ttk.Button(bf, text="转换", command=self._base_conv).grid(row=1, column=2, padx=10)
self.base_res = scrolledtext.ScrolledText(bf, height=5, font=CODE_FONT)
self.base_res.grid(row=2, column=0, columnspan=3, padx=6, pady=6, sticky="nsew")
# 颜色
c_t = tk.Frame(nb, bg=BG_COLOR); nb.add(c_t, text=" 颜色转换 ")
cf = SectionFrame(c_t, "颜色"); cf.pack(fill="x", padx=16, pady=16)
cf.columnconfigure(1, weight=1)
tk.Label(cf, text="HEX(如 #FF5733):", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=6, pady=8)
self.color_hex = ttk.Entry(cf, width=16, font=BODY); self.color_hex.grid(row=0, column=1, padx=6, sticky="w")
ttk.Button(cf, text="选择颜色", command=self._pick_color).grid(row=0, column=2, padx=8)
ttk.Button(cf, text="转换", command=self._color_conv).grid(row=0, column=3, padx=8)
self.color_preview = tk.Canvas(cf, width=60, height=36, bg="#FFFFFF", highlightthickness=1)
self.color_preview.grid(row=0, column=4, padx=6)
self.color_res = ttk.Label(cf, text="结果:", font=(FONT, F14))
self.color_res.grid(row=1, column=0, columnspan=5, pady=10)
# 简繁
z_t = tk.Frame(nb, bg=BG_COLOR); nb.add(z_t, text=" 简繁转换 ")
zf = SectionFrame(z_t, "简繁转换"); zf.pack(fill="both", expand=True, padx=16, pady=16)
zf.columnconfigure(1, weight=1)
tk.Label(zf, text="输入:", bg=CARD_BG, font=BODY).grid(row=0, column=0, sticky="nw", padx=6, pady=6)
self.zh_in = scrolledtext.ScrolledText(zf, height=5, font=(FONT, F14), wrap="word")
self.zh_in.grid(row=0, column=1, padx=6, pady=6, sticky="nsew")
bb = tk.Frame(zf, bg=CARD_BG); bb.grid(row=1, column=1, sticky="w", padx=6)
ttk.Button(bb, text="转简体", command=lambda: self._zh_conv("zh-cn")).pack(side="left", padx=6)
ttk.Button(bb, text="转繁体", command=lambda: self._zh_conv("zh-tw")).pack(side="left", padx=6)
self.zh_out = scrolledtext.ScrolledText(zf, height=5, font=(FONT, F14), wrap="word")
self.zh_out.grid(row=2, column=1, padx=6, pady=6, sticky="nsew")
def _unit_conv(self):
# 统一到米或克再转出
to_meter = {"米": 1, "千米": 1000, "厘米": 0.01, "英尺": 0.3048,
"英寸": 0.0254, "英里": 1609.344}
to_gram = {"公斤": 1000, "克": 1, "磅": 453.592, "盎司": 28.3495}
try:
v = float(self.unit_val.get()); f = self.unit_from.get(); t = self.unit_to.get()
if f in to_meter and t in to_meter:
r = v * to_meter[f] / to_meter[t]
elif f in to_gram and t in to_gram:
r = v * to_gram[f] / to_gram[t]
elif f in to_meter:
# 长度转长度,如果目标是重量则不支持
messagebox.showwarning("提示", "长度与重量不能互转"); return
else:
messagebox.showwarning("提示", "长度与重量不能互转"); return
self.unit_res.config(text=f"结果: {r:.6g} {t}")
except: messagebox.showerror("错误", "无效的输入")
def _temp_conv(self):
try:
v = float(self.temp_val.get()); f = self.temp_from.get(); t = self.temp_to.get()
if f == "华氏度": c = (v - 32) * 5 / 9
elif f == "开氏度": c = v - 273.15
else: c = v
if t == "华氏度": r = c * 9 / 5 + 32
elif t == "开氏度": r = c + 273.15
else: r = c
self.temp_res.config(text=f"结果: {r:.2f} {t}")
except: messagebox.showerror("错误", "无效输入")
def _base_conv(self):
val = self.base_val.get().strip()
try:
base = int(self.base_from.get())
n = int(val, base)
lines = [f"二进制(2): {bin(n)}", f"八进制(8): {oct(n)}",
f"十进制(10): {n}", f"十六进制(16): {hex(n)}"]
self.base_res.delete("1.0", tk.END); self.base_res.insert(tk.END, "\n".join(lines))
except: messagebox.showerror("错误", "无效的数值或进制不匹配")
def _pick_color(self):
c = colorchooser.askcolor()
if c[1]: self.color_hex.delete(0, tk.END); self.color_hex.insert(0, c[1])
def _color_conv(self):
hx = self.color_hex.get().strip()
try:
hx = hx.lstrip("#")
if len(hx) == 3: hx = "".join(c * 2 for c in hx)
r, g, b = int(hx[0:2], 16), int(hx[2:4], 16), int(hx[4:6], 16)
h, s, v = self._rgb_to_hsv(r, g, b)
self.color_preview.config(bg=f"#{r:02x}{g:02x}{b:02x}")
self.color_res.config(
text=f"HEX: #{r:02x}{g:02x}{b:02x} | RGB: ({r}, {g}, {b}) | "
f"HSV: ({h}°, {s}%, {v}%) | CSS: rgb({r},{g},{b})")
except: messagebox.showerror("错误", "无效的颜色值")
@staticmethod
def _rgb_to_hsv(r, g, b):
r, g, b = r / 255, g / 255, b / 255
mx, mn = max(r, g, b), min(r, g, b); d = mx - mn
v = mx * 100
s = (d / mx * 100) if mx else 0
if d == 0: h = 0
elif mx == r: h = 60 * (((g - b) / d) % 6)
elif mx == g: h = 60 * (((b - r) / d) + 2)
else: h = 60 * (((r - g) / d) + 4)
return int(h), int(s), int(v)
def _zh_conv(self, target):
t = self.zh_in.get("1.0", tk.END)
r = zhconv.convert(t, target)
self.zh_out.delete("1.0", tk.END); self.zh_out.insert(tk.END, r)
# ==================== 文本工具 ====================
def _build_text_tools(self):
f = self.frames["text_tools"]
nb = ttk.Notebook(f); nb.pack(fill="both", expand=True)
# JSON
j_t = tk.Frame(nb, bg=BG_COLOR); nb.add(j_t, text=" JSON格式化 ")
tk.Label(j_t, text="输入JSON:", bg=BG_COLOR, font=BODY).pack(anchor="w", padx=16, pady=4)
self.json_in = scrolledtext.ScrolledText(j_t, height=8, font=CODE_FONT)
self.json_in.pack(fill="both", expand=True, padx=16, pady=4)
bb = tk.Frame(j_t, bg=BG_COLOR); bb.pack(fill="x", padx=16)
ttk.Button(bb, text="格式化", command=lambda: self._json_fmt("pretty")).pack(side="left", padx=6, pady=6)
ttk.Button(bb, text="压缩", command=lambda: self._json_fmt("min")).pack(side="left", padx=6)
ttk.Button(bb, text="校验", command=lambda: self._json_fmt("check")).pack(side="left", padx=6)
self.json_out = scrolledtext.ScrolledText(j_t, height=8, font=CODE_FONT)
self.json_out.pack(fill="both", expand=True, padx=16, pady=4)
# 正则
r_t = tk.Frame(nb, bg=BG_COLOR); nb.add(r_t, text=" 正则测试 ")
rc = tk.Frame(r_t, bg=BG_COLOR); rc.pack(fill="x", padx=16, pady=10)
tk.Label(rc, text="正则表达式:", bg=BG_COLOR, font=BODY).pack(side="left", padx=4)
self.regex_e = ttk.Entry(rc, width=30, font=BODY); self.regex_e.pack(side="left", padx=4)
tk.Label(rc, text="标志:", bg=BG_COLOR, font=BODY).pack(side="left", padx=4)
self.regex_flags = ttk.Combobox(rc, values=["无", "忽略大小写(I)", "多行(M)", "点匹配换行(S)"],
state="readonly", width=14, font=BODY)
self.regex_flags.set("无"); self.regex_flags.pack(side="left", padx=4)
ttk.Button(rc, text="匹配", command=self._regex_test).pack(side="left", padx=8)
tk.Label(r_t, text="测试文本:", bg=BG_COLOR, font=BODY).pack(anchor="w", padx=16)
self.regex_text = scrolledtext.ScrolledText(r_t, height=6, font=CODE_FONT)
self.regex_text.pack(fill="both", expand=True, padx=16, pady=4)
self.regex_res = scrolledtext.ScrolledText(r_t, height=6, font=CODE_FONT, fg="#C0392B")
self.regex_res.pack(fill="both", expand=True, padx=16, pady=4)
# 文本统计 & 大小写
s_t = tk.Frame(nb, bg=BG_COLOR); nb.add(s_t, text=" 文本统计/处理 ")
tk.Label(s_t, text="输入文本:", bg=BG_COLOR, font=BODY).pack(anchor="w", padx=16, pady=4)
self.txt_stat_in = scrolledtext.ScrolledText(s_t, height=8, font=CODE_FONT)
self.txt_stat_in.pack(fill="both", expand=True, padx=16, pady=4)
bb2 = tk.Frame(s_t, bg=BG_COLOR); bb2.pack(fill="x", padx=16)
ttk.Button(bb2, text="统计", command=self._text_stat).pack(side="left", padx=6, pady=6)
ttk.Button(bb2, text="转大写", command=lambda: self._text_case("upper")).pack(side="left", padx=6)
ttk.Button(bb2, text="转小写", command=lambda: self._text_case("lower")).pack(side="left", padx=6)
ttk.Button(bb2, text="首字母大写", command=lambda: self._text_case("title")).pack(side="left", padx=6)
ttk.Button(bb2, text="去首尾空白", command=lambda: self._text_case("strip")).pack(side="left", padx=6)
ttk.Button(bb2, text="去所有空白行", command=lambda: self._text_case("rmlines")).pack(side="left", padx=6)
self.txt_stat_res = scrolledtext.ScrolledText(s_t, height=6, font=CODE_FONT)
self.txt_stat_res.pack(fill="both", expand=True, padx=16, pady=4)
def _json_fmt(self, mode):
t = self.json_in.get("1.0", tk.END).strip()
if not t: return
try:
obj = json.loads(t)
if mode == "pretty":
r = json.dumps(obj, indent=4, ensure_ascii=False)
elif mode == "min":
r = json.dumps(obj, separators=(",", ":"), ensure_ascii=False)
else:
r = "✅ JSON 格式合法"
self.json_out.delete("1.0", tk.END); self.json_out.insert(tk.END, r)
except json.JSONDecodeError as e:
self.json_out.delete("1.0", tk.END)
self.json_out.insert(tk.END, f"❌ JSON 格式错误: {e}")
def _regex_test(self):
pat = self.regex_e.get(); txt = self.regex_text.get("1.0", tk.END)
flag_val = self.regex_flags.get()
flags = 0
if "I" in flag_val: flags |= re.IGNORECASE
if "M" in flag_val: flags |= re.MULTILINE
if "S" in flag_val: flags |= re.DOTALL
try:
matches = list(re.finditer(pat, txt, flags))
if not matches:
self.regex_res.delete("1.0", tk.END); self.regex_res.insert(tk.END, "无匹配结果")
else:
lines = [f"共 {len(matches)} 处匹配:\n"]
for i, m in enumerate(matches):
lines.append(f"[{i+1}] 位置 {m.start()}-{m.end()}: {repr(m.group())}")
if m.groups(): lines.append(f" 分组: {m.groups()}")
if m.groupdict(): lines.append(f" 命名: {m.groupdict()}")
self.regex_res.delete("1.0", tk.END); self.regex_res.insert(tk.END, "\n".join(lines))
except re.error as e:
self.regex_res.delete("1.0", tk.END); self.regex_res.insert(tk.END, f"正则错误: {e}")
def _text_stat(self):
t = self.txt_stat_in.get("1.0", tk.END)
chars = len(t); chars_ns = len(t.replace(" ", "").replace("\n", "").replace("\t", ""))
words = len(t.split()); lines = t.count("\n") + (1 if t.strip() else 0)
cjk = sum(1 for c in t if "\u4e00" <= c <= "\u9fff")
r = (f"字符数: {chars} (不含空白: {chars_ns})\n"
f"单词数: {words}\n行数: {lines}\n中文字数: {cjk}")
self.txt_stat_res.delete("1.0", tk.END); self.txt_stat_res.insert(tk.END, r)
def _text_case(self, mode):
t = self.txt_stat_in.get("1.0", tk.END)
if mode == "upper": r = t.upper()
elif mode == "lower": r = t.lower()
elif mode == "title": r = t.title()
elif mode == "strip": r = "\n".join(l.strip() for l in t.splitlines())
elif mode == "rmlines": r = "\n".join(l for l in t.splitlines() if l.strip())
else: r = t
self.txt_stat_res.delete("1.0", tk.END); self.txt_stat_res.insert(tk.END, r)
# ==================== 其他工具 ====================
def _build_other_tools(self):
f = self.frames["other_tools"]
# 密码
pf = SectionFrame(f, "随机密码生成"); pf.pack(fill="x", pady=6)
pf.columnconfigure(4, weight=1)
tk.Label(pf, text="长度:", bg=CARD_BG, font=BODY).grid(row=0, column=0, padx=6, pady=8)
self.pwd_len = ttk.Spinbox(pf, from_=6, to=64, width=5, font=BODY); self.pwd_len.set(16)
self.pwd_len.grid(row=0, column=1, padx=6)
self.pwd_digits = tk.BooleanVar(value=True)
ttk.Checkbutton(pf, text="数字", variable=self.pwd_digits).grid(row=0, column=2, padx=4)
self.pwd_symbols = tk.BooleanVar(value=True)
ttk.Checkbutton(pf, text="符号", variable=self.pwd_symbols).grid(row=0, column=3, padx=4)
ttk.Button(pf, text="生成", command=self._gen_pwd).grid(row=0, column=4, padx=6, sticky="e")
self.pwd_e = ttk.Entry(pf, width=44, font=("Consolas", F14)); self.pwd_e.grid(row=1, column=0, columnspan=5, padx=6, pady=8, sticky="ew")
self.pwd_str = ttk.Label(pf, text="强度:", font=BODY); self.pwd_str.grid(row=2, column=0, columnspan=5, padx=6, sticky="w")
ttk.Button(pf, text="复制", command=lambda: pyperclip.copy(self.pwd_e.get())).grid(row=1, column=5, padx=6)
# 剪贴板
cf = SectionFrame(f, "剪贴板历史"); cf.pack(fill="both", expand=True, pady=6)
cf.columnconfigure(0, weight=1)
bb = tk.Frame(cf, bg=CARD_BG); bb.grid(row=0, column=0, sticky="w", pady=4)
ttk.Button(bb, text="记录当前剪贴板", command=self._record_clip).pack(side="left", padx=6)
ttk.Button(bb, text="清空历史", command=self._clear_clip).pack(side="left", padx=6)
self.clip_lb = tk.Listbox(cf, height=6, font=CODE_FONT)
self.clip_lb.grid(row=1, column=0, padx=6, pady=4, sticky="nsew")
self.clip_lb.bind("<Double-1>", self._copy_clip_item)
# 颜色选择器
clf = SectionFrame(f, "颜色选择器"); clf.pack(fill="x", pady=6)
clf.columnconfigure(1, weight=1)
self.color_disp = tk.Canvas(clf, width=80, height=50, bg="#FFFFFF", highlightthickness=1,
highlightbackground="#BDC3C7")
self.color_disp.grid(row=0, column=0, padx=10, pady=10)
ttk.Button(clf, text="选择颜色", command=self._choose_color_tool).grid(row=0, column=1, padx=6, sticky="w")
self.color_tool_res = ttk.Label(clf, text="点击选择颜色...", font=(FONT, F14))
self.color_tool_res.grid(row=0, column=2, padx=10)
# 屏幕取色
sif = SectionFrame(f, "屏幕取色"); sif.pack(fill="x", pady=6)
ttk.Button(sif, text="开始取色 (点击屏幕任意位置)", command=self._screen_pick).pack(padx=10, pady=10)
self.screen_color_disp = tk.Canvas(sif, width=80, height=50, bg="#FFFFFF", highlightthickness=1,
highlightbackground="#BDC3C7")
self.screen_color_disp.pack(side="left", padx=10, pady=10)
self.screen_color_lbl = ttk.Label(sif, text="点击按钮后取色...", font=(FONT, F14))
self.screen_color_lbl.pack(side="left", padx=10)
def _gen_pwd(self):
try: length = int(self.pwd_len.get())
except: length = 16
chars = string.ascii_letters
if self.pwd_digits.get(): chars += string.digits
if self.pwd_symbols.get(): chars += "!@#$%^&*()_+-=[]{}|;:',.<>?/"
if not chars: chars = string.ascii_letters
pwd = "".join(random.choice(chars) for _ in range(length))
self.pwd_e.delete(0, tk.END); self.pwd_e.insert(0, pwd)
pyperclip.copy(pwd)
score = 0
if any(c.islower() for c in pwd): score += 1
if any(c.isupper() for c in pwd): score += 1
if any(c.isdigit() for c in pwd): score += 1
if any(c in "!@#$%^&*()_+-=[]{}|;:',.<>?/" for c in pwd): score += 1
if len(pwd) >= 12: score += 1
levels = {1: ("弱", "red"), 2: ("较弱", "orange"), 3: ("中等", "#F39C12"),
4: ("较强", "#27AE60"), 5: ("强", "#1ABC9C")}
lv, cl = levels.get(score, ("未知", "black"))
self.pwd_str.config(text=f"强度:{lv} (已复制)", foreground=cl)
def _record_clip(self):
try:
c = self.root.clipboard_get()
if c and c not in self.clip_history:
self.clip_history.append(c)
self.clip_lb.insert(tk.END, c[:60] + ("..." if len(c) > 60 else ""))
self.status_var.set("已记录")
except: pass
def _copy_clip_item(self, _):
sel = self.clip_lb.curselection()
if sel:
pyperclip.copy(self.clip_history[sel[0]])
self.status_var.set("已复制到剪贴板")
def _clear_clip(self):
self.clip_history.clear(); self.clip_lb.delete(0, tk.END)
def _choose_color_tool(self):
c = colorchooser.askcolor()
if c[1]:
r, g, b = c[0]
self.color_disp.config(bg=c[1])
h, s, v = self._rgb_to_hsv(int(r), int(g), int(b))
self.color_tool_res.config(
text=f"HEX: {c[1]} | RGB({int(r)},{int(g)},{int(b)}) | HSV({h}°,{s}%,{v}%)")
def _screen_pick(self):
self.status_var.set("请点击屏幕任意位置取色...")
self.root.config(cursor="crosshair")
self.root.bind("<Button-1>", self._do_screen_pick)
def _do_screen_pick(self, event):
try:
x, y = self.root.winfo_pointerx(), self.root.winfo_pointery()
img = ImageGrab.grab(bbox=(x, y, x + 1, y + 1))
r, g, b = img.getpixel((0, 0))[:3]
hx = f"#{r:02x}{g:02x}{b:02x}"
self.screen_color_disp.config(bg=hx)
self.screen_color_lbl.config(text=f"HEX: {hx} | RGB({r},{g},{b})")
pyperclip.copy(hx)
self.status_var.set(f"取色结果: {hx} (已复制)")
except Exception as e:
messagebox.showerror("错误", str(e))
finally:
self.root.config(cursor="")
self.root.unbind("<Button-1>")
# -------------------- 安全退出 --------------------
def _on_closing(self):
if messagebox.askokcancel("退出", "确定要退出超级工具箱吗?"):
self.chat_server_running = False
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
app = ToolBox(root)
root.mainloop()