react-phone-apps/apps/property-survey/app/index.tsx
lanrtop eab08d8c96 feat: 新增物品分类管理与第四楼支持
- 新增「设置」Tab,支持物品分类的增删改(数据库持久化)
- 物品分类由硬编码枚举迁移为动态分类,通过 CategoriesContext 全局共享
- items 表新增 unit(单位)、spec(规格)字段,附带旧库迁移
- markers 表 CHECK 约束扩展支持第 4 楼(楼顶),附带重建表迁移
- SVG 地图渲染:overdraw 预渲染提升清晰度,嵌入图片改用原生 Image 渲染
- FoldableLayout 改为 showDetail prop 控制动画宽度

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 23:12:52 +09:00

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' },
})