From 087fc9e0aac742def97460ed585aa1292fb178b8 Mon Sep 17 00:00:00 2001 From: xudan Date: Tue, 22 Jul 2025 15:10:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20@react-native-asyn?= =?UTF-8?q?c-storage/async-storage=20=E4=BE=9D=E8=B5=96=EF=BC=8C=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=BB=BB=E5=8A=A1=E4=B8=8A=E4=B8=8B=E6=96=87=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8A=A8=E6=80=81=E5=8F=82=E6=95=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E6=9B=B4=E6=96=B0=E4=BB=BB=E5=8A=A1=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E5=92=8C=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=BB=A5=E4=BD=BF=E7=94=A8=E6=96=B0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=20mock=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.json | 709 ++++++++++++++++++++++++++++++++ package-lock.json | 34 ++ package.json | 1 + src/components/TaskCard.tsx | 11 +- src/components/TaskForm.tsx | 11 +- src/context/TasksContext.tsx | 166 +++++++- src/data/mockData.ts | 63 --- src/navigation/AppNavigator.tsx | 43 +- src/screens/SettingsScreen.tsx | 463 ++++++++++++++++++++- src/screens/TaskListScreen.tsx | 38 +- src/services/configService.ts | 175 ++++++++ src/types/config.ts | 36 ++ src/types/task.ts | 33 +- 13 files changed, 1664 insertions(+), 119 deletions(-) create mode 100644 config.json delete mode 100644 src/data/mockData.ts create mode 100644 src/services/configService.ts create mode 100644 src/types/config.ts diff --git a/config.json b/config.json new file mode 100644 index 0000000..6ee3b53 --- /dev/null +++ b/config.json @@ -0,0 +1,709 @@ +{ + "version": "1.0.0", + "locations": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ], + "locationsBays": [ + { "label": "AS2_2_001", "value": "AS2_2_001" }, + { "label": "AS2_2_002", "value": "AS2_2_002" }, + { "label": "AS2_2_003", "value": "AS2_2_003" }, + { "label": "AS2_2_004", "value": "AS2_2_004" }, + { "label": "AS2_2_005", "value": "AS2_2_005" } + ], + "payloads": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ], + "robotActions": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ], + "tasks": [ + { + "id": "task-001", + "name": "炉前缓存区到热处理上料交接区运输", + "status": "IDLE", + "createdAt": "2024-01-15T08:30:00.000Z", + "parameters": { + "startLocation": { + "label": "起点", + "type": "Select", + "value": "FURNACE_BUFFER", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "endLocation": { + "label": "终点", + "type": "Select", + "value": "HEAT_TREATMENT_LOADING", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "robotAction": { + "label": "机器人动作", + "type": "Select", + "value": "TRANSPORT", + "required": true, + "options": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ] + }, + "payload": { + "label": "载荷", + "type": "Select", + "value": "满料架-A1", + "required": false, + "options": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ] + }, + "locationBay": { + "label": "库位", + "type": "Select", + "value": "AS2_2_001", + "required": false, + "options": [ + { "label": "AS2_2_001", "value": "AS2_2_001" }, + { "label": "AS2_2_002", "value": "AS2_2_002" }, + { "label": "AS2_2_003", "value": "AS2_2_003" }, + { "label": "AS2_2_004", "value": "AS2_2_004" }, + { "label": "AS2_2_005", "value": "AS2_2_005" } + ] + } + } + }, + { + "id": "task-002", + "name": "原料仓库到质检区取货", + "status": "IDLE", + "createdAt": "2024-01-15T09:15:00.000Z", + "parameters": { + "startLocation": { + "label": "起点", + "type": "Select", + "value": "RAW_MATERIAL_WAREHOUSE", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "endLocation": { + "label": "终点", + "type": "Select", + "value": "QUALITY_INSPECTION", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "robotAction": { + "label": "机器人动作", + "type": "Select", + "value": "PICKUP", + "required": true, + "options": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ] + }, + "payload": { + "label": "载荷", + "type": "Select", + "value": "钢材-Q235", + "required": false, + "options": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ] + }, + "locationBay": { + "label": "库位", + "type": "Select", + "value": "AS2_2_002", + "required": false, + "options": [ + { "label": "AS2_2_001", "value": "AS2_2_001" }, + { "label": "AS2_2_002", "value": "AS2_2_002" }, + { "label": "AS2_2_003", "value": "AS2_2_003" }, + { "label": "AS2_2_004", "value": "AS2_2_004" }, + { "label": "AS2_2_005", "value": "AS2_2_005" } + ] + } + } + }, + { + "id": "task-003", + "name": "质检区到成品存储区卸货", + "status": "COMPLETED", + "createdAt": "2024-01-15T10:00:00.000Z", + "parameters": { + "startLocation": { + "label": "起点", + "type": "Select", + "value": "QUALITY_INSPECTION", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "endLocation": { + "label": "终点", + "type": "Select", + "value": "FINISHED_STORAGE", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "robotAction": { + "label": "机器人动作", + "type": "Select", + "value": "DROPOFF", + "required": true, + "options": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ] + }, + "payload": { + "label": "载荷", + "type": "Select", + "value": "铝合金-6061", + "required": false, + "options": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ] + }, + "locationBay": { + "label": "库位", + "type": "Select", + "value": "AS2_2_003", + "required": false, + "options": [ + { "label": "AS2_2_001", "value": "AS2_2_001" }, + { "label": "AS2_2_002", "value": "AS2_2_002" }, + { "label": "AS2_2_003", "value": "AS2_2_003" }, + { "label": "AS2_2_004", "value": "AS2_2_004" }, + { "label": "AS2_2_005", "value": "AS2_2_005" } + ] + } + } + }, + { + "id": "task-004", + "name": "AP-1到AP-2空车运输", + "status": "IDLE", + "createdAt": "2024-01-15T11:30:00.000Z", + "parameters": { + "startLocation": { + "label": "起点", + "type": "Select", + "value": "AP-1", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "endLocation": { + "label": "终点", + "type": "Select", + "value": "AP-2", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "robotAction": { + "label": "机器人动作", + "type": "Select", + "value": "TRANSPORT", + "required": true, + "options": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ] + }, + "payload": { + "label": "载荷", + "type": "Select", + "value": "空车", + "required": false, + "options": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ] + }, + "locationBay": { + "label": "库位", + "type": "Select", + "value": "AS2_2_004", + "required": false, + "options": [ + { "label": "AS2_2_001", "value": "AS2_2_001" }, + { "label": "AS2_2_002", "value": "AS2_2_002" }, + { "label": "AS2_2_003", "value": "AS2_2_003" }, + { "label": "AS2_2_004", "value": "AS2_2_004" }, + { "label": "AS2_2_005", "value": "AS2_2_005" } + ] + } + } + }, + { + "id": "task-005", + "name": "机器人充电任务", + "status": "IDLE", + "createdAt": "2024-01-15T12:00:00.000Z", + "parameters": { + "startLocation": { + "label": "起点", + "type": "Select", + "value": "AP-3", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "endLocation": { + "label": "终点", + "type": "Select", + "value": "AP-3", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "robotAction": { + "label": "机器人动作", + "type": "Select", + "value": "CHARGE", + "required": true, + "options": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ] + }, + "payload": { + "label": "载荷", + "type": "Select", + "value": "空车", + "required": false, + "options": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ] + } + } + }, + { + "id": "task-006", + "name": "成品存储区清洁任务", + "status": "RUNNING", + "createdAt": "2024-01-15T13:15:00.000Z", + "parameters": { + "startLocation": { + "label": "起点", + "type": "Select", + "value": "FINISHED_STORAGE", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "endLocation": { + "label": "终点", + "type": "Select", + "value": "FINISHED_STORAGE", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "robotAction": { + "label": "机器人动作", + "type": "Select", + "value": "CLEAN", + "required": true, + "options": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ] + }, + "payload": { + "label": "载荷", + "type": "Select", + "value": "空车", + "required": false, + "options": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ] + } + } + }, + { + "id": "task-007", + "name": "满料架A2从原料仓库到AP-4运输", + "status": "IDLE", + "createdAt": "2024-01-15T14:20:00.000Z", + "parameters": { + "startLocation": { + "label": "起点", + "type": "Select", + "value": "RAW_MATERIAL_WAREHOUSE", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "endLocation": { + "label": "终点", + "type": "Select", + "value": "AP-4", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "waypoint": { + "label": "途经点", + "type": "Select", + "value": "QUALITY_INSPECTION", + "required": false, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "robotAction": { + "label": "机器人动作", + "type": "Select", + "value": "TRANSPORT", + "required": true, + "options": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ] + }, + "payload": { + "label": "载荷", + "type": "Select", + "value": "满料架-A2", + "required": false, + "options": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ] + }, + "locationBay": { + "label": "库位", + "type": "Select", + "value": "AS2_2_005", + "required": false, + "options": [ + { "label": "AS2_2_001", "value": "AS2_2_001" }, + { "label": "AS2_2_002", "value": "AS2_2_002" }, + { "label": "AS2_2_003", "value": "AS2_2_003" }, + { "label": "AS2_2_004", "value": "AS2_2_004" }, + { "label": "AS2_2_005", "value": "AS2_2_005" } + ] + } + } + }, + { + "id": "task-008", + "name": "AP-5等待任务", + "status": "ERROR", + "createdAt": "2024-01-15T15:45:00.000Z", + "parameters": { + "startLocation": { + "label": "起点", + "type": "Select", + "value": "AP-5", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "endLocation": { + "label": "终点", + "type": "Select", + "value": "AP-5", + "required": true, + "options": [ + { "label": "AP-1", "value": "AP-1" }, + { "label": "AP-2", "value": "AP-2" }, + { "label": "AP-3", "value": "AP-3" }, + { "label": "AP-4", "value": "AP-4" }, + { "label": "AP-5", "value": "AP-5" }, + { "label": "炉前缓存区", "value": "FURNACE_BUFFER" }, + { "label": "热处理上料交接区", "value": "HEAT_TREATMENT_LOADING" }, + { "label": "成品存储区", "value": "FINISHED_STORAGE" }, + { "label": "原料仓库", "value": "RAW_MATERIAL_WAREHOUSE" }, + { "label": "质检区", "value": "QUALITY_INSPECTION" } + ] + }, + "robotAction": { + "label": "机器人动作", + "type": "Select", + "value": "WAIT", + "required": true, + "options": [ + { "label": "运输", "value": "TRANSPORT" }, + { "label": "取货", "value": "PICKUP" }, + { "label": "卸货", "value": "DROPOFF" }, + { "label": "等待", "value": "WAIT" }, + { "label": "充电", "value": "CHARGE" }, + { "label": "清洁", "value": "CLEAN" } + ] + }, + "payload": { + "label": "载荷", + "type": "Select", + "value": "空料架-B1", + "required": false, + "options": [ + { "label": "满料架-A1", "value": "满料架-A1" }, + { "label": "满料架-A2", "value": "满料架-A2" }, + { "label": "空料架-B1", "value": "空料架-B1" }, + { "label": "空料架-B2", "value": "空料架-B2" }, + { "label": "空车", "value": "空车" }, + { "label": "钢材-Q235", "value": "钢材-Q235" }, + { "label": "铝合金-6061", "value": "铝合金-6061" } + ] + } + } + } + ], + "serverUrl": "http://localhost:3000/api" +} diff --git a/package-lock.json b/package-lock.json index e35f80f..ee99a32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "MyReactNativeApp", "version": "0.0.1", "dependencies": { + "@react-native-async-storage/async-storage": "^2.2.0", "@react-native/new-app-screen": "0.80.1", "@react-navigation/bottom-tabs": "^7.4.2", "@react-navigation/native": "^7.1.14", @@ -2742,6 +2743,18 @@ "node": ">= 8" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native-community/cli": { "version": "19.0.0", "resolved": "https://registry.npmmirror.com/@react-native-community/cli/-/cli-19.0.0.tgz", @@ -7677,6 +7690,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", @@ -9368,6 +9390,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index 7f694e9..d1dc4b4 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "test": "jest" }, "dependencies": { + "@react-native-async-storage/async-storage": "^2.2.0", "@react-native/new-app-screen": "0.80.1", "@react-navigation/bottom-tabs": "^7.4.2", "@react-navigation/native": "^7.1.14", diff --git a/src/components/TaskCard.tsx b/src/components/TaskCard.tsx index 461ccb8..52a9b12 100644 --- a/src/components/TaskCard.tsx +++ b/src/components/TaskCard.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, TouchableOpacity } from 'react-native'; +import { StyleSheet, TouchableOpacity, Dimensions } from 'react-native'; import { Card, Chip } from '@rneui/themed'; import { Task, TaskStatus } from '../types/task'; @@ -15,8 +15,10 @@ const statusColors: Record = { ERROR: 'red', }; -const TaskCard: React.FC = ({ task, onPress }) => { +const { width } = Dimensions.get('window'); +const cardWidth = (width - 32) / 2; // 减去padding,除以2得到每个卡片的宽度 +const TaskCard: React.FC = ({ task, onPress }) => { return ( onPress(task)} style={styles.touchable}> @@ -42,12 +44,13 @@ const TaskCard: React.FC = ({ task, onPress }) => { const styles = StyleSheet.create({ touchable: { - flex: 1, - width: '50%', + width: cardWidth, + marginBottom: 8, }, card: { margin: 4, borderRadius: 8, + width: cardWidth - 8, // 减去margin }, title: { marginBottom: 12, diff --git a/src/components/TaskForm.tsx b/src/components/TaskForm.tsx index f01e4da..d787fdf 100644 --- a/src/components/TaskForm.tsx +++ b/src/components/TaskForm.tsx @@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { StyleSheet, ScrollView, TouchableOpacity, View } from 'react-native'; import { Input, BottomSheet, ListItem, Button } from '@rneui/themed'; import { Task, RobotAction } from '../types/task'; -import { LOCATIONS, ROBOT_ACTIONS, PAYLOADS } from '../data/mockData'; +import { useTasks } from '../context/TasksContext'; interface TaskFormProps { task: Task; @@ -10,6 +10,7 @@ interface TaskFormProps { } const TaskForm: React.FC = ({ task, onTaskChange }) => { + const { locations, payloads, robotActions } = useTasks(); const [isVisible, setIsVisible] = useState(false); const [currentField, setCurrentField] = useState(''); const [currentItems, setCurrentItems] = useState< @@ -232,13 +233,13 @@ const TaskForm: React.FC = ({ task, onTaskChange }) => { 'startLocation', '起点', task.parameters.startLocation, - LOCATIONS, + locations, )} {renderDropdown( 'endLocation', '终点', task.parameters.endLocation, - LOCATIONS, + locations, )} = ({ task, onTaskChange }) => { 'robotAction', '机器人动作', task.parameters.robotAction, - ROBOT_ACTIONS, + robotActions, )} - {renderDropdown('payload', '载荷', task.parameters.payload, PAYLOADS)} + {renderDropdown('payload', '载荷', task.parameters.payload, payloads)} {/* 库位字段 */} Task | undefined; updateTask: (updatedTask: Task) => void; runTask: (id: string) => void; + refreshConfig: () => Promise; + isConfigLoaded: boolean; } const TasksContext = createContext({} as TasksContextData); -export const TasksProvider: React.FC<{children: ReactNode}> = ({ children }) => { - const [tasks, setTasks] = useState(MOCK_TASKS); +export const TasksProvider: React.FC<{ children: ReactNode }> = ({ + children, +}) => { + const [tasks, setTasks] = useState([]); + const [locations, setLocations] = useState([]); + const [locationsBays, setLocationsBays] = useState([]); + const [payloads, setPayloads] = useState([]); + const [robotActions, setRobotActions] = useState([]); + const [isConfigLoaded, setIsConfigLoaded] = useState(false); + + // 组件初始化时加载配置 + useEffect(() => { + const loadConfig = async () => { + try { + // 清除缓存以确保加载最新的配置 + await clearCachedConfig(); + + const config = await getConfig(); + if (config) { + applyConfig(config); + setIsConfigLoaded(true); + console.log('成功加载配置,任务数量:', config.tasks?.length || 0); + } else { + console.log('没有找到配置文件,使用空数据'); + // 使用空数据而不是mock数据 + applyConfig({ + version: '0.0.0', + locations: [], + locationsBays: [], + payloads: [], + robotActions: [], + tasks: [], + }); + setIsConfigLoaded(false); + } + } catch (error) { + console.error('加载配置失败:', error); + // 发生错误时也使用空数据 + applyConfig({ + version: '0.0.0', + locations: [], + locationsBays: [], + payloads: [], + robotActions: [], + tasks: [], + }); + setIsConfigLoaded(false); + } + }; + + loadConfig(); + }, []); + + const applyConfig = (config: AppConfig) => { + setTasks(config.tasks || []); + setLocations(config.locations || []); + setLocationsBays(config.locationsBays || []); + setPayloads(config.payloads || []); + setRobotActions(config.robotActions || []); + }; + + const refreshConfig = async () => { + try { + // 刷新时也清除缓存 + await clearCachedConfig(); + + const config = await getConfig(); + if (config) { + applyConfig(config); + setIsConfigLoaded(true); + console.log('成功刷新配置,任务数量:', config.tasks?.length || 0); + } else { + console.log('刷新配置时没有找到配置文件'); + setIsConfigLoaded(false); + } + } catch (error) { + console.error('刷新配置失败:', error); + setIsConfigLoaded(false); + } + }; const getTaskById = (id: string) => { return tasks.find(task => task.id === id); @@ -20,28 +121,59 @@ export const TasksProvider: React.FC<{children: ReactNode}> = ({ children }) => const updateTask = (updatedTask: Task) => { setTasks(prevTasks => - prevTasks.map(task => (task.id === updatedTask.id ? updatedTask : task)) + prevTasks.map(task => (task.id === updatedTask.id ? updatedTask : task)), ); }; - const runTask = (id: string) => { + const runTask = async (id: string) => { + const task = getTaskById(id); + if (!task) return; + + // 更新任务状态为运行中 setTasks(prevTasks => - prevTasks.map(task => - task.id === id ? { ...task, status: 'RUNNING' } : task - ) + prevTasks.map(t => (t.id === id ? { ...t, status: 'RUNNING' } : t)), ); - // 模拟任务完成 - setTimeout(() => { + + try { + // 获取服务器设置并发送任务执行请求 + const settings = await getSettings(); + if (settings.serverUrl) { + await executeTask(settings.serverUrl, task.id, { + name: task.name, + parameters: task.parameters, + }); + } + + // 模拟任务完成(实际项目中应该通过WebSocket或轮询获取任务状态) + setTimeout(() => { setTasks(prevTasks => - prevTasks.map(task => - task.id === id ? { ...task, status: 'COMPLETED' } : task - ) - ); - }, 5000); + prevTasks.map(t => (t.id === id ? { ...t, status: 'COMPLETED' } : t)), + ); + }, 5000); + } catch (error) { + console.error('任务执行失败:', error); + // 任务执行失败,更新状态为错误 + setTasks(prevTasks => + prevTasks.map(t => (t.id === id ? { ...t, status: 'ERROR' } : t)), + ); + } }; return ( - + {children} ); diff --git a/src/data/mockData.ts b/src/data/mockData.ts deleted file mode 100644 index ce6645a..0000000 --- a/src/data/mockData.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Task, RobotAction } from '../types/task'; -import { v4 as uuidv4 } from 'uuid'; - -export const LOCATIONS = Array.from({ length: 100 }, (_, i) => ({ - label: `AP-${i + 1}`, - value: `AP-${i + 1}`, -})); - -export const ROBOT_ACTIONS: { label: string; value: RobotAction }[] = [ - { label: '运输', value: 'TRANSPORT' }, - { label: '取货', value: 'PICKUP' }, - { label: '卸货', value: 'DROPOFF' }, - { label: '等待', value: 'WAIT' }, - { label: '充电', value: 'CHARGE' }, - { label: '清洁', value: 'CLEAN' }, -]; - -export const LOCATIONS_BAYS = Array.from({ length: 100 }, (_, i) => ({ - label: `AS2_2_${String(i + 1).padStart(3, '0')}`, - value: `AS2_2_${String(i + 1).padStart(3, '0')}`, -})); - -export const PAYLOADS = [ - { label: '满料架-A1', value: '满料架-A1' }, - { label: '空料架-B2', value: '空料架-B2' }, - { label: '空料架-C3', value: '空料架-C3' }, - { label: '空车', value: '空车' }, -]; - -const getRandomElement = (arr: T[]): T => - arr[Math.floor(Math.random() * arr.length)]; - -export const MOCK_TASKS: Task[] = Array.from({ length: 15 }, (_, i) => { - const startLocation = getRandomElement(LOCATIONS); - const endLocation = getRandomElement( - LOCATIONS.filter(l => l.value !== startLocation.value), - ); - const robotAction = getRandomElement(ROBOT_ACTIONS); - const payload = getRandomElement(PAYLOADS); - const statusOptions: Task['status'][] = [ - 'IDLE', - 'RUNNING', - 'COMPLETED', - 'ERROR', - ]; - const status = getRandomElement(statusOptions); - - return { - id: uuidv4(), - name: `任务 ${i + 1}: 从 ${startLocation.label} 到 ${endLocation.label}`, - status: status, - createdAt: new Date( - new Date().getTime() - Math.random() * 1000 * 60 * 60 * 24, - ).toISOString(), - parameters: { - startLocation: startLocation.value, - endLocation: endLocation.value, - robotAction: robotAction.value, - payload: payload.value, - locationBay: '', // 库位初始值为空 - }, - }; -}); diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 4ec44a4..d786901 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import TaskListScreen from '../screens/TaskListScreen'; import TaskEditScreen from '../screens/TaskEditScreen'; import RunScreen from '../screens/RunScreen'; @@ -13,16 +14,50 @@ const Tab = createBottomTabNavigator(); function HomeStackNavigator() { return ( - - + + ); } export default function AppNavigator() { return ( - - + ({ + tabBarIcon: ({ color, size }) => { + let iconName: string; + + if (route.name === '主页') { + iconName = 'home'; + } else if (route.name === '运行') { + iconName = 'play-arrow'; + } else if (route.name === '编辑') { + iconName = 'edit'; + } else if (route.name === '设置') { + iconName = 'settings'; + } else { + iconName = 'help'; + } + + return ; + }, + tabBarActiveTintColor: '#2196F3', + tabBarInactiveTintColor: 'gray', + })} + > + diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 1e0eac2..30cc887 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -1,18 +1,467 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import React, { useState, useEffect } from 'react'; +import { + StyleSheet, + Text, + View, + TextInput, + TouchableOpacity, + Alert, + ActivityIndicator, + ScrollView, +} from 'react-native'; +import { AppSettings } from '../types/config'; +import { + getSettings, + saveSettings, + downloadConfig, + getConfig, + clearCachedConfig, +} from '../services/configService'; +import { useTasks } from '../context/TasksContext'; export default function SettingsScreen() { + const { refreshConfig } = useTasks(); + const [settings, setSettings] = useState({ + configFileName: '', + serverUrl: '', + }); + const [loading, setLoading] = useState(false); + const [configStatus, setConfigStatus] = useState('未加载'); + + // 组件加载时获取设置 + useEffect(() => { + loadSettings(); + checkConfigStatus(); + }, []); + + const loadSettings = async () => { + try { + const currentSettings = await getSettings(); + setSettings(currentSettings); + } catch (error) { + Alert.alert('错误', '加载设置失败'); + } + }; + + const checkConfigStatus = async () => { + const config = await getConfig(); + if (config) { + setConfigStatus( + `已加载 (版本: ${config.version}, 任务数: ${ + config.tasks?.length || 0 + })`, + ); + } else { + setConfigStatus('未加载'); + } + }; + + const handleSaveSettings = async () => { + if (!settings.configFileName.trim()) { + Alert.alert('错误', '请输入配置文件名'); + return; + } + if (!settings.serverUrl.trim()) { + Alert.alert('错误', '请输入服务器地址'); + return; + } + + try { + await saveSettings(settings); + Alert.alert('成功', '设置已保存'); + } catch (error) { + Alert.alert('错误', '保存设置失败'); + } + }; + + const handleRefreshLocalConfig = async () => { + setLoading(true); + try { + // 清除缓存并重新加载本地配置 + await clearCachedConfig(); + await refreshConfig(); + await checkConfigStatus(); + + Alert.alert('成功', '本地配置已刷新!'); + } catch (error) { + console.error('刷新本地配置失败:', error); + Alert.alert('错误', '刷新本地配置失败'); + } finally { + setLoading(false); + } + }; + + const handleDownloadConfig = async () => { + if (!settings.configFileName.trim()) { + Alert.alert('错误', '请先输入配置文件名'); + return; + } + if (!settings.serverUrl.trim()) { + Alert.alert('错误', '请先输入服务器地址'); + return; + } + + setLoading(true); + try { + // 先保存设置 + await saveSettings(settings); + + // 下载配置文件 + const config = await downloadConfig( + settings.serverUrl, + settings.configFileName, + ); + + Alert.alert( + '成功', + `配置文件下载成功!\n版本: ${config.version}\n任务数量: ${ + config.tasks?.length || 0 + }`, + [ + { + text: '确定', + onPress: async () => { + await refreshConfig(); + checkConfigStatus(); + }, + }, + ], + ); + } catch (error) { + Alert.alert( + '下载失败', + error instanceof Error ? error.message : '未知错误', + ); + } finally { + setLoading(false); + } + }; + + const handleClearCache = async () => { + Alert.alert( + '确认清除', + '确定要清除缓存的配置文件吗?系统将重新加载本地 config.json 文件。', + [ + { text: '取消', style: 'cancel' }, + { + text: '确定', + onPress: async () => { + setLoading(true); + try { + await clearCachedConfig(); + await refreshConfig(); + checkConfigStatus(); + Alert.alert('成功', '缓存已清除,已重新加载配置'); + } catch (error) { + Alert.alert('错误', '清除缓存失败'); + } finally { + setLoading(false); + } + }, + }, + ], + ); + }; + + const handleTestConnection = async () => { + if (!settings.serverUrl.trim()) { + Alert.alert('错误', '请先输入服务器地址'); + return; + } + + setLoading(true); + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + const response = await fetch(`${settings.serverUrl}/health`, { + method: 'GET', + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (response.ok) { + Alert.alert('连接成功', '服务器连接正常'); + } else { + Alert.alert('连接失败', `HTTP ${response.status}`); + } + } catch (error) { + Alert.alert('连接失败', '无法连接到服务器'); + } finally { + setLoading(false); + } + }; + return ( - - 设置! - + + + {loading && ( + + + 处理中... + + )} + + {/* 配置状态 */} + + 配置状态 + {configStatus} + + + {/* 配置文件设置 */} + + 配置文件 + + + 配置文件名: + + setSettings(prev => ({ ...prev, configFileName: text })) + } + placeholder="config" + placeholderTextColor="#999" + autoCapitalize="none" + /> + + + + + 下载配置文件 + + + + 刷新本地配置 + + + + + + 清除缓存 + + + + + {/* 服务器设置 */} + + 服务器设置 + + + 服务器地址: + + setSettings(prev => ({ ...prev, serverUrl: text })) + } + placeholder="http://localhost:3000/api" + placeholderTextColor="#999" + autoCapitalize="none" + keyboardType="url" + /> + + + + + 测试连接 + + + + 保存设置 + + + + + {/* 说明信息 */} + + 使用说明 + 配置文件加载优先级: + + 1. 缓存的服务器配置(从服务器下载的配置) + + 2. 本地 config.json 文件 + 3. 空数据(如果都没有找到) + + • 点击"下载配置文件"从服务器获取最新配置 + + + • 点击"清除缓存"强制重新加载本地配置文件 + + + + ); } const styles = StyleSheet.create({ - screenContainer: { + container: { flex: 1, - justifyContent: 'center', + backgroundColor: '#f5f5f5', + }, + content: { + padding: 20, + }, + title: { + fontSize: 24, + fontWeight: 'bold', + textAlign: 'center', + marginBottom: 30, + color: '#333', + }, + section: { + backgroundColor: 'white', + borderRadius: 10, + padding: 20, + marginBottom: 20, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.1, + shadowRadius: 3.84, + elevation: 5, + }, + sectionTitle: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 15, + color: '#333', + }, + inputGroup: { + marginBottom: 15, + }, + label: { + fontSize: 16, + marginBottom: 8, + color: '#333', + fontWeight: '500', + }, + inputRow: { + flexDirection: 'row', alignItems: 'center', }, + textInput: { + flex: 1, + borderWidth: 1, + borderColor: '#ddd', + borderRadius: 8, + padding: 12, + fontSize: 16, + backgroundColor: '#fafafa', + }, + extension: { + marginLeft: 8, + fontSize: 16, + color: '#666', + fontWeight: '500', + }, + statusRow: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 15, + }, + statusLabel: { + fontSize: 16, + color: '#333', + fontWeight: '500', + }, + statusText: { + fontSize: 16, + color: '#007AFF', + marginLeft: 8, + }, + button: { + borderRadius: 8, + padding: 12, + alignItems: 'center', + minHeight: 48, + justifyContent: 'center', + }, + downloadButton: { + backgroundColor: '#007AFF', + flex: 1, + marginRight: 10, + }, + refreshButton: { + backgroundColor: '#32D74B', + flex: 1, + }, + testButton: { + backgroundColor: '#34C759', + flex: 1, + marginRight: 10, + }, + saveButton: { + backgroundColor: '#FF9500', + flex: 1, + }, + buttonText: { + color: 'white', + fontSize: 16, + fontWeight: '600', + }, + buttonRow: { + flexDirection: 'row', + marginTop: 10, + }, + infoSection: { + backgroundColor: 'white', + borderRadius: 10, + padding: 20, + marginBottom: 20, + }, + infoTitle: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 15, + color: '#333', + }, + infoText: { + fontSize: 14, + lineHeight: 20, + color: '#666', + marginBottom: 5, + }, + loadingOverlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(255, 255, 255, 0.8)', + justifyContent: 'center', + alignItems: 'center', + zIndex: 1, + }, + loadingText: { + marginTop: 10, + fontSize: 16, + color: '#007AFF', + }, + clearButton: { + backgroundColor: '#FF3B30', + }, }); diff --git a/src/screens/TaskListScreen.tsx b/src/screens/TaskListScreen.tsx index 697a497..355448d 100644 --- a/src/screens/TaskListScreen.tsx +++ b/src/screens/TaskListScreen.tsx @@ -1,16 +1,20 @@ import React from 'react'; -import { View, FlatList, StyleSheet } from 'react-native'; +import { View, ScrollView, StyleSheet } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { StackNavigationProp } from '@react-navigation/stack'; import { useTasks } from '../context/TasksContext'; import TaskCard from '../components/TaskCard'; +import { Task } from '../types/task'; type RootStackParamList = { TaskList: undefined; TaskEdit: { task: Task }; }; -type TaskListNavigationProp = StackNavigationProp; +type TaskListNavigationProp = StackNavigationProp< + RootStackParamList, + 'TaskList' +>; export default function TaskListScreen() { const { tasks } = useTasks(); @@ -22,13 +26,17 @@ export default function TaskListScreen() { return ( - handlePressTask(item)} />} - keyExtractor={item => item.id} - numColumns={2} - contentContainerStyle={styles.list} - /> + + + {tasks.map(task => ( + handlePressTask(task)} + /> + ))} + + ); } @@ -37,6 +45,12 @@ const styles = StyleSheet.create({ container: { flex: 1, }, - list: { - paddingHorizontal: 8, - }}); + scrollContainer: { + padding: 16, + }, + tasksContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + }, +}); diff --git a/src/services/configService.ts b/src/services/configService.ts new file mode 100644 index 0000000..a3abc03 --- /dev/null +++ b/src/services/configService.ts @@ -0,0 +1,175 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { AppConfig, AppSettings } from '../types/config'; + +const SETTINGS_KEY = 'app_settings'; +const CONFIG_CACHE_KEY = 'cached_config'; + +// 默认设置 +const DEFAULT_SETTINGS: AppSettings = { + configFileName: 'config', + serverUrl: 'http://localhost:3000/api', +}; + +// 获取设置 +export const getSettings = async (): Promise => { + try { + const stored = await AsyncStorage.getItem(SETTINGS_KEY); + if (stored) { + return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) }; + } + return DEFAULT_SETTINGS; + } catch (error) { + console.error('获取设置失败:', error); + return DEFAULT_SETTINGS; + } +}; + +// 保存设置 +export const saveSettings = async (settings: AppSettings): Promise => { + try { + await AsyncStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); + } catch (error) { + console.error('保存设置失败:', error); + throw error; + } +}; + +// 加载本地配置文件 +export const loadLocalConfig = async (): Promise => { + try { + // 尝试加载项目根目录下的 config.json 文件 + const localConfig = require('../../config.json'); + console.log('成功加载本地配置文件:', localConfig); + + return localConfig as AppConfig; + } catch (error) { + console.log('本地配置文件不存在或加载失败:', error); + return null; + } +}; + +// 从服务器下载配置文件 +export const downloadConfig = async ( + serverUrl: string, + configFileName: string, +): Promise => { + try { + const url = `${serverUrl.replace(/\/$/, '')}/${configFileName}.json`; + console.log('下载配置文件:', url); + + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const config: AppConfig = await response.json(); + + // 缓存服务器配置文件(标记为服务器来源) + const configWithSource = { ...config, _source: 'server' }; + await AsyncStorage.setItem( + CONFIG_CACHE_KEY, + JSON.stringify(configWithSource), + ); + + return config; + } catch (error) { + console.error('下载配置文件失败:', error); + throw error; + } +}; + +// 获取缓存的配置文件 +export const getCachedConfig = async (): Promise => { + try { + const cached = await AsyncStorage.getItem(CONFIG_CACHE_KEY); + if (cached) { + const config = JSON.parse(cached); + // 移除内部标记字段 + delete config._source; + return config; + } + return null; + } catch (error) { + console.error('获取缓存配置失败:', error); + return null; + } +}; + +// 获取配置文件(优先级:缓存的服务器配置 > 本地config.json > 空数据) +export const getConfig = async (): Promise => { + try { + // 1. 先尝试获取缓存的配置(通常是从服务器下载的) + const cachedConfig = await getCachedConfig(); + if (cachedConfig) { + console.log('使用缓存的配置文件'); + return cachedConfig; + } + + // 2. 如果没有缓存,尝试加载本地配置文件 + const localConfig = await loadLocalConfig(); + if (localConfig) { + console.log('使用本地 config.json 文件'); + // 将本地配置也缓存起来,但标记为本地来源 + const configWithSource = { ...localConfig, _source: 'local' }; + await AsyncStorage.setItem( + CONFIG_CACHE_KEY, + JSON.stringify(configWithSource), + ); + return localConfig; + } + + console.log('没有找到任何配置文件'); + return null; + } catch (error) { + console.error('获取配置文件失败:', error); + return null; + } +}; + +// 清除缓存配置(用于强制重新加载) +export const clearCachedConfig = async (): Promise => { + try { + await AsyncStorage.removeItem(CONFIG_CACHE_KEY); + console.log('已清除缓存配置'); + } catch (error) { + console.error('清除缓存配置失败:', error); + } +}; + +// 向服务器发送任务执行请求 +export const executeTask = async ( + serverUrl: string, + taskId: string, + taskData: any, +): Promise => { + try { + const url = `${serverUrl.replace(/\/$/, '')}/execute-task`; + console.log('执行任务请求:', url, taskData); + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + taskId, + ...taskData, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + return await response.json(); + } catch (error) { + console.error('执行任务失败:', error); + throw error; + } +}; diff --git a/src/types/config.ts b/src/types/config.ts new file mode 100644 index 0000000..b8fd8eb --- /dev/null +++ b/src/types/config.ts @@ -0,0 +1,36 @@ +import { Task, RobotAction } from './task'; + +// 配置文件中的位置选项 +export interface LocationOption { + label: string; + value: string; +} + +// 配置文件中的载荷选项 +export interface PayloadOption { + label: string; + value: string; +} + +// 配置文件中的机器人动作选项 +export interface RobotActionOption { + label: string; + value: RobotAction; +} + +// 完整的配置文件结构 +export interface AppConfig { + version: string; + locations: LocationOption[]; + locationsBays: LocationOption[]; + payloads: PayloadOption[]; + robotActions: RobotActionOption[]; + tasks: Task[]; + serverUrl?: string; // 服务器地址 +} + +// 设置存储接口 +export interface AppSettings { + configFileName: string; + serverUrl: string; +} diff --git a/src/types/task.ts b/src/types/task.ts index f587b0f..cc9d277 100644 --- a/src/types/task.ts +++ b/src/types/task.ts @@ -10,14 +10,33 @@ export type RobotAction = // 任务的状态 export type TaskStatus = 'IDLE' | 'RUNNING' | 'COMPLETED' | 'ERROR'; -// 任务参数 +// 参数选项接口 +export interface ParameterOption { + label: string; + value: string; +} + +// 动态参数配置接口 +export interface DynamicParameter { + label: string; + type: 'Simple' | 'Select' | 'MultiSelect' | 'Text' | 'Number'; + value: string | string[] | number; + required: boolean; + options?: ParameterOption[]; + placeholder?: string; + min?: number; + max?: number; +} + +// 任务参数 - 支持动态配置 export interface TaskParameters { - startLocation: string; // 起点 - endLocation: string; // 终点 - waypoint?: string; // 途经点 (可选) - robotAction: RobotAction; // 机器人动作 - payload: string; // 载荷,比如 '空料架' 或具体的物料ID - locationBay?: string; // 库位 (可选) + startLocation: DynamicParameter; // 起点 + endLocation: DynamicParameter; // 终点 + waypoint?: DynamicParameter; // 途经点 (可选) + robotAction: DynamicParameter; // 机器人动作 + payload: DynamicParameter; // 载荷,比如 '空料架' 或具体的物料ID + locationBay?: DynamicParameter; // 库位 (可选) + [key: string]: DynamicParameter | undefined; // 支持扩展参数 } // 核心任务对象