重构界面:固定值改用编辑框+复选框,日志区替换为提交结果表格,间隔单位改为毫秒

This commit is contained in:
2026-05-15 15:14:55 +08:00
parent e99dfaef4d
commit bf11957f7c
5 changed files with 127 additions and 149 deletions
+112 -144
View File
@@ -23,6 +23,7 @@ from tkinter import (
Y,
BooleanVar,
Button,
Checkbutton,
Entry,
Frame,
Label,
@@ -108,13 +109,25 @@ class App:
self.config = {}
self.excel_path = StringVar()
self.delay_var = StringVar(value="3")
self.delay_var = StringVar(value="500")
self.status_var = StringVar(value="就绪")
self.engine_status_var = StringVar(value="未启动")
self.running = False
self.paused = False
self.results = []
# 固定值编辑框变量
self.fv_product = StringVar()
self.fv_customer = StringVar()
self.fv_destination = StringVar()
self.fv_force_destination = BooleanVar()
self.fv_district_code = StringVar()
self.fv_force_district = BooleanVar()
self.fv_sender_name = StringVar()
self.fv_force_sender_name = BooleanVar()
self.fv_sender_addr = StringVar()
self.fv_force_sender_addr = BooleanVar()
self._build_ui()
# 关闭窗口时清理
@@ -152,7 +165,7 @@ class App:
# 延时设置
row3 = Frame(config_frame)
row3.pack(fill=X, pady=2)
Label(row3, text="间隔(秒):", width=10, anchor="e").pack(side="left")
Label(row3, text="间隔(毫秒):", width=10, anchor="e").pack(side="left")
Entry(row3, textvariable=self.delay_var, width=8).pack(side="left", padx=5)
Label(row3, text="(每单提交之间的等待时间)", fg="gray").pack(side="left")
@@ -176,10 +189,6 @@ class App:
btn_frame, text="⏹ 停止", command=self._stop, width=10, state=DISABLED
)
self.stop_btn.pack(side="left", padx=2)
self.config_btn = Button(
btn_frame, text="编辑配置", command=self._edit_config, width=10
)
self.config_btn.pack(side="right", padx=2)
# 状态
Label(
@@ -187,31 +196,46 @@ class App:
).pack(side="right", padx=10)
# ── 固定值设置 ──
fixed_frame = LabelFrame(self.root, text="固定值设置(优先级:强制覆盖 > API获取值 > 固定值 > 表格 > 默认值)", padx=5, pady=5)
fixed_frame = LabelFrame(self.root, text="固定值设置", padx=10, pady=5)
fixed_frame.pack(fill=X, padx=10, pady=5)
tree_f = Frame(fixed_frame)
tree_f.pack(fill=X)
# Row 0: 产品 | 客户
r0 = Frame(fixed_frame)
r0.pack(fill=X, pady=2)
Label(r0, text="产品:", width=10, anchor="e").pack(side="left")
Entry(r0, textvariable=self.fv_product, width=20).pack(side="left", padx=5)
Label(r0, text="客户:", width=10, anchor="e").pack(side="left", padx=(30, 0))
Entry(r0, textvariable=self.fv_customer, width=20).pack(side="left", padx=5)
columns = ("field", "value", "force")
self.fixed_tree = ttk.Treeview(tree_f, columns=columns, show="headings", height=4)
self.fixed_tree.heading("field", text="字段名")
self.fixed_tree.heading("value", text="固定值")
self.fixed_tree.heading("force", text="强制覆盖")
self.fixed_tree.column("field", width=100)
self.fixed_tree.column("value", width=260)
self.fixed_tree.column("force", width=70, anchor="center")
self.fixed_tree.pack(side="left", fill=X, expand=True)
# Row 1: 寄达地 | 无影响
r1 = Frame(fixed_frame)
r1.pack(fill=X, pady=2)
Label(r1, text="寄达地:", width=10, anchor="e").pack(side="left")
Entry(r1, textvariable=self.fv_destination, width=20).pack(side="left", padx=5)
Checkbutton(r1, text="强制覆盖", variable=self.fv_force_destination).pack(side="left", padx=5)
Label(r1, text="无影响", fg="gray", font=("", 8)).pack(side="left")
btn_f = Frame(fixed_frame)
btn_f.pack(fill=X, pady=2)
Button(btn_f, text="添加", command=self._add_fixed_value, width=6).pack(side="left", padx=2)
Button(btn_f, text="删除", command=lambda: [self.fixed_tree.delete(i) for i in self.fixed_tree.selection()], width=6).pack(side="left", padx=2)
Button(btn_f, text="保存", command=self._save_fixed_values, width=6, bg="#4CAF50", fg="white").pack(side="left", padx=2)
Label(btn_f, text="双击编辑值 | 单击'强制覆盖'列切换 | 留空则不生效", fg="gray").pack(side="left", padx=5)
# Row 2: 寄达地区划代码 | 强制覆盖 | 直接决定寄达地
r2 = Frame(fixed_frame)
r2.pack(fill=X, pady=2)
Label(r2, text="寄达地区划代码:", width=12, anchor="e").pack(side="left")
Entry(r2, textvariable=self.fv_district_code, width=20).pack(side="left", padx=5)
Checkbutton(r2, text="强制覆盖", variable=self.fv_force_district).pack(side="left", padx=5)
Label(r2, text="(直接决定寄达地)", fg="gray", font=("", 8)).pack(side="left")
self.fixed_tree.bind("<Double-1>", self._edit_fixed_cell)
self.fixed_tree.bind("<ButtonRelease-1>", self._toggle_force)
# Row 3: 寄件人姓名 | 强制覆盖
r3 = Frame(fixed_frame)
r3.pack(fill=X, pady=2)
Label(r3, text="寄件人姓名:", width=12, anchor="e").pack(side="left")
Entry(r3, textvariable=self.fv_sender_name, width=20).pack(side="left", padx=5)
Checkbutton(r3, text="强制覆盖", variable=self.fv_force_sender_name).pack(side="left", padx=5)
# Row 4: 寄件地址 | 强制覆盖
r4 = Frame(fixed_frame)
r4.pack(fill=X, pady=2)
Label(r4, text="寄件地址:", width=12, anchor="e").pack(side="left")
Entry(r4, textvariable=self.fv_sender_addr, width=20).pack(side="left", padx=5)
Checkbutton(r4, text="强制覆盖", variable=self.fv_force_sender_addr).pack(side="left", padx=5)
# ── 进度条 ──
progress_frame = Frame(self.root)
@@ -223,16 +247,21 @@ class App:
self.progress_label = Label(progress_frame, text="", fg="gray")
self.progress_label.pack()
# ── 日志区 ──
log_frame = LabelFrame(self.root, text="运行日志", padx=10, pady=5)
log_frame.pack(fill="both", expand=True, padx=10, pady=5)
# ── 提交结果表格 ──
result_frame = LabelFrame(self.root, text="提交结果", padx=10, pady=5)
result_frame.pack(fill="both", expand=True, padx=10, pady=5)
self.log_text = Text(log_frame, wrap="word", state=DISABLED, font=("Consolas", 9))
self.log_text.pack(side="left", fill="both", expand=True)
columns = ("单号", "处理状态", "重量", "产品", "客户", "寄达地区划代码", "寄件人姓名", "寄件地址")
self.result_tree = ttk.Treeview(result_frame, columns=columns, show="headings", height=8)
col_widths = [140, 80, 60, 110, 110, 110, 80, 160]
for col, width in zip(columns, col_widths):
self.result_tree.heading(col, text=col)
self.result_tree.column(col, width=width, anchor="center")
self.result_tree.pack(side="left", fill="both", expand=True)
scroll_y = Scrollbar(log_frame, orient=VERTICAL, command=self.log_text.yview)
scroll_y = Scrollbar(result_frame, orient=VERTICAL, command=self.result_tree.yview)
scroll_y.pack(side=RIGHT, fill=Y)
self.log_text.configure(yscrollcommand=scroll_y.set)
self.result_tree.configure(yscrollcommand=scroll_y.set)
# ─── 配置管理 ───────────────────────────────────────────
@@ -248,18 +277,21 @@ class App:
shutil.copy2(bundled, config_path)
self.config = load_config(config_path)
timing = get_timing_config(self.config)
delay_sec = timing.get("delay_between_rows_ms", 3000) // 1000
self.delay_var.set(str(delay_sec))
delay_ms = timing.get("delay_between_rows_ms", 500)
self.delay_var.set(str(delay_ms))
# 加载固定值到表格
for item in self.fixed_tree.get_children():
self.fixed_tree.delete(item)
for field, cfg in self.config.get("fixed_values", {}).items():
val = cfg.get("value", "")
force = "✓" if cfg.get("force") else ""
self.fixed_tree.insert("", "end", values=(field, val, force))
self._log("配置加载成功")
# 加载固定值到编辑框
fv = self.config.get("fixed_values", {})
self.fv_product.set(fv.get("产品名称", {}).get("value", ""))
self.fv_customer.set(fv.get("客户名称", {}).get("value", ""))
self.fv_destination.set(fv.get("寄达地", {}).get("value", ""))
self.fv_force_destination.set(fv.get("寄达地", {}).get("force", False))
self.fv_district_code.set(fv.get("寄达地区划", {}).get("value", ""))
self.fv_force_district.set(fv.get("寄达地区划", {}).get("force", False))
self.fv_sender_name.set(fv.get("寄件人", {}).get("value", ""))
self.fv_force_sender_name.set(fv.get("寄件人", {}).get("force", False))
self.fv_sender_addr.set(fv.get("寄件地址", {}).get("value", ""))
self.fv_force_sender_addr.set(fv.get("寄件地址", {}).get("force", False))
# 检查已保存的登录会话
self._check_session_status()
@@ -282,99 +314,18 @@ class App:
self.engine_status_var.set("未登录")
self.status_label.configure(fg="orange")
def _edit_config(self):
"""打开配置编辑窗口。"""
config_path = _get_config_path()
# os.startfile(config_path) if sys.platform == "win32" else None
# ─── 固定值保存 ────────────────────────────────────────
win = Toplevel(self.root)
win.title("编辑配置")
win.geometry("700x500")
text = Text(win, wrap="none", font=("Consolas", 10))
text.pack(fill="both", expand=True, padx=5, pady=5)
with open(config_path, "r", encoding="utf-8") as f:
text.insert("1.0", f.read())
def save():
new_config = text.get("1.0", "end-1c")
try:
json.loads(new_config) # 验证 JSON
with open(config_path, "w", encoding="utf-8") as f:
f.write(new_config)
self._load_config()
win.destroy()
self._log("配置已保存并重新加载")
except json.JSONDecodeError as e:
messagebox.showerror("JSON 错误", f"配置格式错误:\n{e}")
Button(win, text="保存", command=save, bg="#4CAF50", fg="white").pack(
side="bottom", pady=5
)
# ─── 固定值设置 ────────────────────────────────────────
def _edit_fixed_cell(self, event):
"""双击编辑固定值单元格(跳过强制覆盖列)。"""
region = self.fixed_tree.identify_region(event.x, event.y)
if region != "cell":
return
column = self.fixed_tree.identify_column(event.x)
if column == "#3": # 强制覆盖列不双击编辑,单击切换
return
item = self.fixed_tree.identify_row(event.y)
if not item:
return
col_idx = int(column.replace("#", "")) - 1
old_val = self.fixed_tree.item(item, "values")[col_idx]
edit = Entry(self.root)
edit.insert(0, old_val)
edit.select_range(0, "end")
bbox = self.fixed_tree.bbox(item, column)
if bbox:
edit.place(x=bbox[0] + 185, y=bbox[1] + 310, width=bbox[2], height=bbox[3])
def save_edit(e=None):
vals = list(self.fixed_tree.item(item, "values"))
vals[col_idx] = edit.get()
self.fixed_tree.item(item, values=vals)
edit.destroy()
edit.bind("<Return>", save_edit)
edit.bind("<FocusOut>", lambda e: edit.destroy())
edit.focus_set()
def _add_fixed_value(self):
"""添加新的固定值行(默认不强制覆盖)。"""
self.fixed_tree.insert("", "end", values=("新固定值", "", ""))
def _toggle_force(self, event):
"""单击'强制覆盖'列切换开关状态。"""
region = self.fixed_tree.identify_region(event.x, event.y)
if region != "cell":
return
column = self.fixed_tree.identify_column(event.x)
if column != "#3": # force 是第3列
return
item = self.fixed_tree.identify_row(event.y)
if not item:
return
vals = list(self.fixed_tree.item(item, "values"))
vals[2] = "" if vals[2] == "✓" else "✓"
self.fixed_tree.item(item, values=vals)
def _save_fixed_values(self):
"""保存固定值到 config.json(新格式:{value, force})。"""
new_fixed = {}
for item in self.fixed_tree.get_children():
vals = self.fixed_tree.item(item, "values")
field_name = vals[0].strip()
if field_name:
new_fixed[field_name] = {
"value": vals[1].strip() if len(vals) > 1 else "",
"force": vals[2] == "✓" if len(vals) > 2 else False,
}
def _save_fixed_from_entries(self):
"""从编辑框保存固定值到 config.json。"""
new_fixed = {
"产品名称": {"value": self.fv_product.get().strip(), "force": False},
"客户名称": {"value": self.fv_customer.get().strip(), "force": False},
"寄达地": {"value": self.fv_destination.get().strip(), "force": self.fv_force_destination.get()},
"寄达地区划": {"value": self.fv_district_code.get().strip(), "force": self.fv_force_district.get()},
"寄件人": {"value": self.fv_sender_name.get().strip(), "force": self.fv_force_sender_name.get()},
"寄件地址": {"value": self.fv_sender_addr.get().strip(), "force": self.fv_force_sender_addr.get()},
}
self.config["fixed_values"] = new_fixed
config_path = _get_config_path()
@@ -384,7 +335,19 @@ class App:
with open(config_path, "w", encoding="utf-8") as f:
json.dump(cfg, f, ensure_ascii=False, indent=4)
self._log(f"固定值已保存: {len(new_fixed)} 项")
def _add_result_row(self, result):
"""添加一行到结果表格。"""
values = (
result.get("单号", ""),
result.get("status", ""),
result.get("重量", ""),
result.get("产品", ""),
result.get("客户", ""),
result.get("寄达地区划代码", ""),
result.get("寄件人姓名", ""),
result.get("寄件地址", ""),
)
self.result_tree.insert("", "end", values=values)
# ─── 文件选择 ───────────────────────────────────────────
@@ -478,12 +441,19 @@ class App:
self.paused = False
self._set_controls_state("running")
# 保存固定值到配置文件
self._save_fixed_from_entries()
# 清空结果表格
for item in self.result_tree.get_children():
self.result_tree.delete(item)
# 创建结果工作簿
self.results = []
# 更新配置中的延时
try:
delay_ms = int(self.delay_var.get()) * 1000
delay_ms = int(self.delay_var.get())
self.config.setdefault("timing", {})["delay_between_rows_ms"] = delay_ms
except ValueError:
pass
@@ -560,6 +530,9 @@ class App:
result = await _engine.process_single_row(row)
self.results.append(result)
# 更新结果表格
self.root.after(0, lambda r=result: self._add_result_row(r))
# 更新进度
self.root.after(0, lambda cur=i+1, total=len(rows): self._update_progress(cur, total))
@@ -796,12 +769,7 @@ class App:
return await future
def _log_sync(self, msg):
timestamp = datetime.now().strftime("%H:%M:%S")
line = f"[{timestamp}] {msg}\n"
self.log_text.configure(state=NORMAL)
self.log_text.insert(END, line)
self.log_text.see(END)
self.log_text.configure(state=DISABLED)
pass
# ─── 入口 ──────────────────────────────────────────────────