feat: 添加任务运行功能,更新相关组件以支持任务执行和状态反馈
This commit is contained in:
parent
ce11581196
commit
f9564796ab
13
App.tsx
13
App.tsx
@ -1,11 +1,22 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import AppNavigator from './src/navigation/AppNavigator';
|
import AppNavigator from './src/navigation/AppNavigator';
|
||||||
|
|
||||||
import { TasksProvider } from './src/context/TasksContext';
|
import { TasksProvider } from './src/context/TasksContext';
|
||||||
import { ThemeProvider } from '@rneui/themed';
|
import { ThemeProvider } from '@rneui/themed';
|
||||||
|
import { clearCachedConfig } from './src/services/configService';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
useEffect(() => {
|
||||||
|
const clearCacheOnStart = async () => {
|
||||||
|
console.log('正在清除缓存的配置文件...');
|
||||||
|
await clearCachedConfig();
|
||||||
|
console.log('缓存已清除,将使用最新的 config.json。');
|
||||||
|
};
|
||||||
|
|
||||||
|
clearCacheOnStart();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<TasksProvider>
|
<TasksProvider>
|
||||||
|
@ -40,6 +40,6 @@
|
|||||||
"apiEndpoints": {
|
"apiEndpoints": {
|
||||||
"getTasks": "/api/vwed-task/list",
|
"getTasks": "/api/vwed-task/list",
|
||||||
"getTaskDetail": "/api/vwed-task/{taskId}",
|
"getTaskDetail": "/api/vwed-task/{taskId}",
|
||||||
"runTask": "/api/vwed-task/execute/{taskId}"
|
"runTask": "/api/vwed-task-edit/run"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import React from 'react';
|
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';
|
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
|
||||||
interface BottomActionBarProps {
|
interface BottomActionBarProps {
|
||||||
@ -8,6 +14,7 @@ interface BottomActionBarProps {
|
|||||||
onUndo: () => void;
|
onUndo: () => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
isSaveDisabled?: boolean;
|
isSaveDisabled?: boolean;
|
||||||
|
isRunLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BottomActionBar: React.FC<BottomActionBarProps> = ({
|
const BottomActionBar: React.FC<BottomActionBarProps> = ({
|
||||||
@ -16,6 +23,7 @@ const BottomActionBar: React.FC<BottomActionBarProps> = ({
|
|||||||
onUndo,
|
onUndo,
|
||||||
onBack,
|
onBack,
|
||||||
isSaveDisabled = true,
|
isSaveDisabled = true,
|
||||||
|
isRunLoading = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@ -48,10 +56,21 @@ const BottomActionBar: React.FC<BottomActionBarProps> = ({
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={onRun}
|
onPress={onRun}
|
||||||
style={[styles.button, styles.runButton]}
|
disabled={isRunLoading}
|
||||||
|
style={[
|
||||||
|
styles.button,
|
||||||
|
styles.runButton,
|
||||||
|
isRunLoading && styles.disabledButton,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Icon name="play" size={24} color="#FFFFFF" />
|
{isRunLoading ? (
|
||||||
<Text style={[styles.buttonText, styles.runButtonText]}>运行</Text>
|
<ActivityIndicator size="small" color="#FFFFFF" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon name="play" size={24} color="#FFFFFF" />
|
||||||
|
<Text style={[styles.buttonText, styles.runButtonText]}>运行</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -51,8 +51,8 @@ export default function AppNavigator() {
|
|||||||
|
|
||||||
if (route.name === '主页') {
|
if (route.name === '主页') {
|
||||||
iconName = 'home';
|
iconName = 'home';
|
||||||
} else if (route.name === '运行') {
|
} else if (route.name === '任务列表') {
|
||||||
iconName = 'play-arrow';
|
iconName = 'view-list';
|
||||||
} else if (route.name === '编辑') {
|
} else if (route.name === '编辑') {
|
||||||
iconName = 'edit';
|
iconName = 'edit';
|
||||||
} else if (route.name === '设置') {
|
} else if (route.name === '设置') {
|
||||||
@ -73,7 +73,7 @@ export default function AppNavigator() {
|
|||||||
component={HomeStackNavigator}
|
component={HomeStackNavigator}
|
||||||
options={{ headerShown: false }}
|
options={{ headerShown: false }}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen name="运行" component={RunScreen} />
|
<Tab.Screen name="任务列表" component={RunScreen} />
|
||||||
<Tab.Screen name="编辑" component={EditScreen} />
|
<Tab.Screen name="编辑" component={EditScreen} />
|
||||||
<Tab.Screen name="设置" component={SettingsScreen} />
|
<Tab.Screen name="设置" component={SettingsScreen} />
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
|
@ -4,7 +4,8 @@ import { StyleSheet, Text, View } from 'react-native';
|
|||||||
export default function RunScreen() {
|
export default function RunScreen() {
|
||||||
return (
|
return (
|
||||||
<View style={styles.screenContainer}>
|
<View style={styles.screenContainer}>
|
||||||
<Text>运行!</Text>
|
<Text style={styles.text}>任务运行状态列表</Text>
|
||||||
|
<Text style={styles.subText}>此功能正在开发中...</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -14,5 +15,16 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#121212',
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#ffffff',
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
subText: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#b0b0b0',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -5,13 +5,15 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
Text,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { useRoute, useNavigation } from '@react-navigation/native';
|
import { useRoute, useNavigation } from '@react-navigation/native';
|
||||||
import { RouteProp } from '@react-navigation/native';
|
import { RouteProp } from '@react-navigation/native';
|
||||||
import { useTasks } from '../context/TasksContext';
|
import { useTasks } from '../context/TasksContext';
|
||||||
import TaskForm from '../components/TaskForm';
|
import TaskForm from '../components/TaskForm';
|
||||||
import BottomActionBar from '../components/BottomActionBar';
|
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 = {
|
type RootStackParamList = {
|
||||||
TaskEdit: { taskId: string };
|
TaskEdit: { taskId: string };
|
||||||
@ -24,19 +26,21 @@ export default function TaskEditScreen() {
|
|||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const { taskId } = route.params;
|
const { taskId } = route.params;
|
||||||
|
|
||||||
const { getTaskById, updateTask, runTask, tasks, fetchTaskDetail } =
|
const { getTaskById, updateTask, tasks, fetchTaskDetail } = useTasks();
|
||||||
useTasks();
|
|
||||||
|
|
||||||
const [task, setTask] = useState<Task | null>(null);
|
const [task, setTask] = useState<Task | null>(null);
|
||||||
const [originalTask, setOriginalTask] = useState<Task | null>(null);
|
const [originalTask, setOriginalTask] = useState<Task | null>(null);
|
||||||
const [isModified, setIsModified] = useState(false);
|
const [isModified, setIsModified] = useState(false);
|
||||||
|
const [isRunLoading, setIsRunLoading] = useState(false);
|
||||||
|
const [runResponse, setRunResponse] = useState<RunTaskApiResponse | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadTask = async () => {
|
const loadTask = async () => {
|
||||||
let taskData = getTaskById(taskId);
|
let taskData = getTaskById(taskId);
|
||||||
if (taskData && !taskData.detail) {
|
if (taskData && !taskData.detail) {
|
||||||
await fetchTaskDetail(taskId);
|
await fetchTaskDetail(taskId);
|
||||||
// Re-fetch task data after details are loaded
|
|
||||||
taskData = getTaskById(taskId);
|
taskData = getTaskById(taskId);
|
||||||
}
|
}
|
||||||
if (taskData) {
|
if (taskData) {
|
||||||
@ -63,10 +67,40 @@ export default function TaskEditScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRun = () => {
|
const handleRun = async () => {
|
||||||
if (task) {
|
if (!task || !task.detail || !task.detail.inputParams) {
|
||||||
runTask(task.id);
|
Alert.alert('错误', '任务数据不完整,无法运行。');
|
||||||
navigation.goBack();
|
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);
|
setIsModified(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRestore = () => {
|
|
||||||
setTask(originalTask);
|
|
||||||
setIsModified(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
@ -92,14 +121,22 @@ export default function TaskEditScreen() {
|
|||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
||||||
<TaskForm task={task} onTaskChange={handleTaskChange} />
|
<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>
|
</ScrollView>
|
||||||
<BottomActionBar
|
<BottomActionBar
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onUndo={handleUndo}
|
onUndo={handleUndo}
|
||||||
onRestore={handleRestore}
|
|
||||||
onBack={() => navigation.goBack()}
|
onBack={() => navigation.goBack()}
|
||||||
isSaveDisabled={!isModified}
|
isSaveDisabled={!isModified}
|
||||||
|
isRunLoading={isRunLoading}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@ -108,7 +145,7 @@ export default function TaskEditScreen() {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#1a1a1a', // 深色背景
|
backgroundColor: '#1a1a1a',
|
||||||
},
|
},
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@ -118,5 +155,23 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
scrollContainer: {
|
scrollContainer: {
|
||||||
padding: 16,
|
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
126
src/services/taskService.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
@ -63,3 +63,33 @@ export interface InputParam {
|
|||||||
max?: number;
|
max?: number;
|
||||||
step?: 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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user