wuzilong 6 月之前
父節點
當前提交
9057b9a91f
共有 26 個文件被更改,包括 709 次插入 和 42 次删除
  1. 1 1
      Untitled
  2. 二進制
      backend-client.zip
  3. 二進制
      client-tauri/SteamSH_Portable.zip
  4. 74 39
      client-tauri/src/views/BuffSelectionView.vue
  5. 二進制
      client-tauri/steamSH-V1.0.5.B-new-测试.exe
  6. 二進制
      dist-portable/steamSH_V1.0.5.B-new-测试.exe
  7. 101 0
      new-software/README.md
  8. 73 0
      new-software/build.bat
  9. 33 0
      new-software/debug_check.py
  10. 383 0
      new-software/profit_calculator.py
  11. 4 0
      new-software/requirements.txt
  12. 38 0
      new-software/扫货利润计算工具.spec
  13. 二進制
      new-software/扫货成功数据_2026-04-26 (1).xlsx
  14. 二進制
      steam-client/steamAPI/bin/Debug/net8.0/SteamAuth.dll
  15. 二進制
      steam-client/steamAPI/bin/Debug/net8.0/SteamAuth.pdb
  16. 二進制
      steam-client/steamAPI/bin/Debug/net8.0/steamAPI.dll
  17. 二進制
      steam-client/steamAPI/bin/Debug/net8.0/steamAPI.exe
  18. 二進制
      steam-client/steamAPI/bin/Debug/net8.0/steamAPI.pdb
  19. 二進制
      steam-client/steamAPI/obj/Debug/net8.0/apphost.exe
  20. 二進制
      steam-client/steamAPI/obj/Debug/net8.0/ref/steamAPI.dll
  21. 二進制
      steam-client/steamAPI/obj/Debug/net8.0/refint/steamAPI.dll
  22. 1 1
      steam-client/steamAPI/obj/Debug/net8.0/steamAPI.AssemblyInfo.cs
  23. 1 1
      steam-client/steamAPI/obj/Debug/net8.0/steamAPI.AssemblyInfoInputs.cache
  24. 二進制
      steam-client/steamAPI/obj/Debug/net8.0/steamAPI.csproj.AssemblyReference.cache
  25. 二進制
      steam-client/steamAPI/obj/Debug/net8.0/steamAPI.dll
  26. 二進制
      steam-client/steamAPI/obj/Debug/net8.0/steamAPI.pdb

+ 1 - 1
Untitled

@@ -1 +1 @@
-http://192.168.2.54:9000
+http://125.122.32.114:9000

二進制
backend-client-new.zip → backend-client.zip


二進制
client-tauri/SteamSH_Portable.zip


+ 74 - 39
client-tauri/src/views/BuffSelectionView.vue

@@ -404,9 +404,6 @@
             </div>
           </div>
           <div class="section-actions">
-            <a-button type="link" size="small" class="btn-export" @click="exportSweepTxt">
-              导出TXT
-            </a-button>
             <a-button type="link" size="small" class="btn-export" :loading="exportingSweepExcel" @click="exportSweepExcel">
               导出Excel
             </a-button>
@@ -419,9 +416,9 @@
         <div class="section-body table-wrap">
           <a-table
             :columns="filteredSweepColumns"
-            :data-source="sweepList"
-            :pagination="sweepPagination"
-            :loading="sweepTableLoading"
+            :data-source="filteredSweepList"
+            :pagination="effectiveSweepPagination"
+            :loading="sweepTableLoading || sweepSearchLoading"
             :row-key="sweepRowKey"
             :scroll="{ y: 400 }"
             size="small"
@@ -506,7 +503,7 @@
                 </span>
                 <span v-else>
                   {{ formatSteamBalance(record.steam_username) }}
-                  <span v-if="getFailCount(record.steam_username) >= 10" style="color: #faad14; margin-left: 4px;">
+                  <span v-if="getFailCount(record.steam_username) >= 5" style="color: #faad14; margin-left: 4px;">
                     <QuestionCircleOutlined title="余额连续查询失败,正在自动重新登录" />
                   </span>
                 </span>
@@ -536,12 +533,12 @@
                 <a-tag v-if="isAutoRelogining(record.steam_username)" color="processing">
                   自动登录中
                 </a-tag>
-                <a-tag v-else-if="getFailCount(record.steam_username) >= 5 && getFailCount(record.steam_username) < 10" color="warning">
-                  登录异常 ({{ getFailCount(record.steam_username) }}/10)
-                </a-tag>
-                <a-tag v-else-if="getFailCount(record.steam_username) >= 10 && !isAutoRelogining(record.steam_username)" color="error">
+                <a-tag v-else-if="getFailCount(record.steam_username) >= 5" color="error">
                   登录失效
                 </a-tag>
+                <a-tag v-else-if="getFailCount(record.steam_username) >= 1" color="warning">
+                  登录异常 ({{ getFailCount(record.steam_username) }}/5)
+                </a-tag>
                 <a-tag v-else color="success">
                   正常
                 </a-tag>
@@ -1092,19 +1089,30 @@ const switchingStatus = computed(() => buffSweepStore.switchingStatus)
 const buffPrepareFor = ref<'selection' | 'sweep' | null>(null)
 const sweepTableLoading = ref(false)
 const sweepList = ref<any[]>([])
+const sweepAllList = ref<any[]>([])  // 存储全部数据,用于检索时过滤
 const sweepSearchKeyword = ref('')
 const exportingSweepExcel = ref(false)
+const sweepSearchLoading = ref(false)  // 检索时加载状态
 
+// 检索词变化时重置分页到第一页,并触发数据加载
+watch(() => sweepSearchKeyword.value, () => {
+  sweepPagination.current = 1
+  fetchSweepResult(false)
+})
+
+// 检索时有检索词则使用全部数据过滤,无检索词则使用分页数据
 const filteredSweepList = computed(() => {
-  if (!sweepSearchKeyword.value.trim()) {
-    return sweepList.value
+  if (sweepSearchKeyword.value.trim()) {
+    // 有检索词时使用全部数据
+    const keyword = sweepSearchKeyword.value.trim().toLowerCase()
+    return sweepAllList.value.filter((item) => {
+      const idMatch = String(item.goods_id || item.id || '').toLowerCase().includes(keyword)
+      const nameMatch = (item.goods_name || item.name || '').toLowerCase().includes(keyword)
+      return idMatch || nameMatch
+    })
   }
-  const keyword = sweepSearchKeyword.value.trim().toLowerCase()
-  return sweepList.value.filter((item) => {
-    const idMatch = String(item.goods_id || item.id || '').toLowerCase().includes(keyword)
-    const nameMatch = (item.goods_name || item.name || '').toLowerCase().includes(keyword)
-    return idMatch || nameMatch
-  })
+  // 无检索词时使用当前页分页数据
+  return sweepList.value
 })
 
 const paginatedSweepList = computed(() => {
@@ -1114,7 +1122,14 @@ const paginatedSweepList = computed(() => {
 })
 
 const filteredSweepColumns = computed(() => {
-  return sweepColumns.filter(col => col.key !== 'listing_id')
+  return sweepColumns.filter(col => col.key !== 'listing_id' && col.key !== 'wear_range')
+})
+
+const effectiveSweepPagination = computed(() => {
+  if (sweepSearchKeyword.value.trim()) {
+    return false
+  }
+  return sweepPagination
 })
 
 const formatSweepTime = (val: unknown) => {
@@ -1158,7 +1173,7 @@ const exportSweepTxt = () => {
       '饰品ID': item.goods_id || '',
       '饰品名称': item.goods_name || '',
       '区间': item.wear_range || '',
-      '已购磨损值': item.purchase_wear || '',
+      '已购磨损值': item.purchase_wear != null ? item.purchase_wear : '',
       '目标价格(美元)': item.target_price_usd || '',
       '成交价格(美元)': item.purchase_price_usd || '',
       '成交价格(人民币)': item.purchase_price_cny || '',
@@ -1201,8 +1216,7 @@ const exportSweepExcel = async () => {
       '序号': index + 1,
       '饰品ID': item.goods_id || '',
       '饰品名称': item.goods_name || '',
-      '区间': item.wear_range || '',
-      '已购磨损值': item.purchase_wear || '',
+      '已购磨损值': item.purchase_wear != null ? String(item.purchase_wear) : '',
       '目标价格(美元)': item.target_price_usd || '',
       '成交价格(美元)': item.purchase_price_usd || '',
       '成交价格(人民币)': item.purchase_price_cny || '',
@@ -1210,6 +1224,14 @@ const exportSweepExcel = async () => {
       '扫货时间': formatSweepTime(item.created_at)
     }))
     const ws = XLSX.utils.json_to_sheet(exportData)
+    // 设置"已购磨损值"列的单元格类型为字符串,确保精度不丢失
+    const wearColIndex = 'D' // 已购磨损值是第4列
+    for (let i = 2; i <= data.length + 1; i++) {
+      const cellRef = `${wearColIndex}${i}`
+      if (ws[cellRef]) {
+        ws[cellRef].t = 's' // 设置为字符串类型
+      }
+    }
     const wb = XLSX.utils.book_new()
     XLSX.utils.book_append_sheet(wb, ws, '扫货成功数据')
     XLSX.writeFile(wb, `扫货成功数据_${new Date().toISOString().slice(0, 10)}.xlsx`)
@@ -1909,8 +1931,8 @@ async function refreshSingleSteamBalance(steamUsername: string, silent: boolean)
         [steamUsername]: newFail
       }
 
-      // 达到阈值(10次),触发自动重新登录
-      if (newFail >= 10 && !autoReloginLock.value[steamUsername]) {
+      // 达到阈值(5次),触发自动重新登录
+      if (newFail >= 5 && !autoReloginLock.value[steamUsername]) {
         // 在后台静默触发自动登录,不阻塞当前流程
         tryAutoReloginIfNeeded(steamUsername)
       }
@@ -1931,8 +1953,8 @@ async function refreshSingleSteamBalance(steamUsername: string, silent: boolean)
       message.error(errText.length > 80 ? `${errText.slice(0, 80)}...` : errText)
     }
 
-    // 达到阈值(10次),触发自动重新登录
-    if (newFail >= 10 && !autoReloginLock.value[steamUsername]) {
+    // 达到阈值(5次),触发自动重新登录
+    if (newFail >= 5 && !autoReloginLock.value[steamUsername]) {
       tryAutoReloginIfNeeded(steamUsername)
     }
 
@@ -2173,7 +2195,7 @@ function startBoundSteamBalancePoll() {
   stopBoundSteamBalancePoll()
   boundSteamBalanceTimer.value = setInterval(() => {
     refreshBoundSteamBalances(true)
-  }, 3 * 60 * 1000)
+  }, 2 * 60 * 1000)
 }
 
 function stopBoundSteamBalancePoll() {
@@ -2840,23 +2862,36 @@ async function fetchSweepResult(showLoading = false): Promise<string | null> {
     sweepTableLoading.value = true
   }
   try {
-    // 后端分页加载
-    const res = await secureApiClient.getBuffSweepResultLocal(
-      sweepPagination.current,
-      sweepPagination.pageSize
-    )
-    if (res.code === 200 && res.data) {
-      const items = Array.isArray(res.data.items) ? res.data.items : []
-      sweepList.value = items
-      sweepPagination.total = res.data.total || 0
-      // 本地数据没有 status,返回 null 让轮询继续
-      return null
+    if (sweepSearchKeyword.value.trim()) {
+      // 有检索词时,一次性加载全部数据进行检索
+      sweepSearchLoading.value = true
+      const res = await secureApiClient.getAllBuffSweepResultLocal()
+      if (res.code === 200 && res.data) {
+        const items = Array.isArray(res.data.items) ? res.data.items : []
+        sweepAllList.value = items
+        sweepPagination.total = items.length
+      }
+    } else {
+      // 无检索词时,后端分页加载
+      const res = await secureApiClient.getBuffSweepResultLocal(
+        sweepPagination.current,
+        sweepPagination.pageSize
+      )
+      if (res.code === 200 && res.data) {
+        const items = Array.isArray(res.data.items) ? res.data.items : []
+        sweepList.value = items
+        sweepPagination.total = res.data.total || 0
+        // 清除全部数据缓存
+        sweepAllList.value = []
+      }
     }
+    // 本地数据没有 status,返回 null 让轮询继续
     return null
   } finally {
     if (showLoading) {
       sweepTableLoading.value = false
     }
+    sweepSearchLoading.value = false
   }
 }
 

二進制
client-tauri/steamSH-V1.0.5.B-new-测试.exe


二進制
client-tauri/steamSH_standalone.exe → dist-portable/steamSH_V1.0.5.B-new-测试.exe


+ 101 - 0
new-software/README.md

@@ -0,0 +1,101 @@
+# 扫货利润计算工具
+
+根据扫货成功数据,计算每笔交易的利润和利润率。
+
+## 功能
+
+- 从数据库加载饰品磨损区间价格数据
+- 支持上传Excel文件进行分析
+- 自动匹配饰品ID和磨损值所在区间
+- 计算利润和利润率
+- 导出包含新增列的Excel文件
+- 支持打包为独立exe文件
+
+## Excel模版要求
+
+| 列号 | 内容 |
+|------|------|
+| 第1列 | 序号 |
+| 第2列 | 饰品ID |
+| 第3列 | 饰品名称 |
+| 第4列 | 已购磨损值 |
+| 第5列 | 目标价格(美元) |
+| 第6列 | 成交价格(美元) |
+| 第7列 | 成交价格(人民币) |
+| 第8列 | 购买账号 |
+| 第9列 | 扫货时间 |
+
+## 输出Excel新增列
+
+| 列号 | 内容 | 计算方式 |
+|------|------|----------|
+| 第10列 | 区间底价 | buff_goods_float_price表中对应区间的qj_min_price |
+| 第11列 | 利润 | 区间底价 - 成交价格(人民币) |
+| 第12列 | 利润率(%) | 利润 / 成交价格(人民币) × 100% |
+
+## 安装依赖
+
+```bash
+pip install -r requirements.txt
+```
+
+## 配置数据库连接
+
+工具会自动从环境变量读取数据库配置,默认值:
+- DB_HOST=125.122.32.114
+- DB_PORT=3306
+- DB_USER=root
+- DB_PASSWORD=Buff123456
+- DB_NAME=buff
+
+## 运行程序
+
+### 方式一:直接运行Python脚本
+```bash
+python profit_calculator.py
+```
+
+### 方式二:打包为exe后运行
+1. 双击 `build.bat` 等待打包完成
+2. 在 `dist` 目录下找到 `扫货利润计算工具.exe`
+3. 直接双击运行exe文件(无需Python环境)
+
+## 使用方法
+
+1. **启动程序**:运行程序后,会自动连接数据库并加载价格数据
+2. **选择文件**:点击"选择Excel文件"按钮,选择需要分析的Excel文件
+3. **查看结果**:程序会自动处理并显示处理结果
+4. **导出数据**:
+   - 点击"导出Excel"保存到上次位置
+   - 点击"另存为..."自定义保存位置
+
+## 打包说明
+
+### 前置要求
+- Windows系统
+- Python 3.8 或更高版本
+- 安装了Visual C++运行库
+
+### 打包步骤
+1. 确保已安装所有依赖:`pip install -r requirements.txt`
+2. (可选)准备一个 `icon.ico` 图标文件放在当前目录
+3. 双击运行 `build.bat`
+4. 等待打包完成,在 `dist` 目录获取exe文件
+
+### 注意事项
+- 打包后的exe文件可以复制到其他电脑使用
+- 无需安装Python环境
+- 首次运行可能需要较长时间加载依赖
+
+## 文件结构
+
+```
+new-software/
+├── profit_calculator.py    # 主程序
+├── requirements.txt        # Python依赖
+├── build.bat              # 打包脚本
+├── README.md              # 使用说明
+├── icon.ico               # 程序图标(可选)
+└── dist/                  # 打包输出目录
+    └── 扫货利润计算工具.exe
+```

+ 73 - 0
new-software/build.bat

@@ -0,0 +1,73 @@
+@echo off
+chcp 65001 > nul
+echo ========================================
+echo    扫货利润计算工具 - 打包脚本
+echo ========================================
+echo.
+
+:: 检查Python
+python --version > nul 2>&1
+if errorlevel 1 (
+    echo [错误] 未找到Python,请先安装Python 3.8+
+    pause
+    exit /b 1
+)
+
+:: 安装依赖
+echo [1/4] 安装依赖包...
+pip install pymysql pandas openpyxl pyinstaller -q
+if errorlevel 1 (
+    echo [错误] 依赖安装失败,请检查网络连接
+    pause
+    exit /b 1
+)
+echo [OK] 依赖安装完成
+
+:: 清理
+echo [2/4] 清理旧文件...
+if exist "build" rmdir /s /q build
+if exist "profit_calculator.spec" del /q "profit_calculator.spec" 2>nul
+if exist "dist" rmdir /s /q dist
+mkdir dist
+
+:: 打包参数
+echo [3/4] 开始打包(请耐心等待)...
+set "PY_ARGS=--name=扫货利润计算工具 --onefile --windowed --noconfirm --clean"
+
+:: 如果有图标就添加
+if exist "icon.ico" (
+    set "PY_ARGS=!PY_ARGS! --icon=icon.ico"
+)
+
+:: 执行打包
+pyinstaller !PY_ARGS! profit_calculator.py
+
+:: 移动exe到dist
+if exist "dist\扫货利润计算工具.exe" (
+    echo [OK] exe文件已生成
+) else (
+    move "扫货利润计算工具.exe" "dist\" > nul 2>&1
+)
+
+echo [4/4] 完成!
+
+echo.
+echo ========================================
+if exist "dist\扫货利润计算工具.exe" (
+    echo ✅ 打包成功!
+    echo.
+    echo 📦 文件位置: 
+    echo    %~dp0dist\扫货利润计算工具.exe
+    echo.
+    echo 💡 使用说明:
+    echo    1. 将exe文件复制到目标电脑
+    echo    2. 首次运行需要安装VC运行库
+    echo       下载: aka.ms/vs/17/release/vc_redist.x64.exe
+    echo    3. 双击exe即可运行,无需Python环境
+) else (
+    echo ❌ 打包失败
+    echo    请确保 profit_calculator.py 文件存在
+)
+echo ========================================
+echo.
+pause

+ 33 - 0
new-software/debug_check.py

@@ -0,0 +1,33 @@
+import pandas as pd
+import pymysql
+
+# Excel数据
+df = pd.read_excel('扫货成功数据_2026-04-26 (1).xlsx')
+row = df.iloc[39]
+wear_value = row.iloc[3]  # 0.036458
+goods_id = str(row.iloc[1])  # 1133139
+deal_price = row.iloc[6]  # NaN
+
+print('Excel数据:')
+print(f'  序号: {row.iloc[0]}')
+print(f'  饰品ID: {goods_id}')
+print(f'  磨损值: {wear_value}')
+print(f'  成交价格(人民币): {deal_price}')
+print()
+
+# 数据库数据
+conn = pymysql.connect(host='125.122.32.114', port=3306, user='root', password='Buff123456', database='buff')
+cursor = conn.cursor(pymysql.cursors.DictCursor)
+cursor.execute('SELECT * FROM buff_goods_float_price WHERE goods_id = %s', (goods_id,))
+rows = cursor.fetchall()
+cursor.close()
+conn.close()
+
+print('数据库中的区间 (goods_id=1133139):')
+for r in rows:
+    in_range = r['min_float'] <= wear_value <= r['max_float']
+    marker = ' <-- 匹配' if in_range else ''
+    print(f"  {r['min_float']:.2f} - {r['max_float']:.2f}: qj_min_price={r['qj_min_price']}{marker}")
+
+print()
+print('问题原因: 成交价格(人民币)为空(NaN),无法计算利润')

+ 383 - 0
new-software/profit_calculator.py

@@ -0,0 +1,383 @@
+# -*- coding: utf-8 -*-
+"""
+扫货利润计算工具
+用于计算扫货成功数据的利润和利润率
+"""
+
+import tkinter as tk
+from tkinter import ttk, filedialog, messagebox
+import pandas as pd
+import pymysql
+import os
+from datetime import datetime
+import sys
+
+# 判断是否为打包环境
+if getattr(sys, 'frozen', False):
+    APPLICATION_PATH = sys._MEIPASS
+else:
+    APPLICATION_PATH = os.path.dirname(os.path.abspath(__file__))
+
+
+class ProfitCalculatorApp:
+    def __init__(self, root):
+        self.root = root
+        self.root.title("扫货利润计算工具 v1.0")
+        self.root.geometry("900x650")
+        self.root.resizable(True, True)
+        
+        # 程序图标 (打包后从资源目录读取)
+        try:
+            if getattr(sys, 'frozen', False):
+                icon_path = os.path.join(APPLICATION_PATH, 'icon.ico')
+                if os.path.exists(icon_path):
+                    root.iconbitmap(icon_path)
+        except Exception:
+            pass
+        
+        # 数据库配置 - 从环境变量读取
+        self.db_config = {
+            'host': os.getenv('DB_HOST', '125.122.32.114'),
+            'port': int(os.getenv('DB_PORT', 3306)),
+            'user': os.getenv('DB_USER', 'root'),
+            'password': os.getenv('DB_PASSWORD', 'Buff123456'),
+            'database': os.getenv('DB_NAME', 'buff'),
+            'charset': 'utf8mb4'
+        }
+        
+        # 价格缓存 - 启动时加载
+        self.price_cache = {}
+        
+        # 当前处理的结果数据
+        self.current_results = []
+        
+        # 上次输出路径
+        self.last_output_path = None
+        
+        self.setup_ui()
+        self.load_price_data()
+    
+    def setup_ui(self):
+        """设置UI界面"""
+        # 主框架
+        main_frame = ttk.Frame(self.root, padding="15")
+        main_frame.pack(fill=tk.BOTH, expand=True)
+        
+        # 顶部区域 - 标题和状态
+        top_frame = ttk.Frame(main_frame)
+        top_frame.pack(fill=tk.X, pady=(0, 10))
+        
+        # 标题
+        title_label = ttk.Label(top_frame, text="扫货利润计算工具", 
+                               font=("微软雅黑", 20, "bold"))
+        title_label.pack(side=tk.LEFT, pady=10)
+        
+        # 状态区域
+        status_frame = ttk.Frame(top_frame)
+        status_frame.pack(side=tk.RIGHT)
+        
+        # 数据库状态
+        self.db_status_label = ttk.Label(status_frame, text="数据库: 连接中...", 
+                                        font=("微软雅黑", 9))
+        self.db_status_label.pack(anchor=tk.E)
+        
+        # 价格数据加载状态
+        self.cache_status_label = ttk.Label(status_frame, text="价格缓存: 加载中...", 
+                                            font=("微软雅黑", 9))
+        self.cache_status_label.pack(anchor=tk.E)
+        
+        # 按钮区域
+        btn_frame = ttk.Frame(main_frame)
+        btn_frame.pack(fill=tk.X, pady=15)
+        
+        # 左侧按钮
+        left_btn_frame = ttk.Frame(btn_frame)
+        left_btn_frame.pack(side=tk.LEFT)
+        
+        self.upload_btn = ttk.Button(left_btn_frame, text="📂 选择Excel文件", 
+                                     command=self.upload_excel, width=18)
+        self.upload_btn.pack(side=tk.LEFT, padx=5)
+        
+        self.reload_btn = ttk.Button(left_btn_frame, text="🔄 重新加载数据", 
+                                     command=self.reload_data, width=18)
+        self.reload_btn.pack(side=tk.LEFT, padx=5)
+        
+        # 右侧导出按钮
+        right_btn_frame = ttk.Frame(btn_frame)
+        right_btn_frame.pack(side=tk.RIGHT)
+        
+        self.export_btn = ttk.Button(right_btn_frame, text="💾 导出Excel", 
+                                     command=self.export_excel, width=18)
+        self.export_btn.pack(side=tk.LEFT, padx=5)
+        self.export_btn.config(state=tk.DISABLED)
+        
+        self.export_as_btn = ttk.Button(right_btn_frame, text="📤 另存为...", 
+                                        command=self.export_as_excel, width=18)
+        self.export_as_btn.pack(side=tk.LEFT, padx=5)
+        self.export_as_btn.config(state=tk.DISABLED)
+        
+        # 文件路径显示
+        self.file_path_var = tk.StringVar(value="未选择文件")
+        self.file_path_label = ttk.Label(main_frame, textvariable=self.file_path_var, 
+                                        font=("微软雅黑", 9), foreground="gray")
+        self.file_path_label.pack(pady=5)
+        
+        # 处理结果区域
+        result_frame = ttk.LabelFrame(main_frame, text="处理结果", padding="10")
+        result_frame.pack(fill=tk.BOTH, expand=True, pady=10)
+        
+        # 结果文本框
+        self.result_text = tk.Text(result_frame, height=18, width=100, 
+                                   font=("微软雅黑", 10))
+        self.result_text.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)
+        
+        # 滚动条
+        scrollbar = ttk.Scrollbar(result_frame, command=self.result_text.yview)
+        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
+        self.result_text.config(yscrollcommand=scrollbar.set)
+        
+        # 底部说明
+        info_frame = ttk.Frame(main_frame)
+        info_frame.pack(fill=tk.X, pady=5)
+        
+        info_label = ttk.Label(info_frame, 
+                              text="Excel模版:序号 | 饰品ID | 饰品名称 | 已购磨损值 | 目标价格(美元) | 成交价格(美元) | 成交价格(人民币) | 购买账号 | 扫货时间",
+                              font=("微软雅黑", 8), foreground="gray")
+        info_label.pack(side=tk.LEFT)
+        
+        # 统计标签
+        self.stats_label = ttk.Label(info_frame, text="", font=("微软雅黑", 9, "bold"))
+        self.stats_label.pack(side=tk.RIGHT)
+    
+    def reload_data(self):
+        """重新加载数据"""
+        self.result_text.delete(1.0, tk.END)
+        self.result_text.insert(tk.END, "正在重新加载数据...\n")
+        self.root.update()
+        self.load_price_data()
+        self.result_text.insert(tk.END, "数据重新加载完成\n")
+    
+    def load_price_data(self):
+        """从数据库加载价格数据到缓存"""
+        try:
+            conn = pymysql.connect(**self.db_config)
+            cursor = conn.cursor(pymysql.cursors.DictCursor)
+            
+            sql = """
+                SELECT goods_id, min_float, max_float, qj_min_price 
+                FROM buff_goods_float_price
+            """
+            cursor.execute(sql)
+            rows = cursor.fetchall()
+            
+            self.price_cache = {}
+            for row in rows:
+                goods_id = str(row['goods_id'])
+                min_float = float(row['min_float'])
+                max_float = float(row['max_float'])
+                qj_min_price = float(row['qj_min_price'])
+                
+                if goods_id not in self.price_cache:
+                    self.price_cache[goods_id] = []
+                
+                self.price_cache[goods_id].append({
+                    'min_float': min_float,
+                    'max_float': max_float,
+                    'qj_min_price': qj_min_price
+                })
+            
+            cursor.close()
+            conn.close()
+            
+            self.db_status_label.config(text="数据库: ✅ 已连接", foreground="green")
+            self.cache_status_label.config(
+                text=f"价格缓存: ✅ {len(self.price_cache)} 个饰品", 
+                foreground="green"
+            )
+            
+        except Exception as e:
+            self.db_status_label.config(text="数据库: ❌ 连接失败", foreground="red")
+            self.cache_status_label.config(text=f"价格缓存: ❌ - {str(e)[:30]}", 
+                                           foreground="red")
+    
+    def find_price_in_range(self, goods_id, wear_value):
+        """根据饰品ID和磨损值查找对应区间的底价"""
+        goods_id_str = str(goods_id)
+        
+        if goods_id_str not in self.price_cache:
+            return None
+        
+        ranges = self.price_cache[goods_id_str]
+        for r in ranges:
+            if r['min_float'] <= wear_value <= r['max_float']:
+                return r['qj_min_price']
+        
+        return None
+    
+    def upload_excel(self):
+        """处理上传的Excel文件"""
+        file_path = filedialog.askopenfilename(
+            title="选择Excel文件",
+            filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
+        )
+        
+        if not file_path:
+            return
+        
+        self.file_path_var.set(file_path)
+        self.result_text.delete(1.0, tk.END)
+        self.result_text.insert(tk.END, f"正在处理: {file_path}\n")
+        self.result_text.insert(tk.END, "=" * 70 + "\n\n")
+        self.root.update()
+        
+        try:
+            df = pd.read_excel(file_path)
+            
+            if len(df.columns) < 7:
+                messagebox.showerror("错误", "Excel文件列数不足,需要至少7列数据")
+                return
+            
+            self.current_results = []
+            found_count = 0
+            not_found_count = 0
+            empty_price_count = 0
+            total_profit = 0
+            
+            for idx, row in df.iterrows():
+                try:
+                    goods_id = str(row.iloc[1])
+                    wear_value = float(row.iloc[3])
+                    deal_price_cny_raw = row.iloc[6]
+                    
+                    # 检查成交价格是否为空
+                    if pd.isna(deal_price_cny_raw):
+                        name_display = str(row.iloc[2])[:25]
+                        if len(str(row.iloc[2])) > 25:
+                            name_display += "..."
+                        self.result_text.insert(tk.END, 
+                            f"⚠️ {row.iloc[0]:>3} | {name_display:<28} | 成交价为空\n")
+                        empty_price_count += 1
+                        continue
+                    
+                    deal_price_cny = float(deal_price_cny_raw)
+                    
+                    range_min_price = self.find_price_in_range(goods_id, wear_value)
+                    
+                    if range_min_price is not None:
+                        profit = range_min_price - deal_price_cny
+                        profit_rate = (profit / deal_price_cny * 100) if deal_price_cny > 0 else 0
+                        total_profit += profit
+                        
+                        self.current_results.append({
+                            '序号': row.iloc[0],
+                            '饰品ID': goods_id,
+                            '饰品名称': row.iloc[2],
+                            '已购磨损值': wear_value,
+                            '目标价格美元': row.iloc[4],
+                            '成交价格美元': row.iloc[5],
+                            '成交价格人民币': deal_price_cny,
+                            '购买账号': row.iloc[7] if len(df.columns) > 7 else '',
+                            '扫货时间': row.iloc[8] if len(df.columns) > 8 else '',
+                            '区间底价': range_min_price,
+                            '利润': round(profit, 2),
+                            '利润率(%)': round(profit_rate, 2)
+                        })
+                        found_count += 1
+                        
+                        name_display = str(row.iloc[2])[:25] + "..." if len(str(row.iloc[2])) > 25 else row.iloc[2]
+                        self.result_text.insert(tk.END, 
+                            f"✅ {row.iloc[0]:>3} | {name_display:<28} | 利润: ¥{round(profit, 2):>8} ({round(profit_rate, 1):>5}%)\n")
+                    else:
+                        not_found_count += 1
+                        name_display = str(row.iloc[2])[:25] + "..." if len(str(row.iloc[2])) > 25 else row.iloc[2]
+                        self.result_text.insert(tk.END, 
+                            f"⚠️ {row.iloc[0]:>3} | {name_display:<28} | 未匹配到区间\n")
+                
+                except Exception as e:
+                    self.result_text.insert(tk.END, 
+                        f"❌ 序号 {row.iloc[0]}: {str(e)[:50]}\n")
+            
+            if self.current_results:
+                # 生成输出文件名
+                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+                base_name = os.path.splitext(os.path.basename(file_path))[0]
+                self.last_output_path = os.path.join(
+                    os.path.dirname(file_path), 
+                    f"{base_name}_利润分析_{timestamp}.xlsx"
+                )
+                
+                result_df = pd.DataFrame(self.current_results)
+                result_df.to_excel(self.last_output_path, index=False)
+                
+                self.result_text.insert(tk.END, "\n" + "=" * 70 + "\n")
+                self.result_text.insert(tk.END, f"✅ 处理完成!\n")
+                self.result_text.insert(tk.END, f"   成功匹配: {found_count} 条\n")
+                self.result_text.insert(tk.END, f"   成交价为空: {empty_price_count} 条\n")
+                self.result_text.insert(tk.END, f"   未匹配区间: {not_found_count} 条\n")
+                self.result_text.insert(tk.END, f"   总利润: ¥{round(total_profit, 2)}\n")
+                self.result_text.insert(tk.END, f"   结果已保存: {self.last_output_path}\n")
+                
+                # 启用导出按钮
+                self.export_btn.config(state=tk.NORMAL)
+                self.export_as_btn.config(state=tk.NORMAL)
+                
+                # 更新统计
+                stats_text = f"成功: {found_count} | 成交价空: {empty_price_count} | 未匹配: {not_found_count} | 利润: ¥{round(total_profit, 2)}"
+                self.stats_label.config(text=stats_text, foreground="green")
+                
+                messagebox.showinfo("处理完成", 
+                    f"成功匹配: {found_count} 条\n成交价为空: {empty_price_count} 条\n未匹配区间: {not_found_count} 条\n总利润: ¥{round(total_profit, 2)}\n\n结果已保存")
+            else:
+                self.stats_label.config(text="无匹配数据", foreground="orange")
+                messagebox.showwarning("无匹配数据", "没有找到任何匹配的饰品数据")
+                
+        except Exception as e:
+            messagebox.showerror("处理错误", f"处理文件时出错:\n{str(e)}")
+            self.result_text.insert(tk.END, f"\n❌ 错误: {str(e)}")
+    
+    def export_excel(self):
+        """导出上次的结果到Excel"""
+        if not self.current_results:
+            messagebox.showwarning("无数据", "没有可导出的数据,请先选择Excel文件处理")
+            return
+        
+        if self.last_output_path and os.path.exists(self.last_output_path):
+            result_df = pd.DataFrame(self.current_results)
+            result_df.to_excel(self.last_output_path, index=False)
+            messagebox.showinfo("导出成功", f"已导出到:\n{self.last_output_path}")
+        else:
+            self.export_as_excel()
+    
+    def export_as_excel(self):
+        """另存为Excel"""
+        if not self.current_results:
+            messagebox.showwarning("无数据", "没有可导出的数据,请先选择Excel文件处理")
+            return
+        
+        file_path = filedialog.asksaveasfilename(
+            title="另存为",
+            defaultextension=".xlsx",
+            filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")],
+            initialfile=f"利润分析_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
+        )
+        
+        if not file_path:
+            return
+        
+        try:
+            result_df = pd.DataFrame(self.current_results)
+            result_df.to_excel(file_path, index=False)
+            self.last_output_path = file_path
+            messagebox.showinfo("导出成功", f"已导出到:\n{file_path}")
+        except Exception as e:
+            messagebox.showerror("导出失败", f"导出时出错:\n{str(e)}")
+
+
+def main():
+    root = tk.Tk()
+    app = ProfitCalculatorApp(root)
+    root.mainloop()
+
+
+if __name__ == "__main__":
+    main()

+ 4 - 0
new-software/requirements.txt

@@ -0,0 +1,4 @@
+pymysql>=1.1.0
+pandas>=2.0.0
+openpyxl>=3.1.0
+pyinstaller>=6.0.0

+ 38 - 0
new-software/扫货利润计算工具.spec

@@ -0,0 +1,38 @@
+# -*- mode: python ; coding: utf-8 -*-
+
+
+a = Analysis(
+    ['profit_calculator.py'],
+    pathex=[],
+    binaries=[],
+    datas=[],
+    hiddenimports=[],
+    hookspath=[],
+    hooksconfig={},
+    runtime_hooks=[],
+    excludes=[],
+    noarchive=False,
+    optimize=0,
+)
+pyz = PYZ(a.pure)
+
+exe = EXE(
+    pyz,
+    a.scripts,
+    a.binaries,
+    a.datas,
+    [],
+    name='扫货利润计算工具',
+    debug=False,
+    bootloader_ignore_signals=False,
+    strip=False,
+    upx=True,
+    upx_exclude=[],
+    runtime_tmpdir=None,
+    console=False,
+    disable_windowed_traceback=False,
+    argv_emulation=False,
+    target_arch=None,
+    codesign_identity=None,
+    entitlements_file=None,
+)

二進制
new-software/扫货成功数据_2026-04-26 (1).xlsx


二進制
steam-client/steamAPI/bin/Debug/net8.0/SteamAuth.dll


二進制
steam-client/steamAPI/bin/Debug/net8.0/SteamAuth.pdb


二進制
steam-client/steamAPI/bin/Debug/net8.0/steamAPI.dll


二進制
steam-client/steamAPI/bin/Debug/net8.0/steamAPI.exe


二進制
steam-client/steamAPI/bin/Debug/net8.0/steamAPI.pdb


二進制
steam-client/steamAPI/obj/Debug/net8.0/apphost.exe


二進制
steam-client/steamAPI/obj/Debug/net8.0/ref/steamAPI.dll


二進制
steam-client/steamAPI/obj/Debug/net8.0/refint/steamAPI.dll


+ 1 - 1
steam-client/steamAPI/obj/Debug/net8.0/steamAPI.AssemblyInfo.cs

@@ -14,7 +14,7 @@ using System.Reflection;
 [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
 [assembly: System.Reflection.AssemblyDescriptionAttribute("Steam login verification local service")]
 [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9b9c1dfa3d8aa14e410d4ac861f990f1b48a4500")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+e98b8642bcfe63fa398d22236811a878826b82fb")]
 [assembly: System.Reflection.AssemblyProductAttribute("steamAPI")]
 [assembly: System.Reflection.AssemblyTitleAttribute("steamAPI")]
 [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

+ 1 - 1
steam-client/steamAPI/obj/Debug/net8.0/steamAPI.AssemblyInfoInputs.cache

@@ -1 +1 @@
-09fc026ea2ef8bbce2485b82a78c3837ec90595718ba59b0f66cc58a9766cbeb
+28467571756ff8026c1117bf49d93e369f8f734e5d8bfea0584ebdca9cbbb2e8

二進制
steam-client/steamAPI/obj/Debug/net8.0/steamAPI.csproj.AssemblyReference.cache


二進制
steam-client/steamAPI/obj/Debug/net8.0/steamAPI.dll


二進制
steam-client/steamAPI/obj/Debug/net8.0/steamAPI.pdb