- 新增「设置」Tab,支持物品分类的增删改(数据库持久化) - 物品分类由硬编码枚举迁移为动态分类,通过 CategoriesContext 全局共享 - items 表新增 unit(单位)、spec(规格)字段,附带旧库迁移 - markers 表 CHECK 约束扩展支持第 4 楼(楼顶),附带重建表迁移 - SVG 地图渲染:overdraw 预渲染提升清晰度,嵌入图片改用原生 Image 渲染 - FoldableLayout 改为 showDetail prop 控制动画宽度 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
139 lines
4.3 KiB
TypeScript
139 lines
4.3 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 { 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, 4]
|
|
const FLOOR_LABEL: Record<FloorNumber, string> = { 1: '1F', 2: '2F', 3: '3F', 4: '楼顶' }
|
|
|
|
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)}
|
|
/>
|
|
: null
|
|
|
|
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]}>
|
|
{FLOOR_LABEL[f]}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{/* 三栏主体 */}
|
|
<FoldableLayout
|
|
left={
|
|
<MarkerList
|
|
floor={currentFloor}
|
|
onMarkerPress={handleMarkerPress}
|
|
selectedMarkerId={selectedMarker?.id}
|
|
refreshKey={markersRefreshKey}
|
|
/>
|
|
}
|
|
right={rightPanel}
|
|
showDetail={selectedMarker !== null}
|
|
/>
|
|
</GestureHandlerRootView>
|
|
)
|
|
}
|
|
|
|
|
|
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' },
|
|
})
|
|
|