From b4e8bcb9c92afe805653edb9ceb1d838d986121a Mon Sep 17 00:00:00 2001 From: risingLee <871066422@qq.com> Date: Mon, 26 Jan 2026 15:11:04 +0800 Subject: [PATCH] =?UTF-8?q?ui=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/API_README.md | 11 +- backend/wrench.db | Bin 45056 -> 45056 bytes backend/接口文档.md | 367 +++++++++++++++++++++++++++++++++++++++++ frontend/wrench_gui.py | 81 +++++++-- 4 files changed, 437 insertions(+), 22 deletions(-) create mode 100644 backend/接口文档.md diff --git a/backend/API_README.md b/backend/API_README.md index 1fc9ca4..f56e444 100644 --- a/backend/API_README.md +++ b/backend/API_README.md @@ -12,9 +12,8 @@ - process_id: 工序号 - process_name: 工序名称 - product_name: 产品名称 - - station: 工位 - operator: 操作员 - - status: 状态 + - status: 状态(系统自动管理,创建时默认为pending) - bolts: 螺栓数据(JSON格式) - created_at: 创建时间 - updated_at: 更新时间 @@ -35,6 +34,8 @@ - process_id: 工序号 - process_name: 工序名称 - bolts: 螺栓结果数据(JSON格式) + - device_sn: 设备SN号 + - device_name: 设备名称 - submitted_at: 提交时间 ## API接口 @@ -50,9 +51,7 @@ "process_id": "P001", "process_name": "前轮装配", "product_name": "汽车底盘组件", - "station": "装配工位A1", "operator": "张三", - "status": "pending", "bolts": [ { "bolt_id": 1, @@ -61,8 +60,7 @@ "mode": 1, "torque_tolerance": 0.10, "angle_min": 1, - "angle_max": 360, - "status": "pending" + "angle_max": 360 } ] } @@ -95,7 +93,6 @@ "process_id": "P001", "process_name": "前轮装配", "product_name": "汽车底盘组件", - "station": "装配工位A1", "operator": "张三", "bolt_count": 2, "status": "pending" diff --git a/backend/wrench.db b/backend/wrench.db index 74a14cf2a52b7482ea72021bacd37c5db55f0a74..b1a844a2d8054e0f1025fd261011231affcf60b5 100644 GIT binary patch delta 172 zcmZp8z|`=7X@V3J+y03%PC#;F!V-Qq1_1^J?o*ou6%KJTv6yWZ6tHC3%&u@)o{?wr zX?qi14qgTZ21fp^4E$U9LxHNy`6oO33$XwNS5It=;r8U=WKb7Xm6tYVn7rCweR8`$ zznGbVp`n$jg_V&R7g+C02L6})mo^JFoaATJo9HM!`I5dUlOfNhgiG=qhNf1A2395} NlW*0_!8wZ>3;-O$EaLzG delta 332 zcmZp8z|`=7X@V3JQ@}(SCm^{oVF^DQBligg?h~5@74~uSc9^nrF{q2G_NR&~8c#mp zFFTnzXEv*mnU#s<Y@0g;tA2Dw*VFF&^+ zC$%Is1;Z*6V+lw KIXGugg8=~L7FrVk diff --git a/backend/接口文档.md b/backend/接口文档.md new file mode 100644 index 0000000..8af6c51 --- /dev/null +++ b/backend/接口文档.md @@ -0,0 +1,367 @@ +# 第三方接口文档 + +## 概述 + +本文档提供第三方系统集成所需的简化API接口,包含工单创建和结果查询功能。 + +**基础URL**: `http://localhost:5000/api` + +**数据格式**: JSON + +**字符编码**: UTF-8 + +--- + +## 1. 创建工单 + +### 接口信息 + +- **URL**: `/api/work-orders/create` +- **方法**: `POST` +- **描述**: 创建新的工单 + +### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| trace_id | string | 是 | 追溯号(唯一标识) | +| process_id | string | 是 | 工序号 | +| process_name | string | 否 | 工序名称 | +| product_name | string | 否 | 产品名称 | +| operator | string | 否 | 操作员 | +| bolts | array | 是 | 螺栓数据列表 | + +**螺栓数据格式** (bolts数组中的每个对象): + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| bolt_id | string/integer | 是 | 螺栓编号 | +| name | string | 否 | 螺栓名称 | +| target_torque | float | 是 | 目标扭矩(单位:牛米/Nm) | +| mode | integer | 否 | 拧紧模式,默认为1 | +| torque_tolerance | float | 否 | 扭矩容差(相对值,如0.10表示±10%),默认0.10 | +| angle_min | integer | 否 | 最小角度(度),默认1 | +| angle_max | integer | 否 | 最大角度(度),默认360 | + +### 请求示例 + +```json +{ + "trace_id": "TR20260119001", + "process_id": "P001", + "process_name": "前轮装配", + "product_name": "汽车底盘组件", + "operator": "张三", + "bolts": [ + { + "bolt_id": "B001", + "name": "前轮螺栓1", + "target_torque": 50.0, + "mode": 1, + "torque_tolerance": 0.10, + "angle_min": 1, + "angle_max": 360 + }, + { + "bolt_id": "B002", + "name": "前轮螺栓2", + "target_torque": 50.0 + } + ] +} +``` + +### 响应示例 + +**成功响应** (HTTP 200): +```json +{ + "success": true, + "message": "工单创建成功", + "data": { + "trace_id": "TR20260119001", + "process_id": "P001" + } +} +``` + +**错误响应** (HTTP 400): +```json +{ + "success": false, + "message": "字段 trace_id 不能为空" +} +``` + +### 注意事项 + +1. `trace_id` 和 `process_id` 的组合必须唯一 +2. 如果已存在相同的 `trace_id` 和 `process_id` 组合,创建会失败 +3. 工单创建后状态默认为 `pending`(待处理) +4. 螺栓数据中,`target_torque` 是必填项,其他参数有默认值 + +--- + +## 2. 查询工单结果 + +### 接口信息 + +- **URL**: `/api/work-results` +- **方法**: `GET` +- **描述**: 查询工单执行结果 + +### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| trace_id | string | 是 | 追溯号 | +| process_id | string | 是 | 工序号 | + +### 请求示例 + +``` +GET /api/work-results?trace_id=TR20260119001&process_id=P001 +``` + +### 响应示例 + +**成功响应 - 有结果** (HTTP 200): +```json +{ + "success": true, + "message": "查询成功", + "data": { + "id": 1, + "trace_id": "TR20260119001", + "process_id": "P001", + "process_name": "前轮装配", + "bolts": [ + { + "bolt_id": "B001", + "name": "前轮螺栓1", + "target_torque": 50.0, + "actual_torque": 50.2, + "success": true, + "timestamp": "2026-01-19 10:30:00" + } + ], + "device_sn": "1234567890", + "device_name": "扳手设备1", + "submitted_at": "2026-01-19 10:35:00" + } +} +``` + +**成功响应 - 无结果** (HTTP 404): +```json +{ + "success": false, + "message": "未找到执行结果", + "data": null +} +``` + +### 响应字段说明 + +**结果对象字段**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | integer | 结果记录ID | +| trace_id | string | 追溯号 | +| process_id | string | 工序号 | +| process_name | string | 工序名称 | +| bolts | array | 螺栓执行结果列表 | +| device_sn | string | 使用的设备SN号 | +| device_name | string | 使用的设备名称 | +| submitted_at | string | 提交时间 | + +**螺栓结果字段** (bolts数组中的每个对象): + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| bolt_id | string/integer | 螺栓编号 | +| name | string | 螺栓名称 | +| target_torque | float | **目标扭矩**(单位:牛米/Nm) | +| actual_torque | float | **实际扭矩**(单位:牛米/Nm) | +| success | boolean | 拧紧是否成功 | +| timestamp | string | 拧紧完成时间 | + +### 注意事项 + +1. 必须同时提供 `trace_id` 和 `process_id` 才能查询到结果 +2. 如果工单还未执行完成,查询会返回404 +3. `actual_torque` 是扳手实际达到的扭矩值 +4. `success` 字段表示拧紧是否在容差范围内成功 +5. 时间格式为:`YYYY-MM-DD HH:MM:SS` + +--- + +## 使用示例 + +### Python示例 + +```python +import requests + +BASE_URL = "http://localhost:5000/api" + +# 1. 创建工单 +def create_work_order(trace_id, process_id, bolts): + url = f"{BASE_URL}/work-orders/create" + data = { + "trace_id": trace_id, + "process_id": process_id, + "process_name": "前轮装配", + "bolts": bolts + } + response = requests.post(url, json=data) + return response.json() + +# 2. 查询工单结果 +def get_work_result(trace_id, process_id): + url = f"{BASE_URL}/work-results" + params = { + "trace_id": trace_id, + "process_id": process_id + } + response = requests.get(url, params=params) + return response.json() + +# 使用示例 +if __name__ == "__main__": + # 创建工单 + bolts = [ + { + "bolt_id": "B001", + "name": "螺栓1", + "target_torque": 50.0 + } + ] + result = create_work_order("TR20260119001", "P001", bolts) + print("创建工单:", result) + + # 查询结果(等待执行完成后) + import time + time.sleep(60) # 等待执行完成 + + result = get_work_result("TR20260119001", "P001") + print("查询结果:", result) +``` + +### cURL示例 + +```bash +# 1. 创建工单 +curl -X POST http://localhost:5000/api/work-orders/create \ + -H "Content-Type: application/json" \ + -d '{ + "trace_id": "TR20260119001", + "process_id": "P001", + "process_name": "前轮装配", + "bolts": [ + { + "bolt_id": "B001", + "name": "螺栓1", + "target_torque": 50.0 + } + ] + }' + +# 2. 查询工单结果 +curl "http://localhost:5000/api/work-results?trace_id=TR20260119001&process_id=P001" +``` + +### JavaScript示例 + +```javascript +const BASE_URL = "http://localhost:5000/api"; + +// 1. 创建工单 +async function createWorkOrder(traceId, processId, bolts) { + const response = await fetch(`${BASE_URL}/work-orders/create`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + trace_id: traceId, + process_id: processId, + bolts: bolts + }) + }); + return await response.json(); +} + +// 2. 查询工单结果 +async function getWorkResult(traceId, processId) { + const url = `${BASE_URL}/work-results?trace_id=${traceId}&process_id=${processId}`; + const response = await fetch(url); + return await response.json(); +} + +// 使用示例 +createWorkOrder("TR20260119001", "P001", [ + { + bolt_id: "B001", + name: "螺栓1", + target_torque: 50.0 + } +]).then(result => { + console.log("创建工单:", result); + + // 等待执行完成后查询结果 + setTimeout(() => { + getWorkResult("TR20260119001", "P001").then(result => { + console.log("查询结果:", result); + }); + }, 60000); +}); +``` + +--- + +## 错误码说明 + +| HTTP状态码 | 说明 | +|-----------|------| +| 200 | 请求成功 | +| 400 | 请求参数错误(如必填字段缺失) | +| 404 | 资源不存在(查询结果时,工单未执行完成) | +| 500 | 服务器内部错误 | + +--- + +## 常见问题 + +### Q1: 如何判断工单是否执行完成? + +**A**: 定期调用查询接口,如果返回404表示还未执行完成,返回200表示已执行完成。 + +### Q2: 创建工单后多久可以查询到结果? + +**A**: 这取决于操作员何时认领和执行工单。建议采用轮询方式,每隔一段时间查询一次。 + +### Q3: 如果工单执行失败怎么办? + +**A**: 即使部分螺栓失败,结果也会被提交。可以通过 `bolts` 数组中每个螺栓的 `success` 字段判断是否成功。 + +### Q4: `trace_id` 和 `process_id` 的格式要求? + +**A**: 没有特殊格式要求,但建议使用有意义的标识符,如 `TR20260119001` 和 `P001`。 + +### Q5: 可以批量创建工单吗? + +**A**: 目前接口只支持单个工单创建,如需批量创建,请循环调用创建接口。 + +--- + +## 联系支持 + +如有问题,请联系技术支持团队。 + +--- + +**版本**: v1.0 +**更新日期**: 2026-01-19 + diff --git a/frontend/wrench_gui.py b/frontend/wrench_gui.py index 5e5f751..808bb7f 100644 --- a/frontend/wrench_gui.py +++ b/frontend/wrench_gui.py @@ -183,7 +183,7 @@ class WrenchGUI: self.operator_label = tk.Label(info_frame, text="操作员: --", font=("微软雅黑", 10)) self.operator_label.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2) - self.device_label = tk.Label(info_frame, text="扳手设备: --", font=("微软雅黑", 10)) + self.device_label = tk.Label(info_frame, text="扳手设备: --", font=("微软雅黑", 10), fg="#2c3e50") self.device_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) # 当前螺栓信息 @@ -287,6 +287,16 @@ class WrenchGUI: device_select_inner.pack() tk.Label(device_select_inner, text="选择扳手:", font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=(0, 5)) + + # 设备状态指示器(圆点) + self.device_status_indicator = tk.Label( + device_select_inner, + text="●", + font=("微软雅黑", 12), + fg="#7f8c8d" # 默认灰色 + ) + self.device_status_indicator.pack(side=tk.LEFT, padx=(0, 3)) + self.device_combo = ttk.Combobox(device_select_inner, width=20, state="readonly", font=("微软雅黑", 10)) self.device_combo.pack(side=tk.LEFT, padx=(0, 5)) self.device_combo.bind("<>", self.on_device_selected) @@ -556,23 +566,43 @@ class WrenchGUI: def update_work_order_info(self): """更新工单信息显示""" - if not self.work_order: - return + if self.work_order: + self.trace_id_label.config(text=f"追溯号: {self.work_order.get('trace_id', '--')}") + self.process_id_label.config(text=f"工序号: {self.work_order.get('process_id', '--')}") + self.process_name_label.config(text=f"工序名称: {self.work_order.get('process_name', '--')}") + self.product_label.config(text=f"产品: {self.work_order.get('product_name', '--')}") + self.operator_label.config(text=f"操作员: {self.work_order.get('operator', '--')}") + else: + self.trace_id_label.config(text="追溯号: --") + self.process_id_label.config(text="工序号: --") + self.process_name_label.config(text="工序名称: --") + self.product_label.config(text="产品: --") + self.operator_label.config(text="操作员: --") - self.trace_id_label.config(text=f"追溯号: {self.work_order.get('trace_id', '--')}") - self.process_id_label.config(text=f"工序号: {self.work_order.get('process_id', '--')}") - self.process_name_label.config(text=f"工序名称: {self.work_order.get('process_name', '--')}") - self.product_label.config(text=f"产品: {self.work_order.get('product_name', '--')}") - self.operator_label.config(text=f"操作员: {self.work_order.get('operator', '--')}") - - # 更新设备显示 + # 更新设备显示(无论是否有工单都要更新) + self.update_device_status_display() + + def update_device_status_display(self): + """更新设备状态显示(带颜色)""" if self.selected_device: device_name = self.selected_device.get('device_name', '--') status = self.selected_device.get('status', 'offline') status_text = "在线" if status == 'online' else "离线" - self.device_label.config(text=f"扳手设备: {device_name} ({status_text})") + # 根据状态设置颜色 + if status == 'online': + status_color = "#27ae60" # 绿色 + else: + status_color = "#e74c3c" # 红色 + + # 强制更新颜色和文本 + self.device_label.config(text="") # 先清空 + self.device_label.config( + text=f"扳手设备: {device_name} ({status_text})", + foreground=status_color # 使用foreground而不是fg + ) + print(f"[DEBUG] 更新设备状态显示: {device_name}, 状态: {status}, 颜色: {status_color}") else: - self.device_label.config(text="扳手设备: 未选择") + self.device_label.config(text="扳手设备: 未选择", foreground="#2c3e50") def update_bolt_list(self): """更新螺栓列表""" @@ -901,13 +931,16 @@ class WrenchGUI: """更新设备下拉框""" device_names = [] for device in self.wrench_devices: - status_icon = "🟢" if device.get('status') == 'online' else "🔴" - device_names.append(f"{status_icon} {device.get('device_name')} ({device.get('ip_address')})") + # 不在下拉框中显示emoji,而是使用状态指示器 + device_names.append(f"{device.get('device_name')} ({device.get('ip_address')})") self.device_combo['values'] = device_names if device_names and not self.device_combo.get(): self.device_combo.current(0) self.on_device_selected(None) + + # 更新状态指示器颜色 + self.update_device_status_indicator() def on_device_selected(self, event): """设备选择事件""" @@ -916,6 +949,21 @@ class WrenchGUI: self.selected_device = self.wrench_devices[selection] self.log(f"已选择扳手设备: {self.selected_device.get('device_name')}") self.update_work_order_info() + # 确保设备状态颜色更新 + self.update_device_status_display() + # 更新状态指示器 + self.update_device_status_indicator() + + def update_device_status_indicator(self): + """更新设备状态指示器(圆点)颜色""" + if self.selected_device: + status = self.selected_device.get('status', 'offline') + if status == 'online': + self.device_status_indicator.config(fg="#27ae60") # 绿色 + else: + self.device_status_indicator.config(fg="#e74c3c") # 红色 + else: + self.device_status_indicator.config(fg="#7f8c8d") # 灰色 def start_device_status_check(self): """启动设备状态检测(后台线程)""" @@ -963,7 +1011,10 @@ class WrenchGUI: for device in devices: if device.get('id') == self.selected_device.get('id'): self.selected_device = device - self.root.after(0, self.update_work_order_info) + # 更新设备状态显示(带颜色) + self.root.after(0, self.update_device_status_display) + # 更新状态指示器 + self.root.after(0, self.update_device_status_indicator) break except: pass