- 添加 monorepo 结构 (apps/property-survey, packages/types) - 配置 Expo + React Native + Android 构建链 - 实现本地数据层 (SQLite/better-sqlite3) - 添加 Blueprint 项目蓝图系统 - 配置 ESLint + Prettier - 添加共享类型定义
171 lines
5.1 KiB
TypeScript
171 lines
5.1 KiB
TypeScript
import { useState } from 'react'
|
|
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
import { Ionicons } from '@expo/vector-icons'
|
|
import { FoldableLayout } from '../src/layout/FoldableLayout'
|
|
import { MarkerList } from '../src/features/map/MarkerList'
|
|
import { ItemFormSheet } from '../src/features/item/ItemFormSheet'
|
|
import { MarkerPhotoPanel } from '../src/features/photo/MarkerPhotoPanel'
|
|
import type { FloorNumber, Marker } from '@repo/types'
|
|
|
|
type AppMode = 'view' | 'edit'
|
|
const FLOORS: FloorNumber[] = [1, 2, 3]
|
|
|
|
export default function RegisterScreen() {
|
|
const insets = useSafeAreaInsets()
|
|
const [mode, setMode] = useState<AppMode>('edit')
|
|
const [currentFloor, setCurrentFloor] = useState<FloorNumber>(1)
|
|
const [selectedMarker, setSelectedMarker] = useState<Marker | null>(null)
|
|
const [markersRefreshKey, setMarkersRefreshKey] = useState(0)
|
|
|
|
function handleMarkerPress(marker: Marker) {
|
|
setSelectedMarker((prev) => (prev?.id === marker.id ? null : marker))
|
|
}
|
|
|
|
function handleModeChange(next: AppMode) {
|
|
setMode(next)
|
|
setSelectedMarker(null)
|
|
}
|
|
|
|
// 右栏:有标记选中时显示内容,否则显示引导占位
|
|
const rightPanel = selectedMarker
|
|
? mode === 'view'
|
|
? <MarkerPhotoPanel marker={selectedMarker} onClose={() => setSelectedMarker(null)} />
|
|
: <ItemFormSheet
|
|
marker={selectedMarker}
|
|
onClose={() => setSelectedMarker(null)}
|
|
onMarkerUpdated={() => setMarkersRefreshKey((k) => k + 1)}
|
|
/>
|
|
: <RightPlaceholder mode={mode} />
|
|
|
|
return (
|
|
<GestureHandlerRootView style={styles.root}>
|
|
{/* 顶栏 */}
|
|
<View style={[styles.topBar, { paddingTop: insets.top }]}>
|
|
<View style={styles.modeToggle}>
|
|
{(['view', 'edit'] as AppMode[]).map((m) => (
|
|
<TouchableOpacity
|
|
key={m}
|
|
style={[styles.modeBtn, mode === m && styles.modeBtnActive]}
|
|
onPress={() => handleModeChange(m)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={[styles.modeBtnText, mode === m && styles.modeBtnTextActive]}>
|
|
{m === 'view' ? '查看' : '编辑'}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
|
|
<View style={styles.floorRow}>
|
|
{FLOORS.map((f) => (
|
|
<TouchableOpacity
|
|
key={f}
|
|
style={[styles.floorBtn, currentFloor === f && styles.floorBtnActive]}
|
|
onPress={() => setCurrentFloor(f)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={[styles.floorBtnText, currentFloor === f && styles.floorBtnTextActive]}>
|
|
{f}F
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{/* 三栏主体 */}
|
|
<FoldableLayout
|
|
left={
|
|
<MarkerList
|
|
floor={currentFloor}
|
|
onMarkerPress={handleMarkerPress}
|
|
selectedMarkerId={selectedMarker?.id}
|
|
refreshKey={markersRefreshKey}
|
|
/>
|
|
}
|
|
right={rightPanel}
|
|
/>
|
|
</GestureHandlerRootView>
|
|
)
|
|
}
|
|
|
|
// 未选中标记时右栏的引导占位
|
|
function RightPlaceholder({ mode }: { mode: AppMode }) {
|
|
return (
|
|
<View style={placeholder.container}>
|
|
<Ionicons
|
|
name={mode === 'view' ? 'eye-outline' : 'create-outline'}
|
|
size={36}
|
|
color="#D1D5DB"
|
|
/>
|
|
<Text style={placeholder.text}>
|
|
{mode === 'view' ? '选择左侧标记点\n查看照片' : '选择左侧标记点\n开始编辑'}
|
|
</Text>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
root: { flex: 1 },
|
|
topBar: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 12,
|
|
paddingBottom: 7,
|
|
backgroundColor: '#fff',
|
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
borderBottomColor: '#E5E7EB',
|
|
},
|
|
modeToggle: {
|
|
flexDirection: 'row',
|
|
backgroundColor: '#F3F4F6',
|
|
borderRadius: 8,
|
|
padding: 2,
|
|
},
|
|
modeBtn: {
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 5,
|
|
borderRadius: 6,
|
|
},
|
|
modeBtnActive: {
|
|
backgroundColor: '#fff',
|
|
shadowColor: '#000',
|
|
shadowOpacity: 0.08,
|
|
shadowRadius: 2,
|
|
shadowOffset: { width: 0, height: 1 },
|
|
elevation: 1,
|
|
},
|
|
modeBtnText: { fontSize: 13, fontWeight: '500', color: '#9CA3AF' },
|
|
modeBtnTextActive: { color: '#111827', fontWeight: '700' },
|
|
floorRow: { flexDirection: 'row', gap: 4 },
|
|
floorBtn: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 5,
|
|
borderRadius: 16,
|
|
backgroundColor: '#F3F4F6',
|
|
},
|
|
floorBtnActive: { backgroundColor: '#10B981' },
|
|
floorBtnText: { fontSize: 13, fontWeight: '600', color: '#6B7280' },
|
|
floorBtnTextActive: { color: '#fff' },
|
|
})
|
|
|
|
const placeholder = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: 12,
|
|
backgroundColor: '#FAFAFA',
|
|
borderLeftWidth: StyleSheet.hairlineWidth,
|
|
borderLeftColor: '#E5E7EB',
|
|
},
|
|
text: {
|
|
fontSize: 13,
|
|
color: '#9CA3AF',
|
|
textAlign: 'center',
|
|
lineHeight: 20,
|
|
},
|
|
})
|