基本功能完成
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
View, Text, TouchableOpacity, Modal, FlatList,
|
||||
StyleSheet, ActivityIndicator, Platform,
|
||||
} from 'react-native';
|
||||
import * as StorageService from '../services/StorageService';
|
||||
import type { SessionInfo } from '../services/StorageService';
|
||||
import { useTheme } from '../design/tokens';
|
||||
import { Radius } from '../design/tokens';
|
||||
|
||||
interface Props {
|
||||
selectedSessionId: string;
|
||||
projectId: string;
|
||||
onSelect: (sessionId: string) => void;
|
||||
}
|
||||
|
||||
export function SessionSelector({ selectedSessionId, projectId, onSelect }: Props) {
|
||||
const theme = useTheme();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sessions, setSessions] = useState<SessionInfo[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const current = sessions.find((s) => s.sessionId === selectedSessionId);
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
setLoading(true);
|
||||
const data = await StorageService.listSessionsByProject(projectId);
|
||||
setSessions(data);
|
||||
setLoading(false);
|
||||
}, [projectId]);
|
||||
|
||||
useEffect(() => { void reload(); }, [reload]);
|
||||
|
||||
const handleOpen = () => {
|
||||
void reload();
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleSelect = (sid: string) => {
|
||||
setVisible(false);
|
||||
if (sid !== selectedSessionId) onSelect(sid);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
style={[S.trigger, { backgroundColor: theme.bg.raised, borderColor: theme.bg.border }]}
|
||||
onPress={handleOpen}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={S.triggerLeft}>
|
||||
<Text style={[S.label, { color: theme.text.muted }]}>测线</Text>
|
||||
<Text style={[S.sessionId, { color: theme.text.primary }]} numberOfLines={1}>
|
||||
{selectedSessionId || '--'}
|
||||
</Text>
|
||||
</View>
|
||||
{current && (
|
||||
<Text style={[S.count, { color: theme.text.ghost }]}>{current.frameCount} 点</Text>
|
||||
)}
|
||||
<Text style={[S.arrow, { color: theme.text.muted }]}>▾</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
|
||||
<TouchableOpacity style={S.backdrop} activeOpacity={1} onPress={() => setVisible(false)} />
|
||||
<View style={[S.sheet, { backgroundColor: theme.bg.surface }]}>
|
||||
<View style={[S.sheetHandle, { backgroundColor: theme.bg.border }]} />
|
||||
<Text style={[S.sheetTitle, { color: theme.text.secondary }]}>选择测线</Text>
|
||||
|
||||
{loading ? (
|
||||
<ActivityIndicator style={{ padding: 24 }} color={theme.blue.fg} />
|
||||
) : sessions.length === 0 ? (
|
||||
<Text style={[S.empty, { color: theme.text.ghost }]}>当前工程无测线</Text>
|
||||
) : (
|
||||
<FlatList
|
||||
data={sessions}
|
||||
keyExtractor={(s) => s.sessionId}
|
||||
style={{ maxHeight: 400 }}
|
||||
renderItem={({ item }) => {
|
||||
const active = item.sessionId === selectedSessionId;
|
||||
const date = new Date(item.createdAt);
|
||||
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[S.item, { borderBottomColor: theme.bg.divider }, active && { backgroundColor: theme.blue.bg }]}
|
||||
onPress={() => handleSelect(item.sessionId)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{active && <View style={[S.dot, { backgroundColor: theme.blue.fg }]} />}
|
||||
<View style={S.itemLeft}>
|
||||
<Text style={[S.itemId, { color: active ? theme.blue.fg : theme.text.primary }]} numberOfLines={1}>
|
||||
{item.sessionId}
|
||||
</Text>
|
||||
<Text style={[S.itemSub, { color: theme.text.muted }]}>
|
||||
{dateStr} · {item.frameCount} 测点
|
||||
</Text>
|
||||
</View>
|
||||
{active && <Text style={[S.activeLabel, { color: theme.blue.fg }]}>当前</Text>}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const MONO = { fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace' } as const;
|
||||
|
||||
const S = StyleSheet.create({
|
||||
trigger: {
|
||||
flexDirection: 'row', alignItems: 'center',
|
||||
paddingHorizontal: 12, paddingVertical: 8,
|
||||
borderRadius: Radius.md, borderWidth: 1, gap: 8,
|
||||
},
|
||||
triggerLeft: { flex: 1, gap: 2 },
|
||||
label: { fontSize: 9, fontWeight: '600' },
|
||||
sessionId: { fontSize: 11, fontWeight: '600', ...MONO },
|
||||
count: { fontSize: 10, ...MONO },
|
||||
arrow: { fontSize: 12 },
|
||||
|
||||
backdrop: { ...StyleSheet.absoluteFill, backgroundColor: 'rgba(0,0,0,0.5)' },
|
||||
sheet: {
|
||||
position: 'absolute', bottom: 0, left: 0, right: 0,
|
||||
maxHeight: '70%',
|
||||
borderTopLeftRadius: 20, borderTopRightRadius: 20,
|
||||
paddingBottom: 32,
|
||||
},
|
||||
sheetHandle: { width: 36, height: 4, borderRadius: 2, alignSelf: 'center', marginTop: 10, marginBottom: 10 },
|
||||
sheetTitle: { fontSize: 14, fontWeight: '700', paddingHorizontal: 16, paddingBottom: 10 },
|
||||
empty: { fontSize: 12, textAlign: 'center', paddingVertical: 24 },
|
||||
|
||||
item: {
|
||||
flexDirection: 'row', alignItems: 'center',
|
||||
paddingHorizontal: 16, paddingVertical: 12,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth, gap: 8,
|
||||
},
|
||||
dot: { width: 6, height: 6, borderRadius: 3 },
|
||||
itemLeft: { flex: 1, gap: 2 },
|
||||
itemId: { fontSize: 12, fontWeight: '600', ...MONO },
|
||||
itemSub: { fontSize: 10 },
|
||||
activeLabel:{ fontSize: 10, fontWeight: '700' },
|
||||
});
|
||||
Reference in New Issue
Block a user