feat: 添加任务管理API支持,更新任务列表和运行屏幕以实现任务的创建、终止和状态管理功能
This commit is contained in:
parent
ece6728e94
commit
97164ff220
12
config.json
12
config.json
@ -1,6 +1,7 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"serverUrl": "http://192.168.189.206:8000",
|
||||
"taskServerUrl": "http://192.168.189.206:8080/jeecg-boot",
|
||||
"headers": {
|
||||
"x-access-token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTIzMzQ5MjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.dKvKIJOU5FxFeFNI3ucJTPLhHmUFZkv8HA2S_VO6klY",
|
||||
"x-tenant-id": 1000
|
||||
@ -9,5 +10,16 @@
|
||||
"getTasks": "/api/vwed-task/list",
|
||||
"getTaskDetail": "/api/vwed-task/{taskId}",
|
||||
"runTask": "/api/vwed-task-edit/run"
|
||||
},
|
||||
"taskApiEndpoints": {
|
||||
"getTaskList": "/task",
|
||||
"createTask": "/task",
|
||||
"getTaskDetail": "/task/{id}",
|
||||
"getTaskProgress": "/task/proce",
|
||||
"adjustTaskPriority": "/task/{id}/priority",
|
||||
"pauseTask": "/task/{id}/paused",
|
||||
"continueTask": "/task/{id}/continue",
|
||||
"terminateTask": "/task/{id}/terminated",
|
||||
"exportTask": "/task/exportXls"
|
||||
}
|
||||
}
|
||||
|
@ -7,57 +7,27 @@ interface TaskCardProps {
|
||||
onPress: (id: string) => void;
|
||||
}
|
||||
|
||||
const statusMap: { [key: number]: { text: string; color: string } } = {
|
||||
0: { text: '待机', color: '#9E9E9E' }, // Grey
|
||||
1: { text: '运行中', color: '#2196F3' }, // Blue
|
||||
2: { text: '完成', color: '#4CAF50' }, // Green
|
||||
3: { text: '失败', color: '#F44336' }, // Red
|
||||
};
|
||||
// 主页任务卡片不需要显示状态,因为都是未运行的任务模板
|
||||
|
||||
const TaskCard: React.FC<TaskCardProps> = ({ task, onPress }) => {
|
||||
const statusInfo = statusMap[task.status] || statusMap[0];
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={() => onPress(task.id)} style={styles.container}>
|
||||
<View style={styles.indicatorContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.statusIndicator,
|
||||
{ backgroundColor: statusInfo.color },
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.contentContainer}>
|
||||
<Text style={styles.title}>{task.label}</Text>
|
||||
<Text style={styles.description} numberOfLines={1}>
|
||||
<Text style={styles.description} numberOfLines={2}>
|
||||
{task.description || '暂无描述'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.statusContainer}>
|
||||
<Text style={[styles.statusText, { color: statusInfo.color }]}>
|
||||
{statusInfo.text}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#1a1a1a',
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
indicatorContainer: {
|
||||
marginRight: 16,
|
||||
},
|
||||
statusIndicator: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: 6,
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
@ -65,18 +35,12 @@ const styles = StyleSheet.create({
|
||||
color: '#FFFFFF',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 4,
|
||||
marginBottom: 8,
|
||||
},
|
||||
description: {
|
||||
color: '#999999',
|
||||
fontSize: 14,
|
||||
},
|
||||
statusContainer: {
|
||||
marginLeft: 16,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
lineHeight: 20,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,30 +1,684 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
FlatList,
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
TextInput,
|
||||
} from 'react-native';
|
||||
import { Picker } from '@react-native-picker/picker';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import { TaskStatus } from '../types/task';
|
||||
import { getTaskRunList, terminateTask } from '../services/taskService';
|
||||
|
||||
// 任务状态选项
|
||||
const statusOptions = [
|
||||
{ label: '全部', value: undefined },
|
||||
{ label: '待分配', value: TaskStatus.WAITING },
|
||||
{ label: '执行中', value: TaskStatus.RUNNING },
|
||||
{ label: '已暂停', value: TaskStatus.PAUSED },
|
||||
{ label: '已完成', value: TaskStatus.COMPLETED },
|
||||
{ label: '已失败', value: TaskStatus.FAILED },
|
||||
{ label: '已终止', value: TaskStatus.TERMINATED },
|
||||
];
|
||||
|
||||
// 状态显示配置
|
||||
const statusDisplayMap: { [key: number]: { text: string; color: string } } = {
|
||||
[TaskStatus.WAITING]: { text: '待分配', color: '#9E9E9E' },
|
||||
[TaskStatus.RUNNING]: { text: '执行中', color: '#2196F3' },
|
||||
[TaskStatus.PAUSED]: { text: '已暂停', color: '#FF9800' },
|
||||
[TaskStatus.COMPLETED]: { text: '已完成', color: '#4CAF50' },
|
||||
[TaskStatus.FAILED]: { text: '已失败', color: '#F44336' },
|
||||
[TaskStatus.TERMINATED]: { text: '已终止', color: '#9C27B0' },
|
||||
};
|
||||
|
||||
interface TaskRunItem {
|
||||
id: string;
|
||||
parentId: string;
|
||||
vwedTaskId: string;
|
||||
vwedTaskParentId: string;
|
||||
sceneId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
priority: number;
|
||||
isPeriodic: number;
|
||||
needAmr: number;
|
||||
amrName: string;
|
||||
status: TaskStatus;
|
||||
stopReason?: string;
|
||||
createBy: string;
|
||||
createTime: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
runDuration: number;
|
||||
}
|
||||
|
||||
export default function RunScreen() {
|
||||
const [tasks, setTasks] = useState<TaskRunItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// 筛选状态
|
||||
const [filterStatus, setFilterStatus] = useState<TaskStatus | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [filterName, setFilterName] = useState('');
|
||||
|
||||
// 操作状态
|
||||
const [operatingTaskId, setOperatingTaskId] = useState<string | null>(null);
|
||||
|
||||
// 批量操作状态
|
||||
const [selectedTasks, setSelectedTasks] = useState<Set<string>>(new Set());
|
||||
const [isSelectionMode, setIsSelectionMode] = useState(false);
|
||||
const [batchOperating, setBatchOperating] = useState(false);
|
||||
|
||||
// 加载任务列表
|
||||
const loadTasks = useCallback(
|
||||
async (isRefresh = false) => {
|
||||
if (isRefresh) {
|
||||
setRefreshing(true);
|
||||
} else {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
try {
|
||||
const params = {
|
||||
status: filterStatus,
|
||||
name: filterName.trim() || undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
};
|
||||
|
||||
const response = await getTaskRunList(params);
|
||||
setTasks(response || []);
|
||||
} catch (error) {
|
||||
console.error('加载任务列表失败:', error);
|
||||
Alert.alert('错误', '加载任务列表失败,请重试');
|
||||
setTasks([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
},
|
||||
[filterStatus, filterName],
|
||||
);
|
||||
|
||||
// 初始加载
|
||||
useEffect(() => {
|
||||
loadTasks();
|
||||
}, [loadTasks]);
|
||||
|
||||
// 刷新任务列表
|
||||
const handleRefresh = () => {
|
||||
loadTasks(true);
|
||||
};
|
||||
|
||||
// 重置筛选
|
||||
const handleResetFilter = () => {
|
||||
setFilterStatus(undefined);
|
||||
setFilterName('');
|
||||
};
|
||||
|
||||
// 切换选择模式
|
||||
const toggleSelectionMode = () => {
|
||||
setIsSelectionMode(!isSelectionMode);
|
||||
setSelectedTasks(new Set());
|
||||
};
|
||||
|
||||
// 选择/取消选择任务
|
||||
const toggleTaskSelection = (taskId: string) => {
|
||||
const newSelection = new Set(selectedTasks);
|
||||
if (newSelection.has(taskId)) {
|
||||
newSelection.delete(taskId);
|
||||
} else {
|
||||
newSelection.add(taskId);
|
||||
}
|
||||
setSelectedTasks(newSelection);
|
||||
};
|
||||
|
||||
// 全选/取消全选
|
||||
const toggleSelectAll = () => {
|
||||
if (selectedTasks.size === tasks.length) {
|
||||
setSelectedTasks(new Set());
|
||||
} else {
|
||||
setSelectedTasks(new Set(tasks.map(task => task.id)));
|
||||
}
|
||||
};
|
||||
|
||||
// 批量终止任务
|
||||
const handleBatchTerminate = async () => {
|
||||
if (selectedTasks.size === 0) {
|
||||
Alert.alert('提示', '请先选择要终止的任务');
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'确认操作',
|
||||
`确定要终止选中的 ${selectedTasks.size} 个任务吗?`,
|
||||
[
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{
|
||||
text: '确定',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
setBatchOperating(true);
|
||||
try {
|
||||
const promises = Array.from(selectedTasks).map(taskId =>
|
||||
terminateTask(taskId),
|
||||
);
|
||||
await Promise.all(promises);
|
||||
Alert.alert('成功', `已成功终止 ${selectedTasks.size} 个任务`);
|
||||
setSelectedTasks(new Set());
|
||||
setIsSelectionMode(false);
|
||||
loadTasks();
|
||||
} catch (error) {
|
||||
console.error('批量终止任务失败:', error);
|
||||
Alert.alert('错误', '批量终止任务失败,请重试');
|
||||
} finally {
|
||||
setBatchOperating(false);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
// 任务操作 - 只保留终止操作
|
||||
const handleTaskOperation = async (taskId: string) => {
|
||||
setOperatingTaskId(taskId);
|
||||
|
||||
try {
|
||||
await terminateTask(taskId);
|
||||
Alert.alert('成功', '终止任务成功');
|
||||
// 重新加载任务列表
|
||||
loadTasks();
|
||||
} catch (error) {
|
||||
console.error('终止任务失败:', error);
|
||||
Alert.alert('错误', '终止任务失败,请重试');
|
||||
} finally {
|
||||
setOperatingTaskId(null);
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeString?: string) => {
|
||||
if (!timeString) return '--';
|
||||
try {
|
||||
return new Date(timeString).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
} catch {
|
||||
return timeString;
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染任务项
|
||||
const renderTaskItem = ({ item }: { item: TaskRunItem }) => {
|
||||
const statusInfo =
|
||||
statusDisplayMap[item.status] || statusDisplayMap[TaskStatus.WAITING];
|
||||
const isOperating = operatingTaskId === item.id;
|
||||
const isSelected = selectedTasks.has(item.id);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.taskItem, isSelected ? styles.taskItemSelected : null]}
|
||||
onPress={() => {
|
||||
if (isSelectionMode) {
|
||||
toggleTaskSelection(item.id);
|
||||
}
|
||||
}}
|
||||
activeOpacity={isSelectionMode ? 0.7 : 1}
|
||||
>
|
||||
<View style={styles.taskHeader}>
|
||||
<View style={styles.taskTitleContainer}>
|
||||
{isSelectionMode && (
|
||||
<TouchableOpacity
|
||||
style={styles.checkboxContainer}
|
||||
onPress={() => toggleTaskSelection(item.id)}
|
||||
>
|
||||
<MaterialIcons
|
||||
name={isSelected ? 'check-box' : 'check-box-outline-blank'}
|
||||
size={20}
|
||||
color={isSelected ? '#4CAF50' : '#666'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<Text
|
||||
style={[
|
||||
styles.taskName,
|
||||
isSelectionMode ? styles.taskNameWithCheckbox : null,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
styles.statusBadge,
|
||||
{ backgroundColor: statusInfo.color },
|
||||
]}
|
||||
>
|
||||
<Text style={styles.statusText}>{statusInfo.text}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.taskDetails}>
|
||||
<Text style={styles.taskDescription} numberOfLines={2}>
|
||||
{item.description || '暂无描述'}
|
||||
</Text>
|
||||
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.timeLabel}>创建时间: </Text>
|
||||
<Text style={styles.timeValue}>{formatTime(item.createTime)}</Text>
|
||||
</View>
|
||||
|
||||
{item.startTime && (
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.timeLabel}>开始时间: </Text>
|
||||
<Text style={styles.timeValue}>{formatTime(item.startTime)}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{item.endTime && (
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.timeLabel}>结束时间: </Text>
|
||||
<Text style={styles.timeValue}>{formatTime(item.endTime)}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{!isSelectionMode && (
|
||||
<View style={styles.actionContainer}>
|
||||
{(item.status === TaskStatus.RUNNING ||
|
||||
item.status === TaskStatus.PAUSED) && (
|
||||
<TouchableOpacity
|
||||
style={[styles.actionButton, styles.terminateButton]}
|
||||
onPress={() => handleTaskOperation(item.id)}
|
||||
disabled={isOperating}
|
||||
>
|
||||
{isOperating ? (
|
||||
<ActivityIndicator size="small" color="#ffffff" />
|
||||
) : (
|
||||
<>
|
||||
<MaterialIcons name="stop" size={16} color="#ffffff" />
|
||||
<Text style={styles.actionButtonText}>终止</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.screenContainer}>
|
||||
<Text style={styles.text}>任务运行状态列表</Text>
|
||||
<Text style={styles.subText}>此功能正在开发中...</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 顶部操作区域 */}
|
||||
<View style={styles.topActionContainer}>
|
||||
<View style={styles.actionButtonsContainer}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.actionBtn,
|
||||
isSelectionMode ? styles.actionBtnActive : null,
|
||||
]}
|
||||
onPress={toggleSelectionMode}
|
||||
>
|
||||
<MaterialIcons
|
||||
name={isSelectionMode ? 'check-circle' : 'radio-button-unchecked'}
|
||||
size={16}
|
||||
color="#ffffff"
|
||||
/>
|
||||
<Text style={styles.actionBtnText}>
|
||||
{isSelectionMode ? '取消选择' : '批量选择'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{isSelectionMode && (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
style={styles.actionBtn}
|
||||
onPress={toggleSelectAll}
|
||||
>
|
||||
<MaterialIcons
|
||||
name={
|
||||
selectedTasks.size === tasks.length
|
||||
? 'deselect'
|
||||
: 'select-all'
|
||||
}
|
||||
size={16}
|
||||
color="#ffffff"
|
||||
/>
|
||||
<Text style={styles.actionBtnText}>
|
||||
{selectedTasks.size === tasks.length ? '取消全选' : '全选'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.actionBtn, styles.terminateBtn]}
|
||||
onPress={handleBatchTerminate}
|
||||
disabled={selectedTasks.size === 0 || batchOperating}
|
||||
>
|
||||
{batchOperating ? (
|
||||
<ActivityIndicator size="small" color="#ffffff" />
|
||||
) : (
|
||||
<MaterialIcons name="stop" size={16} color="#ffffff" />
|
||||
)}
|
||||
<Text style={styles.actionBtnText}>
|
||||
终止 ({selectedTasks.size})
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 筛选区域 */}
|
||||
<View style={styles.filterContainer}>
|
||||
<View style={styles.filterRow}>
|
||||
<View style={styles.statusFilterContainer}>
|
||||
<Text style={styles.filterLabel}>任务状态:</Text>
|
||||
<View style={styles.pickerContainer}>
|
||||
<Picker
|
||||
selectedValue={filterStatus}
|
||||
onValueChange={value => setFilterStatus(value)}
|
||||
style={styles.picker}
|
||||
dropdownIconColor="#ffffff"
|
||||
mode="dropdown"
|
||||
>
|
||||
{statusOptions.map(option => (
|
||||
<Picker.Item
|
||||
key={option.label}
|
||||
label={option.label}
|
||||
value={option.value}
|
||||
/>
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.resetButton}
|
||||
onPress={handleResetFilter}
|
||||
>
|
||||
<MaterialIcons name="refresh" size={16} color="#ffffff" />
|
||||
<Text style={styles.resetButtonText}>重置</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View style={styles.nameFilterContainer}>
|
||||
<TextInput
|
||||
style={styles.nameInput}
|
||||
placeholder="输入任务名称筛选..."
|
||||
placeholderTextColor="#999"
|
||||
value={filterName}
|
||||
onChangeText={setFilterName}
|
||||
returnKeyType="search"
|
||||
onSubmitEditing={() => loadTasks()}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={styles.searchButton}
|
||||
onPress={() => loadTasks()}
|
||||
>
|
||||
<MaterialIcons name="search" size={20} color="#ffffff" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 任务列表 */}
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color="#2196F3" />
|
||||
<Text style={styles.loadingText}>加载中...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
data={tasks}
|
||||
renderItem={renderTaskItem}
|
||||
keyExtractor={item => item.id}
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
||||
ListEmptyComponent={() => (
|
||||
<View style={styles.emptyContainer}>
|
||||
<MaterialIcons name="assignment" size={48} color="#666" />
|
||||
<Text style={styles.emptyText}>暂无任务数据</Text>
|
||||
</View>
|
||||
)}
|
||||
contentContainerStyle={
|
||||
tasks.length === 0 ? styles.emptyList : undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screenContainer: {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#121212',
|
||||
},
|
||||
filterContainer: {
|
||||
backgroundColor: '#1a1a1a',
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#333',
|
||||
},
|
||||
filterRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
statusFilterContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
filterLabel: {
|
||||
color: '#ffffff',
|
||||
fontSize: 14,
|
||||
marginRight: 8,
|
||||
},
|
||||
pickerContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: '#333',
|
||||
borderRadius: 4,
|
||||
marginRight: 12,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
picker: {
|
||||
color: '#ffffff',
|
||||
height: 40,
|
||||
width: '100%',
|
||||
},
|
||||
resetButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#666',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
resetButtonText: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12,
|
||||
marginLeft: 4,
|
||||
},
|
||||
nameFilterContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
nameInput: {
|
||||
flex: 1,
|
||||
backgroundColor: '#333',
|
||||
color: '#ffffff',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 4,
|
||||
marginRight: 8,
|
||||
},
|
||||
searchButton: {
|
||||
backgroundColor: '#2196F3',
|
||||
padding: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#121212',
|
||||
},
|
||||
text: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
loadingText: {
|
||||
color: '#ffffff',
|
||||
marginBottom: 10,
|
||||
marginTop: 8,
|
||||
},
|
||||
subText: {
|
||||
taskItem: {
|
||||
backgroundColor: '#1a1a1a',
|
||||
padding: 16,
|
||||
},
|
||||
taskHeader: {
|
||||
marginBottom: 8,
|
||||
},
|
||||
taskTitleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
taskName: {
|
||||
flex: 1,
|
||||
color: '#ffffff',
|
||||
fontSize: 16,
|
||||
color: '#b0b0b0',
|
||||
fontWeight: 'bold',
|
||||
marginRight: 8,
|
||||
},
|
||||
statusBadge: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
},
|
||||
statusText: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
taskDetails: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
taskDescription: {
|
||||
color: '#ccc',
|
||||
fontSize: 14,
|
||||
marginBottom: 8,
|
||||
},
|
||||
timeContainer: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 4,
|
||||
},
|
||||
timeLabel: {
|
||||
color: '#999',
|
||||
fontSize: 12,
|
||||
minWidth: 60,
|
||||
},
|
||||
timeValue: {
|
||||
color: '#ccc',
|
||||
fontSize: 12,
|
||||
flex: 1,
|
||||
},
|
||||
actionContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
actionButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 4,
|
||||
marginLeft: 8,
|
||||
},
|
||||
pauseButton: {
|
||||
backgroundColor: '#FF9800',
|
||||
},
|
||||
continueButton: {
|
||||
backgroundColor: '#4CAF50',
|
||||
},
|
||||
terminateButton: {
|
||||
backgroundColor: '#F44336',
|
||||
},
|
||||
actionButtonText: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12,
|
||||
marginLeft: 4,
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: '#333',
|
||||
},
|
||||
emptyContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingTop: 100,
|
||||
},
|
||||
emptyText: {
|
||||
color: '#666',
|
||||
fontSize: 16,
|
||||
marginTop: 16,
|
||||
},
|
||||
emptyList: {
|
||||
flex: 1,
|
||||
},
|
||||
topActionContainer: {
|
||||
backgroundColor: '#1a1a1a',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#333',
|
||||
},
|
||||
actionButtonsContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
actionBtn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#2196F3',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 4,
|
||||
marginRight: 8,
|
||||
marginBottom: 4,
|
||||
},
|
||||
actionBtnActive: {
|
||||
backgroundColor: '#4CAF50',
|
||||
},
|
||||
actionBtnText: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12,
|
||||
marginLeft: 4,
|
||||
},
|
||||
terminateBtn: {
|
||||
backgroundColor: '#F44336',
|
||||
},
|
||||
taskItemSelected: {
|
||||
backgroundColor: '#2a2a2a',
|
||||
borderLeftWidth: 4,
|
||||
borderLeftColor: '#4CAF50',
|
||||
},
|
||||
checkboxContainer: {
|
||||
marginRight: 12,
|
||||
padding: 4,
|
||||
},
|
||||
taskNameWithCheckbox: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
@ -56,16 +56,32 @@ api.interceptors.response.use(
|
||||
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;
|
||||
|
||||
// 处理不同的响应数据格式
|
||||
if (response.data) {
|
||||
// 格式1: { success: true, result: { records: [...] } } - 新的任务管理API格式
|
||||
if (response.data.success && response.data.result) {
|
||||
if (
|
||||
response.data.result.records &&
|
||||
Array.isArray(response.data.result.records)
|
||||
) {
|
||||
return response.data.result.records;
|
||||
}
|
||||
return response.data.result;
|
||||
}
|
||||
// 否则返回 data 字段本身
|
||||
return response.data.data;
|
||||
|
||||
// 格式2: { data: { list: [...] } } - 原有格式
|
||||
if (typeof response.data.data !== 'undefined') {
|
||||
if (Array.isArray(response.data.data.list)) {
|
||||
return response.data.data.list;
|
||||
}
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
// 格式3: 直接返回整个响应数据
|
||||
return response.data;
|
||||
}
|
||||
// 如果没有嵌套的 data 字段,直接返回整个响应数据
|
||||
|
||||
return response.data;
|
||||
},
|
||||
error => {
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { getConfig } from './configService';
|
||||
import { RunTaskRequest, RunTaskApiResponse, Task } from '../types/task';
|
||||
import {
|
||||
RunTaskRequest,
|
||||
RunTaskApiResponse,
|
||||
Task,
|
||||
TaskStatus,
|
||||
} from '../types/task';
|
||||
import api from './api';
|
||||
import axios from 'axios';
|
||||
|
||||
// 获取任务列表
|
||||
export const getTasks = async (params: {
|
||||
@ -52,3 +58,267 @@ export const runTask = async (
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// ============ 任务管理相关API ============
|
||||
|
||||
// 创建任务管理API实例
|
||||
const createTaskApi = () => {
|
||||
return axios.create({
|
||||
timeout: 10000,
|
||||
});
|
||||
};
|
||||
|
||||
// 请求拦截器 - 为任务管理API配置
|
||||
const setupTaskApiInterceptors = async (apiInstance: any) => {
|
||||
const config = await getConfig();
|
||||
|
||||
apiInstance.interceptors.request.use(
|
||||
async (requestConfig: any) => {
|
||||
const taskServerUrl = config?.taskServerUrl;
|
||||
if (!taskServerUrl) {
|
||||
throw new Error('任务管理服务器地址未配置');
|
||||
}
|
||||
|
||||
requestConfig.baseURL = taskServerUrl.replace(/\/$/, '');
|
||||
requestConfig.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...config?.headers,
|
||||
...requestConfig.headers,
|
||||
};
|
||||
|
||||
console.log(
|
||||
`[Task API Request] ${requestConfig.method?.toUpperCase()} ${
|
||||
requestConfig.baseURL
|
||||
}${requestConfig.url}`,
|
||||
{ params: requestConfig.params, data: requestConfig.data },
|
||||
);
|
||||
return requestConfig;
|
||||
},
|
||||
(error: any) => {
|
||||
console.error('[Task API Request Error]', error);
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
apiInstance.interceptors.response.use(
|
||||
(response: any) => {
|
||||
console.log(
|
||||
`[Task API Response] ${response.config.method?.toUpperCase()} ${
|
||||
response.config.url
|
||||
}`,
|
||||
response.data,
|
||||
);
|
||||
|
||||
if (response.data && response.data.code && response.data.code !== 200) {
|
||||
throw new Error(`API 错误: ${response.data.message}`);
|
||||
}
|
||||
|
||||
// 处理任务管理API的响应格式: { success: true, result: { records: [...] } }
|
||||
if (response.data && response.data.success && response.data.result) {
|
||||
// 如果result中有records数组,返回records
|
||||
if (
|
||||
response.data.result.records &&
|
||||
Array.isArray(response.data.result.records)
|
||||
) {
|
||||
return response.data.result.records;
|
||||
}
|
||||
// 否则返回整个result对象
|
||||
return response.data.result;
|
||||
}
|
||||
|
||||
return response.data;
|
||||
},
|
||||
(error: any) => {
|
||||
if (error.response) {
|
||||
console.error(
|
||||
`[Task API 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('[Task API Network Error]', error.request);
|
||||
return Promise.reject(new Error('网络错误,请检查您的连接'));
|
||||
} else {
|
||||
console.error('[Task API Request Setup Error]', error.message);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return apiInstance;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取任务运行列表
|
||||
* @param params 查询参数
|
||||
*/
|
||||
export const getTaskRunList = async (params?: {
|
||||
status?: TaskStatus;
|
||||
name?: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<any[]> => {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
const endpoint = config?.taskApiEndpoints?.getTaskList;
|
||||
if (!endpoint) throw new Error('获取任务列表的API端点未配置');
|
||||
|
||||
const taskApi = await setupTaskApiInterceptors(createTaskApi());
|
||||
const response = await taskApi.get(endpoint, { params });
|
||||
return response || [];
|
||||
} catch (error) {
|
||||
console.error('获取任务运行列表失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建任务
|
||||
* @param params 任务参数
|
||||
*/
|
||||
export const createTaskRun = async (params: any): Promise<any> => {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
const endpoint = config?.taskApiEndpoints?.createTask;
|
||||
if (!endpoint) throw new Error('创建任务的API端点未配置');
|
||||
|
||||
const taskApi = await setupTaskApiInterceptors(createTaskApi());
|
||||
const response = await taskApi.post(endpoint, params);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('创建任务失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取任务运行详情
|
||||
* @param id 任务ID
|
||||
*/
|
||||
export const getTaskRunDetail = async (id: string): Promise<any> => {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
let endpoint = config?.taskApiEndpoints?.getTaskDetail;
|
||||
if (!endpoint) throw new Error('获取任务详情的API端点未配置');
|
||||
|
||||
endpoint = endpoint.replace('{id}', id);
|
||||
const taskApi = await setupTaskApiInterceptors(createTaskApi());
|
||||
const response = await taskApi.get(endpoint);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`获取任务运行详情失败 (ID: ${id}):`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取任务进度和动作列表
|
||||
* @param ids 任务ID数组
|
||||
*/
|
||||
export const getTaskProgress = async (ids: string[]): Promise<any> => {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
const endpoint = config?.taskApiEndpoints?.getTaskProgress;
|
||||
if (!endpoint) throw new Error('获取任务进度的API端点未配置');
|
||||
|
||||
const taskApi = await setupTaskApiInterceptors(createTaskApi());
|
||||
const response = await taskApi.get(endpoint, {
|
||||
params: {
|
||||
ids: ids.join(','),
|
||||
},
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('获取任务进度失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 调整任务优先级
|
||||
* @param id 任务ID
|
||||
* @param priority 新优先级
|
||||
*/
|
||||
export const adjustTaskPriority = async (
|
||||
id: string,
|
||||
priority: number,
|
||||
): Promise<any> => {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
let endpoint = config?.taskApiEndpoints?.adjustTaskPriority;
|
||||
if (!endpoint) throw new Error('调整任务优先级的API端点未配置');
|
||||
|
||||
endpoint = endpoint.replace('{id}', id);
|
||||
const taskApi = await setupTaskApiInterceptors(createTaskApi());
|
||||
const response = await taskApi.put(endpoint, { priority });
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`调整任务优先级失败 (ID: ${id}):`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 暂停任务
|
||||
* @param id 任务ID
|
||||
*/
|
||||
export const pauseTask = async (id: string): Promise<any> => {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
let endpoint = config?.taskApiEndpoints?.pauseTask;
|
||||
if (!endpoint) throw new Error('暂停任务的API端点未配置');
|
||||
|
||||
endpoint = endpoint.replace('{id}', id);
|
||||
const taskApi = await setupTaskApiInterceptors(createTaskApi());
|
||||
const response = await taskApi.put(endpoint);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`暂停任务失败 (ID: ${id}):`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 继续任务
|
||||
* @param id 任务ID
|
||||
*/
|
||||
export const continueTask = async (id: string): Promise<any> => {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
let endpoint = config?.taskApiEndpoints?.continueTask;
|
||||
if (!endpoint) throw new Error('继续任务的API端点未配置');
|
||||
|
||||
endpoint = endpoint.replace('{id}', id);
|
||||
const taskApi = await setupTaskApiInterceptors(createTaskApi());
|
||||
const response = await taskApi.put(endpoint);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`继续任务失败 (ID: ${id}):`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 终止任务
|
||||
* @param id 任务ID
|
||||
*/
|
||||
export const terminateTask = async (id: string): Promise<any> => {
|
||||
try {
|
||||
const config = await getConfig();
|
||||
let endpoint = config?.taskApiEndpoints?.terminateTask;
|
||||
if (!endpoint) throw new Error('终止任务的API端点未配置');
|
||||
|
||||
endpoint = endpoint.replace('{id}', id);
|
||||
const taskApi = await setupTaskApiInterceptors(createTaskApi());
|
||||
const response = await taskApi.put(endpoint);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`终止任务失败 (ID: ${id}):`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
@ -2,11 +2,23 @@
|
||||
export interface AppConfig {
|
||||
version: string;
|
||||
serverUrl?: string; // 服务器地址
|
||||
taskServerUrl?: string; // 任务管理服务器地址
|
||||
apiEndpoints?: {
|
||||
getTasks: string;
|
||||
getTaskDetail: string;
|
||||
runTask: string;
|
||||
};
|
||||
taskApiEndpoints?: {
|
||||
getTaskList: string;
|
||||
createTask: string;
|
||||
getTaskDetail: string;
|
||||
getTaskProgress: string;
|
||||
adjustTaskPriority: string;
|
||||
pauseTask: string;
|
||||
continueTask: string;
|
||||
terminateTask: string;
|
||||
exportTask: string;
|
||||
};
|
||||
headers?: {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
|
@ -1,3 +1,15 @@
|
||||
/**
|
||||
* 任务状态枚举
|
||||
*/
|
||||
export enum TaskStatus {
|
||||
WAITING = 0, // 待分配
|
||||
RUNNING = 1, // 执行中
|
||||
PAUSED = 2, // 已暂停
|
||||
COMPLETED = 3, // 已完成
|
||||
FAILED = 4, // 已失败
|
||||
TERMINATED = 5, // 已终止
|
||||
}
|
||||
|
||||
// 机器人的具体动作,现在是一个对象
|
||||
export interface RobotAction {
|
||||
name: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user