Compare commits

...

3 Commits

15 changed files with 341 additions and 358 deletions

13
App.tsx
View File

@ -1,11 +1,22 @@
import React from 'react';
import React, { useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import AppNavigator from './src/navigation/AppNavigator';
import { TasksProvider } from './src/context/TasksContext';
import { ThemeProvider } from '@rneui/themed';
import { clearCachedConfig } from './src/services/configService';
export default function App() {
useEffect(() => {
const clearCacheOnStart = async () => {
console.log('正在清除缓存的配置文件...');
await clearCachedConfig();
console.log('缓存已清除,将使用最新的 config.json。');
};
clearCacheOnStart();
}, []);
return (
<ThemeProvider>
<TasksProvider>

View File

@ -1,45 +1,13 @@
{
"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}",
"runTask": "/api/vwed-task/execute/{taskId}"
"runTask": "/api/vwed-task-edit/run"
}
}

10
package-lock.json generated
View File

@ -16,7 +16,7 @@
"@react-navigation/stack": "^7.4.2",
"@rneui/base": "^4.0.0-rc.8",
"@rneui/themed": "^4.0.0-rc.8",
"axios": "^1.10.0",
"axios": "^1.11.0",
"react": "19.1.0",
"react-native": "0.80.1",
"react-native-gesture-handler": "^2.27.1",
@ -4419,13 +4419,13 @@
}
},
"node_modules/axios": {
"version": "1.10.0",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.10.0.tgz",
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
"version": "1.11.0",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz",
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},

View File

@ -18,7 +18,7 @@
"@react-navigation/stack": "^7.4.2",
"@rneui/base": "^4.0.0-rc.8",
"@rneui/themed": "^4.0.0-rc.8",
"axios": "^1.10.0",
"axios": "^1.11.0",
"react": "19.1.0",
"react-native": "0.80.1",
"react-native-gesture-handler": "^2.27.1",

View File

@ -1,5 +1,11 @@
import React from 'react';
import { View, StyleSheet, TouchableOpacity, Text } from 'react-native';
import {
View,
StyleSheet,
TouchableOpacity,
Text,
ActivityIndicator,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
interface BottomActionBarProps {
@ -8,6 +14,7 @@ interface BottomActionBarProps {
onUndo: () => void;
onBack: () => void;
isSaveDisabled?: boolean;
isRunLoading?: boolean;
}
const BottomActionBar: React.FC<BottomActionBarProps> = ({
@ -16,6 +23,7 @@ const BottomActionBar: React.FC<BottomActionBarProps> = ({
onUndo,
onBack,
isSaveDisabled = true,
isRunLoading = false,
}) => {
return (
<View style={styles.container}>
@ -48,10 +56,21 @@ const BottomActionBar: React.FC<BottomActionBarProps> = ({
</TouchableOpacity>
<TouchableOpacity
onPress={onRun}
style={[styles.button, styles.runButton]}
disabled={isRunLoading}
style={[
styles.button,
styles.runButton,
isRunLoading && styles.disabledButton,
]}
>
<Icon name="play" size={24} color="#FFFFFF" />
<Text style={[styles.buttonText, styles.runButtonText]}></Text>
{isRunLoading ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Icon name="play" size={24} color="#FFFFFF" />
<Text style={[styles.buttonText, styles.runButtonText]}></Text>
</>
)}
</TouchableOpacity>
</View>
);

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

@ -51,8 +51,8 @@ export default function AppNavigator() {
if (route.name === '主页') {
iconName = 'home';
} else if (route.name === '运行') {
iconName = 'play-arrow';
} else if (route.name === '任务列表') {
iconName = 'view-list';
} else if (route.name === '编辑') {
iconName = 'edit';
} else if (route.name === '设置') {
@ -73,7 +73,7 @@ export default function AppNavigator() {
component={HomeStackNavigator}
options={{ headerShown: false }}
/>
<Tab.Screen name="运行" component={RunScreen} />
<Tab.Screen name="任务列表" component={RunScreen} />
<Tab.Screen name="编辑" component={EditScreen} />
<Tab.Screen name="设置" component={SettingsScreen} />
</Tab.Navigator>

View File

@ -4,7 +4,8 @@ import { StyleSheet, Text, View } from 'react-native';
export default function RunScreen() {
return (
<View style={styles.screenContainer}>
<Text>!</Text>
<Text style={styles.text}></Text>
<Text style={styles.subText}>...</Text>
</View>
);
}
@ -14,5 +15,16 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#121212',
},
text: {
fontSize: 22,
fontWeight: 'bold',
color: '#ffffff',
marginBottom: 10,
},
subText: {
fontSize: 16,
color: '#b0b0b0',
},
});

View File

@ -5,13 +5,15 @@ import {
Alert,
ScrollView,
ActivityIndicator,
Text,
} from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { RouteProp } from '@react-navigation/native';
import { useTasks } from '../context/TasksContext';
import TaskForm from '../components/TaskForm';
import BottomActionBar from '../components/BottomActionBar';
import { Task } from '../types/task';
import { Task, RunTaskRequest, RunTaskApiResponse } from '../types/task';
import { runTask as runTaskService } from '../services/taskService';
type RootStackParamList = {
TaskEdit: { taskId: string };
@ -24,19 +26,22 @@ export default function TaskEditScreen() {
const navigation = useNavigation();
const { taskId } = route.params;
const { getTaskById, updateTask, runTask, tasks, fetchTaskDetail } =
useTasks();
const { getTaskById, updateTask, tasks, fetchTaskDetail } = useTasks();
const [task, setTask] = useState<Task | null>(null);
const [originalTask, setOriginalTask] = useState<Task | null>(null);
const [isModified, setIsModified] = useState(false);
const [isRunLoading, setIsRunLoading] = useState(false);
const [runResponse, setRunResponse] = useState<RunTaskApiResponse | null>(
null,
);
useEffect(() => {
const loadTask = async () => {
let taskData = getTaskById(taskId);
debugger;
if (taskData && !taskData.detail) {
await fetchTaskDetail(taskId);
// Re-fetch task data after details are loaded
taskData = getTaskById(taskId);
}
if (taskData) {
@ -63,10 +68,40 @@ export default function TaskEditScreen() {
}
};
const handleRun = () => {
if (task) {
runTask(task.id);
navigation.goBack();
const handleRun = async () => {
if (!task || !task.detail || !task.detail.inputParams) {
Alert.alert('错误', '任务数据不完整,无法运行。');
return;
}
setIsRunLoading(true);
setRunResponse(null);
try {
const params = task.detail.inputParams.map(p => ({
name: p.name,
type: p.type,
label: p.label,
required: p.required,
defaultValue: p.defaultValue,
remark: p.remark,
}));
const request: RunTaskRequest = {
taskId: task.id,
params: params,
source_type: 1,
source_system: 'SYSTEM',
source_device: '666',
};
const result = await runTaskService(request);
setRunResponse(result);
Alert.alert('成功', `任务已启动, 记录ID: ${result.data.taskRecordId}`);
} catch (error: any) {
Alert.alert('运行失败', error.message || '发生未知错误');
} finally {
setIsRunLoading(false);
}
};
@ -75,11 +110,6 @@ export default function TaskEditScreen() {
setIsModified(false);
};
const handleRestore = () => {
setTask(originalTask);
setIsModified(false);
};
if (!task) {
return (
<View style={styles.loadingContainer}>
@ -92,14 +122,22 @@ export default function TaskEditScreen() {
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.scrollContainer}>
<TaskForm task={task} onTaskChange={handleTaskChange} />
{runResponse && (
<View style={styles.responseContainer}>
<Text style={styles.responseTitle}>:</Text>
<Text style={styles.responseText}>
{JSON.stringify(runResponse, null, 2)}
</Text>
</View>
)}
</ScrollView>
<BottomActionBar
onRun={handleRun}
onSave={handleSave}
onUndo={handleUndo}
onRestore={handleRestore}
onBack={() => navigation.goBack()}
isSaveDisabled={!isModified}
isRunLoading={isRunLoading}
/>
</View>
);
@ -108,7 +146,7 @@ export default function TaskEditScreen() {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1a1a1a', // 深色背景
backgroundColor: '#1a1a1a',
},
loadingContainer: {
flex: 1,
@ -118,5 +156,23 @@ const styles = StyleSheet.create({
},
scrollContainer: {
padding: 16,
paddingBottom: 80,
},
responseContainer: {
marginTop: 20,
padding: 15,
backgroundColor: '#2c2c2c',
borderRadius: 8,
},
responseTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#ffffff',
marginBottom: 10,
},
responseText: {
fontSize: 14,
color: '#e0e0e0',
fontFamily: 'monospace',
},
});

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) => {

88
src/services/api.ts Normal file
View File

@ -0,0 +1,88 @@
import axios from 'axios';
import { getConfig, getSettings } from './configService';
// 创建 axios 实例
const api = axios.create({
timeout: 10000, // 请求超时时间
});
// 请求拦截器
api.interceptors.request.use(
async config => {
const appConfig = await getConfig();
const settings = await getSettings();
const serverUrl = settings.serverUrl || appConfig?.serverUrl;
if (!serverUrl) {
const error = new Error('服务器地址未配置');
console.error(error.message);
return Promise.reject(error);
}
config.baseURL = serverUrl.replace(/\/$/, '');
// 设置通用请求头
config.headers = {
'Content-Type': 'application/json',
...appConfig?.headers,
...config.headers,
} as any;
console.log(
`[Request] ${config.method?.toUpperCase()} ${config.baseURL}${
config.url
}`,
{ params: config.params, data: config.data },
);
return config;
},
error => {
console.error('[Request Error]', error);
return Promise.reject(error);
},
);
// 响应拦截器
api.interceptors.response.use(
response => {
// 统一处理API响应
if (response.data && response.data.code && response.data.code !== 200) {
throw new Error(`API 错误: ${response.data.message}`);
}
// 优先处理嵌套的 data 字段
if (response.data && typeof response.data.data !== 'undefined') {
// 如果 data 字段中有 list则返回 list
if (Array.isArray(response.data.data.list)) {
return response.data.data.list;
}
// 否则返回 data 字段本身
return response.data.data;
}
// 如果没有嵌套的 data 字段,直接返回整个响应数据
return response.data;
},
error => {
if (error.response) {
// 请求已发出,但服务器响应的状态码不在 2xx 范围
console.error(
`[Response Error] Status: ${error.response.status}`,
error.response.data,
);
const httpError = new Error(
`HTTP ${error.response.status}: ${
error.response.statusText || 'Unknown Error'
}`,
);
return Promise.reject(httpError);
} else if (error.request) {
// 请求已发出,但没有收到响应
console.error('[Network Error]', error.request);
return Promise.reject(new Error('网络错误,请检查您的连接'));
} else {
// 在设置请求时触发了一个错误
console.error('[Request Setup Error]', error.message);
return Promise.reject(error);
}
},
);
export default api;

View File

@ -0,0 +1,54 @@
import { getConfig } from './configService';
import { RunTaskRequest, RunTaskApiResponse, Task } from '../types/task';
import api from './api';
// 获取任务列表
export const getTasks = async (params: {
pageNum: number;
pageSize: number;
}): Promise<Task[]> => {
try {
const config = await getConfig();
const endpoint = config?.apiEndpoints?.getTasks;
if (!endpoint) throw new Error('获取任务列表的API端点未配置');
const response = await api.get(endpoint, { params });
return response as unknown as Task[];
} catch (error) {
console.error('获取任务列表失败 (service):', error);
throw error;
}
};
// 获取任务详情
export const getTaskDetail = async (taskId: string): Promise<Task> => {
try {
const config = await getConfig();
let endpoint = config?.apiEndpoints?.getTaskDetail;
if (!endpoint) throw new Error('获取任务详情的API端点未配置');
endpoint = endpoint.replace('{taskId}', taskId);
const response = await api.get(endpoint);
return response as unknown as Task;
} catch (error) {
console.error(`获取任务详情失败 (ID: ${taskId}):`, error);
throw error;
}
};
// 运行任务
export const runTask = async (
taskData: RunTaskRequest,
): Promise<RunTaskApiResponse> => {
try {
const config = await getConfig();
const endpoint = config?.apiEndpoints?.runTask;
if (!endpoint) throw new Error('运行任务的API端点未配置');
const response = await api.post(endpoint, taskData);
return response as unknown as RunTaskApiResponse;
} catch (error) {
console.error('运行任务失败:', error);
throw error;
}
};

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;
};
}
// 设置存储接口

View File

@ -63,3 +63,33 @@ export interface InputParam {
max?: number;
step?: number;
}
// 运行任务请求体
export interface RunTaskRequest {
taskId: string;
params: Array<{
name: string;
type: string;
label: string;
required: boolean;
defaultValue: string;
remark: string;
}>;
source_type: number;
source_system: string;
source_device: string;
}
// 运行任务响应数据
export interface RunTaskResponseData {
taskRecordId: string;
status: number;
createTime: string;
}
// 运行任务API响应
export interface RunTaskApiResponse {
code: number;
message: string;
data: RunTaskResponseData;
}