151 lines
5.1 KiB
TypeScript
151 lines
5.1 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, Text, TouchableOpacity, StyleSheet, ScrollView, ActivityIndicator } from 'react-native';
|
|
import { router } from 'expo-router';
|
|
import { DeviceStatusBar } from '../../src/components/DeviceStatusBar';
|
|
import { ParamForm } from '../../src/components/ParamForm';
|
|
import { useDevice } from '../../src/hooks/useDevice';
|
|
import { useDeviceStore } from '../../src/stores/deviceStore';
|
|
import { useDataStore } from '../../src/stores/dataStore';
|
|
|
|
export default function ControlScreen() {
|
|
const { busy, connected, deviceStatus, setup, startContinuous, startSingle, stop } = useDevice();
|
|
const frame = useDataStore((s) => s.currentFrame);
|
|
const [configDirty, setConfigDirty] = useState(false);
|
|
|
|
const handleSetup = async () => {
|
|
await setup();
|
|
setConfigDirty(false);
|
|
};
|
|
|
|
if (!connected) {
|
|
return (
|
|
<View style={styles.notConnected}>
|
|
<Text style={styles.notConnectedText}>未连接设备</Text>
|
|
<TouchableOpacity style={styles.connectBtn} onPress={() => router.replace('/connect')}>
|
|
<Text style={styles.connectBtnText}>前往连接</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const running = deviceStatus === 'running';
|
|
const canStart = !running && !busy;
|
|
const canStop = (running || deviceStatus === 'single') && !busy;
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<DeviceStatusBar />
|
|
|
|
{/* Control buttons */}
|
|
<View style={styles.ctrlRow}>
|
|
<TouchableOpacity
|
|
style={[styles.ctrlBtn, styles.btnStart, !canStart && styles.btnDisabled]}
|
|
onPress={startContinuous}
|
|
disabled={!canStart}
|
|
>
|
|
{busy ? <ActivityIndicator color="#fff" size="small" /> : <Text style={styles.ctrlText}>连续采集</Text>}
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
style={[styles.ctrlBtn, styles.btnSingle, !canStart && styles.btnDisabled]}
|
|
onPress={startSingle}
|
|
disabled={!canStart}
|
|
>
|
|
<Text style={styles.ctrlText}>单次</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
style={[styles.ctrlBtn, styles.btnStop, !canStop && styles.btnDisabled]}
|
|
onPress={stop}
|
|
disabled={!canStop}
|
|
>
|
|
<Text style={styles.ctrlText}>停止</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Last frame info */}
|
|
{frame && (
|
|
<View style={styles.frameInfo}>
|
|
<Text style={styles.frameText}>
|
|
帧 #{frame.frameId} 叠加 {frame.meta ? frame.accNum : '--'} 次 通道 {frame.meta?.channelNum ?? '-'}
|
|
</Text>
|
|
<Text style={styles.frameText}>
|
|
电流峰值: {frame.meta?.current ?? '--'} 温度: {frame.meta?.temperature ?? '--'}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
|
|
{/* Param form */}
|
|
<ScrollView style={styles.formScroll} keyboardShouldPersistTaps="handled">
|
|
<View style={styles.formHeader}>
|
|
<Text style={styles.formTitle}>采集参数</Text>
|
|
{configDirty && <Text style={styles.dirtyBadge}>未下发</Text>}
|
|
</View>
|
|
<ParamForm onAnyChange={() => setConfigDirty(true)} />
|
|
<TouchableOpacity
|
|
style={[styles.setupBtn, busy && styles.btnDisabled]}
|
|
onPress={handleSetup}
|
|
disabled={busy}
|
|
>
|
|
{busy ? <ActivityIndicator color="#fff" /> : <Text style={styles.setupBtnText}>下 发 配 置</Text>}
|
|
</TouchableOpacity>
|
|
<View style={{ height: 24 }} />
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: '#0d0d0d' },
|
|
notConnected: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: 16, backgroundColor: '#0d0d0d' },
|
|
notConnectedText: { color: '#666', fontSize: 16 },
|
|
connectBtn: { backgroundColor: '#4a9eff', borderRadius: 8, paddingVertical: 10, paddingHorizontal: 24 },
|
|
connectBtnText: { color: '#fff', fontWeight: '600' },
|
|
ctrlRow: { flexDirection: 'row', gap: 10, padding: 12 },
|
|
ctrlBtn: {
|
|
flex: 1,
|
|
paddingVertical: 12,
|
|
borderRadius: 8,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
btnStart: { backgroundColor: '#1a5c2a' },
|
|
btnSingle: { backgroundColor: '#1a3a5c' },
|
|
btnStop: { backgroundColor: '#5c1a1a' },
|
|
btnDisabled: { opacity: 0.4 },
|
|
ctrlText: { color: '#fff', fontWeight: '700', fontSize: 14 },
|
|
frameInfo: {
|
|
marginHorizontal: 12,
|
|
backgroundColor: '#181818',
|
|
borderRadius: 8,
|
|
padding: 8,
|
|
marginBottom: 8,
|
|
},
|
|
frameText: { color: '#888', fontSize: 11, fontFamily: 'monospace' },
|
|
formScroll: { flex: 1 },
|
|
formHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 8,
|
|
},
|
|
formTitle: { color: '#4a9eff', fontSize: 13, fontWeight: '600' },
|
|
dirtyBadge: {
|
|
backgroundColor: '#5c3a0a',
|
|
color: '#ffaa00',
|
|
fontSize: 10,
|
|
paddingHorizontal: 6,
|
|
paddingVertical: 2,
|
|
borderRadius: 4,
|
|
},
|
|
setupBtn: {
|
|
margin: 14,
|
|
backgroundColor: '#4a9eff',
|
|
borderRadius: 10,
|
|
paddingVertical: 12,
|
|
alignItems: 'center',
|
|
},
|
|
setupBtnText: { color: '#fff', fontWeight: '700', fontSize: 15, letterSpacing: 2 },
|
|
});
|