refactor: 移除任务表单中的不必要的选择器,简化任务上下文,更新配置文件以支持新的请求头

This commit is contained in:
xudan 2025-07-23 15:46:06 +08:00
parent f9564796ab
commit 37492f0890
6 changed files with 72 additions and 335 deletions

View File

@ -1,42 +1,10 @@
{
"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" }
],
"serverUrl": "http://192.168.189.206:8000",
"headers": {
"x-access-token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTIzMzQ5MjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.dKvKIJOU5FxFeFNI3ucJTPLhHmUFZkv8HA2S_VO6klY",
"x-tenant-id": 1000
},
"apiEndpoints": {
"getTasks": "/api/vwed-task/list",
"getTaskDetail": "/api/vwed-task/{taskId}",

View File

@ -1,9 +1,6 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Input, Slider } from '@rneui/themed';
// @ts-ignore
import { Picker } from '@react-native-picker/picker';
import { useTasks } from '../context/TasksContext';
import { Task, RobotAction, InputParam } from '../types/task';
interface TaskFormProps {
@ -12,8 +9,6 @@ interface TaskFormProps {
}
const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
const { locations, payloads, robotActions, locationsBays } = useTasks();
const handleParamChange = (
field: string,
value: string | number | RobotAction,
@ -58,92 +53,6 @@ const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
/>
</View>
);
case 'location':
case 'location_bay':
const locationData =
param.type.toLowerCase() === 'location' ? locations : locationsBays;
return (
<View key={param.name} style={styles.inputGroup}>
{label}
<View style={styles.pickerContainer}>
<Picker
selectedValue={value as string}
onValueChange={(itemValue: any) =>
handleParamChange(param.name, itemValue)
}
style={styles.picker}
dropdownIconColor="#00ff00"
>
{locationData.map(loc => (
<Picker.Item
key={loc.value}
label={loc.label}
value={loc.value}
color="#000000"
/>
))}
</Picker>
</View>
</View>
);
case 'payload':
return (
<View key={param.name} style={styles.inputGroup}>
{label}
<View style={styles.pickerContainer}>
<Picker
selectedValue={value as string}
onValueChange={(itemValue: any) =>
handleParamChange(param.name, itemValue)
}
style={styles.picker}
dropdownIconColor="#00ff00"
>
{payloads.map(p => (
<Picker.Item
key={p.value}
label={p.label}
value={p.value}
color="#000000"
/>
))}
</Picker>
</View>
</View>
);
case 'robotaction':
return (
<View key={param.name} style={styles.inputGroup}>
{label}
<View style={styles.pickerContainer}>
<Picker
selectedValue={(value as RobotAction)?.name}
onValueChange={(itemValue: any) => {
const selectedAction = robotActions.find(
a => a.value === itemValue,
);
if (selectedAction) {
handleParamChange(param.name, {
name: selectedAction.label,
actionId: selectedAction.actionId,
});
}
}}
style={styles.picker}
dropdownIconColor="#00ff00"
>
{robotActions.map(action => (
<Picker.Item
key={action.actionId}
label={action.label}
value={action.value}
color="#000000"
/>
))}
</Picker>
</View>
</View>
);
case 'number':
return (
<View key={param.name} style={styles.inputGroup}>

View File

@ -8,24 +8,18 @@ import React, {
useCallback,
} from 'react';
import { Task } from '../types/task';
import { AppConfig } from '../types/config';
import { getConfig } from '../services/configService';
import {
AppConfig,
LocationOption,
PayloadOption,
RobotActionOption,
} from '../types/config';
import { getConfig, executeTask } from '../services/configService';
getTasks as getTasksService,
getTaskDetail as getTaskDetailService,
} from '../services/taskService';
interface TasksContextData {
tasks: Task[];
locations: LocationOption[];
locationsBays: LocationOption[];
payloads: PayloadOption[];
robotActions: RobotActionOption[];
serverUrl: string | null;
getTaskById: (id: string) => Task | undefined;
updateTask: (updatedTask: Task) => void;
runTask: (id: string) => void;
refreshConfig: () => Promise<void>;
isConfigLoaded: boolean;
fetchTaskDetail: (taskId: string) => Promise<void>;
@ -37,59 +31,19 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const [tasks, setTasks] = useState<Task[]>([]);
const [locations, setLocations] = useState<LocationOption[]>([]);
const [locationsBays, setLocationsBays] = useState<LocationOption[]>([]);
const [payloads, setPayloads] = useState<PayloadOption[]>([]);
const [robotActions, setRobotActions] = useState<RobotActionOption[]>([]);
const [isConfigLoaded, setIsConfigLoaded] = useState(false);
const [serverUrl, setServerUrl] = useState<string | null>(null);
const fetchTasks = useCallback(async (baseUrl: string, endpoint: string) => {
const fetchTasks = useCallback(async () => {
if (!isConfigLoaded) return;
try {
if (baseUrl && endpoint) {
const fetchUrl = `${baseUrl}${endpoint}?pageNum=1&pageSize=100`;
console.log('Fetching tasks from:', fetchUrl);
const response = await fetch(fetchUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log('responseData', responseData);
if (responseData && responseData.code === 200) {
const fetchedTasks = responseData.data.list.map((task: any) => {
let detail = task.detail;
if (detail && typeof detail === 'string') {
try {
detail = JSON.parse(detail);
} catch (e) {
console.error('解析任务详情失败 (list):', e);
detail = null;
}
}
return {
...task,
name: task.label,
parameters: task.parameters || {},
detail: detail,
};
});
setTasks(fetchedTasks);
} else {
console.error('获取任务列表失败: responseData.code is not 200');
setTasks([]);
}
}
const fetchedTasks = await getTasksService({ pageNum: 1, pageSize: 100 });
setTasks(fetchedTasks);
} catch (error) {
console.error('获取任务列表失败:', error);
if (error instanceof Error) {
console.error('Error message:', error.message);
}
console.error('获取任务列表失败 (context):', error);
setTasks([]);
}
}, []); // Empty dependency array as it has no external dependencies from component scope
}, [isConfigLoaded]);
useEffect(() => {
const loadApp = async () => {
@ -98,9 +52,6 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
if (config) {
applyConfig(config);
setIsConfigLoaded(true);
if (config.serverUrl && config.apiEndpoints) {
await fetchTasks(config.serverUrl, config.apiEndpoints.getTasks);
}
} else {
console.log('没有找到配置文件,使用空数据');
setIsConfigLoaded(false);
@ -112,37 +63,26 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
};
loadApp();
}, [fetchTasks]);
}, []);
const [apiEndpoints, setApiEndpoints] = useState<{
getTasks: string;
getTaskDetail: string;
runTask: string;
} | null>(null);
useEffect(() => {
if (isConfigLoaded) {
fetchTasks();
}
}, [isConfigLoaded, fetchTasks]);
const applyConfig = (config: AppConfig) => {
setLocations(config.locations || []);
setLocationsBays(config.locationsBays || []);
setPayloads(config.payloads || []);
setRobotActions(config.robotActions || []);
if (config.serverUrl) {
setServerUrl(config.serverUrl);
}
if (config.apiEndpoints) {
setApiEndpoints(config.apiEndpoints);
}
};
const refreshConfig = useCallback(async () => {
try {
// await clearCachedConfig(); // 通常刷新不需要清除缓存
const config = await getConfig();
if (config) {
applyConfig(config);
setIsConfigLoaded(true);
if (config.serverUrl && config.apiEndpoints) {
await fetchTasks(config.serverUrl, config.apiEndpoints.getTasks);
}
setIsConfigLoaded(true); // 确保配置加载后再次触发任务获取
} else {
console.log('刷新配置时没有找到配置文件');
setIsConfigLoaded(false);
@ -151,63 +91,28 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
console.error('刷新配置失败:', error);
setIsConfigLoaded(false);
}
}, [fetchTasks]);
}, []);
const getTaskById = useCallback(
(id: string) => {
const task = tasks.find(t => t.id === id);
if (task && !task.detail) {
// fetchTaskDetail(id); // 详情在编辑页面获取
}
return task;
},
[tasks],
);
const fetchTaskDetail = useCallback(
async (taskId: string) => {
try {
if (serverUrl && apiEndpoints) {
const endpoint = apiEndpoints.getTaskDetail.replace(
'{taskId}',
taskId,
);
const url = `${serverUrl}${endpoint}`;
console.log('Fetching task detail from:', url);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log('task detail responseData', responseData);
if (responseData && responseData.code === 200) {
let taskDetail = responseData.data.detail;
if (taskDetail && typeof taskDetail === 'string') {
try {
taskDetail = JSON.parse(taskDetail);
} catch (e) {
console.error('解析任务详情失败 (detail):', e);
taskDetail = null;
}
}
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === taskId ? { ...task, detail: taskDetail } : task,
),
);
} else {
console.error('获取任务详情失败: responseData.code is not 200');
}
}
} catch (error) {
console.error('获取任务详情失败:', error);
}
},
[serverUrl, apiEndpoints],
);
const fetchTaskDetail = useCallback(async (taskId: string) => {
try {
const taskDetail = await getTaskDetailService(taskId);
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === taskId ? { ...task, detail: taskDetail.detail } : task,
),
);
} catch (error) {
console.error('获取任务详情失败 (context):', error);
}
}, []);
const updateTask = useCallback((updatedTask: Task) => {
setTasks(prevTasks =>
@ -215,72 +120,21 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
);
}, []);
const runTask = useCallback(
async (id: string) => {
const task = tasks.find(t => t.id === id);
if (!task || !serverUrl || !apiEndpoints) return;
setTasks(prevTasks =>
prevTasks.map(t => (t.id === id ? { ...t, status: 1 } : t)),
);
try {
const parameters = Object.entries(task.parameters).reduce(
(acc, [key, param]) => {
if (param) {
acc[key] = param.value;
}
return acc;
},
{} as { [key: string]: any },
);
const endpoint = apiEndpoints.runTask.replace('{taskId}', task.id);
await executeTask(serverUrl, endpoint, {
name: task.name,
parameters: parameters,
});
setTimeout(() => {
setTasks(prevTasks =>
prevTasks.map(t => (t.id === id ? { ...t, status: 2 } : t)),
);
}, 5000);
} catch (error) {
console.error('任务执行失败:', error);
setTasks(prevTasks =>
prevTasks.map(t => (t.id === id ? { ...t, status: 3 } : t)),
);
}
},
[tasks, serverUrl, apiEndpoints],
);
const contextValue = useMemo(
() => ({
tasks,
locations,
locationsBays,
payloads,
robotActions,
serverUrl,
getTaskById,
updateTask,
runTask,
refreshConfig,
isConfigLoaded,
fetchTaskDetail,
}),
[
tasks,
locations,
locationsBays,
payloads,
robotActions,
serverUrl,
getTaskById,
updateTask,
runTask,
refreshConfig,
isConfigLoaded,
fetchTaskDetail,

View File

@ -17,6 +17,8 @@ type TaskListNavigationProp = StackNavigationProp<
export default function TaskListScreen() {
const { tasks } = useTasks();
console.log('tasks', tasks);
const navigation = useNavigation<TaskListNavigationProp>();
const handlePressTask = (id: string) => {

View File

@ -1,8 +1,20 @@
import { getSettings, getConfig } from './configService';
import { RunTaskRequest, RunTaskApiResponse, Task } from '../types/task';
// 获取通用请求头
const getHeaders = async () => {
const config = await getConfig();
return {
'Content-Type': 'application/json',
...config?.headers,
};
};
// 获取任务列表
export const getTasks = async (): Promise<Task[]> => {
export const getTasks = async (params: {
pageNum: number;
pageSize: number;
}): Promise<Task[]> => {
try {
const config = await getConfig();
const endpoint = config?.apiEndpoints?.getTasks;
@ -18,10 +30,14 @@ export const getTasks = async (): Promise<Task[]> => {
throw new Error('服务器地址未配置');
}
const url = `${serverUrl.replace(/\/$/, '')}${endpoint}`;
const urlObj = new URL(`${serverUrl.replace(/\/$/, '')}${endpoint}`);
urlObj.searchParams.append('pageNum', String(params.pageNum));
urlObj.searchParams.append('pageSize', String(params.pageSize));
const url = urlObj.toString();
console.log('获取任务列表:', url);
const response = await fetch(url);
const headers = await getHeaders();
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@ -31,7 +47,15 @@ export const getTasks = async (): Promise<Task[]> => {
throw new Error(`API 错误: ${result.message}`);
}
return result.data;
// 兼容两种可能的数据结构: result.data.list or result.data
if (result.data && Array.isArray(result.data.list)) {
return result.data.list;
}
if (Array.isArray(result.data)) {
return result.data;
}
console.warn('任务列表数据格式不正确', result.data);
return [];
} catch (error) {
console.error('获取任务列表失败:', error);
throw error;
@ -59,7 +83,8 @@ export const getTaskDetail = async (taskId: string): Promise<Task> => {
const url = `${serverUrl.replace(/\/$/, '')}${endpoint}`;
console.log('获取任务详情:', url);
const response = await fetch(url);
const headers = await getHeaders();
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@ -98,11 +123,10 @@ export const runTask = async (
const url = `${serverUrl.replace(/\/$/, '')}${endpoint}`;
console.log('运行任务请求:', url, JSON.stringify(taskData, null, 2));
const headers = await getHeaders();
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: headers,
body: JSON.stringify(taskData),
});

View File

@ -1,35 +1,15 @@
// 配置文件中的位置选项
export interface LocationOption {
label: string;
value: string;
}
// 配置文件中的载荷选项
export interface PayloadOption {
label: string;
value: string;
}
// 配置文件中的机器人动作选项
export interface RobotActionOption {
label: string;
value: string; // 动作的名称,作为 Picker 的 value
actionId: string;
}
// 完整的配置文件结构
export interface AppConfig {
version: string;
locations: LocationOption[];
locationsBays: LocationOption[];
payloads: PayloadOption[];
robotActions: RobotActionOption[];
serverUrl?: string; // 服务器地址
apiEndpoints?: {
getTasks: string;
getTaskDetail: string;
runTask: string;
};
headers?: {
[key: string]: string | number;
};
}
// 设置存储接口