<think>
我们要求的功能如下:
1. 将时间单位改为分钟:在原来的代码中,时间是以小时为单位的,现在改为分钟。主要涉及以下几个参数:
- 最大交易时间间隔(原为分钟,不需要改,但注意单位是分钟)
- 时间窗口(原为小时,现在改成分钟,所以需要乘以60)
- 最大出账时间差(原为小时,现在改成分钟,乘以60)
2. 交易对手标签作为多选条件:用户可以在界面上添加多个标签,每个标签对应一个名称和需要的数量。我们需要将这些条件传递到分析过程中。
在分析连续进账交易时,不仅要满足连续次数,还要满足在连续交易中,各个标签出现的次数符合设定的数量。
3. 修改分析逻辑:在`analyze_data_thread`方法中,找到连续序列后,需要检查该连续序列中各个标签的出现次数是否满足条件。
4. 在界面上,我们需要增加一个区域来设置标签条件(标签名称和所需数量)。但由于之前已经添加了“交易对手标签”的添加和管理功能,现在需要扩展为可以设置每个标签的数量。
考虑到界面复杂度,我们可以在“管理交易对手标签”的窗口中,为每个标签添加一个“数量”输入框。但为了不使界面过于复杂,我们暂时不在主界面上增加太多控件,而是通过管理标签的窗口来设置每个标签所需的数量。
修改思路:
- 在`opponent_tags`字典中,原来存储的是标签名称和条件(之前是空列表,现在改为存储所需数量,或者更复杂的条件?)。但为了简单,我们可以存储为:{标签名: 所需数量}。
- 在添加标签时,不仅输入标签名称,还要输入该标签在连续交易中需要出现的次数。因此,修改“添加标签”的界面,增加一个数量输入框。
- 在分析时,对于每个连续序列,我们统计该序列中每个标签出现的次数(即交易对手标签为该标签的交易次数),然后与设定的数量比较。
由于改动较大,我们将分步骤进行:
步骤1:修改添加标签的界面,支持输入数量
- 在`add_opponent_tag`方法中,我们原来只是添加标签名称,现在需要同时输入数量。
- 因此,我们创建一个新的弹窗,包含两个输入框:标签名称和数量。
步骤2:修改`opponent_tags`的存储结构,从原来的{标签名: []}改为{标签名: 数量}
步骤3:在分析连续序列时,增加标签条件的检查。
步骤4:将时间相关的参数转换为分钟。
由于时间关系,我们只修改核心逻辑,界面调整可能较为复杂,我们尽量简化。
但是,用户上一次的问题中要求的是:最小连续次数为5,交易对手标签要求赌客卡数量为1,上分卡数量为2。因此,我们需要同时满足多个标签条件。
注意:一个交易对手标签只能属于一个标签类别吗?是的,我们假设一笔交易对手标签只属于一个类别(例如,一笔交易对手标签要么是“赌客卡”,要么是“上分卡”)。
因此,在连续序列中,我们需要统计每个标签出现的次数,然后和设定的数量进行比较。
由于改动较大,我们重新设计`opponent_tags`的数据结构为字典,键为标签名称,值为该标签在连续交易中应该出现的最小次数(注意:这里我们要求的是恰好等于?还是至少?问题中要求的是“包含1次赌客卡,2次上分卡”,我们理解为至少1次赌客卡和至少2次上分卡,但也可以理解为恰好?但用户的需求可能是至少,所以我们按照至少来设计)。
因此,在连续序列中,对于每一个标签,实际出现的次数必须大于等于设定的次数。
具体修改如下:
1. 在`add_opponent_tag`方法中,我们创建一个新的窗口,包含两个输入框:标签名称和数量(整数)。点击确定后,将{标签名: 数量}存入`self.opponent_tags`。
2. 在`manage_opponent_tags`方法中,显示标签列表时,同时显示每个标签的名称和数量。删除功能不变。
3. 在分析线程中,当我们找到一个连续序列(seq)后,我们统计这个序列中每个标签出现的次数(注意:一笔交易有一个交易对手标签,这个标签应该是用户之前在界面上设置的某个标签吗?这里我们需要明确:在原始数据中,有一列是“交易对手标签”,而我们在界面上设置的标签条件是指定的几个类别。我们需要检查每一笔交易的“交易对手标签”列的值是否等于我们设置的标签名称?
但是,这里有一个问题:原始数据中的交易对手标签是一个具体的字符串,而我们设置的标签名称是一类。例如,原始数据中可能有“赌客卡A”、“赌客卡B”,但我们设置的标签名称是“赌客卡”。这样就需要模糊匹配?或者,我们之前的管理标签的功能,实际上是为某些特定的交易对手卡号打标签?还是为交易对手标签(字符串)打标签?
回顾之前的设计,我们在“添加标签”时,并没有明确标签是如何与交易对手卡号关联的。在之前的代码中,我们只是简单存储了标签名称,然后在分析时,检查交易对手卡号是否包含某些字符串(如果条件不为空)。但现在,我们需要改变设计。
为了满足需求,我们重新定义:交易对手标签是指每一笔交易中,用户给交易对手卡号打上的类别标签。我们需要在管理标签时,为每个标签设置匹配规则(可以是包含某些字符串,或者是精确匹配等)。但这样会使问题复杂化。
考虑到时间,我们简化处理:我们假设在原始数据中,有一列“交易对手标签”,它的值是用户预设的标签类别(如“赌客卡”、“上分卡”)。这样,在连续序列中,我们直接统计这一列的值即可。
因此,我们需要在字段映射中增加一个“交易对手标签”字段的映射。但之前的字段映射中并没有这一字段。所以,我们需要修改字段映射,增加一个“交易对手标签”字段(之前已经存在)。
在之前的代码中,我们已经预留了5个字段映射:交易卡号、交易时间、交易金额、收付标志、对方卡号。现在我们需要增加一个“交易对手标签”字段。
修改步骤:
- 在字段映射区域增加一行:“交易对手标签”
- 在分析时,如果用户没有映射该字段,则无法使用标签条件(可以忽略标签条件,或者提示错误?)
由于这是一个重要改动,我们修改如下:
1. 在初始化时,将`required_fields`增加一个“交易对手标签”:
required_fields = ['交易卡号', '交易时间', '交易金额', '收付标志', '对方卡号', '交易对手标签']
2. 在分析线程中,如果用户没有设置“交易对手标签”的映射,则无法使用标签条件。但我们仍然可以进行连续交易分析,只是标签条件会被忽略?或者,如果用户设置了标签条件(即`self.opponent_tags`不为空),就必须有交易对手标签的映射,否则报错。
3. 在分析连续序列时,统计序列中每个标签出现的次数(根据映射的“交易对手标签”列)。
由于改动非常大,我们逐步修改代码。
由于代码量很大,我们只给出关键部分的修改。完整代码修改如下:
注意:以下修改是基于之前提供的完整代码进行的。
修改1:在`setup_ui`方法中,增加一个字段映射
将原来的:
required_fields = ['交易卡号', '交易时间', '交易金额', '收付标志', '对方卡号']
改为:
required_fields = ['交易卡号', '交易时间', '交易金额', '收付标志', '对方卡号', '交易对手标签']
修改2:在`clean_and_transform_data`方法中,增加对交易对手标签的处理(不需要转换,但要去除空值?):
我们不需要转换,但可以过滤掉交易对手标签为空的行?或者保留?由用户决定。暂时不处理。
修改3:在`analyze_data_thread`方法中,当我们得到一个连续序列`seq`后,我们需要检查标签条件是否满足:
# 首先,检查标签条件(如果用户设置了)
if self.opponent_tags: # 如果设置了标签条件
# 统计序列中每个标签出现的次数
tag_counts = {}
# 初始化,将所有标签的计数设为0
for tag in self.opponent_tags.keys():
tag_counts[tag] = 0
# 遍历序列中的每一笔交易
for idx, row in seq.iterrows():
tag_value = row['交易对手标签']
# 如果该交易的交易对手标签在设定的标签中,则计数
if tag_value in tag_counts:
tag_counts[tag_value] += 1
# 检查是否满足设定的最小数量
for tag, required_count in self.opponent_tags.items():
if tag_counts.get(tag, 0) < required_count:
# 不满足条件,跳过这个序列
break
else: # 如果所有标签都满足条件,才继续后面的逻辑(查找出账)
# 注意:这里用了for-else结构,如果break没有执行,则表示满足
# 后面的逻辑不变(查找出账)
else:
# 没有设置标签条件,直接执行后面的逻辑
pass
修改4:时间单位转换
在`analyze_data_thread`方法中,将时间窗口和出账时间差转换为分钟:
max_gap_minutes = int(self.max_gap_var.get()) # 这个已经是分钟,不用改
time_window_minutes = int(self.time_window_var.get()) * 60 # 将小时转换为分钟
max_out_gap_minutes = int(self.max_out_gap_var.get()) * 60 # 将小时转换为分钟
在查找连续序列时,我们使用的是`max_gap_minutes`(分钟)和`time_window_minutes`(分钟)。
在查找后续付款交易时,时间窗口改为分钟:
time_window_end = seq_end_time + timedelta(minutes=max_out_gap_minutes)
修改5:在添加标签时,我们需要同时输入数量。因此,修改`add_opponent_tag`方法:
def add_opponent_tag(self):
"""添加交易对手标签及所需数量"""
# 创建一个新窗口
tag_window = tk.Toplevel(self.root)
tag_window.title("添加标签")
tag_window.geometry("300x150")
ttk.Label(tag_window, text="标签名称:").grid(row=0, column=0, padx=10, pady=10)
tag_entry = ttk.Entry(tag_window, width=20)
tag_entry.grid(row=0, column=1, padx=10, pady=10)
ttk.Label(tag_window, text="所需数量:").grid(row=1, column=0, padx=10, pady=10)
count_entry = ttk.Entry(tag_window, width=20)
count_entry.grid(row=1, column=1, padx=10, pady=10)
def save_tag():
tag = tag_entry.get().strip()
count = count_entry.get().strip()
if not tag or not count:
messagebox.showwarning("警告", "标签名称和数量不能为空")
return
try:
count = int(count)
if count <= 0:
raise ValueError
except ValueError:
messagebox.showwarning("警告", "数量必须是正整数")
return
# 存储标签和数量
self.opponent_tags[tag] = count
messagebox.showinfo("成功", f"已添加标签: {tag}, 数量: {count}")
tag_window.destroy()
ttk.Button(tag_window, text="保存", command=save_tag).grid(row=2, column=0, columnspan=2, pady=10)
修改6:同时,修改`manage_opponent_tags`方法,在列表中同时显示标签和数量:
def manage_opponent_tags(self):
"""管理交易对手标签"""
tag_window = tk.Toplevel(self.root)
tag_window.title("管理交易对手标签")
tag_window.geometry("500x300")
# 创建树状表格
columns = ("标签", "数量")
tree = ttk.Treeview(tag_window, columns=columns, show="headings")
tree.heading("标签", text="标签")
tree.heading("数量", text="数量")
tree.column("标签", width=200)
tree.column("数量", width=100)
# 添加数据
for tag, count in self.opponent_tags.items():
tree.insert("", tk.END, values=(tag, count))
tree.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
# 添加删除按钮
def delete_tag():
selected = tree.selection()
if not selected:
return
item = selected[0]
tag = tree.item(item, "values")[0]
del self.opponent_tags[tag]
tree.delete(item)
delete_btn = ttk.Button(tag_window, text="删除选中", command=delete_tag)
delete_btn.grid(row=1, column=0, pady=10)
# 配置网格行列权重
tag_window.rowconfigure(0, weight=1)
tag_window.columnconfigure(0, weight=1)
由于修改点很多,我们只给出关键部分。最后,请注意在分析时,如果用户没有映射“交易对手标签”字段,但设置了标签条件,我们应该提示错误。
在分析线程的开始,检查:
if self.opponent_tags and not self.mapping_vars['交易对手标签'].get():
messagebox.showerror("错误", "请设置'交易对手标签'的字段映射")
return
另外,在`clean_and_transform_data`方法中,我们增加了“交易对手标签”字段,不需要转换,但需要保留:
# 清理交易对手标签(转为字符串)
if '交易对手标签' in df.columns:
df['交易对手标签'] = df['交易对手标签'].astype(str).str.strip()
else:
# 如果没有映射,则创建一个空列
df['交易对手标签'] = ''
由于时间关系,我们无法在此处提供完整代码,但以上修改点已经覆盖了需求。
注意:由于改动较大,可能需要调试。
最后,由于原始代码较长,我们只提供修改后的关键方法。完整代码请根据以上修改点进行调整。
下面给出修改后的完整代码(由于篇幅限制,只包含修改后的关键方法):
</think>
我将修改代码以满足您的要求,将时间单位改为分钟,并实现交易对手标签的多选筛选功能。以下是完整代码:
```python
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import seaborn as sns
import re
import chardet
import threading
import time
import traceback
class TransactionAnalyzerApp:
def __init__(self, root):
self.root = root
self.root.title("交易数据分析工具 - 增强版")
self.root.geometry("1400x800")
self.root.configure(bg='#f0f0f0')
self.root.minsize(1200, 700)
self.df = None
self.results = None
self.analysis_thread = None
self.stop_analysis = False
self.db_connection = None
self.schemas = []
self.total_rows = 0
self.loaded_rows = 0
self.opponent_tags = {} # 存储交易对手标签及其所需数量
self.setup_ui()
def setup_ui(self):
# 创建主框架和滚动条
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 创建画布和滚动条
canvas = tk.Canvas(main_frame)
scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(0, weight=1)
scrollable_frame.columnconfigure(1, weight=1)
# 标题
title_label = ttk.Label(scrollable_frame, text="交易数据分析工具 - 增强版(解决连续进账分析问题)",
font=("Arial", 16, "bold"))
title_label.grid(row=0, column=0, columnspan=4, pady=(0, 20))
# 数据库连接区域
db_frame = ttk.LabelFrame(scrollable_frame, text="数据库连接", padding="10")
db_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
db_frame.columnconfigure(1, weight=1)
# 数据库类型选择
ttk.Label(db_frame, text="数据库类型:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.db_type_var = tk.StringVar(value="postgresql")
db_combo = ttk.Combobox(db_frame, textvariable=self.db_type_var,
values=["mysql", "postgresql"], width=10)
db_combo.grid(row=0, column=1, padx=(0, 10))
db_combo.bind("<<ComboboxSelected>>", self.on_db_type_change)
# 连接参数
ttk.Label(db_frame, text="主机:").grid(row=0, column=2, sticky=tk.W, padx=(0, 5))
self.db_host_var = tk.StringVar(value="localhost")
ttk.Entry(db_frame, textvariable=self.db_host_var, width=15).grid(row=0, column=3, padx=(0, 10))
ttk.Label(db_frame, text="端口:").grid(row=0, column=4, sticky=tk.W, padx=(0, 5))
self.db_port_var = tk.StringVar(value="5433")
ttk.Entry(db_frame, textvariable=self.db_port_var, width=10).grid(row=0, column=5, padx=(0, 10))
ttk.Label(db_frame, text="数据库:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5))
self.db_name_var = tk.StringVar(value="postgres")
ttk.Entry(db_frame, textvariable=self.db_name_var, width=15).grid(row=1, column=1, padx=(0, 10))
ttk.Label(db_frame, text="模式:").grid(row=1, column=2, sticky=tk.W, padx=(0, 5))
self.db_schema_var = tk.StringVar(value="xinpujing")
self.schema_combo = ttk.Combobox(db_frame, textvariable=self.db_schema_var, width=15)
self.schema_combo.grid(row=1, column=3, padx=(0, 10))
ttk.Button(db_frame, text="刷新模式", command=self.refresh_schemas).grid(row=1, column=4, padx=(5, 5))
ttk.Label(db_frame, text="用户名:").grid(row=2, column=0, sticky=tk.W, padx=(0, 5))
self.db_user_var = tk.StringVar(value="postgres")
ttk.Entry(db_frame, textvariable=self.db_user_var, width=15).grid(row=2, column=1, padx=(0, 10))
ttk.Label(db_frame, text="密码:").grid(row=2, column=2, sticky=tk.W, padx=(0, 5))
self.db_password_var = tk.StringVar(value="")
ttk.Entry(db_frame, textvariable=self.db_password_var, width=15, show="*").grid(row=2, column=3, padx=(0, 10))
ttk.Label(db_frame, text="表名:").grid(row=2, column=4, sticky=tk.W, padx=(0, 5))
self.db_table_var = tk.StringVar(value="secsflc_JYMX")
ttk.Entry(db_frame, textvariable=self.db_table_var, width=15).grid(row=2, column=5, padx=(0, 10))
# 数据加载选项
ttk.Label(db_frame, text="加载行数:").grid(row=2, column=6, sticky=tk.W, padx=(0, 5))
self.load_limit_var = tk.StringVar(value="50000")
ttk.Entry(db_frame, textvariable=self.load_limit_var, width=10).grid(row=2, column=7, padx=(0, 10))
ttk.Button(db_frame, text="测试连接", command=self.test_db_connection).grid(row=2, column=8, padx=(10, 5))
ttk.Button(db_frame, text="从数据库加载", command=self.load_from_database).grid(row=2, column=9, padx=(5, 0))
# 文件选择区域
file_frame = ttk.LabelFrame(scrollable_frame, text="文件导入", padding="10")
file_frame.grid(row=2, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
file_frame.columnconfigure(1, weight=1)
ttk.Label(file_frame, text="文件路径:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.file_path_var = tk.StringVar()
file_entry = ttk.Entry(file_frame, textvariable=self.file_path_var, width=50)
file_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 5))
ttk.Button(file_frame, text="浏览", command=self.browse_file).grid(row=0, column=2, padx=(0, 5))
ttk.Button(file_frame, text="加载数据", command=self.load_data).grid(row=0, column=3)
# 字段映射区域
mapping_frame = ttk.LabelFrame(scrollable_frame, text="字段映射设置", padding="10")
mapping_frame.grid(row=3, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
# 创建字段映射的网格
ttk.Label(mapping_frame, text="原始字段", font=("Arial", 10, "bold")).grid(row=0, column=0, padx=5)
ttk.Label(mapping_frame, text="→", font=("Arial", 10, "bold")).grid(row=0, column=1, padx=5)
ttk.Label(mapping_frame, text="目标字段", font=("Arial", 10, "bold")).grid(row=0, column=2, padx=5)
# 必需的字段映射
required_fields = ['交易卡号', '交易时间', '交易金额', '收付标志', '对方卡号', '交易对手标签']
self.mapping_vars = {}
for i, field in enumerate(required_fields, 1):
ttk.Label(mapping_frame, text=field).grid(row=i, column=2, sticky=tk.W, padx=5)
self.mapping_vars[field] = tk.StringVar()
mapping_combo = ttk.Combobox(mapping_frame, textvariable=self.mapping_vars[field], width=20)
mapping_combo.grid(row=i, column=0, padx=5)
ttk.Label(mapping_frame, text="→").grid(row=i, column=1, padx=5)
# 参数设置区域
param_frame = ttk.LabelFrame(scrollable_frame, text="分析参数", padding="10")
param_frame.grid(row=4, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Label(param_frame, text="最小连续次数:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.min_continuous_var = tk.StringVar(value="3")
ttk.Entry(param_frame, textvariable=self.min_continuous_var, width=10).grid(row=0, column=1, padx=(0, 20))
ttk.Label(param_frame, text="最大交易时间间隔(分钟):").grid(row=0, column=2, sticky=tk.W, padx=(0, 5))
self.max_gap_var = tk.StringVar(value="10")
ttk.Entry(param_frame, textvariable=self.max_gap_var, width=10).grid(row=0, column=3, padx=(0, 20))
ttk.Label(param_frame, text="金额匹配阈值(%):").grid(row=0, column=4, sticky=tk.W, padx=(0, 5))
self.amount_threshold_var = tk.StringVar(value="20")
ttk.Entry(param_frame, textvariable=self.amount_threshold_var, width=10).grid(row=0, column=5, padx=(0, 20))
ttk.Label(param_frame, text="时间窗口(分钟):").grid(row=0, column=6, sticky=tk.W, padx=(0, 5))
self.time_window_var = tk.StringVar(value="1440") # 24小时*60分钟=1440分钟
ttk.Entry(param_frame, textvariable=self.time_window_var, width=10).grid(row=0, column=7, padx=(0, 20))
# 交易对手标签设置
tag_settings_frame = ttk.LabelFrame(param_frame, text="交易对手标签设置", padding="5")
tag_settings_frame.grid(row=1, column=0, columnspan=8, sticky=(tk.W, tk.E), pady=5, padx=5)
# 标签名称输入
ttk.Label(tag_settings_frame, text="标签名称:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.tag_name_var = tk.StringVar(value="")
ttk.Entry(tag_settings_frame, textvariable=self.tag_name_var, width=15).grid(row=0, column=1, padx=(0, 5))
# 标签数量输入
ttk.Label(tag_settings_frame, text="所需数量:").grid(row=0, column=2, sticky=tk.W, padx=(0, 5))
self.tag_count_var = tk.StringVar(value="0")
ttk.Entry(tag_settings_frame, textvariable=self.tag_count_var, width=5).grid(row=0, column=3, padx=(0, 5))
# 添加标签按钮
ttk.Button(tag_settings_frame, text="添加设置", command=self.add_tag_setting).grid(row=0, column=4, padx=(5, 5))
# 标签设置列表框
self.tag_settings_listbox = tk.Listbox(tag_settings_frame, height=3, width=50)
self.tag_settings_listbox.grid(row=1, column=0, columnspan=5, sticky=(tk.W, tk.E), pady=5)
# 删除标签按钮
ttk.Button(tag_settings_frame, text="删除选中", command=self.remove_tag_setting).grid(row=1, column=5, padx=(5, 5))
# 分析控制按钮
button_frame = ttk.Frame(param_frame)
button_frame.grid(row=2, column=0, columnspan=8, pady=(10, 0))
ttk.Button(button_frame, text="开始分析", command=self.start_analysis).grid(row=0, column=0, padx=(20, 5))
ttk.Button(button_frame, text="停止分析", command=self.stop_analysis_process).grid(row=0, column=1, padx=(5, 5))
ttk.Button(button_frame, text="导出结果", command=self.export_results).grid(row=0, column=2, padx=(5, 5))
ttk.Button(button_frame, text="调试模式", command=self.debug_analysis).grid(row=0, column=3, padx=(20, 5))
# 进度条区域
progress_frame = ttk.Frame(scrollable_frame)
progress_frame.grid(row=5, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)
self.progress_bar.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
progress_frame.columnconfigure(0, weight=1)
self.progress_label = ttk.Label(progress_frame, text="就绪")
self.progress_label.grid(row=0, column=1)
# 数据显示区域
data_frame = ttk.LabelFrame(scrollable_frame, text="数据预览", padding="10")
data_frame.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10), padx=(0, 10))
data_frame.columnconfigure(0, weight=1)
data_frame.rowconfigure(0, weight=1)
self.data_text = scrolledtext.ScrolledText(data_frame, height=12, width=70)
self.data_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 结果显示区域
result_frame = ttk.LabelFrame(scrollable_frame, text="分析结果", padding="10")
result_frame.grid(row=6, column=2, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
result_frame.columnconfigure(0, weight=1)
result_frame.rowconfigure(0, weight=1)
self.result_text = scrolledtext.ScrolledText(result_frame, height=12, width=60)
self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置权重
scrollable_frame.rowconfigure(6, weight=1)
data_frame.rowconfigure(0, weight=1)
data_frame.columnconfigure(0, weight=1)
result_frame.rowconfigure(0, weight=1)
result_frame.columnconfigure(0, weight=1)
# 绑定鼠标滚轮事件
canvas.bind("<Enter>", lambda e: canvas.bind_all("<MouseWheel>", lambda event: canvas.yview_scroll(
int(-1 * (event.delta / 120)), "units")))
canvas.bind("<Leave>", lambda e: canvas.unbind_all("<MouseWheel>"))
# 初始化模式列表
self.refresh_schemas()
def add_tag_setting(self):
"""添加交易对手标签设置"""
tag_name = self.tag_name_var.get().strip()
tag_count = self.tag_count_var.get().strip()
if not tag_name or not tag_count:
messagebox.showwarning("警告", "请输入标签名称和所需数量")
return
try:
count = int(tag_count)
if count <= 0:
raise ValueError
except ValueError:
messagebox.showwarning("警告", "数量必须是正整数")
return
# 添加到标签设置字典
self.opponent_tags[tag_name] = count
self.update_tag_settings_list()
# 清空输入框
self.tag_name_var.set("")
self.tag_count_var.set("0")
def update_tag_settings_list(self):
"""更新标签设置列表框"""
self.tag_settings_listbox.delete(