吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8016|回复: 35
上一主题 下一主题
收起左侧

[Python 原创] 网络工具箱4.0全新版本更新了

  [复制链接]
跳转到指定楼层
楼主
suneryzgg123 发表于 2025-3-20 18:45 回帖奖励
本帖最后由 suneryzgg123 于 2026-5-5 21:14 编辑

现已更新全新代码,软件界面全部重写,并且已打包成EXE文件供下载本人也是初学者能力有限,软件刚刚打开的情况下左侧切换功能会有卡顿,一个一个点进去过个十几秒就会全部加载出来了
已更新4.0版本(更新时间2026.5.5)

蓝奏云网盘:https://wwbro.lanzouu.com/im5c33osvmji    密码:1234
源码下面附上,有能力的大佬可以继续修改优化
[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="&#128736;", 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 = [
            ("&#127968;  仪表盘", "dashboard"),
            ("&#128193;  文件工具", "file_tools"),
            ("&#128272;  编解码工具", "encoder_tools"),
            ("&#128241;  二维码工具", "qrcode_tools"),
            ("&#129516;  哈希工具", "hash_tools"),
            ("&#9203;  时间工具", "time_tools"),
            ("&#127760;  网络工具", "network_tools"),
            ("&#9878;&#65039;  转换工具", "conversion_tools"),
            ("&#128221;  文本工具", "text_tools"),
            ("&#128736;  其他工具", "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 = [
            ("&#128193; 文件操作", [("搜索文件", "file_tools"), ("批量重命名", "file_tools"),
                            ("压缩 / 解压", "file_tools")]),
            ("&#128272; 编解码", [("Base64/32", "encoder_tools"), ("URL编解码", "encoder_tools"),
                           ("Hex / ROT13 / Morse", "encoder_tools"), ("JWT解码", "encoder_tools")]),
            ("&#128241; 二维码", [("生成二维码", "qrcode_tools"), ("带Logo二维码", "qrcode_tools")]),
            ("&#129516; 哈希加密", [("字符串哈希", "hash_tools"), ("文件哈希", "hash_tools"), ("HMAC", "hash_tools")]),
            ("&#9203; 时间", [("时间戳转换", "time_tools"), ("时区转换", "time_tools")]),
            ("&#127760; 网络", [("IP查询", "network_tools"), ("端口扫描", "network_tools"),
                          ("HTTP请求", "network_tools"), ("汇率转换", "network_tools")]),
            ("&#9878;&#65039; 转换", [("单位 / 温度", "conversion_tools"), ("进制转换", "conversion_tools"),
                          ("颜色转换", "conversion_tools"), ("简繁转换", "conversion_tools")]),
            ("&#128221; 文本", [("JSON格式化", "text_tools"), ("正则测试", "text_tools"),
                          ("文本统计", "text_tools"), ("大小写转换", "text_tools")]),
            ("&#128736; 其他", [("密码生成", "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="&#128269; 开始搜索", 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 = "&#9989; 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"&#10060; 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()



屏幕截图 2025-03-24 201531.png (86.67 KB, 下载次数: 5)

屏幕截图 2025-03-24 201531.png

免费评分

参与人数 11吾爱币 +13 热心值 +9 收起 理由
清炒藕片丶 + 2 + 1 支持支持,要是能教人打包更好了
ratangao + 1 + 1 谢谢@Thanks!
dada02 + 1 谢谢@Thanks!
lizdn1 + 1 谢谢@Thanks!
beatone + 1 谢谢@Thanks!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
tomas1992 + 1 谢谢@Thanks!
lvlc + 1 谢谢@Thanks!
superboy8286 + 1 谢谢@Thanks!
daxz + 1 + 1 谢谢@Thanks!
kikyo293 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

来自 #
 楼主| suneryzgg123 发表于 2025-3-23 20:30 |楼主
各位有什么需要的功能可以说出来
推荐
ygxly123151 发表于 2025-3-27 09:52
大哥你都写出来了何不直接提取出来一个程序,这样用的人会方便很多
推荐
netdna518 发表于 2025-4-19 11:03
建议楼主加上窗口居中:
[Python] 纯文本查看 复制代码
# 设置窗口尺寸和居中位置
        window_width = 900
        window_height = 800
        screen_width = root.winfo_screenwidth()
        screen_height = root.winfo_screenheight()
        x = int((screen_width - window_width) / 2)
        y = int((screen_height - window_height) / 2)
        root.geometry(f"{window_width}x{window_height}+{x}+{y}")
沙发
一场荒唐半生梦 发表于 2025-3-20 18:58
3#
biaogedexiaohao 发表于 2025-3-20 19:12
厉害了,先看看,这个东西看上去不错
4#
lilof 发表于 2025-3-20 19:17
感觉很实用,感谢分享
5#
daxz 发表于 2025-3-20 19:53
一场荒唐半生梦 发表于 2025-3-20 18:58
楼主类似像这种能写吗?https://www.52pojie.cn/forum.php?mod=viewthread&tid=1878218&highlight=%CD%BC%C ...

大佬怎么搞汉字名称的?
6#
museum 发表于 2025-3-20 20:14
很实用,谢谢分享!
7#
一场荒唐半生梦 发表于 2025-3-20 21:05
daxz 发表于 2025-3-20 19:53
大佬怎么搞汉字名称的?

直接命名的呀。几年前注册的了
8#
dysunb 发表于 2025-3-20 21:43
功能改进了不少啊
9#
穆塔muta 发表于 2025-3-21 00:47
这到底是干嘛的呢
10#
 楼主| suneryzgg123 发表于 2025-3-21 09:20 |楼主
穆塔muta 发表于 2025-3-21 00:47
这到底是干嘛的呢

自己运行一下不就行了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-17 01:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表