chore(邮件发送模块): 更新SQLite共享内存临时文件
This commit is contained in:
@@ -6,7 +6,8 @@
|
|||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios",
|
||||||
"web": "expo start --web"
|
"web": "expo start --web",
|
||||||
|
"start_web_comment": "仅用于本机快速调试 UI。正式 Web 版本不是这里构建,见孪生兄弟目录 public/tools/web_mobile_calendar_reminder。"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
// 自动化部署脚本:构建后自动修正路径并复制到 public
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const SOURCE_DIR = path.join(__dirname, 'dist');
|
|
||||||
const TARGET_DIR = path.join(__dirname, '..', '..', 'public', 'tools', 'web_order_box');
|
|
||||||
|
|
||||||
// 确保目标目录存在
|
|
||||||
if (!fs.existsSync(TARGET_DIR)) {
|
|
||||||
fs.mkdirSync(TARGET_DIR, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制所有文件
|
|
||||||
function copyDir(src, dest) {
|
|
||||||
fs.readdirSync(src).forEach(file => {
|
|
||||||
const srcPath = path.join(src, file);
|
|
||||||
const destPath = path.join(dest, file);
|
|
||||||
if (fs.statSync(srcPath).isDirectory()) {
|
|
||||||
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
|
|
||||||
copyDir(srcPath, destPath);
|
|
||||||
} else {
|
|
||||||
fs.copyFileSync(srcPath, destPath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
copyDir(SOURCE_DIR, TARGET_DIR);
|
|
||||||
|
|
||||||
// 复制字体文件到 public/assets/fonts
|
|
||||||
const FONTS_SOURCE = path.join(__dirname, 'node_modules', '@expo', 'vector-icons', 'build', 'vendor', 'react-native-vector-icons', 'Fonts');
|
|
||||||
const FONTS_TARGET = path.join(TARGET_DIR, 'assets', 'fonts');
|
|
||||||
|
|
||||||
if (fs.existsSync(FONTS_SOURCE)) {
|
|
||||||
if (!fs.existsSync(FONTS_TARGET)) fs.mkdirSync(FONTS_TARGET, { recursive: true });
|
|
||||||
fs.readdirSync(FONTS_SOURCE).forEach(file => {
|
|
||||||
if (file.endsWith('.ttf')) {
|
|
||||||
fs.copyFileSync(path.join(FONTS_SOURCE, file), path.join(FONTS_TARGET, file));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log('✅ 字体文件已复制到 assets/fonts/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修正 index.html:添加 base 标签并修改路径为相对路径
|
|
||||||
const indexPath = path.join(TARGET_DIR, 'index.html');
|
|
||||||
let indexContent = fs.readFileSync(indexPath, 'utf-8');
|
|
||||||
|
|
||||||
// 在 head 中添加 base 标签
|
|
||||||
const baseTag = '<base href="/tools/web_order_box/" />';
|
|
||||||
if (!indexContent.includes('<base')) {
|
|
||||||
indexContent = indexContent.replace('</head>', ` ${baseTag}\n</head>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将绝对路径改为相对路径(使 base 标签生效)
|
|
||||||
indexContent = indexContent.replace(/src="\/_expo\//g, 'src="_expo/');
|
|
||||||
indexContent = indexContent.replace(/href="\/favicon\.ico"/g, 'href="favicon.ico"');
|
|
||||||
indexContent = indexContent.replace(/href="\/assets\//g, 'href="assets/');
|
|
||||||
// 修正字体路径
|
|
||||||
indexContent = indexContent.replace(/\/assets\/node_modules\/@expo\/vector-icons\/build\/vendor\/react-native-vector-icons\/Fonts\//g, 'assets/fonts/');
|
|
||||||
|
|
||||||
fs.writeFileSync(indexPath, indexContent, 'utf-8');
|
|
||||||
|
|
||||||
console.log('✅ 部署完成!');
|
|
||||||
console.log(`📁 目标目录:${TARGET_DIR}`);
|
|
||||||
console.log('🌐 访问地址:https://traesite.umersoft.com:8975/tools/web_order_box');
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Modal,
|
Modal,
|
||||||
|
ScaledSize,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
@@ -32,6 +33,24 @@ export const RunnerModal: React.FC<RunnerModalProps> = ({ runner, onClose }) =>
|
|||||||
// 保持屏幕常亮
|
// 保持屏幕常亮
|
||||||
useKeepAwake();
|
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 () => {
|
const handleBackground = useCallback(async () => {
|
||||||
if (!runnerState || runnerState.status !== 'running') return;
|
if (!runnerState || runnerState.status !== 'running') return;
|
||||||
@@ -129,9 +148,97 @@ export const RunnerModal: React.FC<RunnerModalProps> = ({ runner, onClose }) =>
|
|||||||
const isAction = currentStep?.type === 'action';
|
const isAction = currentStep?.type === 'action';
|
||||||
const stepBorderColor = isAction ? theme.colors.primary : theme.colors.success;
|
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 (
|
return (
|
||||||
<Modal visible={!!runnerState} animationType="slide" onRequestClose={handleStop}>
|
<Modal visible={!!runnerState} animationType="slide" onRequestClose={handleStop}>
|
||||||
<View style={styles.runnerContainer}>
|
<View style={[styles.runnerContainer, splitMode && styles.runnerContainerLandscape]}>
|
||||||
<View style={styles.runnerHeader}>
|
<View style={styles.runnerHeader}>
|
||||||
<Text style={[styles.runnerBadge, { borderColor: stepBorderColor }]}>
|
<Text style={[styles.runnerBadge, { borderColor: stepBorderColor }]}>
|
||||||
{isAction ? '动作' : '休息'}
|
{isAction ? '动作' : '休息'}
|
||||||
@@ -143,83 +250,22 @@ export const RunnerModal: React.FC<RunnerModalProps> = ({ runner, onClose }) =>
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Progress bar */}
|
|
||||||
<View style={styles.progressBarContainer}>
|
<View style={styles.progressBarContainer}>
|
||||||
<View style={[styles.progressBar, { width: `${stepProgress * 100}%` }]} />
|
<View style={[styles.progressBar, { width: `${stepProgress * 100}%` }]} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.runnerMain}>
|
{splitMode ? (
|
||||||
<Text style={styles.runnerLabel}>当前动作</Text>
|
<View style={styles.splitLayout}>
|
||||||
<Text style={[styles.runnerCurrentText, isAction && { color: stepBorderColor }]}>{currentStep?.name || '—'}</Text>
|
<View style={styles.splitColLeft}>{leftColumn}</View>
|
||||||
<Text style={styles.runnerTimer}>{formatDuration(runnerState.remainingSec || 0)}</Text>
|
<View style={[styles.splitDivider]} />
|
||||||
|
<View style={styles.splitColRight}>{rightColumn}</View>
|
||||||
<View style={[styles.runnerNextCard, { borderColor: stepBorderColor }]}>
|
|
||||||
<Text style={styles.runnerLabel}>下一个动作</Text>
|
|
||||||
<Text style={styles.runnerNextText}>{nextStep?.name || '本轮结束'}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
) : (
|
||||||
|
<>
|
||||||
<View style={styles.runnerFooter}>
|
{leftColumn}
|
||||||
{/* 执行信息卡片 */}
|
{rightColumn}
|
||||||
<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>
|
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
@@ -233,6 +279,10 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: theme.spacing.lg,
|
paddingHorizontal: theme.spacing.lg,
|
||||||
paddingBottom: theme.spacing.xl,
|
paddingBottom: theme.spacing.xl,
|
||||||
},
|
},
|
||||||
|
runnerContainerLandscape: {
|
||||||
|
paddingTop: 24,
|
||||||
|
paddingHorizontal: 28,
|
||||||
|
},
|
||||||
runnerHeader: {
|
runnerHeader: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: theme.spacing.sm,
|
gap: theme.spacing.sm,
|
||||||
@@ -265,6 +315,30 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: theme.colors.primary,
|
backgroundColor: theme.colors.primary,
|
||||||
borderRadius: 5,
|
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: {
|
runnerMain: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -281,11 +355,18 @@ const styles = StyleSheet.create({
|
|||||||
color: theme.colors.text,
|
color: theme.colors.text,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
|
runnerCurrentTextLandscape: {
|
||||||
|
fontSize: 46,
|
||||||
|
},
|
||||||
runnerTimer: {
|
runnerTimer: {
|
||||||
fontSize: 90,
|
fontSize: 90,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: theme.colors.primary,
|
color: theme.colors.primary,
|
||||||
},
|
},
|
||||||
|
runnerTimerLandscape: {
|
||||||
|
fontSize: 128,
|
||||||
|
marginVertical: 8,
|
||||||
|
},
|
||||||
runnerNextCard: {
|
runnerNextCard: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
backgroundColor: theme.colors.surface,
|
backgroundColor: theme.colors.surface,
|
||||||
@@ -297,12 +378,18 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: theme.spacing.xl,
|
marginTop: theme.spacing.xl,
|
||||||
gap: theme.spacing.sm,
|
gap: theme.spacing.sm,
|
||||||
},
|
},
|
||||||
|
runnerNextCardLandscape: {
|
||||||
|
marginTop: 16,
|
||||||
|
},
|
||||||
runnerNextText: {
|
runnerNextText: {
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: theme.colors.text,
|
color: theme.colors.text,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
|
runnerNextTextLandscape: {
|
||||||
|
fontSize: 34,
|
||||||
|
},
|
||||||
runnerFooter: {
|
runnerFooter: {
|
||||||
gap: theme.spacing.md,
|
gap: theme.spacing.md,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios",
|
||||||
"web": "expo start --web"
|
"web": "expo start --web",
|
||||||
|
"start_web_comment": "仅用于本机快速调试 UI。正式 Web 版本不是这里构建,见 web_order_box 孪生兄弟目录。"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo run:android",
|
"android": "expo run:android",
|
||||||
"ios": "expo run:ios",
|
"ios": "expo run:ios",
|
||||||
"web": "expo start --web"
|
"web": "expo start --web",
|
||||||
|
"start_web_comment": "仅用于本机快速调试 UI。正式 Web 版本不是这里构建,见孪生兄弟目录 public/tools/web_zen_box。"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
|
|||||||
@@ -385,6 +385,60 @@ input[type='number'] { -moz-appearance: textfield; appearance: textfield; }
|
|||||||
.runner-main { flex: 1; display: flex; flex-direction: column; }
|
.runner-main { flex: 1; display: flex; flex-direction: column; }
|
||||||
.runner-label { font-size: 16px; color: var(--text-secondary); margin-bottom: 4px; }
|
.runner-label { font-size: 16px; color: var(--text-secondary); margin-bottom: 4px; }
|
||||||
|
|
||||||
|
/* 横屏/大屏两栏容器 — JS 用 .runner-split-layout / .runner-col-left / .runner-col-right */
|
||||||
|
.runner-split-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.runner-col-left, .runner-col-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.runner-col-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.runner-col-right .runner-footer {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: landscape) and (min-height: 420px), (min-width: 900px) {
|
||||||
|
.runner-container { padding: 18px 28px; padding-top: calc(12px + env(safe-area-inset-top)); }
|
||||||
|
.runner-header { margin-bottom: 10px; }
|
||||||
|
.runner-progress { margin-bottom: 12px; }
|
||||||
|
.runner-split-layout {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.runner-col-left,
|
||||||
|
.runner-col-right {
|
||||||
|
flex: 1 1 0;
|
||||||
|
}
|
||||||
|
.runner-col-left {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
.runner-col-right {
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-left: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.runner-col-left .runner-next-card { margin-bottom: 0; }
|
||||||
|
.runner-timer {
|
||||||
|
font-size: clamp(72px, 14vw, 128px);
|
||||||
|
margin: 8px 0 16px;
|
||||||
|
}
|
||||||
|
.runner-current { font-size: clamp(28px, 4.2vw, 46px); }
|
||||||
|
.runner-next { font-size: clamp(22px, 3vw, 34px); }
|
||||||
|
}
|
||||||
|
|
||||||
/* 当前动作名 — JS 用 .runner-current */
|
/* 当前动作名 — JS 用 .runner-current */
|
||||||
.runner-current { font-size: 34px; font-weight: 700; margin-bottom: 8px; line-height: 1.2; word-break: break-word; }
|
.runner-current { font-size: 34px; font-weight: 700; margin-bottom: 8px; line-height: 1.2; word-break: break-word; }
|
||||||
.runner-current.current-action { color: var(--primary); }
|
.runner-current.current-action { color: var(--primary); }
|
||||||
|
|||||||
@@ -1261,39 +1261,43 @@
|
|||||||
'<div class="runner-loop-text">第 ' + rs.currentLoop + ' 轮 ' + (totalLoops ? '/ 共 ' + totalLoops + ' 轮' : '/ 无限循环') + ' · 步骤 ' + (rs.currentStepIndex + 1) + ' / ' + totalSteps + '</div>' +
|
'<div class="runner-loop-text">第 ' + rs.currentLoop + ' 轮 ' + (totalLoops ? '/ 共 ' + totalLoops + ' 轮' : '/ 无限循环') + ' · 步骤 ' + (rs.currentStepIndex + 1) + ' / ' + totalSteps + '</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="runner-progress"><div class="runner-progress-bar" id="runnerStepBar"></div></div>' +
|
'<div class="runner-progress"><div class="runner-progress-bar" id="runnerStepBar"></div></div>' +
|
||||||
'<div class="runner-main">' +
|
'<div class="runner-split-layout">' +
|
||||||
'<div class="runner-label">当前动作</div>' +
|
'<div class="runner-col-left runner-main">' +
|
||||||
'<div class="runner-current ' + (isAction ? 'current-action' : 'current-rest') + '">' + escHtml(step.name || '—') + '</div>' +
|
'<div class="runner-label">当前动作</div>' +
|
||||||
'<div class="runner-timer" id="runnerTimer">--:--</div>' +
|
'<div class="runner-current ' + (isAction ? 'current-action' : 'current-rest') + '">' + escHtml(step.name || '—') + '</div>' +
|
||||||
'<div class="runner-next-card">' +
|
'<div class="runner-timer" id="runnerTimer">--:--</div>' +
|
||||||
'<div class="runner-label">下一个动作</div>' +
|
'<div class="runner-next-card">' +
|
||||||
'<div class="runner-next">' + (nextStep ? escHtml(nextStep.name) : '本轮结束') + '</div>' +
|
'<div class="runner-label">下一个动作</div>' +
|
||||||
|
'<div class="runner-next">' + (nextStep ? escHtml(nextStep.name) : '本轮结束') + '</div>' +
|
||||||
|
'</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>' +
|
'<div class="runner-col-right">' +
|
||||||
'<div class="runner-footer">' +
|
'<div class="runner-footer">' +
|
||||||
'<div class="runner-info-card">' +
|
'<div class="runner-info-card">' +
|
||||||
'<div class="runner-info-row"><span class="runner-info-label">已执行</span><span class="runner-info-value">第 ' + rs.currentLoop + ' 轮,步骤 ' + (rs.currentStepIndex + 1) + '/' + totalSteps + '</span></div>' +
|
'<div class="runner-info-row"><span class="runner-info-label">已执行</span><span class="runner-info-value">第 ' + rs.currentLoop + ' 轮,步骤 ' + (rs.currentStepIndex + 1) + '/' + totalSteps + '</span></div>' +
|
||||||
'<div class="runner-info-row"><span class="runner-info-label">预计剩余</span><span class="runner-info-value runner-info-highlight" id="runnerInfoRemaining">—</span></div>' +
|
'<div class="runner-info-row"><span class="runner-info-label">预计剩余</span><span class="runner-info-value runner-info-highlight" id="runnerInfoRemaining">—</span></div>' +
|
||||||
'<div class="runner-info-row"><span class="runner-info-label">全局时长</span><span class="runner-info-value runner-info-highlight">' + (globalDurationSec !== null ? '约 ' + formatDuration(globalDurationSec) : '无限循环') + '</span></div>' +
|
'<div class="runner-info-row"><span class="runner-info-label">全局时长</span><span class="runner-info-value runner-info-highlight">' + (globalDurationSec !== null ? '约 ' + formatDuration(globalDurationSec) : '无限循环') + '</span></div>' +
|
||||||
'</div>' +
|
|
||||||
'<div class="runner-buttons">' +
|
|
||||||
'<button type="button" class="runner-btn" id="btnRunnerPause"><span class="btn-text">' + (rs.status === 'paused' ? '继续' : '暂停') + '</span></button>' +
|
|
||||||
'<button type="button" class="runner-btn" id="btnRunnerSkip"><span class="btn-text">跳过</span></button>' +
|
|
||||||
'<button type="button" class="runner-btn runner-btn-danger" id="btnRunnerStop"><span class="btn-text">结束</span></button>' +
|
|
||||||
'</div>' +
|
|
||||||
(globalDurationSec !== null && globalDurationSec > 0 ? (
|
|
||||||
'<div class="runner-global">' +
|
|
||||||
'<div class="runner-global-header"><span class="runner-global-label">执行进度</span><span class="runner-global-percent" id="runnerGlobalPercent">0%</span></div>' +
|
|
||||||
'<div class="runner-global-bar-wrap"><div class="runner-global-bar" id="runnerGlobalBar"></div></div>' +
|
|
||||||
'<div class="runner-global-stats">' +
|
|
||||||
'<span id="runnerGlobalExecuted">已执行 ' + formatDuration(0) + '</span>' +
|
|
||||||
'<span class="runner-global-divider">/</span>' +
|
|
||||||
'<span>总时长 ' + formatDuration(globalDurationSec) + '</span>' +
|
|
||||||
'<span class="runner-global-divider">·</span>' +
|
|
||||||
'<span class="runner-global-remaining" id="runnerGlobalRemaining">剩余 ' + formatDuration(0) + '</span>' +
|
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>'
|
'<div class="runner-buttons">' +
|
||||||
) : '') +
|
'<button type="button" class="runner-btn" id="btnRunnerPause"><span class="btn-text">' + (rs.status === 'paused' ? '继续' : '暂停') + '</span></button>' +
|
||||||
|
'<button type="button" class="runner-btn" id="btnRunnerSkip"><span class="btn-text">跳过</span></button>' +
|
||||||
|
'<button type="button" class="runner-btn runner-btn-danger" id="btnRunnerStop"><span class="btn-text">结束</span></button>' +
|
||||||
|
'</div>' +
|
||||||
|
(globalDurationSec !== null && globalDurationSec > 0 ? (
|
||||||
|
'<div class="runner-global">' +
|
||||||
|
'<div class="runner-global-header"><span class="runner-global-label">执行进度</span><span class="runner-global-percent" id="runnerGlobalPercent">0%</span></div>' +
|
||||||
|
'<div class="runner-global-bar-wrap"><div class="runner-global-bar" id="runnerGlobalBar"></div></div>' +
|
||||||
|
'<div class="runner-global-stats">' +
|
||||||
|
'<span id="runnerGlobalExecuted">已执行 ' + formatDuration(0) + '</span>' +
|
||||||
|
'<span class="runner-global-divider">/</span>' +
|
||||||
|
'<span>总时长 ' + formatDuration(globalDurationSec) + '</span>' +
|
||||||
|
'<span class="runner-global-divider">·</span>' +
|
||||||
|
'<span class="runner-global-remaining" id="runnerGlobalRemaining">剩余 ' + formatDuration(0) + '</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>'
|
||||||
|
) : '') +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
container.classList.toggle('is-paused', rs.status === 'paused');
|
container.classList.toggle('is-paused', rs.status === 'paused');
|
||||||
|
|||||||
Reference in New Issue
Block a user