这什么破名起的 发表于 2026-3-8 19:10

【控制台】星空物品查询器_V2.0 不用在各个网站找冷门代码了 自制小软件

昨天在玩游戏的时候需要黄油来进行研究旧地球美食2,结果在度娘上搜出来一堆Galgame的选项:L,简直一整个大无语,自己找的话还要一个一个网站的去搜基础代码,找了半天在代码堆里找到了我要的黄油代码,干脆直接基于python做了个小软件修修改改总算是也能用了,没有调用网络只使用本地数据库(如果有我没有录入的代码可以使用TXT手动录入格式为“代码”+ 空格2个/TAB +“名称”例:00225FC9      怪物装扮 )导入的txt文件内代码上一行加上sorting 太空服orXX是分类,软件会自动识别sorting关键词进行分类操作,下一行输入代码+名字就行,一个TXT内多分类也可以 之间空出一行继续添加sorting+空格+XX 就可以。


目前我自己在网络上搜索到的代码为2016条代码,包括技能,武器,弹药,头盔,太空服,背包,服饰,救援,食物,资源,矿物,书籍,杂志等,starfield_data.json这个文件就是建立的本地数据库。

除了自己搜索用之外别的小功能还有导出Excel功能,备份功能,收藏功能(右键菜单中,会置顶显示),鼠标左键双击条目自动复制到剪切板,下方代码生成工具箱可以为剪切板中的代码添加前缀比如复制“番茄00092B96”代码到-原始代码ID点击物品会为其添加player.additme前缀,右侧一件复制命令就可以直接在游戏内粘贴,自己加上数字就好。

软件基于python,源代码放到2楼有别的功能需求可以自己再改改{:3_118:}

查毒 https://www.virscan.org/report/f ... 4218daf5d3da926ad2a
**** Hidden Message *****

这什么破名起的 发表于 2026-3-8 19:11

import os
import json
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
from pathlib import Path
import threading
import re
import sys
import shutil

# 尝试导入 openpyxl
try:
    from openpyxl import Workbook
    from openpyxl.styles import Font
    EXCEL_SUPPORT = True
except ImportError:
    EXCEL_SUPPORT = False

# --- 配置常量 ---
VERSION = "V2.0-Unique"
DB_FILE_NAME = "starfield_data.json"
WINDOW_TITLE = f" 星空物品查询器 {VERSION}"
FONT_FAMILY = "Microsoft YaHei UI"
FONT_SIZE = 10
CODE_FONT = ("Consolas", 10)

class StarfieldLiteApp:
    def __init__(self, root):
      self.root = root
      self.root.title(WINDOW_TITLE)
      self.root.geometry("950x700")
      
      # 高 DPI 支持
      try:
            from ctypes import windll
            windll.shcore.SetProcessDpiAwareness(1)
      except:
            pass

      self.database = {}
      self.filtered_data = []
      
      # 【新增】代码反向索引:{ "00035A48": "战刀" }
      # 用于在导入时快速判断代码是否已存在
      self.code_index = {}

      # 智能获取运行目录
      if getattr(sys, 'frozen', False):
            self.base_dir = Path(sys.executable).parent.resolve()
      else:
            self.base_dir = Path(__file__).parent.resolve()
            
      self.db_path = self.base_dir / DB_FILE_NAME

      self.setup_styles()
      self.create_ui()
      self.load_database()

    def setup_styles(self):
      style = ttk.Style()
      if 'vista' in style.theme_names():
            style.theme_use('vista')
      elif 'clam' in style.theme_names():
            style.theme_use('clam')
            
      style.configure(".", font=(FONT_FAMILY, FONT_SIZE))
      style.configure("Treeview", rowheight=28, font=(FONT_FAMILY, 10))
      style.configure("Treeview.Heading", font=(FONT_FAMILY, 10, "bold"))
      style.configure("Status.TLabel", foreground="#555555", background="#f0f0f0")
      style.configure("Tool.TButton", padding=(10, 5))

    def create_ui(self):
      # === 顶部:搜索与操作 ===
      top_frame = ttk.Frame(self.root, padding="10")
      top_frame.pack(side=tk.TOP, fill=tk.X)
      
      search_frame = ttk.LabelFrame(top_frame, text=" 搜索 (名称/代码/分类)", padding=5)
      search_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
      
      self.search_var = tk.StringVar()
      self.search_var.trace_add("write", lambda *args: self.perform_search())
      
      self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, font=(FONT_FAMILY, 12))
      self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
      self.search_entry.focus_set()
      
      file_frame = ttk.Frame(top_frame)
      file_frame.pack(side=tk.RIGHT)
      
      ttk.Button(file_frame, text=" 手动加载库", command=self.manual_load_database).pack(side=tk.LEFT, padx=5)
      ttk.Button(file_frame, text=" 智能导入 TXT", command=self.import_data_threaded).pack(side=tk.LEFT, padx=2)
      
      if EXCEL_SUPPORT:
            ttk.Button(file_frame, text=" 导出 Excel", command=self.export_to_excel).pack(side=tk.LEFT, padx=2)
      else:
            ttk.Button(file_frame, text="❌ 无 Excel", command=self.show_excel_error).pack(side=tk.LEFT, padx=2)
            
      ttk.Button(file_frame, text=" 导出备份", command=self.export_data).pack(side=tk.LEFT, padx=2)
      ttk.Button(file_frame, text="️ 清空", command=self.clear_data).pack(side=tk.LEFT, padx=2)

      # === 中部:列表展示区 ===
      list_frame = ttk.Frame(self.root, padding="10")
      list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
      
      columns = ("cat", "name", "code")
      self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="browse")
      
      self.tree.heading("cat", text="分类", anchor="center")
      self.tree.heading("name", text="物品名称")
      self.tree.heading("code", text="代码 (ID)", anchor="center")
      
      self.tree.column("cat", width=90, minwidth=70, anchor="center")
      self.tree.column("name", width=350, minwidth=200)
      self.tree.column("code", width=150, minwidth=100, anchor="center")
      
      scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
      self.tree.configure(yscrollcommand=scrollbar.set)
      
      self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
      scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
      
      self.tree.bind("<Double-1>", self.on_double_click)
      self.tree.bind("<Button-3>", self.on_right_click)

      # === 下部:代码工具箱 ===
      tool_frame = ttk.LabelFrame(self.root, text="️ 代码生成工具箱", padding=10)
      tool_frame.pack(side=tk.TOP, fill=tk.X, padx=10, pady=5)
      
      row1 = ttk.Frame(tool_frame)
      row1.pack(fill=tk.X, pady=(0, 5))
      
      ttk.Label(row1, text="原始代码 ID:").pack(side=tk.LEFT, padx=(0, 5))
      self.input_raw = ttk.Entry(row1, width=20, font=CODE_FONT)
      self.input_raw.pack(side=tk.LEFT, padx=(0, 10))
      
      ttk.Button(row1, text=" 物品", command=lambda: self.generate_code("player.additem")).pack(side=tk.LEFT, padx=2)
      ttk.Button(row1, text="⭐ 技能", command=lambda: self.generate_code("player.addperk")).pack(side=tk.LEFT, padx=2)
      ttk.Button(row1, text=" 属性", command=lambda: self.generate_code("player.setav")).pack(side=tk.LEFT, padx=2)
      
      row2 = ttk.Frame(tool_frame)
      row2.pack(fill=tk.X)
      
      ttk.Label(row2, text="生成命令:").pack(side=tk.LEFT, padx=(0, 5))
      self.input_result = ttk.Entry(row2, width=50, font=CODE_FONT, state="readonly")
      self.input_result.pack(side=tk.LEFT, padx=(0, 10), fill=tk.X, expand=True)
      
      self.btn_copy_tool = ttk.Button(row2, text=" 复制命令", command=self.copy_tool_result)
      self.btn_copy_tool.pack(side=tk.RIGHT)

      # === 底部:状态栏 ===
      self.status_var = tk.StringVar()
      self.status_var.set(f"就绪 | 版本 {VERSION}")
      status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W, style="Status.TLabel")
      status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    # --- 核心逻辑 ---

    def load_database(self):
      if not self.db_path.exists():
            self.status_var.set(f"未找到数据库 | 版本 {VERSION} (请导入 TXT)")
            self.database = {}
            self.code_index = {}
            self.update_status()
            return
      
      try:
            with open(self.db_path, 'r', encoding='utf-8') as f:
                content = f.read().strip()
                self.database = json.loads(content) if content else {}
            
            # 【关键】加载时重建代码索引
            self.rebuild_code_index()
            
            self.perform_search()
            self.status_var.set(f"已加载:{len(self.database)}条数据 | 版本 {VERSION}")
      except Exception as e:
            messagebox.showerror("数据库错误", f"读取失败:\n{e}\n建议清空或重新导入。")
            self.database = {}
            self.code_index = {}
            self.update_status()

    def rebuild_code_index(self):
      """遍历数据库,重建 code -> name 映射"""
      self.code_index = {}
      for name, info in self.database.items():
            code = info.get('code', '').upper()
            if code:
                # 如果发现有多个名字对应同一个代码(脏数据),保留第一个遇到的
                if code not in self.code_index:
                  self.code_index = name

    def manual_load_database(self):
      file_path = filedialog.askopenfilename(
            title="选择数据库文件 (JSON)",
            filetypes=[("JSON Files", "*.json"), ("All Files", "*.*")],
            initialdir=self.base_dir
      )
      if not file_path: return
            
      try:
            with open(file_path, 'r', encoding='utf-8') as f:
                temp_data = json.load(f)
            
            count = len(temp_data)
            if not messagebox.askyesno("确认加载", f"找到 {count} 条数据。\n确定加载并设为默认?\n注意:这将覆盖当前内存中的所有数据。"):
                return

            shutil.copy(file_path, self.db_path)
            self.database = temp_data
            self.rebuild_code_index()
            self.perform_search()
            self.status_var.set(f"✅ 已加载 {count} 条 | 版本 {VERSION}")
            messagebox.showinfo("成功", "数据库加载成功!")
            
      except json.JSONDecodeError:
            messagebox.showerror("格式错误", "文件不是有效的 JSON。")
      except Exception as e:
            messagebox.showerror("加载失败", str(e))

    def save_database(self):
      try:
            with open(self.db_path, 'w', encoding='utf-8') as f:
                json.dump(self.database, f, ensure_ascii=False, indent=2)
            # 保存后不需要重建索引,因为只是修改现有数据,除非新增了条目
            # 但为了保险,如果在导入过程中调用了 save,索引已经在内存中维护好了
            self.update_status()
      except Exception as e:
            messagebox.showerror("保存错误", str(e))

    def normalize_category(self, cat_str):
      cat_str = cat_str.strip()
      aliases = {
            "weapon": "武器", "suit": "太空服", "armor": "太空服",
            "helmet": "头盔", "bag": "背包", "skill": "技能",
            "perk": "技能", "stat": "属性", "ammo": "弹药",
            "throwable": "投掷物", "note": "笔记", "resource": "资源",
            "misc": "杂项", "rescue": "救援", "med": "救援"
      }
      lower_cat = cat_str.lower()
      if lower_cat in aliases:
            return aliases
      return cat_str if cat_str else "杂项"

    def perform_search(self):
      query = self.search_var.get().lower()
      self.filtered_data = []
      
      for name, info in self.database.items():
            cat = info.get('cat', '杂项')
            code = info['code']
            fav = info.get('fav', False)
            
            match = not query or (query in name.lower() or query in code.lower() or query in cat.lower())
            
            if match:
                self.filtered_data.append({"name": name, "code": code, "cat": cat, "fav": fav})
      
      self.filtered_data.sort(key=lambda x: (not x['fav'], x['cat'], x['name']))
      self.refresh_tree()
      self.update_status()

    def refresh_tree(self):
      self.tree.delete(*self.tree.get_children())
      for item in self.filtered_data:
            name = item['name']
            code = item['code']
            cat = item['cat']
            fav = item['fav']
            
            display_name = f"⭐ {name}" if fav else name
            display_cat = f"[{cat}]"
            self.tree.insert("", tk.END, values=(display_cat, display_name, code))

    def update_status(self):
      total = len(self.database)
      current = len(self.filtered_data)
      self.status_var.set(f"就绪 | 版本 {VERSION} | 总数:{total} | 结果:{current}")

    # --- 交互事件 ---

    def on_double_click(self, event):
      selection = self.tree.selection()
      if not selection: return
      code = self.tree.item(selection)['values']
      self.copy_to_clipboard(str(code))
      self.status_var.set(f"✅ 已复制:{code}")

    def on_right_click(self, event):
      item_id = self.tree.identify_row(event.y)
      if not item_id: return
      
      self.tree.selection_set(item_id)
      item = self.tree.item(item_id)
      vals = item['values']
      
      real_cat = vals.strip("[]")
      name_raw = vals
      code = vals
      
      is_fav = name_raw.startswith("⭐ ")
      real_name = name_raw if is_fav else name_raw

      menu = tk.Menu(self.root, tearoff=0)
      menu.add_command(label=" 复制代码", command=lambda: self.copy_to_clipboard(code))
      menu.add_command(label="✏️ 修改代码", command=lambda: self.edit_item(real_name, 'code'))
      menu.add_command(label="️ 修改分类", command=lambda: self.edit_item(real_name, 'cat'))
      menu.add_separator()
      fav_text = "❌ 取消收藏" if is_fav else "⭐ 加入收藏"
      menu.add_command(label=fav_text, command=lambda: self.toggle_favorite(real_name))
      menu.add_separator()
      menu.add_command(label="️ 删除此项", command=lambda: self.delete_item(real_name), foreground="red")
      
      menu.post(event.x_root, event.y_root)

    def edit_item(self, name, field):
      if name not in self.database: return
      current_val = self.database.get(field, '杂项' if field == 'cat' else '')
      prompt = f"请输入新的{field}:" if field == 'code' else "请输入新的分类 (如:武器):"
      
      new_val = simpledialog.askstring(f"修改 {field}", prompt, initialvalue=current_val)
      if new_val and new_val.strip():
            old_code = self.database.get('code', '')
            
            if field == 'code':
                # 如果修改了代码,需要更新索引
                new_code = new_val.strip().upper()
                if new_code != old_code:
                  # 移除旧索引
                  if old_code in self.code_index and self.code_index == name:
                        del self.code_index
                  # 添加新索引(如果新代码没被占用)
                  if new_code in self.code_index:
                        messagebox.showwarning("代码冲突", f"代码 {new_code} 已存在于库中(属于 {self.code_index})。\n已强制更新当前物品的代码。")
                  self.code_index = name
            
            if field == 'cat':
                new_val = self.normalize_category(new_val)
               
            self.database = new_val
            self.save_database()
            self.perform_search()

    def toggle_favorite(self, name):
      if name not in self.database: return
      self.database['fav'] = not self.database.get('fav', False)
      self.save_database()
      self.perform_search()

    def delete_item(self, name):
      if messagebox.askyesno("确认删除", f"确定删除「{name}」?"):
            code = self.database.get('code', '')
            # 从索引中移除
            if code in self.code_index and self.code_index == name:
                del self.code_index
            
            del self.database
            self.save_database()
            self.perform_search()

    def copy_to_clipboard(self, text):
      self.root.clipboard_clear()
      self.root.clipboard_append(text)
      self.root.update()

    def generate_code(self, prefix):
      raw = self.input_raw.get().strip()
      if not raw:
            messagebox.showwarning("提示", "请先输入代码 ID")
            return
      clean_raw = re.sub(r'^player\.\w+\s+', '', raw)
      result = f"{prefix} {clean_raw}"
      self.input_result.config(state="normal")
      self.input_result.delete(0, tk.END)
      self.input_result.insert(0, result)
      self.input_result.config(state="readonly")

    def copy_tool_result(self):
      val = self.input_result.get()
      if val:
            self.copy_to_clipboard(val)
            self.status_var.set(f"✅ 已复制:{val}")

    # --- 导入/导出逻辑 (核心修改部分) ---

    def import_data_threaded(self):
      file_path = filedialog.askopenfilename(title="选择数据文件 (TXT)", filetypes=[("Text Files", "*.txt")])
      if not file_path: return
      
      win = tk.Toplevel(self.root)
      win.title("正在智能导入...")
      win.geometry("300x100")
      win.transient(self.root)
      win.grab_set()
      
      ttk.Label(win, text="正在解析文件...\n检测 Sorting 标记").pack(pady=20)
      pb = ttk.Progressbar(win, mode='indeterminate')
      pb.pack(fill=tk.X, padx=20)
      pb.start(10)
      
      def worker():
            try:
                stats = self.process_import_file_smart(file_path)
                self.root.after(0, lambda: self.finish_import(win, stats))
            except Exception as e:
                self.root.after(0, lambda: messagebox.showerror("导入失败", str(e)))
                self.root.after(0, win.destroy)
      
      threading.Thread(target=worker, daemon=True).start()

    def process_import_file_smart(self, path):
      """
      智能导入逻辑:
      1. 检测 'sorting 分类名' 行来切换当前分类。
      2. 检测代码唯一性,如果代码已存在,跳过该行(防止重复)。
      """
      new_count = 0
      dup_code_count = 0
      dup_name_count = 0
      current_category = "杂项"
      categories_found = set()
      
      with open(path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
      
      if not lines:
            return {"new": 0, "dup_code": 0, "dup_name": 0, "cats": []}

      for line in lines:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            
            # 【策略 1】检测 Sorting 标记
            # 匹配 "sorting 武器" 或 "Sorting:头盔" 等格式
            sort_match = re.match(r'^sorting\s*[::]?\s*(.+)$', line, re.IGNORECASE)
            if sort_match:
                current_category = self.normalize_category(sort_match.group(1))
                categories_found.add(current_category)
                continue
            
            # 【策略 2】解析数据行
            # 支持 Tab 或多个空格分隔
            parts = re.split(r'\t+|\s{2,}', line)
            
            if len(parts) >= 2:
                code = parts.strip().upper() # 统一转大写方便比对
                name = parts.strip()
               
                if not code or not name:
                  continue

                # 【策略 3】代码唯一性检查
                if code in self.code_index:
                  # 代码已存在
                  existing_name = self.code_index
                  if existing_name == name:
                        # 完全重复(名和码都一样)
                        dup_code_count += 1
                  else:
                        # 代码相同但名字不同(如:战刀 vs 战斗刀)
                        # 策略:保留旧数据,跳过新数据,计数为“代码重复”
                        dup_code_count += 1
                  continue
               
                # 【策略 4】名字重复检查(可选,防止同名不同码,但通常代码唯一更重要)
                # 这里我们主要依赖代码唯一性。如果名字重复但代码不同,视为不同物品(可能是翻译差异)
                # 如果希望名字也唯一,可以解开下面注释
                """
                if name in self.database:
                  dup_name_count += 1
                  continue
                """

                # 入库
                self.database = {
                  "code": code,
                  "cat": current_category,
                  "fav": False
                }
                # 更新索引
                self.code_index = name
                new_count += 1
      
      self.save_database()
      return {
            "new": new_count,
            "dup_code": dup_code_count,
            "dup_name": dup_name_count,
            "cats": sorted(list(categories_found))
      }

    def finish_import(self, win, stats):
      win.destroy()
      
      cat_list = ", ".join(stats['cats']) if stats['cats'] else "无新分类"
      msg = (f"✅ 智能导入完成!\n\n"
               f" 涉及分类:【{cat_list}】\n"
               f"➕ 新增物品:{stats['new']}\n"
               f"⚠️ 跳过代码重复:{stats['dup_code']} (代码已存在,保留旧数据)\n")
               # f"⚠️ 跳过名字重复:{stats['dup_name']}") # 如果启用了名字检查
      
      messagebox.showinfo("导入结果", msg)
      self.perform_search()
      self.status_var.set(f"导入成功 | 新增 {stats['new']} 条 | 版本 {VERSION}")

    def export_data(self):
      path = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text Files", "*.txt")])
      if not path: return
      try:
            grouped = {}
            for n, i in self.database.items():
                c = i.get('cat', '杂项')
                grouped.setdefault(c, []).append((n, i))
            
            with open(path, 'w', encoding='utf-8') as f:
                f.write("# Starfield Database Backup\n")
                for c, items in sorted(grouped.items()):
                  f.write(f"sorting {c}\n") # 导出时也带上 sorting 标记,方便下次导入
                  for n, i in items:
                        f.write(f"{i['code']}\t{n}\n")
                  f.write("\n")
            messagebox.showinfo("成功", f"文本备份已导出:\n{path}")
      except Exception as e:
            messagebox.showerror("错误", str(e))

    def show_excel_error(self):
      messagebox.showwarning("缺少依赖",
            "未检测到 'openpyxl' 库。\n\n"
            "如需导出 Excel,请在命令行运行:\n"
            "pip install openpyxl\n"
            "然后重新打包程序。")

    def export_to_excel(self):
      if not EXCEL_SUPPORT:
            self.show_excel_error()
            return
            
      path = filedialog.asksaveasfilename(
            defaultextension=".xlsx",
            filetypes=[("Excel Files", "*.xlsx")],
            initialfile=f"Starfield_Data_{VERSION}.xlsx"
      )
      if not path: return
      
      try:
            wb = Workbook()
            ws = wb.active
            ws.title = "Items"
            
            headers = ["分类", "物品名称", "代码 ID", "收藏"]
            ws.append(headers)
            
            for name, info in self.database.items():
                cat = info.get('cat', '杂项')
                code = info['code']
                fav = "是" if info.get('fav', False) else "否"
                ws.append()
            
            for col in ws.columns:
                max_length = 0
                column = col.column_letter
                for cell in col:
                  if cell.value:
                        max_length = max(max_length, len(str(cell.value)))
                ws.column_dimensions.width = min(max_length + 2, 50)
            
            bold_font = Font(bold=True)
            for cell in ws:
                cell.font = bold_font
               
            wb.save(path)
            messagebox.showinfo("成功", f"Excel 已导出:\n{path}\n共 {len(self.database)} 条数据。")
            
      except Exception as e:
            messagebox.showerror("导出失败", str(e))

    def clear_data(self):
      if messagebox.askyesno("高危警告", "确定要清空所有数据吗?\n此操作不可恢复!"):
            self.database = {}
            self.code_index = {}
            self.save_database()
            self.perform_search()
            messagebox.showinfo("已清空", "数据库已重置。")

if __name__ == "__main__":
    root = tk.Tk()
    app = StarfieldLiteApp(root)
    root.mainloop()

这什么破名起的 发表于 2026-3-8 19:13

import os
import json
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
from pathlib import Path
import threading
import re
import sys
import shutil

# 尝试导入 openpyxl
try:
    from openpyxl import Workbook
    from openpyxl.styles import Font
    EXCEL_SUPPORT = True
except ImportError:
    EXCEL_SUPPORT = False

# --- 配置常量 ---
VERSION = "V2.0-Unique"
DB_FILE_NAME = "starfield_data.json"
WINDOW_TITLE = f" 星空物品查询器 {VERSION}"
FONT_FAMILY = "Microsoft YaHei UI"
FONT_SIZE = 10
CODE_FONT = ("Consolas", 10)

class StarfieldLiteApp:
    def __init__(self, root):
      self.root = root
      self.root.title(WINDOW_TITLE)
      self.root.geometry("950x700")
      
      # 高 DPI 支持
      try:
            from ctypes import windll
            windll.shcore.SetProcessDpiAwareness(1)
      except:
            pass

      self.database = {}
      self.filtered_data = []
      
      # 【新增】代码反向索引:{ "00035A48": "战刀" }
      # 用于在导入时快速判断代码是否已存在
      self.code_index = {}

      # 智能获取运行目录
      if getattr(sys, 'frozen', False):
            self.base_dir = Path(sys.executable).parent.resolve()
      else:
            self.base_dir = Path(__file__).parent.resolve()
            
      self.db_path = self.base_dir / DB_FILE_NAME

      self.setup_styles()
      self.create_ui()
      self.load_database()

    def setup_styles(self):
      style = ttk.Style()
      if 'vista' in style.theme_names():
            style.theme_use('vista')
      elif 'clam' in style.theme_names():
            style.theme_use('clam')
            
      style.configure(".", font=(FONT_FAMILY, FONT_SIZE))
      style.configure("Treeview", rowheight=28, font=(FONT_FAMILY, 10))
      style.configure("Treeview.Heading", font=(FONT_FAMILY, 10, "bold"))
      style.configure("Status.TLabel", foreground="#555555", background="#f0f0f0")
      style.configure("Tool.TButton", padding=(10, 5))

    def create_ui(self):
      # === 顶部:搜索与操作 ===
      top_frame = ttk.Frame(self.root, padding="10")
      top_frame.pack(side=tk.TOP, fill=tk.X)
      
      search_frame = ttk.LabelFrame(top_frame, text=" 搜索 (名称/代码/分类)", padding=5)
      search_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
      
      self.search_var = tk.StringVar()
      self.search_var.trace_add("write", lambda *args: self.perform_search())
      
      self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, font=(FONT_FAMILY, 12))
      self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
      self.search_entry.focus_set()
      
      file_frame = ttk.Frame(top_frame)
      file_frame.pack(side=tk.RIGHT)
      
      ttk.Button(file_frame, text=" 手动加载库", command=self.manual_load_database).pack(side=tk.LEFT, padx=5)
      ttk.Button(file_frame, text=" 智能导入 TXT", command=self.import_data_threaded).pack(side=tk.LEFT, padx=2)
      
      if EXCEL_SUPPORT:
            ttk.Button(file_frame, text=" 导出 Excel", command=self.export_to_excel).pack(side=tk.LEFT, padx=2)
      else:
            ttk.Button(file_frame, text="❌ 无 Excel", command=self.show_excel_error).pack(side=tk.LEFT, padx=2)
            
      ttk.Button(file_frame, text=" 导出备份", command=self.export_data).pack(side=tk.LEFT, padx=2)
      ttk.Button(file_frame, text="️ 清空", command=self.clear_data).pack(side=tk.LEFT, padx=2)

      # === 中部:列表展示区 ===
      list_frame = ttk.Frame(self.root, padding="10")
      list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
      
      columns = ("cat", "name", "code")
      self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="browse")
      
      self.tree.heading("cat", text="分类", anchor="center")
      self.tree.heading("name", text="物品名称")
      self.tree.heading("code", text="代码 (ID)", anchor="center")
      
      self.tree.column("cat", width=90, minwidth=70, anchor="center")
      self.tree.column("name", width=350, minwidth=200)
      self.tree.column("code", width=150, minwidth=100, anchor="center")
      
      scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
      self.tree.configure(yscrollcommand=scrollbar.set)
      
      self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
      scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
      
      self.tree.bind("<Double-1>", self.on_double_click)
      self.tree.bind("<Button-3>", self.on_right_click)

      # === 下部:代码工具箱 ===
      tool_frame = ttk.LabelFrame(self.root, text="️ 代码生成工具箱", padding=10)
      tool_frame.pack(side=tk.TOP, fill=tk.X, padx=10, pady=5)
      
      row1 = ttk.Frame(tool_frame)
      row1.pack(fill=tk.X, pady=(0, 5))
      
      ttk.Label(row1, text="原始代码 ID:").pack(side=tk.LEFT, padx=(0, 5))
      self.input_raw = ttk.Entry(row1, width=20, font=CODE_FONT)
      self.input_raw.pack(side=tk.LEFT, padx=(0, 10))
      
      ttk.Button(row1, text=" 物品", command=lambda: self.generate_code("player.additem")).pack(side=tk.LEFT, padx=2)
      ttk.Button(row1, text="⭐ 技能", command=lambda: self.generate_code("player.addperk")).pack(side=tk.LEFT, padx=2)
      ttk.Button(row1, text=" 属性", command=lambda: self.generate_code("player.setav")).pack(side=tk.LEFT, padx=2)
      
      row2 = ttk.Frame(tool_frame)
      row2.pack(fill=tk.X)
      
      ttk.Label(row2, text="生成命令:").pack(side=tk.LEFT, padx=(0, 5))
      self.input_result = ttk.Entry(row2, width=50, font=CODE_FONT, state="readonly")
      self.input_result.pack(side=tk.LEFT, padx=(0, 10), fill=tk.X, expand=True)
      
      self.btn_copy_tool = ttk.Button(row2, text=" 复制命令", command=self.copy_tool_result)
      self.btn_copy_tool.pack(side=tk.RIGHT)

      # === 底部:状态栏 ===
      self.status_var = tk.StringVar()
      self.status_var.set(f"就绪 | 版本 {VERSION}")
      status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W, style="Status.TLabel")
      status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    # --- 核心逻辑 ---

    def load_database(self):
      if not self.db_path.exists():
            self.status_var.set(f"未找到数据库 | 版本 {VERSION} (请导入 TXT)")
            self.database = {}
            self.code_index = {}
            self.update_status()
            return
      
      try:
            with open(self.db_path, 'r', encoding='utf-8') as f:
                content = f.read().strip()
                self.database = json.loads(content) if content else {}
            
            # 【关键】加载时重建代码索引
            self.rebuild_code_index()
            
            self.perform_search()
            self.status_var.set(f"已加载:{len(self.database)}条数据 | 版本 {VERSION}")
      except Exception as e:
            messagebox.showerror("数据库错误", f"读取失败:\n{e}\n建议清空或重新导入。")
            self.database = {}
            self.code_index = {}
            self.update_status()

    def rebuild_code_index(self):
      """遍历数据库,重建 code -> name 映射"""
      self.code_index = {}
      for name, info in self.database.items():
            code = info.get('code', '').upper()
            if code:
                # 如果发现有多个名字对应同一个代码(脏数据),保留第一个遇到的
                if code not in self.code_index:
                  self.code_index = name

    def manual_load_database(self):
      file_path = filedialog.askopenfilename(
            title="选择数据库文件 (JSON)",
            filetypes=[("JSON Files", "*.json"), ("All Files", "*.*")],
            initialdir=self.base_dir
      )
      if not file_path: return
            
      try:
            with open(file_path, 'r', encoding='utf-8') as f:
                temp_data = json.load(f)
            
            count = len(temp_data)
            if not messagebox.askyesno("确认加载", f"找到 {count} 条数据。\n确定加载并设为默认?\n注意:这将覆盖当前内存中的所有数据。"):
                return

            shutil.copy(file_path, self.db_path)
            self.database = temp_data
            self.rebuild_code_index()
            self.perform_search()
            self.status_var.set(f"✅ 已加载 {count} 条 | 版本 {VERSION}")
            messagebox.showinfo("成功", "数据库加载成功!")
            
      except json.JSONDecodeError:
            messagebox.showerror("格式错误", "文件不是有效的 JSON。")
      except Exception as e:
            messagebox.showerror("加载失败", str(e))

    def save_database(self):
      try:
            with open(self.db_path, 'w', encoding='utf-8') as f:
                json.dump(self.database, f, ensure_ascii=False, indent=2)
            # 保存后不需要重建索引,因为只是修改现有数据,除非新增了条目
            # 但为了保险,如果在导入过程中调用了 save,索引已经在内存中维护好了
            self.update_status()
      except Exception as e:
            messagebox.showerror("保存错误", str(e))

    def normalize_category(self, cat_str):
      cat_str = cat_str.strip()
      aliases = {
            "weapon": "武器", "suit": "太空服", "armor": "太空服",
            "helmet": "头盔", "bag": "背包", "skill": "技能",
            "perk": "技能", "stat": "属性", "ammo": "弹药",
            "throwable": "投掷物", "note": "笔记", "resource": "资源",
            "misc": "杂项", "rescue": "救援", "med": "救援"
      }
      lower_cat = cat_str.lower()
      if lower_cat in aliases:
            return aliases
      return cat_str if cat_str else "杂项"

    def perform_search(self):
      query = self.search_var.get().lower()
      self.filtered_data = []
      
      for name, info in self.database.items():
            cat = info.get('cat', '杂项')
            code = info['code']
            fav = info.get('fav', False)
            
            match = not query or (query in name.lower() or query in code.lower() or query in cat.lower())
            
            if match:
                self.filtered_data.append({"name": name, "code": code, "cat": cat, "fav": fav})
      
      self.filtered_data.sort(key=lambda x: (not x['fav'], x['cat'], x['name']))
      self.refresh_tree()
      self.update_status()

    def refresh_tree(self):
      self.tree.delete(*self.tree.get_children())
      for item in self.filtered_data:
            name = item['name']
            code = item['code']
            cat = item['cat']
            fav = item['fav']
            
            display_name = f"⭐ {name}" if fav else name
            display_cat = f"[{cat}]"
            self.tree.insert("", tk.END, values=(display_cat, display_name, code))

    def update_status(self):
      total = len(self.database)
      current = len(self.filtered_data)
      self.status_var.set(f"就绪 | 版本 {VERSION} | 总数:{total} | 结果:{current}")

    # --- 交互事件 ---

    def on_double_click(self, event):
      selection = self.tree.selection()
      if not selection: return
      code = self.tree.item(selection)['values']
      self.copy_to_clipboard(str(code))
      self.status_var.set(f"✅ 已复制:{code}")

    def on_right_click(self, event):
      item_id = self.tree.identify_row(event.y)
      if not item_id: return
      
      self.tree.selection_set(item_id)
      item = self.tree.item(item_id)
      vals = item['values']
      
      real_cat = vals.strip("[]")
      name_raw = vals
      code = vals
      
      is_fav = name_raw.startswith("⭐ ")
      real_name = name_raw if is_fav else name_raw

      menu = tk.Menu(self.root, tearoff=0)
      menu.add_command(label=" 复制代码", command=lambda: self.copy_to_clipboard(code))
      menu.add_command(label="✏️ 修改代码", command=lambda: self.edit_item(real_name, 'code'))
      menu.add_command(label="️ 修改分类", command=lambda: self.edit_item(real_name, 'cat'))
      menu.add_separator()
      fav_text = "❌ 取消收藏" if is_fav else "⭐ 加入收藏"
      menu.add_command(label=fav_text, command=lambda: self.toggle_favorite(real_name))
      menu.add_separator()
      menu.add_command(label="️ 删除此项", command=lambda: self.delete_item(real_name), foreground="red")
      
      menu.post(event.x_root, event.y_root)

    def edit_item(self, name, field):
      if name not in self.database: return
      current_val = self.database.get(field, '杂项' if field == 'cat' else '')
      prompt = f"请输入新的{field}:" if field == 'code' else "请输入新的分类 (如:武器):"
      
      new_val = simpledialog.askstring(f"修改 {field}", prompt, initialvalue=current_val)
      if new_val and new_val.strip():
            old_code = self.database.get('code', '')
            
            if field == 'code':
                # 如果修改了代码,需要更新索引
                new_code = new_val.strip().upper()
                if new_code != old_code:
                  # 移除旧索引
                  if old_code in self.code_index and self.code_index == name:
                        del self.code_index
                  # 添加新索引(如果新代码没被占用)
                  if new_code in self.code_index:
                        messagebox.showwarning("代码冲突", f"代码 {new_code} 已存在于库中(属于 {self.code_index})。\n已强制更新当前物品的代码。")
                  self.code_index = name
            
            if field == 'cat':
                new_val = self.normalize_category(new_val)
               
            self.database = new_val
            self.save_database()
            self.perform_search()

    def toggle_favorite(self, name):
      if name not in self.database: return
      self.database['fav'] = not self.database.get('fav', False)
      self.save_database()
      self.perform_search()

    def delete_item(self, name):
      if messagebox.askyesno("确认删除", f"确定删除「{name}」?"):
            code = self.database.get('code', '')
            # 从索引中移除
            if code in self.code_index and self.code_index == name:
                del self.code_index
            
            del self.database
            self.save_database()
            self.perform_search()

    def copy_to_clipboard(self, text):
      self.root.clipboard_clear()
      self.root.clipboard_append(text)
      self.root.update()

    def generate_code(self, prefix):
      raw = self.input_raw.get().strip()
      if not raw:
            messagebox.showwarning("提示", "请先输入代码 ID")
            return
      clean_raw = re.sub(r'^player\.\w+\s+', '', raw)
      result = f"{prefix} {clean_raw}"
      self.input_result.config(state="normal")
      self.input_result.delete(0, tk.END)
      self.input_result.insert(0, result)
      self.input_result.config(state="readonly")

    def copy_tool_result(self):
      val = self.input_result.get()
      if val:
            self.copy_to_clipboard(val)
            self.status_var.set(f"✅ 已复制:{val}")

    # --- 导入/导出逻辑 (核心修改部分) ---

    def import_data_threaded(self):
      file_path = filedialog.askopenfilename(title="选择数据文件 (TXT)", filetypes=[("Text Files", "*.txt")])
      if not file_path: return
      
      win = tk.Toplevel(self.root)
      win.title("正在智能导入...")
      win.geometry("300x100")
      win.transient(self.root)
      win.grab_set()
      
      ttk.Label(win, text="正在解析文件...\n检测 Sorting 标记").pack(pady=20)
      pb = ttk.Progressbar(win, mode='indeterminate')
      pb.pack(fill=tk.X, padx=20)
      pb.start(10)
      
      def worker():
            try:
                stats = self.process_import_file_smart(file_path)
                self.root.after(0, lambda: self.finish_import(win, stats))
            except Exception as e:
                self.root.after(0, lambda: messagebox.showerror("导入失败", str(e)))
                self.root.after(0, win.destroy)
      
      threading.Thread(target=worker, daemon=True).start()

    def process_import_file_smart(self, path):
      """
      智能导入逻辑:
      1. 检测 'sorting 分类名' 行来切换当前分类。
      2. 检测代码唯一性,如果代码已存在,跳过该行(防止重复)。
      """
      new_count = 0
      dup_code_count = 0
      dup_name_count = 0
      current_category = "杂项"
      categories_found = set()
      
      with open(path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
      
      if not lines:
            return {"new": 0, "dup_code": 0, "dup_name": 0, "cats": []}

      for line in lines:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            
            # 【策略 1】检测 Sorting 标记
            # 匹配 "sorting 武器" 或 "Sorting:头盔" 等格式
            sort_match = re.match(r'^sorting\s*[::]?\s*(.+)$', line, re.IGNORECASE)
            if sort_match:
                current_category = self.normalize_category(sort_match.group(1))
                categories_found.add(current_category)
                continue
            
            # 【策略 2】解析数据行
            # 支持 Tab 或多个空格分隔
            parts = re.split(r'\t+|\s{2,}', line)
            
            if len(parts) >= 2:
                code = parts.strip().upper() # 统一转大写方便比对
                name = parts.strip()
               
                if not code or not name:
                  continue

                # 【策略 3】代码唯一性检查
                if code in self.code_index:
                  # 代码已存在
                  existing_name = self.code_index
                  if existing_name == name:
                        # 完全重复(名和码都一样)
                        dup_code_count += 1
                  else:
                        # 代码相同但名字不同(如:战刀 vs 战斗刀)
                        # 策略:保留旧数据,跳过新数据,计数为“代码重复”
                        dup_code_count += 1
                  continue
               
                # 【策略 4】名字重复检查(可选,防止同名不同码,但通常代码唯一更重要)
                # 这里我们主要依赖代码唯一性。如果名字重复但代码不同,视为不同物品(可能是翻译差异)
                # 如果希望名字也唯一,可以解开下面注释
                """
                if name in self.database:
                  dup_name_count += 1
                  continue
                """

                # 入库
                self.database = {
                  "code": code,
                  "cat": current_category,
                  "fav": False
                }
                # 更新索引
                self.code_index = name
                new_count += 1
      
      self.save_database()
      return {
            "new": new_count,
            "dup_code": dup_code_count,
            "dup_name": dup_name_count,
            "cats": sorted(list(categories_found))
      }

    def finish_import(self, win, stats):
      win.destroy()
      
      cat_list = ", ".join(stats['cats']) if stats['cats'] else "无新分类"
      msg = (f"✅ 智能导入完成!\n\n"
               f" 涉及分类:【{cat_list}】\n"
               f"➕ 新增物品:{stats['new']}\n"
               f"⚠️ 跳过代码重复:{stats['dup_code']} (代码已存在,保留旧数据)\n")
               # f"⚠️ 跳过名字重复:{stats['dup_name']}") # 如果启用了名字检查
      
      messagebox.showinfo("导入结果", msg)
      self.perform_search()
      self.status_var.set(f"导入成功 | 新增 {stats['new']} 条 | 版本 {VERSION}")

    def export_data(self):
      path = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text Files", "*.txt")])
      if not path: return
      try:
            grouped = {}
            for n, i in self.database.items():
                c = i.get('cat', '杂项')
                grouped.setdefault(c, []).append((n, i))
            
            with open(path, 'w', encoding='utf-8') as f:
                f.write("# Starfield Database Backup\n")
                for c, items in sorted(grouped.items()):
                  f.write(f"sorting {c}\n") # 导出时也带上 sorting 标记,方便下次导入
                  for n, i in items:
                        f.write(f"{i['code']}\t{n}\n")
                  f.write("\n")
            messagebox.showinfo("成功", f"文本备份已导出:\n{path}")
      except Exception as e:
            messagebox.showerror("错误", str(e))

    def show_excel_error(self):
      messagebox.showwarning("缺少依赖",
            "未检测到 'openpyxl' 库。\n\n"
            "如需导出 Excel,请在命令行运行:\n"
            "pip install openpyxl\n"
            "然后重新打包程序。")

    def export_to_excel(self):
      if not EXCEL_SUPPORT:
            self.show_excel_error()
            return
            
      path = filedialog.asksaveasfilename(
            defaultextension=".xlsx",
            filetypes=[("Excel Files", "*.xlsx")],
            initialfile=f"Starfield_Data_{VERSION}.xlsx"
      )
      if not path: return
      
      try:
            wb = Workbook()
            ws = wb.active
            ws.title = "Items"
            
            headers = ["分类", "物品名称", "代码 ID", "收藏"]
            ws.append(headers)
            
            for name, info in self.database.items():
                cat = info.get('cat', '杂项')
                code = info['code']
                fav = "是" if info.get('fav', False) else "否"
                ws.append()
            
            for col in ws.columns:
                max_length = 0
                column = col.column_letter
                for cell in col:
                  if cell.value:
                        max_length = max(max_length, len(str(cell.value)))
                ws.column_dimensions.width = min(max_length + 2, 50)
            
            bold_font = Font(bold=True)
            for cell in ws:
                cell.font = bold_font
               
            wb.save(path)
            messagebox.showinfo("成功", f"Excel 已导出:\n{path}\n共 {len(self.database)} 条数据。")
            
      except Exception as e:
            messagebox.showerror("导出失败", str(e))

    def clear_data(self):
      if messagebox.askyesno("高危警告", "确定要清空所有数据吗?\n此操作不可恢复!"):
            self.database = {}
            self.code_index = {}
            self.save_database()
            self.perform_search()
            messagebox.showinfo("已清空", "数据库已重置。")

if __name__ == "__main__":
    root = tk.Tk()
    app = StarfieldLiteApp(root)
    root.mainloop()

姚嘞个摇1 发表于 2026-3-8 19:15

666666666a ···

这什么破名起的 发表于 2026-3-8 19:18

查毒https://www.virscan.org/report/fcf1b0d1c3971dd6c2c47cc2df007db13f81b96a3deca4218daf5d3da926ad2a

恩策 发表于 2026-3-10 16:16

谢谢分享

992482880 发表于 2026-3-15 18:32

66666666666666666666666666

3dm_26796333 发表于 2026-3-16 23:26

65666666666666666666666666

jackleey 发表于 2026-3-18 23:02

这个很棒啊

laobi184 发表于 2026-3-19 09:56

666666666666

晨钟鼓暮 发表于 2026-3-19 21:28

66666666666

浮云无意 发表于 2026-3-23 17:03

感谢分享

不解释你懂得の 发表于 2026-3-23 18:50

666666666666666

新一和志保 发表于 2026-3-25 13:11

6666666666666666

714751267 发表于 2026-3-26 13:06

2111111111111111111111111

yyyyjerry 发表于 2026-3-26 14:34

retetetetetete

Benyua 发表于 2026-3-26 18:51

6666666666666666

wyl146 发表于 2026-3-29 14:03

666666666666666666666666666666

acheroone 发表于 2026-3-29 20:07

给楼主点赞,希望继续分享!楼主太棒了!我先下为敬!

政委同志 发表于 2026-3-30 01:03

感谢分享

╰浪漫小男R3nつ 发表于 2026-4-1 12:08

111111111111111111111

zxszcl123 发表于 2026-4-2 10:04

6666666666666

rockanelei 发表于 2026-4-3 00:01

11111111111111111111111111

xiaoshuilong11 发表于 2026-4-3 13:13

666666666666

dengzhangfei 发表于 2026-4-3 23:54

23333333333333333333333333

141826464 发表于 2026-4-3 23:57

66666666666666666666666666

不服你来打我撒 发表于 2026-4-6 21:33

66666666666666666668

安泽安泽 发表于 2026-4-6 22:28

感谢分享

魏666 发表于 2026-4-7 19:36

6666666666666

yixieluocha 发表于 2026-4-8 00:19

6666666666666666666
页: [1] 2 3 4 5 6
查看完整版本: 【控制台】星空物品查询器_V2.0 不用在各个网站找冷门代码了 自制小软件