This commit is contained in:
oneao committed 2026-01-21 22:28:54 +08:00
1 parent 52a20dfa22
commit 03dfc75c3a
9 files changed
+332 -38

No files matched your search

+36 -1
View File
@@ -1,3 +1,5 @@
import { useEvent } from 'expo'
import ExpoQQ from 'expo-qq'
import { useRouter } from 'expo-router'
import { useEffect } from 'react'
import { Image, Text, View } from 'react-native'
@@ -11,6 +13,39 @@ export default function AuthIndex() {
router.push(url)
}
/// 监听登录结果,你也可以使用ExpoQQ.addListener('onLoginFinished', callback)来实现,但请不要忘记移除监听
const onLoginFinished = useEvent(ExpoQQ, 'onLoginFinished')
/// 国内应用需要在用户接受隐私协议后再初始化
useEffect(() => {
const init = async () => {
await ExpoQQ.init('102826474', 'universal link')
}
init()
}, [])
useEffect(() => {
const run = async () => {
if (!onLoginFinished)
return
console.log('onLoginFinished', onLoginFinished)
const tokenInfoRes = await ExpoQQ.getLoginTokenInfo()
console.log(tokenInfoRes)
const userInfoRes = await ExpoQQ.sendGetUserInfoReq()
console.log(userInfoRes)
}
run()
}, [onLoginFinished])
/// 调用qq登陆
const loginByQQ = async () => {
const res = await ExpoQQ.login(['get_user_info'])
console.log(res)
}
return (
<View className="flex-1">
<CustomHeader
@@ -35,7 +70,7 @@ export default function AuthIndex() {
</Text>
<View className="w-full flex flex-col gap-3 mt-12 px-3">
<Button onPress={() => toPath('/auth/login')}>
<Button onPress={() => loginByQQ()}>
<ButtonText>登录</ButtonText>
</Button>
@@ -1,8 +1,7 @@
import type { MoneyCategoryEnum, MoneyCategoryItem } from '@/types/money/category'
import { useEffect, useRef, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import { Drawer } from 'react-native-drawer-layout'
import { AButton, AInput } from '@/components/common/ao'
import { AButton, ADrawer, AInput } from '@/components/common/ao'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
import { useMoneyCategoryStore } from '@/store/moneyCategory'
@@ -62,12 +61,7 @@ export default function CreateCategory({
}
return (
<Drawer
open={isOpen}
onClose={handleClose}
drawerPosition="bottom"
>
</Drawer>
<ADrawer visible={isOpen} hideModal={handleClose} title="你好" Height="80%" duration={300} />
)
}
@@ -0,0 +1,261 @@
import type { ReactNode } from 'react'
import type { DimensionValue, ViewStyle } from 'react-native'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import {
Animated,
Dimensions,
Modal,
Platform,
StyleSheet,
Text,
TouchableWithoutFeedback,
View,
} from 'react-native'
import LucideIcon from './LucideIcon'
const WINDOW = Dimensions.get('window')
interface DrawerMenuProps {
visible: boolean
duration?: number
menuPosition?: 'left' | 'right' | 'top' | 'bottom'
Height?: DimensionValue
hideModal: () => void
children?: ReactNode
isCircle?: boolean
CircleNum?: number
isShadowHideModal?: boolean
title?: string
}
export default function Drawer({
visible,
Height,
hideModal,
duration = 300,
isCircle,
menuPosition = 'bottom',
children,
CircleNum,
isShadowHideModal = true,
title,
}: DrawerMenuProps) {
const [menuPositionStyle, setMenuPositionStyle] = useState<ViewStyle>()
const [drawerMenuBox, setDrawerMenuBox] = useState<ViewStyle>()
const [innerVisible, setInnerVisible] = useState(false)
const anim = useRef(new Animated.Value(0)).current
/* ================== 同步外部 visible ================== */
useEffect(() => {
if (visible) {
setInnerVisible(true)
open()
}
else {
close()
}
}, [visible])
/* ================== 布局样式 ================== */
useEffect(() => {
switch (menuPosition) {
case 'left':
setMenuPositionStyle({
flex: 1,
backgroundColor: '#fff',
borderTopRightRadius: isCircle ? CircleNum : 0,
borderBottomRightRadius: isCircle ? CircleNum : 0,
})
setDrawerMenuBox({
width: Height,
height: WINDOW.height,
position: 'absolute',
left: 0,
top: 0,
})
break
case 'right':
setMenuPositionStyle({
flex: 1,
backgroundColor: '#fff',
borderTopLeftRadius: isCircle ? CircleNum : 0,
borderBottomLeftRadius: isCircle ? CircleNum : 0,
})
setDrawerMenuBox({
width: Height,
height: WINDOW.height,
position: 'absolute',
right: 0,
top: 0,
})
break
case 'top':
setMenuPositionStyle({
flex: 1,
backgroundColor: '#fff',
borderBottomLeftRadius: isCircle ? CircleNum : 0,
borderBottomRightRadius: isCircle ? CircleNum : 0,
})
setDrawerMenuBox({
width: WINDOW.width,
height: Height,
position: 'absolute',
top: 0,
right: 0,
})
break
case 'bottom':
default:
setMenuPositionStyle({
flex: 1,
backgroundColor: '#fff',
borderTopLeftRadius: isCircle ? CircleNum : 0,
borderTopRightRadius: isCircle ? CircleNum : 0,
})
setDrawerMenuBox({
width: WINDOW.width,
height: Height,
position: 'absolute',
bottom: 0,
right: 0,
})
break
}
}, [menuPosition, Height, isCircle, CircleNum])
/* ================== 动画 ================== */
const open = () => {
Animated.timing(anim, {
toValue: 1,
duration,
useNativeDriver: true,
}).start()
}
const close = () => {
Animated.timing(anim, {
toValue: 0,
duration,
useNativeDriver: true,
}).start(({ finished }) => {
if (finished) {
setInnerVisible(false)
hideModal()
}
})
}
/* ================== 位移 ================== */
const getTransform = useCallback(() => {
switch (menuPosition) {
case 'left':
return {
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [-WINDOW.width, 0],
}),
}
case 'right':
return {
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [WINDOW.width, 0],
}),
}
case 'top':
return {
translateY: anim.interpolate({
inputRange: [0, 1],
outputRange: [-WINDOW.height, 0],
}),
}
case 'bottom':
default:
return {
translateY: anim.interpolate({
inputRange: [0, 1],
outputRange: [WINDOW.height, 0],
}),
}
}
}, [menuPosition])
if (!innerVisible)
return null
return (
<Modal transparent visible animationType="none">
{/* 遮罩 */}
<TouchableWithoutFeedback onPress={() => isShadowHideModal && close()}>
<Animated.View
style={[
styles.mask,
{
opacity: anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.5],
}),
},
]}
/>
</TouchableWithoutFeedback>
{/* Drawer */}
<View style={drawerMenuBox}>
<Animated.View
style={[
styles.container,
{
transform: [getTransform()],
},
]}
>
<View style={menuPositionStyle}>
{menuPosition !== 'bottom' && (
<View style={{ paddingTop: Platform.OS === 'ios' ? 50 : 0 }} />
)}
<View style={styles.headTitle}>
<Text style={styles.title}>{title}</Text>
<LucideIcon
icon="X"
size={24}
color="black"
style={{ position: 'absolute', right: 15, top: 11 }}
onPress={close}
/>
</View>
{children}
</View>
</Animated.View>
</View>
</Modal>
)
}
const styles = StyleSheet.create({
mask: {
...StyleSheet.absoluteFillObject,
backgroundColor: '#000',
},
container: {
height: '100%',
width: '100%',
position: 'absolute',
},
headTitle: {
height: 40,
width: '100%',
padding: 10,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 18,
},
})
@@ -3,6 +3,8 @@ import { Cell, CellGroup } from './Cell'
import Checkbox from './Checkbox'
import DatePicker from './DatePicker'
import Divider from './Divider'
import Drawer from './Drawer'
import Form, { FormRef } from './Form'
import FormItem from './FormItem'
import Input from './Input'
@@ -19,6 +21,7 @@ export {
Checkbox as ACheckbox,
DatePicker as ADatePicker,
Divider as ADivider,
Drawer as ADrawer,
Form as AForm,
FormItem as AFormItem,
FormRef as AFormRef,
+2 -2
View File
@@ -32,8 +32,8 @@ function isWhiteListed(url: string) {
}
const alovaInstance = createAlova({
// baseURL: 'http://192.168.5.6:8086/app',
baseURL: 'http://192.168.1.137:8086/app',
baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,