From cdffc19db1dc16501ef037124917a79b2605ddb6 Mon Sep 17 00:00:00 2001 From: xudan Date: Wed, 23 Jul 2025 17:58:27 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20RunScreen=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=8F=90=E5=8F=96=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E9=A1=B9=E4=B8=BA=E7=8B=AC=E7=AB=8B=E7=BB=84=E4=BB=B6=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20useMemo=20=E5=92=8C=20useCallback=20=E6=8F=90=E9=AB=98?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E6=95=88=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/screens/RunScreen.tsx | 481 +++++++++++++++++++++++--------------- 1 file changed, 287 insertions(+), 194 deletions(-) diff --git a/src/screens/RunScreen.tsx b/src/screens/RunScreen.tsx index c1943d5..5352fb4 100644 --- a/src/screens/RunScreen.tsx +++ b/src/screens/RunScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { StyleSheet, Text, @@ -25,16 +25,6 @@ const statusOptions = [ { 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; @@ -56,184 +46,62 @@ interface TaskRunItem { runDuration: number; } -export default function RunScreen() { - const [tasks, setTasks] = useState([]); - const [loading, setLoading] = useState(false); - const [refreshing, setRefreshing] = useState(false); - - // 筛选状态 - const [filterStatus, setFilterStatus] = useState( - undefined, - ); - const [filterName, setFilterName] = useState(''); - - // 操作状态 - const [operatingTaskId, setOperatingTaskId] = useState(null); - - // 批量操作状态 - const [selectedTasks, setSelectedTasks] = useState>(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 }) => { +// 提取任务项组件并使用 memo 优化 +const TaskItem = React.memo<{ + item: TaskRunItem; + isSelectionMode: boolean; + isSelected: boolean; + isOperating: boolean; + onToggleSelection: (taskId: string) => void; + onTaskOperation: (taskId: string) => void; + statusDisplayMap: { [key: number]: { text: string; color: string } }; +}>( + ({ + item, + isSelectionMode, + isSelected, + isOperating, + onToggleSelection, + onTaskOperation, + statusDisplayMap, + }) => { const statusInfo = statusDisplayMap[item.status] || statusDisplayMap[TaskStatus.WAITING]; - const isOperating = operatingTaskId === item.id; - const isSelected = selectedTasks.has(item.id); + + // 格式化时间 - 移到组件内部避免重复计算 + const formatTime = useCallback((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 handlePress = useCallback(() => { + if (isSelectionMode) { + onToggleSelection(item.id); + } + }, [isSelectionMode, onToggleSelection, item.id]); + + const handleCheckboxPress = useCallback(() => { + onToggleSelection(item.id); + }, [onToggleSelection, item.id]); + + const handleOperationPress = useCallback(() => { + onTaskOperation(item.id); + }, [onTaskOperation, item.id]); return ( { - if (isSelectionMode) { - toggleTaskSelection(item.id); - } - }} + onPress={handlePress} activeOpacity={isSelectionMode ? 0.7 : 1} > @@ -241,7 +109,7 @@ export default function RunScreen() { {isSelectionMode && ( toggleTaskSelection(item.id)} + onPress={handleCheckboxPress} > handleTaskOperation(item.id)} + onPress={handleOperationPress} disabled={isOperating} > {isOperating ? ( @@ -318,7 +186,223 @@ export default function RunScreen() { )} ); - }; + }, +); + +TaskItem.displayName = 'TaskItem'; + +export default function RunScreen() { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + + // 筛选状态 + const [filterStatus, setFilterStatus] = useState( + undefined, + ); + const [filterName, setFilterName] = useState(''); + + // 操作状态 + const [operatingTaskId, setOperatingTaskId] = useState(null); + + // 批量操作状态 + const [selectedTasks, setSelectedTasks] = useState>(new Set()); + const [isSelectionMode, setIsSelectionMode] = useState(false); + const [batchOperating, setBatchOperating] = useState(false); + + // 使用 useMemo 缓存状态显示配置 + const statusDisplayMap = useMemo( + () => ({ + [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' }, + }), + [], + ); + + // 加载任务列表 + 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 = useCallback(() => { + loadTasks(true); + }, [loadTasks]); + + // 重置筛选 + const handleResetFilter = useCallback(() => { + setFilterStatus(undefined); + setFilterName(''); + }, []); + + // 切换选择模式 + const toggleSelectionMode = useCallback(() => { + setIsSelectionMode(!isSelectionMode); + setSelectedTasks(new Set()); + }, [isSelectionMode]); + + // 选择/取消选择任务 + const toggleTaskSelection = useCallback((taskId: string) => { + setSelectedTasks(prev => { + const newSelection = new Set(prev); + if (newSelection.has(taskId)) { + newSelection.delete(taskId); + } else { + newSelection.add(taskId); + } + return newSelection; + }); + }, []); + + // 全选/取消全选 + const toggleSelectAll = useCallback(() => { + if (selectedTasks.size === tasks.length) { + setSelectedTasks(new Set()); + } else { + setSelectedTasks(new Set(tasks.map(task => task.id))); + } + }, [selectedTasks.size, tasks]); + + // 批量终止任务 + const handleBatchTerminate = useCallback(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); + } + }, + }, + ], + ); + }, [selectedTasks, loadTasks]); + + // 任务操作 - 只保留终止操作 + const handleTaskOperation = useCallback( + async (taskId: string) => { + setOperatingTaskId(taskId); + + try { + await terminateTask(taskId); + Alert.alert('成功', '终止任务成功'); + // 重新加载任务列表 + loadTasks(); + } catch (error) { + console.error('终止任务失败:', error); + Alert.alert('错误', '终止任务失败,请重试'); + } finally { + setOperatingTaskId(null); + } + }, + [loadTasks], + ); + + // 优化的渲染任务项函数 + const renderTaskItem = useCallback( + ({ item }: { item: TaskRunItem }) => { + const isOperating = operatingTaskId === item.id; + const isSelected = selectedTasks.has(item.id); + + return ( + + ); + }, + [ + operatingTaskId, + selectedTasks, + isSelectionMode, + toggleTaskSelection, + handleTaskOperation, + statusDisplayMap, + ], + ); + + // 优化 keyExtractor + const keyExtractor = useCallback((item: TaskRunItem) => item.id, []); + + // 渲染分隔符 + const renderSeparator = useCallback( + () => , + [], + ); + + // 渲染空列表组件 + const renderEmptyComponent = useCallback( + () => ( + + + 暂无任务数据 + + ), + [], + ); return ( @@ -443,19 +527,26 @@ export default function RunScreen() { item.id} + keyExtractor={keyExtractor} refreshing={refreshing} onRefresh={handleRefresh} - ItemSeparatorComponent={() => } - ListEmptyComponent={() => ( - - - 暂无任务数据 - - )} + ItemSeparatorComponent={renderSeparator} + ListEmptyComponent={renderEmptyComponent} contentContainerStyle={ tasks.length === 0 ? styles.emptyList : undefined } + // 性能优化配置 + removeClippedSubviews={true} + maxToRenderPerBatch={10} + windowSize={10} + initialNumToRender={10} + updateCellsBatchingPeriod={50} + // 避免重复渲染 + extraData={{ + selectedTasks: selectedTasks.size, + isSelectionMode, + operatingTaskId, + }} /> )} @@ -508,6 +599,8 @@ const styles = StyleSheet.create({ paddingHorizontal: 12, paddingVertical: 8, borderRadius: 4, + height: 40, + justifyContent: 'center', }, resetButtonText: { color: '#ffffff',