feat: 添加任务运行功能,更新相关组件以支持任务执行和状态反馈

This commit is contained in:
xudan 2025-07-23 15:05:14 +08:00
parent ce11581196
commit f9564796ab
8 changed files with 278 additions and 25 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

@ -40,6 +40,6 @@
"apiEndpoints": {
"getTasks": "/api/vwed-task/list",
"getTaskDetail": "/api/vwed-task/{taskId}",
"runTask": "/api/vwed-task/execute/{taskId}"
"runTask": "/api/vwed-task-edit/run"
}
}

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

@ -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,21 @@ 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);
if (taskData && !taskData.detail) {
await fetchTaskDetail(taskId);
// Re-fetch task data after details are loaded
taskData = getTaskById(taskId);
}
if (taskData) {
@ -63,10 +67,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 +109,6 @@ export default function TaskEditScreen() {
setIsModified(false);
};
const handleRestore = () => {
setTask(originalTask);
setIsModified(false);
};
if (!task) {
return (
<View style={styles.loadingContainer}>
@ -92,14 +121,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 +145,7 @@ export default function TaskEditScreen() {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1a1a1a', // 深色背景
backgroundColor: '#1a1a1a',
},
loadingContainer: {
flex: 1,
@ -118,5 +155,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',
},
});

126
src/services/taskService.ts Normal file
View File

@ -0,0 +1,126 @@
import { getSettings, getConfig } from './configService';
import { RunTaskRequest, RunTaskApiResponse, Task } from '../types/task';
// 获取任务列表
export const getTasks = async (): Promise<Task[]> => {
try {
const config = await getConfig();
const endpoint = config?.apiEndpoints?.getTasks;
if (!endpoint) {
throw new Error('获取任务列表的API端点未配置');
}
const settings = await getSettings();
const serverUrl = settings.serverUrl || config?.serverUrl;
if (!serverUrl) {
throw new Error('服务器地址未配置');
}
const url = `${serverUrl.replace(/\/$/, '')}${endpoint}`;
console.log('获取任务列表:', url);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (result.code !== 200) {
throw new Error(`API 错误: ${result.message}`);
}
return result.data;
} catch (error) {
console.error('获取任务列表失败:', 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 settings = await getSettings();
const serverUrl = settings.serverUrl || config?.serverUrl;
if (!serverUrl) {
throw new Error('服务器地址未配置');
}
const url = `${serverUrl.replace(/\/$/, '')}${endpoint}`;
console.log('获取任务详情:', url);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (result.code !== 200) {
throw new Error(`API 错误: ${result.message}`);
}
return result.data;
} 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 settings = await getSettings();
const serverUrl = settings.serverUrl || config?.serverUrl;
if (!serverUrl) {
throw new Error('服务器地址未配置');
}
const url = `${serverUrl.replace(/\/$/, '')}${endpoint}`;
console.log('运行任务请求:', url, JSON.stringify(taskData, null, 2));
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(taskData),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const result: RunTaskApiResponse = await response.json();
console.log('运行任务响应:', result);
if (result.code !== 200) {
throw new Error(`API 错误: ${result.message}`);
}
return result;
} catch (error) {
console.error('运行任务失败:', error);
throw error;
}
};

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;
}