diff --git a/code/app/base-app/package.json b/code/app/base-app/package.json
index 58c82e85..fd085342 100644
--- a/code/app/base-app/package.json
+++ b/code/app/base-app/package.json
@@ -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",
diff --git a/code/app/base-app/pnpm-lock.yaml b/code/app/base-app/pnpm-lock.yaml
index 63e4c2df..7c282d66 100644
--- a/code/app/base-app/pnpm-lock.yaml
+++ b/code/app/base-app/pnpm-lock.yaml
@@ -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: {}
diff --git a/code/app/base-app/src/app/(tabs)/_layout.tsx b/code/app/base-app/src/app/(tabs)/_layout.tsx
index 02833c5a..7167ecb7 100644
--- a/code/app/base-app/src/app/(tabs)/_layout.tsx
+++ b/code/app/base-app/src/app/(tabs)/_layout.tsx
@@ -6,7 +6,7 @@ import { Animated, Pressable, View } from 'react-native'
const tabItems = [
{ name: 'finance', icon: },
{ name: 'dating', icon: },
- { name: 'app-center', icon: },
+ { name: 'app-center', icon: },
{ name: 'chat', icon: },
{ name: 'profile', icon: },
]
@@ -16,7 +16,7 @@ function CustomTabBar({ state, descriptors, navigation }: any) {
const scales = useRef(tabItems.map(() => new Animated.Value(1))).current
return (
-
+
{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"
>
-
+
{React.cloneElement(item.icon, { color })}
diff --git a/code/app/base-app/src/app/(tabs)/finance.tsx b/code/app/base-app/src/app/(tabs)/finance.tsx
index c6ab032f..60f2e48f 100644
--- a/code/app/base-app/src/app/(tabs)/finance.tsx
+++ b/code/app/base-app/src/app/(tabs)/finance.tsx
@@ -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 (
-
- Tab [Credit]
+
+
+ User Info:
+ {userInfo ? userInfo.username : '未登录'}
+
+
+ Token:
+ {token || '无'}
+
+
+
)
}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- },
-})
diff --git a/code/app/base-app/src/store/userStore.ts b/code/app/base-app/src/store/userStore.ts
new file mode 100644
index 00000000..37e58b80
--- /dev/null
+++ b/code/app/base-app/src/store/userStore.ts
@@ -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()(
+ 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),
+ },
+ ),
+)
diff --git a/code/app/base-app/test.html b/code/app/base-app/test.html
index be12b94e..fabd4742 100644
--- a/code/app/base-app/test.html
+++ b/code/app/base-app/test.html
@@ -1,84 +1,91 @@
-
-
-Curved TabBar
-
+ /* 图标示例,用圆圈代替 */
+ .icon {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ background-color: currentColor;
+ margin-bottom: 4px;
+ }
+
+
+
这里是页面内容,TabBar 放在底部。
+
背景是浅灰渐变,所以毛玻璃效果会明显。
+
-
-
+
diff --git a/notes/前端开发/react-native/expo.md b/notes/前端开发/react-native/expo.md
index e9877571..c384a86c 100644
--- a/notes/前端开发/react-native/expo.md
+++ b/notes/前端开发/react-native/expo.md
@@ -700,6 +700,99 @@ lottie例子网站:https://lottiefiles.com/free-animations
+## 集成zustand和@react-native-async-storage
+
+安装依赖:
+
+```bash
+npx expo install zustand
+npx expo install @react-native-async-storage/async-storage
+```
+
+使用demo:
+```ts
+// 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()(
+ 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),
+ },
+ ),
+)
+```
+
+```tsx
+// 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])
+
+ return (
+
+
+ User Info:
+ {userInfo ? userInfo.username : '未登录'}
+
+
+ Token:
+ {token || '无'}
+
+
+ {
+ setUserInfo({ userId: '1', username: '张三' })
+ setToken('abc123')
+ }}
+ />
+
+ {
+ logout()
+ }}
+ />
+
+ )
+}
+```
+
+
+
+
+
## 配置深度链接
作用:通过链接就可以跳转到该app。