u
This commit is contained in:
1 parent
2aa2b513eb
commit
200f72eb86
7 files changed
+316
-87
No files matched your search
@@ -14,7 +14,9 @@
|
||||
"@gluestack-ui/core": "^3.0.10",
|
||||
"@gluestack-ui/utils": "^3.0.11",
|
||||
"@gluestack/ui-next-adapter": "^3.0.3",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"expo": "~54.0.20",
|
||||
"expo-blur": "~15.0.7",
|
||||
"expo-constants": "~18.0.10",
|
||||
"expo-font": "~14.0.9",
|
||||
"expo-linking": "~8.0.8",
|
||||
@@ -30,7 +32,8 @@
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-worklets": "0.5.1"
|
||||
"react-native-worklets": "0.5.1",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^6.1.0",
|
||||
|
||||
Generated
+70
@@ -20,9 +20,15 @@ importers:
|
||||
'@gluestack/ui-next-adapter':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
'@react-native-async-storage/async-storage':
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||
expo:
|
||||
specifier: ~54.0.20
|
||||
version: 54.0.20(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.13)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
expo-blur:
|
||||
specifier: ~15.0.7
|
||||
version: 15.0.7(expo@54.0.20)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
expo-constants:
|
||||
specifier: ~18.0.10
|
||||
version: 18.0.10(expo@54.0.20)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||
@@ -71,6 +77,9 @@ importers:
|
||||
react-native-worklets:
|
||||
specifier: 0.5.1
|
||||
version: 0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
zustand:
|
||||
specifier: ^5.0.8
|
||||
version: 5.0.8(@types/react@19.1.17)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0))
|
||||
devDependencies:
|
||||
'@antfu/eslint-config':
|
||||
specifier: ^6.1.0
|
||||
@@ -1521,6 +1530,11 @@ packages:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
|
||||
'@react-native-async-storage/async-storage@2.2.0':
|
||||
resolution: {integrity: sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==}
|
||||
peerDependencies:
|
||||
react-native: ^0.0.0-0 || >=0.65 <1.0
|
||||
|
||||
'@react-native/assets-registry@0.81.5':
|
||||
resolution: {integrity: sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==}
|
||||
engines: {node: '>= 20.19.4'}
|
||||
@@ -2915,6 +2929,13 @@ packages:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
expo-blur@15.0.7:
|
||||
resolution: {integrity: sha512-SugQQbQd+zRPy8z2G5qDD4NqhcD7srBF7fN7O7yq6q7ZFK59VWvpDxtMoUkmSfdxgqONsrBN/rLdk00USADrMg==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
expo-constants@18.0.10:
|
||||
resolution: {integrity: sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw==}
|
||||
peerDependencies:
|
||||
@@ -3332,6 +3353,10 @@ packages:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
is-plain-obj@2.1.0:
|
||||
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-wsl@2.2.0:
|
||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3718,6 +3743,10 @@ packages:
|
||||
memoize-one@6.0.0:
|
||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||
|
||||
merge-options@3.0.4:
|
||||
resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
merge-stream@2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
|
||||
@@ -5211,6 +5240,24 @@ packages:
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
||||
zustand@5.0.8:
|
||||
resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=18.0.0'
|
||||
immer: '>=9.0.6'
|
||||
react: '>=18.0.0'
|
||||
use-sync-external-store: '>=1.2.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
immer:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
use-sync-external-store:
|
||||
optional: true
|
||||
|
||||
zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
|
||||
@@ -7285,6 +7332,11 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-dom: 19.2.0(react@19.1.0)
|
||||
|
||||
'@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))':
|
||||
dependencies:
|
||||
merge-options: 3.0.4
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
'@react-native/assets-registry@0.81.5': {}
|
||||
|
||||
'@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.28.5)':
|
||||
@@ -8985,6 +9037,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
expo-blur@15.0.7(expo@54.0.20)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
expo: 54.0.20(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.13)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
expo-constants@18.0.10(expo@54.0.20)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)):
|
||||
dependencies:
|
||||
'@expo/config': 12.0.10
|
||||
@@ -9420,6 +9478,8 @@ snapshots:
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
is-plain-obj@2.1.0: {}
|
||||
|
||||
is-wsl@2.2.0:
|
||||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
@@ -9859,6 +9919,10 @@ snapshots:
|
||||
|
||||
memoize-one@6.0.0: {}
|
||||
|
||||
merge-options@3.0.4:
|
||||
dependencies:
|
||||
is-plain-obj: 2.1.0
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
@@ -11722,4 +11786,10 @@ snapshots:
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
zustand@5.0.8(@types/react@19.1.17)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)):
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.17
|
||||
react: 19.1.0
|
||||
use-sync-external-store: 1.6.0(react@19.1.0)
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
@@ -6,7 +6,7 @@ import { Animated, Pressable, View } from 'react-native'
|
||||
const tabItems = [
|
||||
{ name: 'finance', icon: <CircleDollarSign strokeWidth={1.5} size={24} /> },
|
||||
{ name: 'dating', icon: <Heart strokeWidth={1.5} size={24} /> },
|
||||
{ name: 'app-center', icon: <Layers strokeWidth={1.5} size={24} /> },
|
||||
{ name: 'app-center', icon: <Layers strokeWidth={2} size={28} /> },
|
||||
{ name: 'chat', icon: <MessageSquare strokeWidth={1.5} size={24} /> },
|
||||
{ name: 'profile', icon: <CircleUserRound strokeWidth={1.5} size={24} /> },
|
||||
]
|
||||
@@ -16,7 +16,7 @@ function CustomTabBar({ state, descriptors, navigation }: any) {
|
||||
const scales = useRef(tabItems.map(() => new Animated.Value(1))).current
|
||||
|
||||
return (
|
||||
<View className="flex-row justify-around bg-white border-t border-gray-200 py-2">
|
||||
<View className="flex-row justify-around bg-white">
|
||||
{state.routes.map((route: any, index: number) => {
|
||||
const item = tabItems.find(i => i.name === route.name)
|
||||
if (!item)
|
||||
@@ -49,7 +49,7 @@ function CustomTabBar({ state, descriptors, navigation }: any) {
|
||||
onPressOut={onPressOut}
|
||||
className="flex-1 items-center justify-center"
|
||||
>
|
||||
<Animated.View style={{ transform: [{ scale: scales[index] }] }} className="items-center p-2">
|
||||
<Animated.View style={{ transform: [{ scale: scales[index] }] }} className="items-center p-3">
|
||||
{React.cloneElement(item.icon, { color })}
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
// App.tsx
|
||||
import React, { useEffect } from 'react'
|
||||
import { Button, Text, View } from 'react-native'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
|
||||
export default function App() {
|
||||
const { userInfo, token, setUserInfo, setToken, logout } = useUserStore()
|
||||
|
||||
// 模拟 App 启动后读取状态
|
||||
useEffect(() => {
|
||||
console.log('用户信息:', userInfo)
|
||||
console.log('Token:', token)
|
||||
}, [userInfo, token])
|
||||
|
||||
export default function Tab() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Tab [Credit]</Text>
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>
|
||||
User Info:
|
||||
{userInfo ? userInfo.username : '未登录'}
|
||||
</Text>
|
||||
<Text>
|
||||
Token:
|
||||
{token || '无'}
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
title="登录"
|
||||
onPress={() => {
|
||||
setUserInfo({ userId: '1', username: '张三' })
|
||||
setToken('abc123')
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="退出登录"
|
||||
onPress={() => {
|
||||
logout()
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
// store/userStore.ts
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware'
|
||||
|
||||
interface UserState {
|
||||
userInfo: { userId: string, username: string } | null
|
||||
token: string | null
|
||||
setUserInfo: (userInfo: UserState['userInfo']) => void
|
||||
setToken: (token: string) => void
|
||||
logout: () => void
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
userInfo: null,
|
||||
token: null,
|
||||
setUserInfo: userInfo => set({ userInfo }),
|
||||
setToken: token => set({ token }),
|
||||
logout: () => {
|
||||
console.log('当前 token:', get().token) // 使用 get 获取最新状态
|
||||
set({ userInfo: null, token: null })
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'user-storage', // 存储在 AsyncStorage 的 key
|
||||
storage: createJSONStorage(() => AsyncStorage),
|
||||
},
|
||||
),
|
||||
)
|
||||
+78
-71
@@ -1,84 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Curved TabBar</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TabBar 毛玻璃示例</title>
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: Arial, sans-serif;
|
||||
background:red; /* 浅灰渐变背景 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end; /* TabBar 固定底部 */
|
||||
}
|
||||
|
||||
/* TabBar 容器 */
|
||||
.tabbar-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
z-index: 10;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* TabBar 背景弧形 */
|
||||
.tabbar-bg {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: white;
|
||||
border-top-left-radius: 30px;
|
||||
border-top-right-radius: 30px;
|
||||
box-shadow: 0 -2px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
.tabbar {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
height: 70px;
|
||||
backdrop-filter: blur(12px); /* 毛玻璃效果 */
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
background-color: rgba(255, 255, 255, 0.4); /* 半透明叠加色,让白色背景可见毛玻璃 */
|
||||
border-top: 1px solid rgba(200, 200, 200, 0.3);
|
||||
border-radius: 20px 20px 0 0;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
/* Tab 按钮 */
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
font-size: 24px;
|
||||
color: #9ca3af;
|
||||
transition: transform 0.2s;
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
.tabbar-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #333333;
|
||||
}
|
||||
.tabbar-item.active {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
/* 中间凸起按钮 */
|
||||
.tab-item.center {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background: #4f46e5;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
line-height: 70px;
|
||||
margin-top: -20px; /* 嵌入凹槽 */
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||
font-size: 28px;
|
||||
}
|
||||
</style>
|
||||
/* 图标示例,用圆圈代替 */
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: currentColor;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<p>这里是页面内容,TabBar 放在底部。</p>
|
||||
<p>背景是浅灰渐变,所以毛玻璃效果会明显。</p>
|
||||
</div>
|
||||
|
||||
<div class="tabbar-container">
|
||||
<div class="tabbar-bg"></div>
|
||||
<div class="tab-item">💰</div>
|
||||
<div class="tab-item">❤️</div>
|
||||
<div class="tab-item center">🗂</div>
|
||||
<div class="tab-item">💬</div>
|
||||
<div class="tab-item">👤</div>
|
||||
</div>
|
||||
|
||||
<div class="tabbar">
|
||||
<a href="#" class="tabbar-item active">
|
||||
<div class="icon"></div>
|
||||
Finance
|
||||
</a>
|
||||
<a href="#" class="tabbar-item">
|
||||
<div class="icon"></div>
|
||||
Dating
|
||||
</a>
|
||||
<a href="#" class="tabbar-item">
|
||||
<div class="icon"></div>
|
||||
App
|
||||
</a>
|
||||
<a href="#" class="tabbar-item">
|
||||
<div class="icon"></div>
|
||||
Chat
|
||||
</a>
|
||||
<a href="#" class="tabbar-item">
|
||||
<div class="icon"></div>
|
||||
Profile
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user