This commit is contained in:
oneao committed 2026-04-12 21:21:07 +08:00
1 parent 4fe17847d0
commit 61e29b5f76
3 files changed
+208 -63

No files matched your search

+1
View File
@@ -41,6 +41,7 @@
"react-native-reanimated": "4.2.1",
"react-native-safe-area-context": "~5.6.2",
"react-native-screens": "~4.23.0",
"react-native-shadow-2": "^7.1.2",
"react-native-svg": "15.15.3",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.7.2",
+21 -25
View File
@@ -98,6 +98,9 @@ importers:
react-native-screens:
specifier: ~4.23.0
version: 4.23.0(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
react-native-shadow-2:
specifier: ^7.1.2
version: 7.1.2(react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))
react-native-svg:
specifier: 15.15.3
version: 15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
@@ -1002,56 +1005,48 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-arm64-musl@0.44.0':
resolution: {integrity: sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxfmt/binding-linux-ppc64-gnu@0.44.0':
resolution: {integrity: sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-riscv64-gnu@0.44.0':
resolution: {integrity: sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-riscv64-musl@0.44.0':
resolution: {integrity: sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxfmt/binding-linux-s390x-gnu@0.44.0':
resolution: {integrity: sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-x64-gnu@0.44.0':
resolution: {integrity: sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-x64-musl@0.44.0':
resolution: {integrity: sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxfmt/binding-openharmony-arm64@0.44.0':
resolution: {integrity: sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw==}
@@ -1124,56 +1119,48 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-arm64-musl@1.59.0':
resolution: {integrity: sha512-3CtsKp7NFB3OfqQzbuAecrY7GIZeiv7AD+xutU4tefVQzlfmTI7/ygWLrvkzsDEjTlMq41rYHxgsn6Yh8tybmA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-ppc64-gnu@1.59.0':
resolution: {integrity: sha512-K0diOpT3ncDmOfl9I1HuvpEsAuTxkts0VYwIv/w6Xiy9CdwyPBVX88Ga9l8VlGgMrwBMnSY4xIvVlVY/fkQk7Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-gnu@1.59.0':
resolution: {integrity: sha512-xAU7+QDU6kTJJ7mJLOGgo7oOjtAtkKyFZ0Yjdb5cEo3DiCCPFLvyr08rWiQh6evZ7RiUTf+o65NY/bqttzJiQQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-musl@1.59.0':
resolution: {integrity: sha512-KUmZmKlTTyauOnvUNVxK7G40sSSx0+w5l1UhaGsC6KPpOYHenx2oqJTnabmpLJicok7IC+3Y6fXAUOMyexaeJQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-s390x-gnu@1.59.0':
resolution: {integrity: sha512-4usRxC8gS0PGdkHnRmwJt/4zrQNZyk6vL0trCxwZSsAKM+OxhB8nKiR+mhjdBbl8lbMh2gc3bZpNN/ik8c4c2A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-gnu@1.59.0':
resolution: {integrity: sha512-s/rNE2gDmbwAOOP493xk2X7M8LZfI1LJFSSW1+yanz3vuQCFPiHkx4GY+O1HuLUDtkzGlhtMrIcxxzyYLv308w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-musl@1.59.0':
resolution: {integrity: sha512-+yYj1udJa2UvvIUmEm0IcKgc0UlPMgz0nsSTvkPL2y6n0uU5LgIHSwVu4AHhrve6j9BpVSoRksnz8c9QcvITJA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxlint/binding-openharmony-arm64@1.59.0':
resolution: {integrity: sha512-bUplUb48LYsB3hHlQXP2ZMOenpieWoOyppLAnnAhuPag3MGPnt+7caxE3w/Vl9wpQsTA3gzLntQi9rxWrs7Xqg==}
@@ -2438,6 +2425,9 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
colord@2.9.2:
resolution: {integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==}
commander@12.1.0:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
@@ -2951,7 +2941,7 @@ packages:
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -3227,56 +3217,48 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.27.0:
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.27.0:
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.27.0:
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.27.0:
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
@@ -3894,6 +3876,12 @@ packages:
react: '*'
react-native: '*'
react-native-shadow-2@7.1.2:
resolution: {integrity: sha512-K2McEwf3D6MbtofmhQyeEJxTC9L0uebed53vxQcaUvKYIcoDUmfsqgRJtftvx9NHxJwuF5f/K6SfUyhYV2qr4w==}
peerDependencies:
react-native: '*'
react-native-svg: '>=12.1.0'
react-native-svg@15.15.3:
resolution: {integrity: sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA==}
peerDependencies:
@@ -7560,6 +7548,8 @@ snapshots:
color-convert: 2.0.1
color-string: 1.9.1
colord@2.9.2: {}
commander@12.1.0: {}
commander@2.20.3: {}
@@ -9085,6 +9075,12 @@ snapshots:
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
warn-once: 0.1.1
react-native-shadow-2@7.1.2(react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)):
dependencies:
colord: 2.9.2
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
react-native-svg: 15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
dependencies:
css-select: 5.2.2
+186 -38
View File
@@ -1,72 +1,220 @@
import { useSafeAreaInsets } from "react-native-safe-area-context";
import React, { ReactNode } from "react";
import { View, Text } from "react-native";
import Ionicons from "@expo/vector-icons/Ionicons";
import AntDesign from "@expo/vector-icons/AntDesign";
import { ChevronLeft, SwatchBook, CircleChevronLeft, CirclePower } from "lucide-react-native";
import { View, Text, Pressable, StyleSheet } from "react-native";
import {
ChevronLeft,
SlidersHorizontal,
CirclePower,
House,
LayoutGrid,
} from "lucide-react-native";
import { Shadow } from "react-native-shadow-2";
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";
import Svg, { Path } from "react-native-svg";
type LayoutProps = {
children: ReactNode;
};
const AnimatedView = Animated.View;
export default function Layout({ children }: LayoutProps) {
const insets = useSafeAreaInsets();
// 返回按钮
const scale = useSharedValue(1);
const style = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
// 右侧按钮动画
const leftScale = useSharedValue(1);
const rightScale = useSharedValue(1);
const leftStyle = useAnimatedStyle(() => ({
transform: [{ scale: leftScale.value }],
opacity: leftScale.value === 1 ? 1 : 0.7,
}));
const rightStyle = useAnimatedStyle(() => ({
transform: [{ scale: rightScale.value }],
opacity: rightScale.value === 1 ? 1 : 0.7,
}));
return (
<View className="flex-1 bg-white">
{/* 内容区域 */}
<View
className="flex-1"
style={{
flex: 1,
paddingTop: insets.top + 44,
paddingBottom: insets.bottom + 56,
}}
>
{children}
</View>
{/* Header */}
<View
pointerEvents="box-none"
className="absolute top-0 left-0 right-0"
style={{
paddingTop: insets.top,
height: insets.top + 48,
}}
style={{ paddingTop: insets.top }}
>
<View className="h-12 flex-row items-center px-3">
{/* 左区域 */}
<View className="h-12 w-12 rounded-full bg-white items-center justify-center shadow-md">
<ChevronLeft size={28} />
<View className="h-12 flex-row items-center mx-3">
{/* 返回按钮 */}
<Pressable
onPressIn={() => {
scale.value = withSpring(0.8);
}}
onPressOut={() => {
scale.value = withSpring(1);
}}
>
<AnimatedView style={style}>
<Shadow
distance={32}
startColor="rgba(0,0,0,0.03)"
endColor="rgba(255,255,255,0)"
offset={[0, 0]}
paintInside={false}
safeRender
style={styles.iconButtonShadow}
>
<ChevronLeft size={24} />
</Shadow>
</AnimatedView>
</Pressable>
{/* 标题 */}
<View className="flex-1 px-4">
<Text className="text-xl font-semibold tracking-tight text-black/90">记账</Text>
</View>
{/* 中间标题 */}
<View className="flex-1 items-center ">
<Text className="text-xl font-semibold text-black">记账</Text>
</View>
{/* 右侧按钮组 */}
<Shadow
distance={32}
startColor="rgba(0,0,0,0.03)"
endColor="rgba(255,255,255,0)"
offset={[0, 0]}
paintInside={false}
safeRender
style={styles.groupShadow}
>
<Pressable
onPressIn={() => {
leftScale.value = withSpring(0.8);
}}
onPressOut={() => {
leftScale.value = withSpring(1);
}}
>
<AnimatedView style={leftStyle} className="w-14 items-center justify-center">
<SlidersHorizontal size={20} />
</AnimatedView>
</Pressable>
{/* 右区域 */}
<View className="w-28 ">
<View className="h-12 flex-row items-center rounded-full bg-white shadow-md overflow-hidden">
<View className="px-4 items-center justify-center">
<SwatchBook size={22} />
</View>
<View className="w-[1px] h-4 bg-black/10" />
<View className="w-[1px] h-4 bg-black/10" />
<View className="px-4 items-center justify-center">
<CirclePower size={22} />
</View>
</View>
</View>
{/* 右按钮 */}
<Pressable
onPressIn={() => {
rightScale.value = withSpring(0.8);
}}
onPressOut={() => {
rightScale.value = withSpring(1);
}}
>
<AnimatedView style={rightStyle} className="w-14 items-center justify-center">
<CirclePower size={20} />
</AnimatedView>
</Pressable>
</Shadow>
</View>
</View>
{/* tabbar */}
<View
style={{
position: "absolute",
left: 0,
right: 0,
bottom: 0,
paddingBottom: insets.bottom,
}}
className="absolute left-0 right-0 bottom-0"
style={{ paddingBottom: insets.bottom + 6 }}
>
<Text>footer</Text>
<View className="m-3">
<Shadow
distance={32}
startColor="rgba(0,0,0,0.03)"
endColor="rgba(255,255,255,0)"
offset={[0, 0]}
paintInside={false}
safeRender
style={styles.tabbarShadown}
>
<View className="w-full h-16 flex-row items-center justify-between">
<View className="flex-1 items-center justify-center">
<House size={24} color="#666" />
<Text className="text-[10px] text-[#666] mt-1">首页</Text>
</View>
<View className="flex-1 items-center justify-center">
<House size={24} color="#666" />
<Text className="text-[10px] text-[#666] mt-1">分类</Text>
</View>
<View className="flex-1 items-center justify-center" style={{ position: "relative" }}>
<Pressable>
<View
style={{
width: 68,
height: 68,
borderRadius: 999,
backgroundColor: "#5B8CFF",
alignItems: "center",
justifyContent: "center",
borderWidth: 8,
borderColor: "white",
}}
>
<LayoutGrid size={24} color="white" />
</View>
</Pressable>
</View>
<View className="flex-1 items-center justify-center">
<House size={24} color="#666" />
<Text className="text-[10px] text-[#666] mt-1">统计</Text>
</View>
<View className="flex-1 items-center justify-center">
<House size={24} color="#666" />
<Text className="text-[10px] text-[#666] mt-1">我的</Text>
</View>
</View>
</Shadow>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
iconButtonShadow: {
borderRadius: 999,
width: 36,
height: 36,
alignItems: "center",
justifyContent: "center",
borderWidth: 0.5,
borderColor: "rgba(255,255,255,0.6)",
},
groupShadow: {
borderRadius: 999,
height: 36,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
borderWidth: 0.5,
borderColor: "rgba(255,255,255,0.6)",
},
tabbarShadown: {
borderRadius: 999,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
borderWidth: 0.5,
borderColor: "rgba(255,255,255,0.6)",
},
});