webapp/src/screens/SettingsScreen.tsx

468 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
TouchableOpacity,
Alert,
ActivityIndicator,
ScrollView,
} from 'react-native';
import { AppSettings } from '../types/config';
import {
getSettings,
saveSettings,
downloadConfig,
getConfig,
clearCachedConfig,
} from '../services/configService';
import { useTasks } from '../context/TasksContext';
export default function SettingsScreen() {
const { refreshConfig } = useTasks();
const [settings, setSettings] = useState<AppSettings>({
configFileName: '',
serverUrl: '',
});
const [loading, setLoading] = useState(false);
const [configStatus, setConfigStatus] = useState<string>('未加载');
// 组件加载时获取设置
useEffect(() => {
loadSettings();
checkConfigStatus();
}, []);
const loadSettings = async () => {
try {
const currentSettings = await getSettings();
setSettings(currentSettings);
} catch (error) {
Alert.alert('错误', '加载设置失败');
}
};
const checkConfigStatus = async () => {
const config = await getConfig();
if (config) {
setConfigStatus(
`已加载 (版本: ${config.version}, 任务数: ${
config.tasks?.length || 0
})`,
);
} else {
setConfigStatus('未加载');
}
};
const handleSaveSettings = async () => {
if (!settings.configFileName.trim()) {
Alert.alert('错误', '请输入配置文件名');
return;
}
if (!settings.serverUrl.trim()) {
Alert.alert('错误', '请输入服务器地址');
return;
}
try {
await saveSettings(settings);
Alert.alert('成功', '设置已保存');
} catch (error) {
Alert.alert('错误', '保存设置失败');
}
};
const handleRefreshLocalConfig = async () => {
setLoading(true);
try {
// 清除缓存并重新加载本地配置
await clearCachedConfig();
await refreshConfig();
await checkConfigStatus();
Alert.alert('成功', '本地配置已刷新!');
} catch (error) {
console.error('刷新本地配置失败:', error);
Alert.alert('错误', '刷新本地配置失败');
} finally {
setLoading(false);
}
};
const handleDownloadConfig = async () => {
if (!settings.configFileName.trim()) {
Alert.alert('错误', '请先输入配置文件名');
return;
}
if (!settings.serverUrl.trim()) {
Alert.alert('错误', '请先输入服务器地址');
return;
}
setLoading(true);
try {
// 先保存设置
await saveSettings(settings);
// 下载配置文件
const config = await downloadConfig(
settings.serverUrl,
settings.configFileName,
);
Alert.alert(
'成功',
`配置文件下载成功!\n版本: ${config.version}\n任务数量: ${
config.tasks?.length || 0
}`,
[
{
text: '确定',
onPress: async () => {
await refreshConfig();
checkConfigStatus();
},
},
],
);
} catch (error) {
Alert.alert(
'下载失败',
error instanceof Error ? error.message : '未知错误',
);
} finally {
setLoading(false);
}
};
const handleClearCache = async () => {
Alert.alert(
'确认清除',
'确定要清除缓存的配置文件吗?系统将重新加载本地 config.json 文件。',
[
{ text: '取消', style: 'cancel' },
{
text: '确定',
onPress: async () => {
setLoading(true);
try {
await clearCachedConfig();
await refreshConfig();
checkConfigStatus();
Alert.alert('成功', '缓存已清除,已重新加载配置');
} catch (error) {
Alert.alert('错误', '清除缓存失败');
} finally {
setLoading(false);
}
},
},
],
);
};
const handleTestConnection = async () => {
if (!settings.serverUrl.trim()) {
Alert.alert('错误', '请先输入服务器地址');
return;
}
setLoading(true);
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(`${settings.serverUrl}/health`, {
method: 'GET',
signal: controller.signal,
});
clearTimeout(timeoutId);
if (response.ok) {
Alert.alert('连接成功', '服务器连接正常');
} else {
Alert.alert('连接失败', `HTTP ${response.status}`);
}
} catch (error) {
Alert.alert('连接失败', '无法连接到服务器');
} finally {
setLoading(false);
}
};
return (
<ScrollView style={styles.container}>
<View style={styles.content}>
{loading && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.loadingText}>...</Text>
</View>
)}
{/* 配置状态 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}></Text>
<Text style={styles.statusText}>{configStatus}</Text>
</View>
{/* 配置文件设置 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}></Text>
<View style={styles.inputGroup}>
<Text style={styles.label}></Text>
<TextInput
style={styles.textInput}
value={settings.configFileName}
onChangeText={text =>
setSettings(prev => ({ ...prev, configFileName: text }))
}
placeholder="config"
placeholderTextColor="#999"
autoCapitalize="none"
/>
</View>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.downloadButton]}
onPress={handleDownloadConfig}
disabled={loading}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.refreshButton]}
onPress={handleRefreshLocalConfig}
disabled={loading}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.clearButton]}
onPress={handleClearCache}
disabled={loading}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
</View>
{/* 服务器设置 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}></Text>
<View style={styles.inputGroup}>
<Text style={styles.label}></Text>
<TextInput
style={styles.textInput}
value={settings.serverUrl}
onChangeText={text =>
setSettings(prev => ({ ...prev, serverUrl: text }))
}
placeholder="http://localhost:3000/api"
placeholderTextColor="#999"
autoCapitalize="none"
keyboardType="url"
/>
</View>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.testButton]}
onPress={handleTestConnection}
disabled={loading}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.saveButton]}
onPress={handleSaveSettings}
disabled={loading}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
</View>
{/* 说明信息 */}
<View style={styles.infoSection}>
<Text style={styles.infoTitle}>使</Text>
<Text style={styles.infoText}></Text>
<Text style={styles.infoText}>
1.
</Text>
<Text style={styles.infoText}>2. config.json </Text>
<Text style={styles.infoText}>3. </Text>
<Text style={styles.infoText}>
"下载配置文件"
</Text>
<Text style={styles.infoText}>
"清除缓存"
</Text>
</View>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
content: {
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
section: {
backgroundColor: 'white',
borderRadius: 10,
padding: 20,
marginBottom: 20,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.1,
shadowRadius: 3.84,
elevation: 5,
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 15,
color: '#333',
},
inputGroup: {
marginBottom: 15,
},
label: {
fontSize: 16,
marginBottom: 8,
color: '#333',
fontWeight: '500',
},
inputRow: {
flexDirection: 'row',
alignItems: 'center',
},
textInput: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
fontSize: 16,
backgroundColor: '#fafafa',
},
extension: {
marginLeft: 8,
fontSize: 16,
color: '#666',
fontWeight: '500',
},
statusRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 15,
},
statusLabel: {
fontSize: 16,
color: '#333',
fontWeight: '500',
},
statusText: {
fontSize: 16,
color: '#007AFF',
marginLeft: 8,
},
button: {
borderRadius: 8,
padding: 12,
alignItems: 'center',
minHeight: 48,
justifyContent: 'center',
},
downloadButton: {
backgroundColor: '#007AFF',
flex: 1,
marginRight: 10,
},
refreshButton: {
backgroundColor: '#32D74B',
flex: 1,
},
testButton: {
backgroundColor: '#34C759',
flex: 1,
marginRight: 10,
},
saveButton: {
backgroundColor: '#FF9500',
flex: 1,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
},
buttonRow: {
flexDirection: 'row',
marginTop: 10,
},
infoSection: {
backgroundColor: 'white',
borderRadius: 10,
padding: 20,
marginBottom: 20,
},
infoTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 15,
color: '#333',
},
infoText: {
fontSize: 14,
lineHeight: 20,
color: '#666',
marginBottom: 5,
},
loadingOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(255, 255, 255, 0.8)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1,
},
loadingText: {
marginTop: 10,
fontSize: 16,
color: '#007AFF',
},
clearButton: {
backgroundColor: '#FF3B30',
},
});