#!/usr/bin/env python # -*- coding: utf-8 -*- """ 扳手设备管理窗口 """ import tkinter as tk from tkinter import ttk, messagebox import requests import threading import sys import os # 添加父目录到路径 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from wrench_controller import WrenchController class DeviceManagerWindow: """设备管理窗口""" def __init__(self, parent, api_base_url, refresh_callback=None): self.parent = parent self.api_base_url = api_base_url self.refresh_callback = refresh_callback # 创建窗口 self.window = tk.Toplevel(parent) self.window.title("扳手设备管理") self.window.geometry("800x600") self.window.transient(parent) self.window.grab_set() self.devices = [] self.selected_device_id = None self._create_widgets() self.load_devices() def _create_widgets(self): """创建界面组件""" # 标题 title_frame = tk.Frame(self.window, bg="#2c3e50", height=50) title_frame.pack(fill=tk.X) title_frame.pack_propagate(False) title_label = tk.Label( title_frame, text="扳手设备管理", font=("微软雅黑", 16, "bold"), fg="white", bg="#2c3e50" ) title_label.pack(pady=10) # 设备列表 list_frame = tk.LabelFrame(self.window, text="设备列表", font=("微软雅黑", 12), padx=10, pady=10) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) columns = ("ID", "设备名称", "SN码", "IP地址", "端口", "地址码", "状态", "最后心跳") self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=10) for col in columns: self.tree.heading(col, text=col) self.tree.column("ID", width=50, anchor=tk.CENTER) self.tree.column("设备名称", width=120, anchor=tk.W) self.tree.column("SN码", width=120, anchor=tk.CENTER) self.tree.column("IP地址", width=120, anchor=tk.CENTER) self.tree.column("端口", width=60, anchor=tk.CENTER) self.tree.column("地址码", width=60, anchor=tk.CENTER) self.tree.column("状态", width=80, anchor=tk.CENTER) self.tree.column("最后心跳", width=150, anchor=tk.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("<>", self.on_device_select) # 配置标签颜色 self.tree.tag_configure("online", foreground="#27ae60") self.tree.tag_configure("offline", foreground="#e74c3c") # 操作按钮 button_frame = tk.Frame(self.window, padx=10, pady=10) button_frame.pack(fill=tk.X) self.add_button = tk.Button( button_frame, text="添加设备", font=("微软雅黑", 10), bg="#27ae60", fg="white", command=self.add_device ) self.add_button.pack(side=tk.LEFT, padx=5) self.edit_button = tk.Button( button_frame, text="编辑设备", font=("微软雅黑", 10), bg="#3498db", fg="white", command=self.edit_device, state=tk.DISABLED ) self.edit_button.pack(side=tk.LEFT, padx=5) self.delete_button = tk.Button( button_frame, text="删除设备", font=("微软雅黑", 10), bg="#e74c3c", fg="white", command=self.delete_device, state=tk.DISABLED ) self.delete_button.pack(side=tk.LEFT, padx=5) self.test_connect_button = tk.Button( button_frame, text="测试连接", font=("微软雅黑", 10), bg="#9b59b6", fg="white", command=self.test_connection, state=tk.DISABLED ) self.test_connect_button.pack(side=tk.LEFT, padx=5) self.refresh_button = tk.Button( button_frame, text="刷新列表", font=("微软雅黑", 10), bg="#95a5a6", fg="white", command=self.load_devices ) self.refresh_button.pack(side=tk.RIGHT, padx=5) def load_devices(self): """加载设备列表""" try: url = f"{self.api_base_url}/wrench-devices" response = requests.get(url, timeout=5) if response.status_code == 200: data = response.json() if data.get("success"): self.devices = data.get("data", []) self.update_device_list() else: messagebox.showerror("错误", f"加载设备列表失败: {data.get('message')}") except Exception as e: messagebox.showerror("错误", f"无法连接到后端服务器\n\n错误: {e}") def update_device_list(self): """更新设备列表显示""" self.tree.delete(*self.tree.get_children()) for device in self.devices: status = device.get('status', 'offline') status_text = "在线" if status == 'online' else "离线" tag = "online" if status == 'online' else "offline" self.tree.insert("", tk.END, values=( device.get('id'), device.get('device_name', '--'), device.get('device_sn', '--'), device.get('ip_address', '--'), device.get('port', 7888), device.get('address_code', 1), status_text, device.get('last_heartbeat', '--') ), tags=(tag,)) def on_device_select(self, event): """设备选择事件""" selected = self.tree.selection() if selected: item = self.tree.item(selected[0]) values = item['values'] if values: self.selected_device_id = values[0] self.edit_button.config(state=tk.NORMAL) self.delete_button.config(state=tk.NORMAL) self.test_connect_button.config(state=tk.NORMAL) else: self.selected_device_id = None self.edit_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) self.test_connect_button.config(state=tk.DISABLED) def add_device(self): """添加设备""" DeviceEditDialog(self.window, self.api_base_url, None, self.load_devices) def edit_device(self): """编辑设备""" if not self.selected_device_id: return device = None for d in self.devices: if d.get('id') == self.selected_device_id: device = d break if device: DeviceEditDialog(self.window, self.api_base_url, device, self.load_devices) def delete_device(self): """删除设备""" if not self.selected_device_id: return device = None for d in self.devices: if d.get('id') == self.selected_device_id: device = d break if not device: return result = messagebox.askyesno("确认删除", f"确定要删除设备 '{device.get('device_name')}' 吗?") if result: try: url = f"{self.api_base_url}/wrench-devices/{self.selected_device_id}" response = requests.delete(url, timeout=5) if response.status_code == 200: data = response.json() if data.get("success"): messagebox.showinfo("成功", "设备删除成功") self.load_devices() if self.refresh_callback: self.refresh_callback() else: messagebox.showerror("错误", data.get('message', '删除失败')) else: messagebox.showerror("错误", f"删除失败: HTTP {response.status_code}") except Exception as e: messagebox.showerror("错误", f"删除失败: {e}") def test_connection(self): """测试设备连接""" if not self.selected_device_id: return device = None for d in self.devices: if d.get('id') == self.selected_device_id: device = d break if not device: return def test_thread(): try: device_config = { "ip_address": device.get('ip_address'), "port": device.get('port', 7888), "address_code": device.get('address_code', 1) } wrench = WrenchController(device_config=device_config) if wrench.connect(): wrench.disconnect() # 更新设备状态为在线 self.window.after(0, lambda: self.update_device_status(device.get('id'), 'online')) self.window.after(0, lambda: messagebox.showinfo("成功", "设备连接成功")) else: self.window.after(0, lambda: self.update_device_status(device.get('id'), 'offline')) self.window.after(0, lambda: messagebox.showerror("失败", "设备连接失败")) except Exception as e: self.window.after(0, lambda: self.update_device_status(device.get('id'), 'offline')) self.window.after(0, lambda: messagebox.showerror("错误", f"连接测试失败: {e}")) threading.Thread(target=test_thread, daemon=True).start() def update_device_status(self, device_id, status): """更新设备状态""" try: url = f"{self.api_base_url}/wrench-devices/{device_id}/status" response = requests.post(url, json={"status": status}, timeout=5) if response.status_code == 200: self.load_devices() if self.refresh_callback: self.refresh_callback() except: pass class DeviceEditDialog: """设备编辑对话框""" def __init__(self, parent, api_base_url, device=None, callback=None): self.parent = parent self.api_base_url = api_base_url self.device = device self.callback = callback self.dialog = tk.Toplevel(parent) self.dialog.title("添加设备" if not device else "编辑设备") self.dialog.geometry("400x300") self.dialog.transient(parent) self.dialog.grab_set() self._create_widgets() def _create_widgets(self): """创建界面组件""" frame = tk.Frame(self.dialog, padx=20, pady=20) frame.pack(fill=tk.BOTH, expand=True) # 设备名称 tk.Label(frame, text="设备名称:", font=("微软雅黑", 10)).grid(row=0, column=0, sticky=tk.W, pady=10) self.name_var = tk.StringVar(value=self.device.get('device_name', '') if self.device else '') tk.Entry(frame, textvariable=self.name_var, width=30, font=("微软雅黑", 10)).grid(row=0, column=1, pady=10) # SN码 tk.Label(frame, text="SN码:", font=("微软雅黑", 10)).grid(row=1, column=0, sticky=tk.W, pady=10) self.sn_var = tk.StringVar(value=self.device.get('device_sn', '') if self.device else '') sn_entry = tk.Entry(frame, textvariable=self.sn_var, width=30, font=("微软雅黑", 10)) sn_entry.grid(row=1, column=1, pady=10) tk.Label(frame, text="(留空可自动查询)", font=("微软雅黑", 8), fg="#7f8c8d").grid(row=1, column=2, sticky=tk.W) # IP地址 tk.Label(frame, text="IP地址:", font=("微软雅黑", 10)).grid(row=2, column=0, sticky=tk.W, pady=10) self.ip_var = tk.StringVar(value=self.device.get('ip_address', '') if self.device else '') tk.Entry(frame, textvariable=self.ip_var, width=30, font=("微软雅黑", 10)).grid(row=2, column=1, pady=10) # 端口 tk.Label(frame, text="端口:", font=("微软雅黑", 10)).grid(row=3, column=0, sticky=tk.W, pady=10) self.port_var = tk.StringVar(value=str(self.device.get('port', 7888)) if self.device else '7888') tk.Entry(frame, textvariable=self.port_var, width=30, font=("微软雅黑", 10)).grid(row=3, column=1, pady=10) # 地址码 tk.Label(frame, text="地址码:", font=("微软雅黑", 10)).grid(row=4, column=0, sticky=tk.W, pady=10) self.address_var = tk.StringVar(value=str(self.device.get('address_code', 1)) if self.device else '1') tk.Entry(frame, textvariable=self.address_var, width=30, font=("微软雅黑", 10)).grid(row=4, column=1, pady=10) # 按钮 button_frame = tk.Frame(frame) button_frame.grid(row=5, column=0, columnspan=2, pady=20) tk.Button( button_frame, text="保存", font=("微软雅黑", 10), bg="#27ae60", fg="white", width=10, command=self.save_device ).pack(side=tk.LEFT, padx=5) tk.Button( button_frame, text="取消", font=("微软雅黑", 10), bg="#95a5a6", fg="white", width=10, command=self.dialog.destroy ).pack(side=tk.LEFT, padx=5) def save_device(self): """保存设备""" device_name = self.name_var.get().strip() ip_address = self.ip_var.get().strip() if not device_name or not ip_address: messagebox.showwarning("警告", "设备名称和IP地址不能为空") return try: port = int(self.port_var.get().strip() or 7888) address_code = int(self.address_var.get().strip() or 1) except ValueError: messagebox.showerror("错误", "端口和地址码必须是数字") return device_data = { "device_name": device_name, "device_sn": self.sn_var.get().strip() or None, "ip_address": ip_address, "port": port, "address_code": address_code } try: if self.device: # 更新设备 url = f"{self.api_base_url}/wrench-devices/{self.device.get('id')}" response = requests.put(url, json=device_data, timeout=5) else: # 创建设备 url = f"{self.api_base_url}/wrench-devices" response = requests.post(url, json=device_data, timeout=5) if response.status_code == 200: data = response.json() if data.get("success"): messagebox.showinfo("成功", "设备保存成功") self.dialog.destroy() if self.callback: self.callback() else: messagebox.showerror("错误", data.get('message', '保存失败')) else: messagebox.showerror("错误", f"保存失败: HTTP {response.status_code}") except Exception as e: messagebox.showerror("错误", f"保存失败: {e}")