chore(邮件发送模块): 更新SQLite共享内存临时文件
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Dimensions,
|
||||
Modal,
|
||||
ScaledSize,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
@@ -32,6 +33,24 @@ export const RunnerModal: React.FC<RunnerModalProps> = ({ runner, onClose }) =>
|
||||
// 保持屏幕常亮
|
||||
useKeepAwake();
|
||||
|
||||
const [windowSize, setWindowSize] = useState<ScaledSize>(() => Dimensions.get('window'));
|
||||
useEffect(() => {
|
||||
const listener = ({ window }: { window: ScaledSize }) => setWindowSize(window);
|
||||
const subscription = Dimensions.addEventListener('change', listener);
|
||||
return () => {
|
||||
if (subscription && typeof subscription.remove === 'function') {
|
||||
subscription.remove();
|
||||
} else {
|
||||
try {
|
||||
Dimensions.removeEventListener('change', listener);
|
||||
} catch (e) { /* noop */ }
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
const isLandscape = useMemo(() => windowSize.width > windowSize.height, [windowSize]);
|
||||
const isWide = windowSize.width >= 900;
|
||||
const splitMode = isLandscape || isWide;
|
||||
|
||||
// 进入后台时保存状态
|
||||
const handleBackground = useCallback(async () => {
|
||||
if (!runnerState || runnerState.status !== 'running') return;
|
||||
@@ -129,9 +148,97 @@ export const RunnerModal: React.FC<RunnerModalProps> = ({ runner, onClose }) =>
|
||||
const isAction = currentStep?.type === 'action';
|
||||
const stepBorderColor = isAction ? theme.colors.primary : theme.colors.success;
|
||||
|
||||
const leftColumn = (
|
||||
<View style={styles.runnerMain}>
|
||||
<Text style={styles.runnerLabel}>当前动作</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.runnerCurrentText,
|
||||
splitMode && styles.runnerCurrentTextLandscape,
|
||||
isAction && { color: stepBorderColor },
|
||||
]}
|
||||
>
|
||||
{currentStep?.name || '—'}
|
||||
</Text>
|
||||
<Text style={[styles.runnerTimer, splitMode && styles.runnerTimerLandscape]}>
|
||||
{formatDuration(runnerState.remainingSec || 0)}
|
||||
</Text>
|
||||
|
||||
<View style={[styles.runnerNextCard, splitMode && styles.runnerNextCardLandscape, { borderColor: stepBorderColor }]}>
|
||||
<Text style={styles.runnerLabel}>下一个动作</Text>
|
||||
<Text style={[styles.runnerNextText, splitMode && styles.runnerNextTextLandscape]}>
|
||||
{nextStep?.name || '本轮结束'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
const rightColumn = (
|
||||
<View style={styles.runnerFooter}>
|
||||
<View style={styles.runnerInfoCard}>
|
||||
<View style={styles.runnerInfoRow}>
|
||||
<Text style={styles.runnerInfoLabel}>已执行</Text>
|
||||
<Text style={styles.runnerInfoValue}>
|
||||
第 {runnerState.currentLoop} 轮,步骤 {runnerState.currentStepIndex + 1}/{totalSteps}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.runnerInfoRow}>
|
||||
<Text style={styles.runnerInfoLabel}>预计剩余</Text>
|
||||
<Text style={[styles.runnerInfoValue, styles.runnerInfoHighlight]}>
|
||||
约 {formatDuration(remainingStepsSec)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.runnerInfoRow}>
|
||||
<Text style={styles.runnerInfoLabel}>全局时长</Text>
|
||||
<Text style={[styles.runnerInfoValue, styles.runnerInfoHighlight]}>
|
||||
{globalDurationSec !== null
|
||||
? `约 ${formatDuration(globalDurationSec)}`
|
||||
: '无限循环'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.runnerButtonRow}>
|
||||
<TouchableOpacity
|
||||
style={styles.runnerButton}
|
||||
onPress={() => handleAction(runnerState.status === 'paused' ? 'resume' : 'pause')}
|
||||
>
|
||||
<Text style={styles.runnerButtonText}>{runnerState.status === 'paused' ? '继续' : '暂停'}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.runnerButton} onPress={() => handleAction('skip')}>
|
||||
<Text style={styles.runnerButtonText}>跳过</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.runnerButton, styles.runnerDangerButton]} onPress={handleStop}>
|
||||
<Text style={styles.runnerButtonText}>结束</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{globalDurationSec !== null && globalDurationSec > 0 && (
|
||||
<View style={styles.globalProgressWrap}>
|
||||
<View style={styles.globalProgressHeader}>
|
||||
<Text style={styles.globalProgressLabel}>执行进度</Text>
|
||||
<Text style={styles.globalProgressPercent}>{Math.round(globalProgress * 100)}%</Text>
|
||||
</View>
|
||||
<View style={styles.globalProgressBarContainer}>
|
||||
<View style={[styles.globalProgressBar, { width: `${globalProgress * 100}%` }]} />
|
||||
</View>
|
||||
<View style={styles.globalProgressStats}>
|
||||
<Text style={styles.globalProgressStat}>已执行 {formatDuration(executedSec)}</Text>
|
||||
<Text style={styles.globalProgressStatDivider}>/</Text>
|
||||
<Text style={styles.globalProgressStat}>总时长 {formatDuration(globalDurationSec)}</Text>
|
||||
<Text style={styles.globalProgressStatDivider}>·</Text>
|
||||
<Text style={[styles.globalProgressStat, styles.globalProgressRemaining]}>
|
||||
剩余 {formatDuration(remainingStepsSec)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal visible={!!runnerState} animationType="slide" onRequestClose={handleStop}>
|
||||
<View style={styles.runnerContainer}>
|
||||
<View style={[styles.runnerContainer, splitMode && styles.runnerContainerLandscape]}>
|
||||
<View style={styles.runnerHeader}>
|
||||
<Text style={[styles.runnerBadge, { borderColor: stepBorderColor }]}>
|
||||
{isAction ? '动作' : '休息'}
|
||||
@@ -143,83 +250,22 @@ export const RunnerModal: React.FC<RunnerModalProps> = ({ runner, onClose }) =>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Progress bar */}
|
||||
<View style={styles.progressBarContainer}>
|
||||
<View style={[styles.progressBar, { width: `${stepProgress * 100}%` }]} />
|
||||
</View>
|
||||
|
||||
<View style={styles.runnerMain}>
|
||||
<Text style={styles.runnerLabel}>当前动作</Text>
|
||||
<Text style={[styles.runnerCurrentText, isAction && { color: stepBorderColor }]}>{currentStep?.name || '—'}</Text>
|
||||
<Text style={styles.runnerTimer}>{formatDuration(runnerState.remainingSec || 0)}</Text>
|
||||
|
||||
<View style={[styles.runnerNextCard, { borderColor: stepBorderColor }]}>
|
||||
<Text style={styles.runnerLabel}>下一个动作</Text>
|
||||
<Text style={styles.runnerNextText}>{nextStep?.name || '本轮结束'}</Text>
|
||||
{splitMode ? (
|
||||
<View style={styles.splitLayout}>
|
||||
<View style={styles.splitColLeft}>{leftColumn}</View>
|
||||
<View style={[styles.splitDivider]} />
|
||||
<View style={styles.splitColRight}>{rightColumn}</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.runnerFooter}>
|
||||
{/* 执行信息卡片 */}
|
||||
<View style={styles.runnerInfoCard}>
|
||||
<View style={styles.runnerInfoRow}>
|
||||
<Text style={styles.runnerInfoLabel}>已执行</Text>
|
||||
<Text style={styles.runnerInfoValue}>
|
||||
第 {runnerState.currentLoop} 轮,步骤 {runnerState.currentStepIndex + 1}/{totalSteps}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.runnerInfoRow}>
|
||||
<Text style={styles.runnerInfoLabel}>预计剩余</Text>
|
||||
<Text style={[styles.runnerInfoValue, styles.runnerInfoHighlight]}>
|
||||
约 {formatDuration(remainingStepsSec)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.runnerInfoRow}>
|
||||
<Text style={styles.runnerInfoLabel}>全局时长</Text>
|
||||
<Text style={[styles.runnerInfoValue, styles.runnerInfoHighlight]}>
|
||||
{globalDurationSec !== null
|
||||
? `约 ${formatDuration(globalDurationSec)}`
|
||||
: '无限循环'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 控制按钮 */}
|
||||
<View style={styles.runnerButtonRow}>
|
||||
<TouchableOpacity
|
||||
style={styles.runnerButton}
|
||||
onPress={() => handleAction(runnerState.status === 'paused' ? 'resume' : 'pause')}
|
||||
>
|
||||
<Text style={styles.runnerButtonText}>{runnerState.status === 'paused' ? '继续' : '暂停'}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.runnerButton} onPress={() => handleAction('skip')}>
|
||||
<Text style={styles.runnerButtonText}>跳过</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.runnerButton, styles.runnerDangerButton]} onPress={handleStop}>
|
||||
<Text style={styles.runnerButtonText}>结束</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* 全局进度条 */}
|
||||
{globalDurationSec !== null && globalDurationSec > 0 && (
|
||||
<View style={styles.globalProgressWrap}>
|
||||
<View style={styles.globalProgressHeader}>
|
||||
<Text style={styles.globalProgressLabel}>执行进度</Text>
|
||||
<Text style={styles.globalProgressPercent}>{Math.round(globalProgress * 100)}%</Text>
|
||||
</View>
|
||||
<View style={styles.globalProgressBarContainer}>
|
||||
<View style={[styles.globalProgressBar, { width: `${globalProgress * 100}%` }]} />
|
||||
</View>
|
||||
<View style={styles.globalProgressStats}>
|
||||
<Text style={styles.globalProgressStat}>已执行 {formatDuration(executedSec)}</Text>
|
||||
<Text style={styles.globalProgressStatDivider}>/</Text>
|
||||
<Text style={styles.globalProgressStat}>总时长 {formatDuration(globalDurationSec)}</Text>
|
||||
<Text style={styles.globalProgressStatDivider}>·</Text>
|
||||
<Text style={[styles.globalProgressStat, styles.globalProgressRemaining]}>剩余 {formatDuration(remainingStepsSec)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
{leftColumn}
|
||||
{rightColumn}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
@@ -233,6 +279,10 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
paddingBottom: theme.spacing.xl,
|
||||
},
|
||||
runnerContainerLandscape: {
|
||||
paddingTop: 24,
|
||||
paddingHorizontal: 28,
|
||||
},
|
||||
runnerHeader: {
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing.sm,
|
||||
@@ -265,6 +315,30 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: 5,
|
||||
},
|
||||
splitLayout: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
gap: 24,
|
||||
alignItems: 'stretch',
|
||||
minHeight: 0,
|
||||
},
|
||||
splitColLeft: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
},
|
||||
splitColRight: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
justifyContent: 'space-between',
|
||||
overflow: 'scroll',
|
||||
},
|
||||
splitDivider: {
|
||||
width: StyleSheet.hairlineWidth,
|
||||
backgroundColor: theme.colors.border,
|
||||
marginVertical: 4,
|
||||
},
|
||||
runnerMain: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
@@ -281,11 +355,18 @@ const styles = StyleSheet.create({
|
||||
color: theme.colors.text,
|
||||
textAlign: 'center',
|
||||
},
|
||||
runnerCurrentTextLandscape: {
|
||||
fontSize: 46,
|
||||
},
|
||||
runnerTimer: {
|
||||
fontSize: 90,
|
||||
fontWeight: '700',
|
||||
color: theme.colors.primary,
|
||||
},
|
||||
runnerTimerLandscape: {
|
||||
fontSize: 128,
|
||||
marginVertical: 8,
|
||||
},
|
||||
runnerNextCard: {
|
||||
width: '100%',
|
||||
backgroundColor: theme.colors.surface,
|
||||
@@ -297,12 +378,18 @@ const styles = StyleSheet.create({
|
||||
marginTop: theme.spacing.xl,
|
||||
gap: theme.spacing.sm,
|
||||
},
|
||||
runnerNextCardLandscape: {
|
||||
marginTop: 16,
|
||||
},
|
||||
runnerNextText: {
|
||||
fontSize: 26,
|
||||
fontWeight: '700',
|
||||
color: theme.colors.text,
|
||||
textAlign: 'center',
|
||||
},
|
||||
runnerNextTextLandscape: {
|
||||
fontSize: 34,
|
||||
},
|
||||
runnerFooter: {
|
||||
gap: theme.spacing.md,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user