feat: 添加 ListEmpty 组件,优化空数据和加载失败的展示逻辑
This commit is contained in:
parent
159ebbae45
commit
474b4ca8b7
|
|
@ -0,0 +1,25 @@
|
|||
import { memo } from 'react'
|
||||
|
||||
import Block from './Block'
|
||||
import Text from './Text'
|
||||
|
||||
const ListEmpty = memo((props: any) => {
|
||||
const { hasError = false, handleRetry = () => {} } = props
|
||||
if (hasError) {
|
||||
return (
|
||||
<Block className="items-center justify-center py-[40px]">
|
||||
<Text className="mb-[8px] text-[14px] text-white/60">加载失败</Text>
|
||||
<Block className="-skew-x-6 border-2 border-white bg-white px-[16px] py-[6px]" onClick={handleRetry}>
|
||||
<Text className="text-[12px] font-[900] text-black">重试</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Block className="items-center justify-center py-[40px]">
|
||||
<Text className="text-[14px] text-white/60">暂无数据</Text>
|
||||
</Block>
|
||||
)
|
||||
})
|
||||
|
||||
export default ListEmpty
|
||||
|
|
@ -2,6 +2,7 @@ export { default as Block } from './Block'
|
|||
export { default as ConfirmModal } from './ConfirmModal'
|
||||
export { default as Img } from './Img'
|
||||
export { default as Input } from './Input'
|
||||
export { default as ListEmpty } from './ListEmpty'
|
||||
export { default as Modal } from './Modal'
|
||||
export { default as ModalPortal } from './ModalPortal'
|
||||
export { default as Text } from './Text'
|
||||
|
|
|
|||
|
|
@ -1,36 +1,47 @@
|
|||
import { FontAwesome, Fontisto, Ionicons } from '@expo/vector-icons'
|
||||
import { FlashList } from '@shopify/flash-list'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ActivityIndicator, Platform, RefreshControl } from 'react-native'
|
||||
import { ActivityIndicator, RefreshControl } from 'react-native'
|
||||
import { useAnimatedStyle } from 'react-native-reanimated'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
|
||||
import { imgPicker } from '@/@share/apis/imgPicker'
|
||||
import { Block, Img, Input, Text } from '@/@share/components'
|
||||
import { Block, ConfirmModal, Img, Input, ListEmpty, Text, Toast, VideoBox } from '@/@share/components'
|
||||
import BannerSection from '@/components/BannerSection'
|
||||
import { useFileUpload } from '@/hooks/actions'
|
||||
import { useTemplateActions } from '@/hooks/actions/use-template-actions'
|
||||
import { useTemplates } from '@/hooks/data/use-templates'
|
||||
import { screenHeight, screenWidth } from '@/utils'
|
||||
import { userBalanceStore, userStore } from '@/stores'
|
||||
import { screenHeight, screenWidth, uploadFile } from '@/utils'
|
||||
|
||||
const CATEGORY_ID = process.env.EXPO_PUBLIC_GENERATE_GROUP_ID
|
||||
const CATEGORY_ID = 'cmk3qbw9p0008j2eb84etgxcb'
|
||||
|
||||
type Template = {
|
||||
id: string
|
||||
name: string
|
||||
image: string
|
||||
videoUrl: string
|
||||
type: 'video'
|
||||
price?: number
|
||||
data?: any
|
||||
}
|
||||
/** =========================
|
||||
* Entry page
|
||||
* ========================= */
|
||||
|
||||
export default function Generate() {
|
||||
const Generate = observer(function Generate() {
|
||||
const router = useRouter()
|
||||
const env = process.env.EXPO_PUBLIC_ENV
|
||||
|
||||
const { user, isAuthenticated } = userStore
|
||||
|
||||
const [prompt, setPrompt] = useState(`${env} update`)
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState('')
|
||||
const [meImg, setMeImg] = useState('')
|
||||
const [friendImg, setFriendImg] = useState('')
|
||||
const [meImg, setMeImg] = useState({ uri: '', url: '' })
|
||||
const [friendImg, setFriendImg] = useState({ uri: '', url: '' })
|
||||
|
||||
const templates = useTemplates()
|
||||
|
||||
const { uploadFile, loading: uploadLoading } = useFileUpload()
|
||||
const { runTemplate } = useTemplateActions()
|
||||
|
||||
useEffect(() => {
|
||||
templates.execute({ categoryId: CATEGORY_ID, page: 1, limit: 12, sortBy: 'createdAt', sortOrder: 'desc' })
|
||||
|
|
@ -39,16 +50,19 @@ export default function Generate() {
|
|||
const displayTemplates = useMemo(() => {
|
||||
const regular = templates.data?.templates || []
|
||||
const all = regular
|
||||
return all.map(
|
||||
(t: any): Template => ({
|
||||
return all.map((t: any): Template => {
|
||||
return {
|
||||
id: t.id,
|
||||
name: t.title,
|
||||
image: t.coverImageUrl,
|
||||
videoUrl: t.previewUrl,
|
||||
type: 'video' as const,
|
||||
price: t.price,
|
||||
data: t,
|
||||
}),
|
||||
)
|
||||
data: {
|
||||
startNodes: t?.formSchema?.startNodes || [],
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [templates.data])
|
||||
|
||||
const selectedTemplate = useMemo(() => {
|
||||
|
|
@ -62,13 +76,97 @@ export default function Generate() {
|
|||
}, [displayTemplates, selectedTemplateId])
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
router.push('/searchTemplate')
|
||||
router.push('/')
|
||||
}, [router])
|
||||
|
||||
const handleGenerate = useCallback(() => {
|
||||
const handleGenerate = async () => {
|
||||
if (!selectedTemplate) return
|
||||
setTimeout(() => {}, 2000)
|
||||
}, [selectedTemplate])
|
||||
|
||||
if (!isAuthenticated) {
|
||||
Toast.show({ title: '请先登录' })
|
||||
return
|
||||
}
|
||||
if (!selectedTemplate) {
|
||||
Toast.show({ title: '请先选择一个模板' })
|
||||
return
|
||||
}
|
||||
|
||||
// 显示加载状态并刷新余额
|
||||
Toast.showLoading()
|
||||
try {
|
||||
await userBalanceStore.load(true) // 生成前刷新余额
|
||||
|
||||
// 使用最新的余额数据进行检查
|
||||
const currentBalance = userBalanceStore.balance
|
||||
if (currentBalance < selectedTemplate?.price) {
|
||||
Toast.show({ title: '余额不足,请充值' })
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show({ title: '余额加载失败,请重试' })
|
||||
return
|
||||
} finally {
|
||||
Toast.hideLoading()
|
||||
}
|
||||
|
||||
Toast.showModal(
|
||||
<ConfirmModal
|
||||
content={
|
||||
<Text className="text-[14px] font-bold">
|
||||
生成将消耗
|
||||
<Text className="mx-[4px] text-[20px] text-[#e61e25]">{selectedTemplate.price} Goo</Text>
|
||||
</Text>
|
||||
}
|
||||
onCancel={Toast.hideModal}
|
||||
onConfirm={handleConfirmGenerate}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
|
||||
const handleConfirmGenerate = async () => {
|
||||
Toast.hideModal()
|
||||
Toast.showLoading()
|
||||
|
||||
console.log('meImg.url-------', meImg)
|
||||
|
||||
const data = {} as any
|
||||
const startNodes = selectedTemplate?.data?.startNodes || []
|
||||
|
||||
startNodes.map((node: any) => {
|
||||
if (node.type === 'text') {
|
||||
data[node.id] = prompt
|
||||
} else if (node.type === 'image') {
|
||||
data[node.id] = meImg.url
|
||||
}
|
||||
})
|
||||
console.log('data==========', data)
|
||||
|
||||
try {
|
||||
// 先进行乐观更新,扣减余额
|
||||
userBalanceStore.deductBalance(selectedTemplate?.price as number)
|
||||
|
||||
const { generationId, error } = await runTemplate({
|
||||
templateId: selectedTemplate?.id as string,
|
||||
data: data,
|
||||
})
|
||||
|
||||
if (generationId && user?.id) {
|
||||
// 生成成功后强制刷新余额以获取准确数据
|
||||
await userBalanceStore.load(true)
|
||||
Toast.show({ title: '生成任务开启,请在我的生成中查看' })
|
||||
} else {
|
||||
// 生成失败,恢复余额
|
||||
userBalanceStore.setBalance(userBalanceStore.balance + selectedItem.price)
|
||||
Toast.show({ title: error?.message || '生成失败' })
|
||||
}
|
||||
} catch (error) {
|
||||
// 异常情况下恢复余额
|
||||
userBalanceStore.setBalance(userBalanceStore.balance + selectedItem.price)
|
||||
Toast.show({ title: '网络异常,请重试' })
|
||||
} finally {
|
||||
Toast.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
const pickImage = useCallback(async (target: 'me' | 'friend') => {
|
||||
const assetList = (await imgPicker({ maxImages: 1, resultType: 'asset' })) as string[]
|
||||
|
|
@ -77,25 +175,28 @@ export default function Generate() {
|
|||
if (!result) return
|
||||
|
||||
const uri = result?.uri
|
||||
if (target === 'me') setMeImg(uri)
|
||||
else setFriendImg(uri)
|
||||
if (target === 'me') setMeImg({ uri, url: '' })
|
||||
else setFriendImg({ uri, url: '' })
|
||||
|
||||
const file = {
|
||||
name: result.fileName || `image_${Date.now()}.jpg`,
|
||||
type: result.mimeType || 'image/jpeg',
|
||||
uri: Platform.OS === 'android' ? result.uri : result.uri.replace('file://', ''),
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', file as any)
|
||||
const url = await uploadFile({
|
||||
uri: result.uri,
|
||||
mimeType: result.mimeType,
|
||||
fileName: result.fileName,
|
||||
})
|
||||
|
||||
console.log('handlePick------------', url)
|
||||
|
||||
const { url, error } = await uploadFile(file as any)
|
||||
// console.log('pickImage---------url:', url, 'error:', error)
|
||||
|
||||
if (error || !url) {
|
||||
return
|
||||
if (target === 'me') {
|
||||
setMeImg((state) => {
|
||||
return { uri: state.uri, url }
|
||||
})
|
||||
} else {
|
||||
setFriendImg((state) => {
|
||||
return { uri: state.uri, url }
|
||||
})
|
||||
}
|
||||
if (target === 'me') setMeImg(url)
|
||||
else setFriendImg(url)
|
||||
}, [])
|
||||
|
||||
const handleRandom = useCallback(() => {
|
||||
|
|
@ -152,19 +253,27 @@ export default function Generate() {
|
|||
[selectedTemplateId, itemWidth, handleSelectTemplate],
|
||||
)
|
||||
|
||||
const ListHeader = useMemo(
|
||||
() => (
|
||||
const ListHeader = useMemo(() => {
|
||||
const startNodes = selectedTemplate?.data?.startNodes || []
|
||||
const textCount = startNodes.filter((node: any) => node?.type === 'text').length
|
||||
|
||||
return (
|
||||
<Block className="">
|
||||
<Header onSearch={handleSearch} />
|
||||
<Block className="px-12px relative z-10 flex-1">
|
||||
<UploadSection friendImg={friendImg} meImg={meImg} onPickFriend={onPickFriend} onPickMe={onPickMe} />
|
||||
<PromptSection prompt={prompt} onChangePrompt={setPrompt} />
|
||||
<UploadSection
|
||||
friendImg={friendImg?.uri}
|
||||
meImg={meImg?.uri}
|
||||
onPickFriend={onPickFriend}
|
||||
onPickMe={onPickMe}
|
||||
selectedTemplate={selectedTemplate}
|
||||
/>
|
||||
{!!textCount && <PromptSection prompt={prompt} onChangePrompt={setPrompt} />}
|
||||
<TemplateSectionHeader onRandom={handleRandom} />
|
||||
</Block>
|
||||
</Block>
|
||||
),
|
||||
[handleSearch, friendImg, meImg, onPickFriend, onPickMe, prompt, handleRandom],
|
||||
)
|
||||
)
|
||||
}, [handleSearch, friendImg, meImg, onPickFriend, onPickMe, prompt, handleRandom, selectedTemplate])
|
||||
|
||||
const ListFooter = useMemo(() => {
|
||||
if (isLoadingMore) {
|
||||
|
|
@ -177,24 +286,6 @@ export default function Generate() {
|
|||
return <Block className="h-[200px] w-full" />
|
||||
}, [isLoadingMore])
|
||||
|
||||
const ListEmpty = useMemo(() => {
|
||||
if (hasError) {
|
||||
return (
|
||||
<Block className="items-center justify-center py-[40px]">
|
||||
<Text className="mb-[8px] text-[14px] text-white/60">加载失败</Text>
|
||||
<Block className="-skew-x-6 border-2 border-white bg-white px-[16px] py-[6px]" onClick={handleRetry}>
|
||||
<Text className="text-[12px] font-[900] text-black">重试</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Block className="items-center justify-center py-[40px]">
|
||||
<Text className="text-[14px] text-white/60">暂无模板</Text>
|
||||
</Block>
|
||||
)
|
||||
}, [isLoading, hasError, handleRetry])
|
||||
|
||||
return (
|
||||
<Block className="relative flex-1 overflow-visible bg-black">
|
||||
<BannerSection />
|
||||
|
|
@ -207,7 +298,7 @@ export default function Generate() {
|
|||
estimatedItemSize={itemWidth}
|
||||
extraData={selectedTemplateId}
|
||||
keyExtractor={(item) => item.id}
|
||||
ListEmptyComponent={ListEmpty}
|
||||
ListEmptyComponent={<ListEmpty hasError={hasError} handleRetry={handleRetry} />}
|
||||
ListFooterComponent={ListFooter}
|
||||
ListHeaderComponent={ListHeader}
|
||||
numColumns={3}
|
||||
|
|
@ -223,8 +314,9 @@ export default function Generate() {
|
|||
<GenerateSection selectedTemplate={selectedTemplate} onGenerate={handleGenerate} />
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default Generate
|
||||
/** =========================
|
||||
* Small memo components
|
||||
* ========================= */
|
||||
|
|
@ -298,12 +390,27 @@ type UploadSectionProps = {
|
|||
friendImg: string
|
||||
onPickMe: () => void
|
||||
onPickFriend: () => void
|
||||
selectedTemplate: Template | undefined
|
||||
}
|
||||
const UploadSection = memo<UploadSectionProps>(function UploadSection({ meImg, friendImg, onPickMe, onPickFriend }) {
|
||||
const UploadSection = memo<UploadSectionProps>(function UploadSection({
|
||||
meImg,
|
||||
friendImg,
|
||||
onPickMe,
|
||||
onPickFriend,
|
||||
selectedTemplate,
|
||||
}) {
|
||||
const imageCount = useMemo(() => {
|
||||
if (!selectedTemplate) return true
|
||||
const startNodes = selectedTemplate?.data?.startNodes || []
|
||||
const imageNodeCount = startNodes.filter((node: any) => node?.type === 'image').length
|
||||
return imageNodeCount
|
||||
}, [selectedTemplate])
|
||||
|
||||
if (imageCount === 0) return null
|
||||
return (
|
||||
<Block className="z-10 flex h-[160px] w-full flex-row gap-x-[12px]">
|
||||
<UploadCard img={meImg} variant="me" onPick={onPickMe} />
|
||||
<UploadCard img={friendImg} variant="friend" onPick={onPickFriend} />
|
||||
{imageCount >= 1 && <UploadCard img={meImg} variant="me" onPick={onPickMe} />}
|
||||
{imageCount >= 2 && <UploadCard img={friendImg} variant="friend" onPick={onPickFriend} />}
|
||||
</Block>
|
||||
)
|
||||
})
|
||||
|
|
@ -335,15 +442,6 @@ const PromptSection = memo<PromptSectionProps>(function PromptSection({ prompt,
|
|||
)
|
||||
})
|
||||
|
||||
type Template = {
|
||||
id: string
|
||||
name: string
|
||||
image: string
|
||||
type: 'video'
|
||||
price?: number
|
||||
data?: any
|
||||
}
|
||||
|
||||
type TemplateItemProps = {
|
||||
item: Template
|
||||
itemWidth: number
|
||||
|
|
@ -364,7 +462,8 @@ const TemplateItem = memo<TemplateItemProps>(function TemplateItem({ item, itemW
|
|||
}}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<Img className="size-full" contentFit="cover" src={item.image} />
|
||||
{/* <Img className="size-full" contentFit="cover" src={item.image} /> */}
|
||||
<VideoBox style={{ width: itemWidth, height: itemWidth }} url={item.videoUrl} />
|
||||
|
||||
{isSelected && <Block className="absolute inset-0 bg-black/60" />}
|
||||
{isSelected && <Block className="absolute inset-[-5px] border-[3px] border-accent" />}
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ const Index = observer(function Index() {
|
|||
useCallback(() => {
|
||||
// 页面获得焦点时使用防抖加载余额,只有在用户已登录时才加载
|
||||
if (isAuthenticated && user?.id) {
|
||||
userBalanceStore.load(false)
|
||||
userBalanceStore.load(true)
|
||||
}
|
||||
}, [isAuthenticated, user?.id]),
|
||||
)
|
||||
|
|
@ -302,7 +302,6 @@ const Index = observer(function Index() {
|
|||
const { generationId, error } = await runTemplate({
|
||||
templateId: selectedItem.id,
|
||||
data: {},
|
||||
originalUrl: selectedItem.url,
|
||||
})
|
||||
|
||||
if (generationId && user?.id) {
|
||||
|
|
@ -347,6 +346,15 @@ const Index = observer(function Index() {
|
|||
</Block>
|
||||
</Block>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Block className="mt-[40px] items-center justify-center gap-[16px] py-[60px]">
|
||||
<Block className="size-[80px] items-center justify-center rounded-full border-4 border-white/20 bg-white/10">
|
||||
<Ionicons color="rgba(255,255,255,0.6)" name="images-outline" size={40} />
|
||||
</Block>
|
||||
<Text className="text-[16px] font-bold text-white/80">暂无数据</Text>
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ const Sync = observer(() => {
|
|||
const [selectedItem, setSelectedItem] = useState({
|
||||
id: '',
|
||||
imageUrl: '',
|
||||
// url 是网络地址,本地预览使用 imageUrl
|
||||
url: '',
|
||||
originalUrl: '',
|
||||
templateId: '',
|
||||
|
|
@ -81,21 +82,25 @@ const Sync = observer(() => {
|
|||
const generations = generationsData?.data || []
|
||||
return generations
|
||||
.filter((gen: any) => gen?.id) // 过滤掉没有 id 的记录
|
||||
.map((gen: any) => ({
|
||||
id: gen?.id,
|
||||
imageUrl: Array.isArray(gen?.resultUrl) ? gen?.resultUrl[0] : gen?.resultUrl,
|
||||
originalUrl: gen?.originalUrl,
|
||||
templateId: gen?.templateId,
|
||||
type: gen?.type,
|
||||
status: gen?.status,
|
||||
createdAt: gen?.createdAt,
|
||||
title: `生成-${gen?.id.slice(0, 6)}`,
|
||||
rank: 'S',
|
||||
author: user?.name || 'User',
|
||||
avatarUrl:
|
||||
user?.image ||
|
||||
'https://image.pollinations.ai/prompt/cool%20anime%20boy%20avatar%20hoodie?seed=123&nologo=true',
|
||||
}))
|
||||
.map((gen: any) => {
|
||||
const imageUrl = Array.isArray(gen?.resultUrl) ? gen?.resultUrl[0] : gen?.resultUrl
|
||||
return {
|
||||
id: gen?.id,
|
||||
imageUrl: imageUrl,
|
||||
url: imageUrl,
|
||||
originalUrl: gen?.originalUrl,
|
||||
templateId: gen?.templateId,
|
||||
type: gen?.type,
|
||||
status: gen?.status,
|
||||
createdAt: gen?.createdAt,
|
||||
title: `生成-${gen?.id.slice(0, 6)}`,
|
||||
rank: 'S',
|
||||
author: user?.name || 'User',
|
||||
avatarUrl:
|
||||
user?.image ||
|
||||
'https://image.pollinations.ai/prompt/cool%20anime%20boy%20avatar%20hoodie?seed=123&nologo=true',
|
||||
}
|
||||
})
|
||||
}, [generationsData, user])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -106,7 +111,7 @@ const Sync = observer(() => {
|
|||
const newItem = {
|
||||
id: firstItem.id,
|
||||
imageUrl: firstItem.imageUrl,
|
||||
url: firstItem.imageUrl,
|
||||
url: firstItem.url,
|
||||
originalUrl: firstItem.originalUrl,
|
||||
templateId: firstItem.templateId,
|
||||
}
|
||||
|
|
@ -172,7 +177,7 @@ const Sync = observer(() => {
|
|||
duration: 0,
|
||||
})
|
||||
|
||||
transferMediaSingle(selectedItem?.imageUrl)
|
||||
transferMediaSingle(selectedItem?.url)
|
||||
.then(() => {
|
||||
Toast.show({ title: '同步成功' })
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,18 +18,11 @@ import { useUserSession } from '@/stores/UserStore'
|
|||
|
||||
Sentry.init({
|
||||
dsn: 'https://ef710a118839b1e86e38a3833a9a3c6c@o4507705403965440.ingest.us.sentry.io/4510576286302208',
|
||||
// Adds more context data to events (IP address, cookies, user, etc.)
|
||||
// For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
|
||||
sendDefaultPii: true,
|
||||
enableLogs: true,
|
||||
// debug: true,
|
||||
enabled: !__DEV__, // 仅在生产环境启用 Sentry
|
||||
enabled: !__DEV__,
|
||||
})
|
||||
|
||||
// 目前无需求,先注释掉
|
||||
// SplashScreen.preventAutoHideAsync()
|
||||
|
||||
// 全局启用 fetch 日志记录
|
||||
if (__DEV__) {
|
||||
setupGlobalFetchLogger()
|
||||
}
|
||||
|
|
@ -38,7 +31,6 @@ export const unstable_settings = {
|
|||
anchor: '(tabs)',
|
||||
}
|
||||
|
||||
// 路由层
|
||||
function RootLayout() {
|
||||
const ref = useNavigationContainerRef()
|
||||
|
||||
|
|
@ -46,16 +38,17 @@ function RootLayout() {
|
|||
if (!ref?.current) return
|
||||
|
||||
const unsubscribe = ref.addListener('state', (e) => {
|
||||
const routes = e.data.state?.routes
|
||||
if (routes && routes.length > 0) {
|
||||
// Get the current active route
|
||||
const currentRoute = routes[routes.length - 1]
|
||||
try {
|
||||
const routes = e.data.state?.routes
|
||||
if (!routes || routes.length === 0) return
|
||||
|
||||
const currentRoute = routes[routes.length - 1]
|
||||
if (!currentRoute) return
|
||||
|
||||
// Extract the actual screen name from the route
|
||||
let screenName = currentRoute.name
|
||||
let params = currentRoute.params
|
||||
|
||||
// Handle nested routes and get the actual screen name
|
||||
// 处理嵌套路由
|
||||
if (currentRoute.state?.routes) {
|
||||
const nestedRoutes = currentRoute.state.routes
|
||||
const activeNestedRoute = nestedRoutes[nestedRoutes.length - 1]
|
||||
|
|
@ -65,7 +58,16 @@ function RootLayout() {
|
|||
}
|
||||
}
|
||||
|
||||
console.warn(`screenName------------${screenName}, params ----------${JSON.stringify(params)}`)
|
||||
// 使用 debug 而非 warn,避免噪音
|
||||
if (__DEV__) {
|
||||
console.debug(`📍 Navigation: ${screenName}`, params ? `(${JSON.stringify(params)})` : '')
|
||||
}
|
||||
|
||||
// 上报到 Sentry(可选)
|
||||
Sentry.captureMessage(`Navigation: ${screenName}`, 'info')
|
||||
} catch (error) {
|
||||
console.error('❌ Navigation listener error:', error)
|
||||
Sentry.captureException(error)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -102,9 +104,7 @@ function Providers({ children }: { children: React.ReactNode }) {
|
|||
<HotUpdate />
|
||||
{children}
|
||||
</SafeAreaView>
|
||||
{/* modals */}
|
||||
|
||||
{/* 挂载全局方法 */}
|
||||
<ModalPortal ref={(ref) => ((global as any).actionSheet = ref)} />
|
||||
<ModalPortal ref={(ref) => ((global as any).modal = ref)} />
|
||||
<ModalPortal ref={(ref) => ((global as any).loading = ref)} />
|
||||
|
|
|
|||
Loading…
Reference in New Issue