From 6d45d126561b41f85e339735bc7c7d8080ad0cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?COT001=5C=E6=9D=8E=E6=97=AD=E5=85=89?= <871066422@qq.com> Date: Fri, 6 Mar 2026 15:27:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E5=88=B6=E5=8C=96=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + backend/app.py | 39 ++++++++ backend/database.py | 38 +++++++- backend/wrench.db | Bin 45056 -> 45056 bytes config.json | 2 +- frontend/admin_panel.py | 148 +++++++++++++++++++++++++++++ frontend/device_manager.py | 9 +- frontend/start | 7 ++ frontend/start_admin.bat | 9 ++ frontend/wrench_gui.py | 188 +++++++++++++++++++++++++++++++++++-- wrench_controller.py | 9 +- wrench_gui.py | 4 + 12 files changed, 439 insertions(+), 17 deletions(-) create mode 100644 frontend/admin_panel.py create mode 100644 frontend/start create mode 100644 frontend/start_admin.bat diff --git a/.gitignore b/.gitignore index 158b43a..5cbdb97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ __pycache__/ backend/build/ backend/dist/ +venv/ +frontend/build/ +frontend/dist/ diff --git a/backend/app.py b/backend/app.py index 8c652a8..406e97d 100644 --- a/backend/app.py +++ b/backend/app.py @@ -113,6 +113,45 @@ def claim_work_order(): }), 500 +@app.route('/api/work-orders/unclaim', methods=['POST']) +def unclaim_work_order(): + """退领工单""" + data = request.get_json() + trace_id = data.get('trace_id') + process_id = data.get('process_id') + + if not trace_id or not process_id: + return jsonify({"success": False, "message": "追溯号和工序号不能为空"}), 400 + + if db.unclaim_work_order(trace_id, process_id): + return jsonify({"success": True, "message": "退领成功"}) + else: + return jsonify({"success": False, "message": "退领失败"}), 500 + + +@app.route('/api/work-orders/delete', methods=['POST']) +def delete_work_order(): + """删除工单""" + data = request.get_json() + trace_id = data.get('trace_id') + process_id = data.get('process_id') + + if not trace_id or not process_id: + return jsonify({"success": False, "message": "追溯号和工序号不能为空"}), 400 + + if db.delete_work_order(trace_id, process_id): + return jsonify({"success": True, "message": "删除成功"}) + else: + return jsonify({"success": False, "message": "删除失败"}), 500 + + +@app.route('/api/work-orders/claimed', methods=['GET']) +def get_claimed_orders(): + """获取所有已认领的工单""" + orders = db.get_all_claimed_orders() + return jsonify({"success": True, "data": orders}) + + @app.route('/api/work-orders/submit', methods=['POST']) def submit_work_order(): """ diff --git a/backend/database.py b/backend/database.py index 3e90c58..3a83fbf 100644 --- a/backend/database.py +++ b/backend/database.py @@ -24,8 +24,9 @@ class Database: def get_connection(self): """获取数据库连接""" - conn = sqlite3.connect(self.db_path) - conn.row_factory = sqlite3.Row # 使查询结果可以按列名访问 + conn = sqlite3.connect(self.db_path, timeout=1.0, check_same_thread=False) + conn.row_factory = sqlite3.Row + conn.execute('PRAGMA journal_mode=WAL') return conn def init_database(self): @@ -352,6 +353,19 @@ class Database: print(f"查询认领工单失败: {e}") return None + def get_all_claimed_orders(self) -> List[Dict]: + """获取所有已认领的工单""" + try: + conn = self.get_connection() + cursor = conn.cursor() + cursor.execute('SELECT * FROM claimed_orders WHERE status = "claimed" ORDER BY claimed_at DESC') + rows = cursor.fetchall() + conn.close() + return [dict(row) for row in rows] + except Exception as e: + print(f"获取已认领工单失败: {e}") + return [] + def claim_work_order(self, trace_id: str, process_id: str, operator: str) -> bool: """ 认领工单 @@ -411,7 +425,25 @@ class Database: print(f"释放工单失败: {e}") return False - def submit_work_order(self, trace_id: str, process_id: str, bolts: List[Dict], + def unclaim_work_order(self, trace_id: str, process_id: str) -> bool: + """退领工单""" + return self.release_work_order(trace_id, process_id) + + def delete_work_order(self, trace_id: str, process_id: str) -> bool: + """删除工单""" + try: + conn = self.get_connection() + cursor = conn.cursor() + cursor.execute('DELETE FROM work_orders WHERE trace_id = ? AND process_id = ?', (trace_id, process_id)) + cursor.execute('DELETE FROM claimed_orders WHERE trace_id = ? AND process_id = ?', (trace_id, process_id)) + conn.commit() + conn.close() + return True + except Exception as e: + print(f"删除工单失败: {e}") + return False + + def submit_work_order(self, trace_id: str, process_id: str, bolts: List[Dict], device_sn: str = None, device_name: str = None) -> bool: """ 提交工单结果 diff --git a/backend/wrench.db b/backend/wrench.db index c3ceff7192e7fb9e5cb8c0654109a8060d11a033..e0998d7ffcfb569aa1cc38f21c5f339645894bf9 100644 GIT binary patch delta 891 zcmZ{i&ubG=5Xax{CXyz$laxxR4YD>AE0Xlhdr6wSOGS_Vs9HT0OiQxC&@_Q_* zVc|qHWD&3c0cGdEgH1JjHhw`J7~`B{_sQm7+umzGn!7HmvB1)p%cl`(8W}tDqfx2U zs&&I@nxb-utTO~>f}tm2ig6wDf;yx5R1u=Q9E+dxV1M3y>Arf%nhuH`qS8ol{HrgB z=299Zp_g&!cDC ztEy-ba>S26#&2(OP{tT-9`4eQlUY(C z@;P!$#zvEE=VtSpph?BdmuEBt$w-=_%DmXS?!~-a&lXHJo_uhwC|FJGnul +echo ======================================== +echo 启动管理员面板 +echo ======================================== +echo. +cd /d %~dp0 +python admin_panel.py +pause diff --git a/frontend/wrench_gui.py b/frontend/wrench_gui.py index ac25385..c407ec9 100644 --- a/frontend/wrench_gui.py +++ b/frontend/wrench_gui.py @@ -199,9 +199,13 @@ class WrenchGUI: col1_frame.grid(row=0, column=0, padx=(0, 20), sticky=tk.W) tk.Label(col1_frame, text="工单操作", font=("微软雅黑", 9, "bold"), fg="#2c3e50").pack(anchor=tk.W, pady=(0, 3)) + + order_buttons_frame = tk.Frame(col1_frame) + order_buttons_frame.pack() + self.claim_button = tk.Button( - col1_frame, - text="认领工单", + order_buttons_frame, + text="认领工单", font=("微软雅黑", 10), bg="#f39c12", fg="white", @@ -210,7 +214,47 @@ class WrenchGUI: cursor="hand2", command=self.claim_work_order ) - self.claim_button.pack() + self.claim_button.pack(side=tk.LEFT, padx=(0, 5)) + + self.unclaim_button = tk.Button( + order_buttons_frame, + text="退领工单", + font=("微软雅黑", 10), + bg="#e67e22", + fg="white", + width=10, + height=1, + cursor="hand2", + state=tk.DISABLED, + command=self.unclaim_work_order + ) + self.unclaim_button.pack(side=tk.LEFT, padx=(0, 5)) + + self.delete_button = tk.Button( + order_buttons_frame, + text="删除工单", + font=("微软雅黑", 10), + bg="#e74c3c", + fg="white", + width=10, + height=1, + cursor="hand2", + command=self.delete_work_order + ) + self.delete_button.pack(side=tk.LEFT, padx=(0, 5)) + + self.admin_button = tk.Button( + order_buttons_frame, + text="管理工单", + font=("微软雅黑", 10), + bg="#9b59b6", + fg="white", + width=10, + height=1, + cursor="hand2", + command=self.open_admin_panel + ) + self.admin_button.pack(side=tk.LEFT) # 第二列:设备选择 col2_frame = tk.Frame(button_frame) @@ -478,7 +522,7 @@ class WrenchGUI: try: url = f"{self.api_base_url}/work-orders" # 不传参数,获取所有可用工单 - response = requests.get(url, timeout=5) + response = requests.get(url, timeout=2) if response.status_code == 200: data = response.json() @@ -584,16 +628,22 @@ class WrenchGUI: while self.is_polling: try: url = f"{self.api_base_url}/work-orders" - # 不传参数,获取所有可用工单 - response = requests.get(url, timeout=5) + response = requests.get(url, timeout=2) if response.status_code == 200: data = response.json() if data.get("success"): orders = data.get("data", []) - # 只在数据变化时更新 self.root.after(0, lambda o=orders: self.update_order_list(o)) - # 更新状态标签 + + # 检查当前认领的工单是否被退领 + if self.work_order: + trace_id = self.work_order.get('trace_id') + process_id = self.work_order.get('process_id') + # 如果当前工单出现在可用列表中,说明被退领了 + if any(o.get('trace_id') == trace_id and o.get('process_id') == process_id for o in orders): + self.root.after(0, self.clear_claimed_order) + if len(orders) > 0: self.root.after(0, lambda: self.poll_status_label.config( text=f"🔄 已找到 {len(orders)} 个可用工单", fg="#27ae60" @@ -627,7 +677,7 @@ class WrenchGUI: "process_id": process_id, "operator": "操作员" # 可以从配置或输入获取 } - response = requests.post(url, json=data, timeout=5) + response = requests.post(url, json=data, timeout=2) if response.status_code == 200: result = response.json() @@ -636,7 +686,14 @@ class WrenchGUI: self.update_work_order_info() self.update_bolt_list() self.claim_button.config(state=tk.DISABLED) + self.unclaim_button.config(state=tk.NORMAL) self.start_button.config(state=tk.NORMAL) + # 从列表中移除已认领的工单 + for item in self.order_tree.get_children(): + values = self.order_tree.item(item)['values'] + if values[0] == trace_id and values[1] == process_id: + self.order_tree.delete(item) + break self.log(f"✅ 成功认领工单: {trace_id} - {process_id}", "SUCCESS") else: messagebox.showerror("错误", result.get("message", "认领失败")) @@ -648,6 +705,89 @@ class WrenchGUI: messagebox.showerror("错误", f"无法连接到后端服务器\n\n错误: {e}") self.log(f"认领失败: {e}", "ERROR") + def unclaim_work_order(self): + """退领工单""" + if not self.work_order: + messagebox.showwarning("警告", "当前没有已认领的工单") + return + + if self.is_running: + messagebox.showwarning("警告", "工单正在执行中,无法退领") + return + + trace_id = self.work_order.get('trace_id') + process_id = self.work_order.get('process_id') + + result = messagebox.askyesno("确认退领", f"确定要退领工单 {trace_id} - {process_id} 吗?") + if not result: + return + + try: + url = f"{self.api_base_url}/work-orders/unclaim" + data = {"trace_id": trace_id, "process_id": process_id} + response = requests.post(url, json=data, timeout=2) + + if response.status_code == 200: + result = response.json() + if result.get("success"): + self.work_order = None + self.update_work_order_info() + self.update_bolt_list() + self.claim_button.config(state=tk.NORMAL) + self.unclaim_button.config(state=tk.DISABLED) + self.start_button.config(state=tk.DISABLED) + self.log(f"✅ 成功退领工单: {trace_id} - {process_id}", "SUCCESS") + self.query_work_orders() + else: + messagebox.showerror("错误", result.get("message", "退领失败")) + else: + messagebox.showerror("错误", f"退领失败: HTTP {response.status_code}") + except requests.exceptions.RequestException as e: + messagebox.showerror("错误", f"无法连接到后端服务器\n\n错误: {e}") + + def delete_work_order(self): + """删除工单""" + selected = self.order_tree.selection() + if not selected: + messagebox.showwarning("警告", "请先选择要删除的工单") + return + + item = self.order_tree.item(selected[0]) + values = item['values'] + trace_id = values[0] + process_id = values[1] + + result = messagebox.askyesno("确认删除", f"确定要删除工单 {trace_id} - {process_id} 吗?\n此操作不可恢复!") + if not result: + return + + try: + url = f"{self.api_base_url}/work-orders/delete" + data = {"trace_id": trace_id, "process_id": process_id} + response = requests.post(url, json=data, timeout=2) + + if response.status_code == 200: + result = response.json() + if result.get("success"): + self.log(f"✅ 成功删除工单: {trace_id} - {process_id}", "SUCCESS") + self.query_work_orders() + else: + messagebox.showerror("错误", result.get("message", "删除失败")) + else: + messagebox.showerror("错误", f"删除失败: HTTP {response.status_code}") + except requests.exceptions.RequestException as e: + messagebox.showerror("错误", f"无法连接到后端服务器\n\n错误: {e}") + + def clear_claimed_order(self): + """清空当前认领的工单""" + self.work_order = None + self.update_work_order_info() + self.update_bolt_list() + self.claim_button.config(state=tk.NORMAL) + self.unclaim_button.config(state=tk.DISABLED) + self.start_button.config(state=tk.DISABLED) + self.log("⚠️ 当前工单已被管理员退领", "WARN") + def update_work_order_info(self): """更新工单信息显示""" if self.work_order: @@ -796,6 +936,10 @@ class WrenchGUI: self.log("✅ 已启用远程控制", "SUCCESS") + # 等待扳手初始化完成 + time.sleep(0.5) + self.log("扳手初始化完成,准备开始拧紧") + # 遍历所有螺栓 bolts = self.work_order.get('bolts', []) bolt_results = [] @@ -1119,6 +1263,17 @@ class WrenchGUI: def open_device_manager(self): """打开设备管理窗口""" DeviceManagerWindow(self.root, self.api_base_url, self.load_wrench_devices) + + def open_admin_panel(self): + """打开管理员面板(需要密码)""" + from tkinter import simpledialog + password = simpledialog.askstring("管理员验证", "请输入管理员密码:", show='*') + if password == "123": + from admin_panel import AdminPanel + admin_window = tk.Toplevel(self.root) + AdminPanel(admin_window, self.api_base_url) + elif password is not None: + messagebox.showerror("错误", "密码错误") def main(): @@ -1126,6 +1281,21 @@ def main(): app = WrenchGUI(root) def on_closing(): + # 如果有未完成的工单,提示用户 + if app.work_order: + trace_id = app.work_order.get('trace_id') + process_id = app.work_order.get('process_id') + result = messagebox.askokcancel( + "确认关闭", + f"当前有工单 {trace_id} - {process_id} 未完成\n\n关闭程序将自动退领该工单" + ) + if not result: + return + try: + url = f"{app.api_base_url}/work-orders/unclaim" + requests.post(url, json={"trace_id": trace_id, "process_id": process_id}, timeout=1) + except: + pass app.stop_polling() root.destroy() diff --git a/wrench_controller.py b/wrench_controller.py index 4783494..7c84b7e 100644 --- a/wrench_controller.py +++ b/wrench_controller.py @@ -8,6 +8,7 @@ import socket import struct import json +import time from datetime import datetime from typing import Tuple, Optional from pathlib import Path @@ -439,7 +440,13 @@ class WrenchController: print(f" 报文: {data.hex(' ').upper()}") # 发送命令 - self._send_command(bytes(data)) + if not self._send_command(bytes(data)): + print("❌ 发送设定参数命令失败") + return bytes(data) + + # 等待扳手处理参数(0x10命令无响应,需要短暂延迟) + time.sleep(1) + print("✅ 参数设定命令已发送,等待扳手处理完成") return bytes(data) diff --git a/wrench_gui.py b/wrench_gui.py index 7896fab..bb37d85 100644 --- a/wrench_gui.py +++ b/wrench_gui.py @@ -325,6 +325,10 @@ class WrenchGUI: self.log("✅ 已启用远程控制", "SUCCESS") + # 等待扳手初始化完成 + time.sleep(0.5) + self.log("扳手初始化完成,准备开始拧紧") + # 遍历所有螺栓 bolts = self.work_order['bolts'] for index, bolt in enumerate(bolts):