This commit is contained in:
oneao committed 2026-01-29 17:29:08 +08:00
1 parent d33cef825a
commit 295b3306a3
7 files changed
+477 -291

No files matched your search

+1
View File
@@ -15,6 +15,7 @@
"@gluestack-ui/core": "^3.0.12", "@gluestack-ui/core": "^3.0.12",
"@gluestack-ui/utils": "^3.0.11", "@gluestack-ui/utils": "^3.0.11",
"@gluestack/ui-next-adapter": "^3.0.3", "@gluestack/ui-next-adapter": "^3.0.3",
"@legendapp/list": "^2.0.19",
"@legendapp/motion": "2.4.0", "@legendapp/motion": "2.4.0",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/drawer": "^7.5.0", "@react-navigation/drawer": "^7.5.0",
+29 -6
View File
@@ -23,6 +23,9 @@ importers:
'@gluestack/ui-next-adapter': '@gluestack/ui-next-adapter':
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3 version: 3.0.3
'@legendapp/list':
specifier: ^2.0.19
version: 2.0.19(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
'@legendapp/motion': '@legendapp/motion':
specifier: 2.4.0 specifier: 2.4.0
version: 2.4.0(nativewind@4.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@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))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(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))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) version: 2.4.0(nativewind@4.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@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))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(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))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -1149,6 +1152,12 @@ packages:
'@jridgewell/trace-mapping@0.3.31': '@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@legendapp/list@2.0.19':
resolution: {integrity: sha512-zDWg8yg0smKxxk+M7gwAbZAnf5uczohPA+IjqLSkImz7+e9ytxeT0Mq35RBO9RTKODOXfV/aIgm1uqUHLBEdmg==}
peerDependencies:
react: '*'
react-native: '*'
'@legendapp/motion@2.4.0': '@legendapp/motion@2.4.0':
resolution: {integrity: sha512-AAYpRLGvxGD5hIGl9sVHyoUufr66zoH82PuxYcKiPSMdCBI3jwZFWh6CuHjV1leRKVIRk2py1rSvIVabG8eqcw==} resolution: {integrity: sha512-AAYpRLGvxGD5hIGl9sVHyoUufr66zoH82PuxYcKiPSMdCBI3jwZFWh6CuHjV1leRKVIRk2py1rSvIVabG8eqcw==}
peerDependencies: peerDependencies:
@@ -4018,48 +4027,56 @@ packages:
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
lightningcss-linux-arm64-gnu@1.30.2: lightningcss-linux-arm64-gnu@1.30.2:
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.27.0: lightningcss-linux-arm64-musl@1.27.0:
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
lightningcss-linux-arm64-musl@1.30.2: lightningcss-linux-arm64-musl@1.30.2:
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.27.0: lightningcss-linux-x64-gnu@1.27.0:
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
lightningcss-linux-x64-gnu@1.30.2: lightningcss-linux-x64-gnu@1.30.2:
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.27.0: lightningcss-linux-x64-musl@1.27.0:
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
lightningcss-linux-x64-musl@1.30.2: lightningcss-linux-x64-musl@1.30.2:
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.27.0: lightningcss-win32-arm64-msvc@1.27.0:
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
@@ -6098,7 +6115,7 @@ snapshots:
'@babel/helpers@7.28.4': '@babel/helpers@7.28.4':
dependencies: dependencies:
'@babel/template': 7.27.2 '@babel/template': 7.28.6
'@babel/types': 7.28.6 '@babel/types': 7.28.6
'@babel/highlight@7.25.9': '@babel/highlight@7.25.9':
@@ -7181,6 +7198,12 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
'@legendapp/list@2.0.19(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
dependencies:
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
use-sync-external-store: 1.6.0(react@19.1.0)
'@legendapp/motion@2.4.0(nativewind@4.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@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))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(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))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': '@legendapp/motion@2.4.0(nativewind@4.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@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))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(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))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
dependencies: dependencies:
'@legendapp/tools': 2.0.1(react@19.1.0) '@legendapp/tools': 2.0.1(react@19.1.0)
@@ -8692,7 +8715,7 @@ snapshots:
'@types/babel__template@7.4.4': '@types/babel__template@7.4.4':
dependencies: dependencies:
'@babel/parser': 7.28.5 '@babel/parser': 7.28.6
'@babel/types': 7.28.6 '@babel/types': 7.28.6
'@types/babel__traverse@7.28.0': '@types/babel__traverse@7.28.0':
@@ -9038,7 +9061,7 @@ snapshots:
babel-plugin-jest-hoist@29.6.3: babel-plugin-jest-hoist@29.6.3:
dependencies: dependencies:
'@babel/template': 7.27.2 '@babel/template': 7.28.6
'@babel/types': 7.28.6 '@babel/types': 7.28.6
'@types/babel__core': 7.20.5 '@types/babel__core': 7.20.5
'@types/babel__traverse': 7.28.0 '@types/babel__traverse': 7.28.0
@@ -10494,7 +10517,7 @@ snapshots:
istanbul-lib-instrument@5.2.1: istanbul-lib-instrument@5.2.1:
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
'@babel/parser': 7.28.5 '@babel/parser': 7.28.6
'@istanbuljs/schema': 0.1.3 '@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2 istanbul-lib-coverage: 3.2.2
semver: 6.3.1 semver: 6.3.1
@@ -10536,7 +10559,7 @@ snapshots:
jest-message-util@29.7.0: jest-message-util@29.7.0:
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.28.6
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/stack-utils': 2.0.3 '@types/stack-utils': 2.0.3
chalk: 4.1.2 chalk: 4.1.2
@@ -11541,7 +11564,7 @@ snapshots:
parse-json@5.2.0: parse-json@5.2.0:
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.28.6
error-ex: 1.3.4 error-ex: 1.3.4
json-parse-even-better-errors: 2.3.1 json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4 lines-and-columns: 1.2.4
+139 -135
View File
@@ -1,13 +1,14 @@
import { useEvent } from 'expo' import { useEvent } from 'expo'
import ExpoQQ from 'expo-qq' // import ExpoQQ from 'expo-qq'
import { useRouter } from 'expo-router' import { useRouter } from 'expo-router'
import ExpoWeChat from 'expo-wechat' // import ExpoWeChat from 'expo-wechat'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Image, Text, View } from 'react-native' import { Image, Text, View } from 'react-native'
import { AButton, ACheckbox } from '@/components/common/ao' import { AButton, ACheckbox } from '@/components/common/ao'
import CustomHeader from '@/components/custom/Header' import CustomHeader from '@/components/custom/Header'
import { IconQq, IconWeixin } from '@/components/iconfont' import { IconQq, IconWeixin } from '@/components/iconfont'
import { loginQqApi, loginWxApi } from '@/request/api/auth' import { loginQqApi, loginWxApi } from '@/request/api/auth'
import { useAppStore } from '@/store/app'
import { useUserStore } from '@/store/user' import { useUserStore } from '@/store/user'
import { toast } from '@/utils/toast' import { toast } from '@/utils/toast'
@@ -22,161 +23,162 @@ export default function Auth() {
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const appStore = useAppStore()
const onLoginFinished = useEvent(ExpoQQ, 'onLoginFinished') // const onLoginFinished = useEvent(ExpoQQ, 'onLoginFinished')
const onGetUserInfo = useEvent(ExpoQQ, 'onGetUserInfo') // const onGetUserInfo = useEvent(ExpoQQ, 'onGetUserInfo')
const authResult = useEvent(ExpoWeChat, 'onAuthResult') // const authResult = useEvent(ExpoWeChat, 'onAuthResult')
async function onQQLogin() { // async function onQQLogin() {
setLoading(true) // setLoading(true)
setForm(val => ({ // setForm(val => ({
...val, // ...val,
type: 1, // type: 1,
})) // }))
await ExpoQQ.login(['get_user_info']) // await ExpoQQ.login(['get_user_info'])
} // }
async function onWeChatLogin() { // async function onWeChatLogin() {
setLoading(true) // setLoading(true)
// 检查微信是否安装 // // 检查微信是否安装
const isInstalled = await ExpoWeChat.isWXAppInstalled() // const isInstalled = await ExpoWeChat.isWXAppInstalled()
if (!isInstalled) { // if (!isInstalled) {
toast.show({ // toast.show({
title: onLoginFinished?.errorMessage || '请先安装微信', // title: onLoginFinished?.errorMessage || '请先安装微信',
}) // })
setLoading(false) // setLoading(false)
return // return
} // }
setForm(val => ({ // setForm(val => ({
...val, // ...val,
type: 0, // type: 0,
})) // }))
ExpoWeChat.sendAuthRequest('snsapi_userinfo', 'state123') // ExpoWeChat.sendAuthRequest('snsapi_userinfo', 'state123')
} // }
useEffect(() => { // useEffect(() => {
ExpoQQ.init('102826474', '') // ExpoQQ.init('102826474', '')
ExpoWeChat.registerApp('wxdab3e21a1f7e392f', '') // ExpoWeChat.registerApp('wxdab3e21a1f7e392f', '')
}, []) // }, [])
useEffect(() => { // useEffect(() => {
if (!onLoginFinished) { // if (!onLoginFinished) {
return // return
} // }
if (!onLoginFinished.success) { // if (!onLoginFinished.success) {
toast.show({ // toast.show({
title: onLoginFinished?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权', // title: onLoginFinished?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权',
}) // })
setLoading(false) // setLoading(false)
return // return
} // }
const fetchUserInfo = async () => { // const fetchUserInfo = async () => {
try { // try {
const tokenInfo = await ExpoQQ.getLoginTokenInfo() // const tokenInfo = await ExpoQQ.getLoginTokenInfo()
if (!tokenInfo.openId) { // if (!tokenInfo.openId) {
toast.show({ // toast.show({
title: tokenInfo?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权', // title: tokenInfo?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权',
}) // })
setLoading(false) // setLoading(false)
return // return
} // }
setForm(val => ({ // setForm(val => ({
...val, // ...val,
openid: String(tokenInfo.openId), // openid: String(tokenInfo.openId),
})) // }))
await ExpoQQ.sendGetUserInfoReq() // await ExpoQQ.sendGetUserInfoReq()
} // }
catch (e) { // catch (e) {
console.error('QQ 登录未完成', e) // console.error('QQ 登录未完成', e)
setLoading(false) // setLoading(false)
} // }
} // }
fetchUserInfo() // fetchUserInfo()
}, [onLoginFinished]) // }, [onLoginFinished])
// QQ登录 // // QQ登录
useEffect(() => { // useEffect(() => {
const handleLogin = async () => { // const handleLogin = async () => {
if (!onGetUserInfo) // if (!onGetUserInfo)
return // return
try { // try {
const loginRes = await loginQqApi({ // const loginRes = await loginQqApi({
openid: form.openid, // openid: form.openid,
username: onGetUserInfo.nickname || '', // username: onGetUserInfo.nickname || '',
avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2, // avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2,
}) // })
if (loginRes.isSuccess) { // if (loginRes.isSuccess) {
userStore.setUserInfo({ // userStore.setUserInfo({
token: loginRes.data.token, // token: loginRes.data.token,
id: loginRes.data.userId, // id: loginRes.data.userId,
username: loginRes.data.username, // username: loginRes.data.username,
avatar: loginRes.data.avatar, // avatar: loginRes.data.avatar,
}) // })
console.log(loginRes.data.token) // console.log(loginRes.data.token)
router.push('/') // router.push('/')
} // }
} // }
catch (err) { // catch (err) {
console.error('登录接口出错', err) // console.error('登录接口出错', err)
} // }
finally { // finally {
setLoading(false) // setLoading(false)
} // }
} // }
handleLogin() // handleLogin()
}, [onGetUserInfo]) // }, [onGetUserInfo])
// 微信登录 // // 微信登录
useEffect(() => { // useEffect(() => {
if (!authResult) // if (!authResult)
return // return
const handleLogin = async () => { // const handleLogin = async () => {
if (authResult.errorCode === 0) { // if (authResult.errorCode === 0) {
try { // try {
const loginRes = await loginWxApi({ // const loginRes = await loginWxApi({
code: authResult.code, // code: authResult.code,
}) // })
if (loginRes.isSuccess) { // if (loginRes.isSuccess) {
userStore.setUserInfo({ // userStore.setUserInfo({
token: loginRes.data.token, // token: loginRes.data.token,
id: loginRes.data.userId, // id: loginRes.data.userId,
username: loginRes.data.username, // username: loginRes.data.username,
avatar: loginRes.data.avatar, // avatar: loginRes.data.avatar,
}) // })
router.push('/') // router.push('/')
} // }
} // }
catch (err) { // catch (err) {
console.error('登录接口出错', err) // console.error('登录接口出错', err)
} // }
finally { // finally {
setLoading(false) // setLoading(false)
} // }
} // }
else { // else {
console.log('授权失败:', authResult.errorMessage) // console.log('授权失败:', authResult.errorMessage)
setLoading(false) // setLoading(false)
} // }
} // }
handleLogin() // handleLogin()
}, [authResult]) // }, [authResult])
function testLogin() { function testLogin() {
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3NjY3ODU5ODIxNjUwNjEsImlhdCI6MTc2OTM0ODI0Nn0.17ZNaHD3HSLM8dVn_JQK-gvsG9MKj47IRYV9D4xZkzs // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3NjY3ODU5ODIxNjUwNjEsImlhdCI6MTc2OTM0ODI0Nn0.17ZNaHD3HSLM8dVn_JQK-gvsG9MKj47IRYV9D4xZkzs
@@ -188,6 +190,8 @@ export default function Auth() {
avatar: 'http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100', avatar: 'http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100',
}) })
appStore.init()
router.push('/') router.push('/')
} }
@@ -208,7 +212,7 @@ export default function Auth() {
</View> </View>
<View className="flex flex-col gap-4"> <View className="flex flex-col gap-4">
<AButton {/* <AButton
leftIcon={ leftIcon={
<IconWeixin color="#fff" /> <IconWeixin color="#fff" />
} }
@@ -227,7 +231,7 @@ export default function Auth() {
onPress={onQQLogin} onPress={onQQLogin}
> >
QQ QQ
</AButton> </AButton> */}
<AButton <AButton
variant="outline" variant="outline"
@@ -1,6 +1,8 @@
import type { LegendListRef } from '@legendapp/list'
import type { MoneyCategoryItem } from '@/types/money/category' import type { MoneyCategoryItem } from '@/types/money/category'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { LegendList } from '@legendapp/list'
import { FlatList, Pressable, Text, View } from 'react-native' import { useEffect, useRef, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import { AButton, ADrawer, AInput } from '@/components/common/ao' import { AButton, ADrawer, AInput } from '@/components/common/ao'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon' import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
import { useMoneyCategoryStore } from '@/store/moneyCategory' import { useMoneyCategoryStore } from '@/store/moneyCategory'
@@ -33,11 +35,8 @@ export default function CreateCategory({
const [form, setForm] = useState<EditCategoryForm>({ icon: '', name: '' }) const [form, setForm] = useState<EditCategoryForm>({ icon: '', name: '' })
const [confirmDisabled, setConfirmDisabled] = useState(true) const [confirmDisabled, setConfirmDisabled] = useState(true)
const [confirmLoading, setConfirmLoading] = useState(false) const [confirmLoading, setConfirmLoading] = useState(false)
const flatListData = Object.entries(moneyCategoryStore.ICON_GROUPS) const listRef = useRef<LegendListRef>(null)
const flatListData2 = Object.values(moneyCategoryStore.ICON_GROUPS).flat()
const flatListRef = useRef<FlatList<any>>(null)
// Drawer 打开/关闭控制
useEffect(() => { useEffect(() => {
setIsOpen(open) setIsOpen(open)
if (!open) if (!open)
@@ -47,24 +46,24 @@ export default function CreateCategory({
// 确认按钮状态 // 确认按钮状态
useEffect(() => { useEffect(() => {
setConfirmDisabled(!(form.name && form.name.length <= 5 && form.icon)) setConfirmDisabled(!(form.name && form.name.length <= 5 && form.icon))
const itemIndex = flatListData2.findIndex(item => item === form.icon)
if (itemIndex === -1 || !flatListRef.current)
return
if (form.icon) {
setTimeout(() => {
if (flatListRef.current) {
flatListRef.current.scrollToIndex({
index: Math.floor(itemIndex / 5),
animated: true,
viewPosition: 0.5,
})
}
}, 0)
}
}, [form.name, form.icon]) }, [form.name, form.icon])
useEffect(() => {
if (!form.icon || !listRef.current) {
return
}
const index = moneyCategoryStore.findIconGroupsRow(form.icon)
if (index !== -1) {
listRef.current.scrollToIndex({
index,
viewPosition: 0.5,
animated: true,
})
}
}, [form.icon])
// 编辑已有分类时填充数据 // 编辑已有分类时填充数据
useEffect(() => { useEffect(() => {
if (category) if (category)
@@ -77,55 +76,6 @@ export default function CreateCategory({
onClose?.() onClose?.()
} }
// 渲染每组 Icon
const renderGroup = useCallback(
({ item }: { item: [string, string[]] }) => {
const [groupName, icons] = item
return (
<View className="mb-4">
<Text className="text-center text-sm mb-3">{groupName}</Text>
<View className="flex flex-row flex-wrap">
{icons.map((icon) => {
const selected = form.icon === icon
return (
<Pressable
key={icon}
className="w-1/5 mb-3 flex items-center"
onPress={() =>
setForm(v => ({
...v,
icon,
}))}
>
<View
style={{
width: 46,
height: 46,
borderRadius: 28,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: selected ? '#333' : '#F3F4F6',
}}
>
<MoneyCategoryIcon
name={icon}
size={32}
color={selected ? '#fff' : '#000'}
/>
</View>
</Pressable>
)
})}
</View>
</View>
)
},
[form.icon],
)
return ( return (
<ADrawer <ADrawer
visible={isOpen} visible={isOpen}
@@ -161,15 +111,72 @@ export default function CreateCategory({
</View> </View>
)} )}
> >
<FlatList <View className="flex-1">
ref={flatListRef} <AInput
data={flatListData} className="mx-4 mb-4"
keyExtractor={([groupName]) => groupName} placeholder="分类名称"
renderItem={renderGroup} value={form.name}
getItemLayout={(data, index) => { onChangeText={text => setForm(prevForm => ({ ...prevForm, name: text }))}
return { length: 80, offset: 80 * index, index } />
}} <LegendList
/> ref={listRef}
data={moneyCategoryStore.IconGroups}
extraData={form.icon}
keyExtractor={item => String(item.index)}
recycleItems
initialContainerPoolRatio={20}
style={{ flex: 1 }}
initialScrollIndex={moneyCategoryStore.findIconGroupsRow(category?.icon || '')}
renderItem={({ item }) => (
<View className="mb-1" key={item.index}>
{
item.type === 'title'
? (
<Text className="text-center text-sm mb-3">{item.value}</Text>
)
: (
<View className="flex flex-row flex-wrap">
{item.value.map((icon) => {
const selected = form.icon === icon
return (
<Pressable
key={icon}
className="w-1/5 mb-3 flex items-center"
onPress={() => {
setForm(prevForm => ({
...prevForm,
icon,
}))
}}
>
<View
style={{
width: 46,
height: 46,
borderRadius: 28,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: selected ? '#333' : '#F3F4F6',
}}
>
<MoneyCategoryIcon
name={icon}
size={32}
color={selected ? '#fff' : '#000'}
/>
</View>
</Pressable>
)
})}
</View>
)
}
</View>
)}
/>
</View>
</ADrawer> </ADrawer>
) )
} }
+2 -2
View File
@@ -28,8 +28,8 @@ function isWhiteListed(url: string) {
} }
const alovaInstance = createAlova({ const alovaInstance = createAlova({
baseURL: 'http://192.168.5.6:8086/app', // baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.137:8086/app', baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(), requestAdapter: axiosRequestAdapter(),
timeout: 5000, timeout: 5000,
cacheFor: null, cacheFor: null,
+199 -68
View File
@@ -1,10 +1,10 @@
import type { MoneyCategoryItem } from '@/types/money/category' import type { IconGroupType, MoneyCategoryItem } from '@/types/money/category'
import { create } from 'zustand' import { create } from 'zustand'
import { createMoneyCategoryApi, deleteMoneyCategoryApi, queryMoneyCategoryApi, updateMoneyCategoryApi, updateMoneyCategorySortApi } from '@/request/api/money/category' import { createMoneyCategoryApi, deleteMoneyCategoryApi, queryMoneyCategoryApi, updateMoneyCategoryApi, updateMoneyCategorySortApi } from '@/request/api/money/category'
import { MoneyCategoryEnum } from '@/types/money/category' import { MoneyCategoryEnum } from '@/types/money/category'
interface MoneyCategoryStore { interface MoneyCategoryStore {
ICON_GROUPS: Record<string, string[]> IconGroups: IconGroupType[]
categories: MoneyCategoryItem[] categories: MoneyCategoryItem[]
init: () => Promise<void> init: () => Promise<void>
@@ -22,77 +22,195 @@ interface MoneyCategoryStore {
isDuplicateCategory: (name: string, type: number, excludeId: number | null) => boolean isDuplicateCategory: (name: string, type: number, excludeId: number | null) => boolean
findIconGroupsRow: (iconName: string) => number
clear: () => void clear: () => void
} }
export const useMoneyCategoryStore = create<MoneyCategoryStore>()( export const useMoneyCategoryStore = create<MoneyCategoryStore>()(
(set, get) => ({ (set, get) => ({
ICON_GROUPS: { IconGroups: [
餐饮: [ {
'money_canyin', index: 0,
'money_yinpin', type: 'title',
'money_lingshi', value: '餐饮',
'money_xiaoye', },
], {
娱乐: [ index: 1,
'money_yule', type: 'icons',
'money_youxi', value: [
'money_dianying', 'money_canyin',
], 'money_yinpin',
交通: [ 'money_lingshi',
'money_jiaotong', 'money_xiaoye',
'money_tingchefei', ],
'money_jiayou', },
], {
生活消费: [ index: 2,
'money_lijin', type: 'title',
'money_jiadian', value: '娱乐',
'money_riyongpin', },
'money_shucai', {
'money_meifa', index: 3,
'money_shuidian', type: 'icons',
'money_wenju', value: [
'money_zhufang', 'money_yule',
'money_baoxian', 'money_youxi',
'money_baojianpin', 'money_dianying',
'money_huafei', ],
'money_lvyou', },
'money_gouwu', {
'money_shuben', index: 4,
'money_chongwu', type: 'title',
'money_ruanjian', value: '交通',
], },
健康: [ {
'money_tijian', index: 5,
'money_yao', type: 'icons',
'money_jiuyi', value: [
], 'money_jiaotong',
公益: [ 'money_tingchefei',
'money_gongyi', 'money_jiayou',
], ],
兼职与收入: [ },
'money_gongzi', {
'money_jianzhi', index: 6,
], type: 'title',
鲜花与水果: [ value: '生活消费',
'money_shuiguo', },
'money_xianhua', {
], index: 7,
转账与转移: [ type: 'icons',
'money_zhuanzhang', value: [
], 'money_lijin',
家庭与家电: [ 'money_jiadian',
'money_kuaidi', 'money_riyongpin',
'money_weixiu', 'money_shucai',
'money_jiaju', 'money_meifa',
'money_muying', ],
'money_liwu', },
], {
运动与娱乐: [ index: 8,
'money_yundong', type: 'icons',
'money_peixun', value: [
], 'money_shuidian',
}, 'money_wenju',
'money_zhufang',
'money_baoxian',
'money_baojianpin',
],
},
{
index: 9,
type: 'icons',
value: [
'money_huafei',
'money_lvyou',
'money_gouwu',
'money_shuben',
'money_chongwu',
],
},
{
index: 10,
type: 'icons',
value: [
'money_ruanjian',
],
},
{
index: 11,
type: 'title',
value: '健康',
},
{
index: 12,
type: 'icons',
value: [
'money_tijian',
'money_yao',
'money_jiuyi',
],
},
{
index: 13,
type: 'title',
value: '公益',
},
{
index: 14,
type: 'icons',
value: [
'money_gongyi',
],
},
{
index: 15,
type: 'title',
value: '兼职与收入',
},
{
index: 16,
type: 'icons',
value: [
'money_gongzi',
'money_jianzhi',
],
},
{
index: 17,
type: 'title',
value: '鲜花与水果',
},
{
index: 18,
type: 'icons',
value: [
'money_shuiguo',
'money_xianhua',
],
},
{
index: 19,
type: 'title',
value: '转账与转移',
},
{
index: 20,
type: 'icons',
value: [
'money_zhuanzhang',
],
},
{
index: 21,
type: 'title',
value: '家庭与家电',
},
{
index: 22,
type: 'icons',
value: [
'money_kuaidi',
'money_weixiu',
'money_jiaju',
'money_muying',
'money_liwu',
],
},
{
index: 23,
type: 'title',
value: '运动与娱乐',
},
{
index: 24,
type: 'icons',
value: [
'money_yundong',
'money_peixun',
],
},
],
categories: [], categories: [],
@@ -247,5 +365,18 @@ export const useMoneyCategoryStore = create<MoneyCategoryStore>()(
&& c.id !== excludeId, && c.id !== excludeId,
) )
}, },
findIconGroupsRow: (iconName) => {
const iconGroups = get().IconGroups
for (const group of iconGroups) {
if (group.type === 'icons' && group.value.includes(iconName)) {
return group.index
}
}
return 0
},
}), }),
) )
@@ -52,3 +52,23 @@ export interface UpdateMoneyCategoryParams {
} }
export interface UpdateMoneyCategoryResult { export interface UpdateMoneyCategoryResult {
} }
// 图标分组基础接口
interface BaseIconGroupType {
index: number
}
// 当 type 为 'title' 时,value 必须是 string
interface TitleIconGroupType extends BaseIconGroupType {
type: 'title'
value: string
}
// 当 type 为 'icons' 时,value 必须是 string[]
interface IconsIconGroupType extends BaseIconGroupType {
type: 'icons'
value: string[]
}
// 联合类型
export type IconGroupType = TitleIconGroupType | IconsIconGroupType