u
This commit is contained in:
1 parent
8f3f6982db
commit
5a79414319
10 files changed
+354
-292
No files matched your search
@@ -1,11 +1,80 @@
|
|||||||
import { TabSlot } from 'expo-router/ui'
|
import type { icons } from 'lucide-react-native'
|
||||||
import { Text, View } from 'react-native'
|
import type { SortableGridRenderItem } from 'react-native-sortables'
|
||||||
|
import { Slot, usePathname, useRouter } from 'expo-router'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { Pressable, Text, TouchableOpacity, Vibration, View } from 'react-native'
|
||||||
|
import Animated, { useAnimatedRef } from 'react-native-reanimated'
|
||||||
|
import Sortable from 'react-native-sortables'
|
||||||
|
import { ADrawer, LucideIcon } from '@/components/common/ao'
|
||||||
|
|
||||||
|
interface NavItem {
|
||||||
|
label: string
|
||||||
|
path: string
|
||||||
|
icon: keyof typeof icons
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAV_MENUS: NavItem[] = [
|
||||||
|
{ label: '记账', path: '/money', icon: 'CircleDollarSign' },
|
||||||
|
{ label: '恋爱记', path: '/love', icon: 'Heart' },
|
||||||
|
{ label: '聊天', path: '/chat', icon: 'MessageCircle' },
|
||||||
|
{ label: '个人中心', path: '/user', icon: 'CircleUserRound' },
|
||||||
|
]
|
||||||
|
|
||||||
export default function TabsLayout() {
|
export default function TabsLayout() {
|
||||||
|
const scrollableRef = useAnimatedRef<Animated.ScrollView>()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const [showNavDrawer, setShowNavDrawer] = useState(false)
|
||||||
|
const [draggingId, setDraggingId] = useState<string>('')
|
||||||
|
const [currentNav, setCurrentNav] = useState<NavItem>({
|
||||||
|
label: '',
|
||||||
|
path: '',
|
||||||
|
icon: 'AArrowDown',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const current = NAV_MENUS.find(item => item.path === pathname)
|
||||||
|
if (current) {
|
||||||
|
setCurrentNav(current)
|
||||||
|
}
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
const refreshPage = () => {
|
||||||
|
router.replace(pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderNavItem = useCallback<SortableGridRenderItem<NavItem>>(
|
||||||
|
({ item }) => (
|
||||||
|
<Pressable
|
||||||
|
className="
|
||||||
|
flex
|
||||||
|
flex-col
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
bg-[white]
|
||||||
|
rounded-md
|
||||||
|
shadow-md
|
||||||
|
p-3
|
||||||
|
"
|
||||||
|
onPress={() => {
|
||||||
|
setShowNavDrawer(false)
|
||||||
|
router.push(item.path)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LucideIcon icon={item.icon} size={24} strokeWidth={2} clickable={false} />
|
||||||
|
<Text className="text-sm mt-2">
|
||||||
|
{item.label}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="flex-1 bg-gray-100">
|
<View className="flex-1 bg-gray-100">
|
||||||
<View className="flex-1 px-4 pt-4 pb-24">
|
<View className="flex-1 bg-white">
|
||||||
<TabSlot />
|
<Slot />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
@@ -18,10 +87,55 @@ export default function TabsLayout() {
|
|||||||
gap-4
|
gap-4
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<View className="bg-white rounded-md shadow-lg h-12 w-12"></View>
|
<View className="bg-white rounded-md shadow-lg h-12 w-12">
|
||||||
<View className="flex-1 bg-white rounded-md shadow-lg h-12"></View>
|
<TouchableOpacity className="w-full h-full items-center justify-center">
|
||||||
<View className="bg-white rounded-md shadow-lg h-12 w-12"></View>
|
<LucideIcon icon="Bot" size={26} clickable={false} strokeWidth={2} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="flex-1 bg-white rounded-md shadow-lg h-12">
|
||||||
|
<TouchableOpacity
|
||||||
|
className="w-full h-full items-center justify-center flex flex-row"
|
||||||
|
onPress={() => {
|
||||||
|
setShowNavDrawer(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LucideIcon icon={currentNav.icon} size={20} strokeWidth={2} />
|
||||||
|
<Text className="font-semibold text-lg ml-2">{currentNav.label}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<View className="bg-white rounded-md shadow-lg h-12 w-12">
|
||||||
|
<TouchableOpacity className="w-full h-full items-center justify-center" onPress={refreshPage}>
|
||||||
|
<LucideIcon icon="RefreshCcw" size={26} clickable={false} strokeWidth={2} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<ADrawer
|
||||||
|
visible={showNavDrawer}
|
||||||
|
height="60%"
|
||||||
|
title="菜单导航"
|
||||||
|
isCircle
|
||||||
|
hideModal={() => {
|
||||||
|
setShowNavDrawer(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Animated.ScrollView
|
||||||
|
ref={scrollableRef}
|
||||||
|
className="px-4 py-2 flex-1"
|
||||||
|
>
|
||||||
|
<Sortable.Grid
|
||||||
|
columnGap={16}
|
||||||
|
rowGap={16}
|
||||||
|
columns={3}
|
||||||
|
data={NAV_MENUS}
|
||||||
|
keyExtractor={item => item.path}
|
||||||
|
renderItem={renderNavItem}
|
||||||
|
scrollableRef={scrollableRef}
|
||||||
|
autoScrollActivationOffset={200}
|
||||||
|
/>
|
||||||
|
</Animated.ScrollView>
|
||||||
|
</ADrawer>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
|
import { useRouter } from 'expo-router'
|
||||||
import { Text, View } from 'react-native'
|
import { Text, View } from 'react-native'
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
|
import { AButton } from '@/components/common/ao'
|
||||||
// 这个页面的作用是作为占位页,避免进入 (tabs) layout 时出现 404 页面。
|
// 这个页面的作用是作为占位页,避免进入 (tabs) layout 时出现 404 页面。
|
||||||
// 如果想指定默认页面,可以通过 router.replace 或 TabTrigger 指向具体子页面。
|
// 如果想指定默认页面,可以通过 router.replace 或 TabTrigger 指向具体子页面。
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<SafeAreaView>
|
||||||
<Text>Index</Text>
|
<View>
|
||||||
</View>
|
<AButton onPress={() => {
|
||||||
|
router.push('/money')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
测试
|
||||||
|
</AButton>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ export default function NotFound() {
|
|||||||
title="(´・ω・`) 页面走丢了~"
|
title="(´・ω・`) 页面走丢了~"
|
||||||
description="回首页看看吧!"
|
description="回首页看看吧!"
|
||||||
footer={(
|
footer={(
|
||||||
<AButton onPress={() => router.replace('/')} leftIcon={{ icon: 'ArrowLeft', size: 18, color: '#fff' }}>
|
<AButton onPress={() => router.replace('/')}>
|
||||||
返回应用首页
|
返回应用首页
|
||||||
</AButton>
|
</AButton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) {
|
|||||||
title="(´・ω・`) 出错啦~"
|
title="(´・ω・`) 出错啦~"
|
||||||
description={`发生了一点小问题:\n${error?.message || '请稍后再试'}`}
|
description={`发生了一点小问题:\n${error?.message || '请稍后再试'}`}
|
||||||
footer={(
|
footer={(
|
||||||
<AButton onPress={retry} leftIcon={{ icon: 'RotateCcw', size: 18, color: '#fff' }}>
|
<AButton onPress={retry}>
|
||||||
再试一下
|
再试一下
|
||||||
</AButton>
|
</AButton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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'
|
||||||
@@ -11,7 +11,7 @@ import { loginQqApi, loginWxApi } from '@/request/api/auth'
|
|||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
|
|
||||||
export default function LoginScreen() {
|
export default function Auth() {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [isAgree, setIsAgree] = useState(true)
|
const [isAgree, setIsAgree] = useState(true)
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
@@ -23,160 +23,160 @@ export default function LoginScreen() {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
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
|
||||||
@@ -208,7 +208,7 @@ export default function LoginScreen() {
|
|||||||
</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 +227,7 @@ export default function LoginScreen() {
|
|||||||
onPress={onQQLogin}
|
onPress={onQQLogin}
|
||||||
>
|
>
|
||||||
QQ
|
QQ
|
||||||
</AButton>
|
</AButton> */}
|
||||||
|
|
||||||
<AButton
|
<AButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -149,11 +149,6 @@ export default function MoneyCategorySetting() {
|
|||||||
<AButton
|
<AButton
|
||||||
className="rounded-full"
|
className="rounded-full"
|
||||||
size="sm"
|
size="sm"
|
||||||
leftIcon={{
|
|
||||||
icon: 'Plus',
|
|
||||||
color: '#fff',
|
|
||||||
clickable: false,
|
|
||||||
}}
|
|
||||||
onPress={(() =>
|
onPress={(() =>
|
||||||
setshowEditCategory(true)
|
setshowEditCategory(true)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function Drawer({
|
|||||||
isCircle,
|
isCircle,
|
||||||
menuPosition = 'bottom',
|
menuPosition = 'bottom',
|
||||||
children,
|
children,
|
||||||
CircleNum,
|
CircleNum = 8,
|
||||||
isShadowHideModal = true,
|
isShadowHideModal = true,
|
||||||
header,
|
header,
|
||||||
title,
|
title,
|
||||||
@@ -48,7 +48,6 @@ export default function Drawer({
|
|||||||
|
|
||||||
const anim = useRef(new Animated.Value(0)).current
|
const anim = useRef(new Animated.Value(0)).current
|
||||||
|
|
||||||
/* ================== 同步外部 visible ================== */
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
setInnerVisible(true)
|
setInnerVisible(true)
|
||||||
@@ -59,7 +58,6 @@ export default function Drawer({
|
|||||||
}
|
}
|
||||||
}, [visible])
|
}, [visible])
|
||||||
|
|
||||||
/* ================== 布局样式 ================== */
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
switch (menuPosition) {
|
switch (menuPosition) {
|
||||||
case 'left':
|
case 'left':
|
||||||
@@ -207,8 +205,9 @@ export default function Drawer({
|
|||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
|
|
||||||
{/* Drawer */}
|
{/* Drawer */}
|
||||||
<View style={drawerMenuBox}>
|
<View style={drawerMenuBox} pointerEvents="box-none">
|
||||||
<Animated.View
|
<Animated.View
|
||||||
|
pointerEvents="box-none"
|
||||||
style={[
|
style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export default function LucideIcon({
|
|||||||
return <IconComponent size={size} color={color} strokeWidth={strokeWidth} style={style} className={className} />
|
return <IconComponent size={size} color={color} strokeWidth={strokeWidth} style={style} className={className} />
|
||||||
}
|
}
|
||||||
|
|
||||||
// clickable 为 true,包 Pressable
|
|
||||||
return (
|
return (
|
||||||
<Pressable onPress={onPress} style={style} className={className}>
|
<Pressable onPress={onPress} style={style} className={className}>
|
||||||
<IconComponent size={size} color={color} strokeWidth={strokeWidth} />
|
<IconComponent size={size} color={color} strokeWidth={strokeWidth} />
|
||||||
|
|||||||
@@ -30,8 +30,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,
|
||||||
|
|||||||
+75
-131
@@ -1,147 +1,91 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Bottom Nav Demo</title>
|
<title>全局底部导航 Demo</title>
|
||||||
<link rel="stylesheet" href="style.css" />
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
body {
|
||||||
box-sizing: border-box;
|
margin: 0;
|
||||||
margin: 0;
|
font-family: sans-serif;
|
||||||
padding: 0;
|
background-color: #f0f2f5;
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont;
|
display: flex;
|
||||||
}
|
justify-content: center;
|
||||||
|
align-items: flex-end;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
/* 弹出面板 */
|
||||||
background: #f6f7f9;
|
.bottom-panel {
|
||||||
height: 100vh;
|
width: 100%;
|
||||||
}
|
max-width: 400px;
|
||||||
|
background: white;
|
||||||
|
border-top-left-radius: 20px;
|
||||||
|
border-top-right-radius: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
/* 每个导航 item */
|
||||||
padding: 16px;
|
.nav-item {
|
||||||
padding-bottom: 80px;
|
background: #f9f9f9;
|
||||||
color: #555;
|
border-radius: 16px;
|
||||||
}
|
padding: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, background 0.2s;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* 底部栏 */
|
.nav-item:hover {
|
||||||
.bottom-bar {
|
background: #e0e0ff;
|
||||||
position: fixed;
|
transform: translateY(-3px) scale(1.05);
|
||||||
bottom: 0;
|
}
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 56px;
|
|
||||||
background: #fff;
|
|
||||||
border-top: 1px solid #e5e5e5;
|
|
||||||
|
|
||||||
display: flex;
|
.nav-icon {
|
||||||
align-items: center;
|
font-size: 28px;
|
||||||
justify-content: space-between;
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 左右按钮 */
|
|
||||||
.nav-btn {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 中间区域 */
|
|
||||||
.nav-center {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #222;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-center .arrow {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 扩展菜单 */
|
|
||||||
.extension {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 56px;
|
|
||||||
left: 12px;
|
|
||||||
right: 12px;
|
|
||||||
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 8px 24px rgba(0,0,0,0.08);
|
|
||||||
|
|
||||||
transform: translateY(20px);
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension.show {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension-item {
|
|
||||||
padding: 14px 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension-item.danger {
|
|
||||||
color: #d93026;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
.nav-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<main class="content">
|
<div class="bottom-panel">
|
||||||
<p>当前工具内容区域</p>
|
<div class="nav-item" onclick="alert('跳转到首页')">
|
||||||
</main>
|
<div class="nav-icon">🏠</div>
|
||||||
|
<div class="nav-name">首页</div>
|
||||||
<!-- 扩展菜单 -->
|
</div>
|
||||||
<div class="extension" id="extension">
|
<div class="nav-item" onclick="alert('跳转到记账')">
|
||||||
<div class="extension-item">视图切换</div>
|
<div class="nav-icon">📝</div>
|
||||||
<div class="extension-item">工具设置</div>
|
<div class="nav-name">记账</div>
|
||||||
<div class="extension-item">导出数据</div>
|
</div>
|
||||||
<div class="extension-item danger">清空记录</div>
|
<div class="nav-item" onclick="alert('跳转到分类')">
|
||||||
|
<div class="nav-icon">📂</div>
|
||||||
|
<div class="nav-name">分类</div>
|
||||||
|
</div>
|
||||||
|
<div class="nav-item" onclick="alert('跳转到分析')">
|
||||||
|
<div class="nav-icon">📊</div>
|
||||||
|
<div class="nav-name">分析</div>
|
||||||
|
</div>
|
||||||
|
<div class="nav-item" onclick="alert('跳转到设置')">
|
||||||
|
<div class="nav-icon">⚙️</div>
|
||||||
|
<div class="nav-name">设置</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部导航 -->
|
|
||||||
<nav class="bottom-bar">
|
|
||||||
<button class="nav-btn">←</button>
|
|
||||||
|
|
||||||
<div class="nav-center">
|
|
||||||
<span class="title">记账 · 本月</span>
|
|
||||||
<span class="arrow">▾</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="nav-btn" onclick="toggleExt()">⋯</button>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function toggleExt() {
|
|
||||||
document.getElementById('extension')
|
|
||||||
.classList.toggle('show')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in new issue
Block a user