From 3c9b5fcb08dfdfdc3057ab0ab532ea43bde8f640 Mon Sep 17 00:00:00 2001 From: km2025 Date: Sun, 4 Jan 2026 18:41:04 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E5=AE=89=E5=8D=93=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E5=AE=9D=E4=B8=8D=E8=AE=BE=E7=BD=AEschema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @share/components/ConfirmModal.tsx | 2 +- @share/components/Video.tsx | 4 +- app/(tabs)/_layout.tsx | 2 +- app/(tabs)/explore.tsx | 2 +- app/(tabs)/generate.tsx | 23 +-- app/(tabs)/index.tsx | 40 ++++-- app/(tabs)/my.tsx | 219 ++++++++++++++++------------- app/(tabs)/sync.tsx | 75 ++++++---- app/auth.tsx | 16 +-- app/pointList.native.tsx | 13 +- app/pointList.tsx | 4 +- components/auth-loading-screen.tsx | 2 +- lib/fetch-logger.ts | 82 ++++++----- 13 files changed, 276 insertions(+), 208 deletions(-) diff --git a/@share/components/ConfirmModal.tsx b/@share/components/ConfirmModal.tsx index 9b18cc4..5116292 100644 --- a/@share/components/ConfirmModal.tsx +++ b/@share/components/ConfirmModal.tsx @@ -50,7 +50,7 @@ const ConfirmModal: React.FC = ({ {badge} - {title} + {title} diff --git a/@share/components/Video.tsx b/@share/components/Video.tsx index 6b6b528..32c1f98 100644 --- a/@share/components/Video.tsx +++ b/@share/components/Video.tsx @@ -15,7 +15,7 @@ const VideoBox = ({ url, needWeb = true, width = 256, style, ...videoProps }: Pr const [urlFinal, setUrlFinal] = useState('') const createUrl = (url: string) => { - return `https://modal-dev.bowong.cc/api/custom/video/converter/${encodeURI(url)}?options=compression_level=3,quality=70,loop=true,resolution=${width}x${width},fps=24` + return `https://modal-dev.bowong.cc/api/custom/video/converter/v2?media_url=${encodeURI(url)}&options=compression_level=3,quality=70,loop=true,resolution=${width}x${width},fps=24` } const isImg = (url: any) => { @@ -55,7 +55,7 @@ const VideoBox = ({ url, needWeb = true, width = 256, style, ...videoProps }: Pr const webpUrl = createUrl(url!) const finalUrl = await resolveRedirect(webpUrl) - // console.log('finalUrl-----------', finalUrl) + // console.log('setRedirectUrl finalUrl-----------', finalUrl) setUrlFinal(finalUrl!) } diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index ae0fb8b..f794117 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -34,7 +34,7 @@ function renderNavItem(item: (typeof navItems)[0], isActive: boolean, onRouteCha {isActive && ( - + {label} )} diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx index 616cedd..218cd44 100644 --- a/app/(tabs)/explore.tsx +++ b/app/(tabs)/explore.tsx @@ -426,7 +426,7 @@ const styles = StyleSheet.create({ fontSize: 11, opacity: 0.6, marginTop: 2, - fontStyle: 'italic', + fontStyle: ' ', }, connectionStatus: { fontSize: 12, diff --git a/app/(tabs)/generate.tsx b/app/(tabs)/generate.tsx index e26b972..8a615e6 100644 --- a/app/(tabs)/generate.tsx +++ b/app/(tabs)/generate.tsx @@ -183,7 +183,7 @@ export default function Generate() { 加载失败 - 重试 + 重试 ) @@ -262,11 +262,11 @@ const UploadCard = memo(function UploadCard({ variant, img, onP {isMe ? ( - + ) : ( - 朋友 + 朋友 )} @@ -285,7 +285,7 @@ const UploadCard = memo(function UploadCard({ variant, img, onP )} - 点击上传 + 点击上传 )} @@ -317,7 +317,7 @@ const PromptSection = memo(function PromptSection({ prompt, - 提示词 + 提示词 @@ -353,12 +353,13 @@ type TemplateItemProps = { const TemplateItem = memo(function TemplateItem({ item, itemWidth, isSelected, onSelect }) { return ( (function TemplateItem({ item, itemW {isSelected && } - {item.name} + {item.name} {item.type === 'video' && ( @@ -399,14 +400,14 @@ const TemplateSectionHeader = memo(function Template style={style} > - 视频模版 + 视频模版 - 换一波 + 换一波 @@ -434,14 +435,14 @@ const GenerateSection = memo(function GenerateSection({ se > {/* 左侧文字 */} - 立即生成 + 立即生成 开始创作 {/* 右侧 Goo 标签 */} - {price} Goo + {price} Goo diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 3c09699..c8fe1fe 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -108,6 +108,7 @@ export default function Sync() { if (tab === 'like') { if (!isAuthenticated) { setHasMore(false) + newItems = [] } else { const { data } = await loadFavorites({ page, limit: pageSize }) newItems = @@ -252,6 +253,30 @@ export default function Sync() { ) } + const renderListEmpty = () => { + if (activeTab === 'like' && !isAuthenticated) { + return ( + + + + + 查看您收藏的模板 + 登录后即可查看和管理收藏 + router.push('/auth')} + > + + + 立即登录 + + + + ) + } + } + /** ================= UI ================= */ return ( @@ -299,6 +324,7 @@ export default function Sync() { onSelect={() => setSelectedItem(item)} /> )} + ListEmptyComponent={renderListEmpty} showsVerticalScrollIndicator={false} data={allItems} // @ts-ignore @@ -394,7 +420,7 @@ const GooActions = memo(function GooActions({ gooPoints, onAddG className="flex-row items-center gap-[4px] rounded-full border-[3px] border-white bg-black px-[12px] py-[8px] shadow-[3px_3px_0px_rgba(0,0,0,0.5)]" onClick={onAddGoo} > - {gooPoints} Goo + {gooPoints} Goo @@ -459,7 +485,7 @@ const HeroCircle = memo(function HeroCircle({ selectedItem, onQ - + {selectedItem?.id} @@ -469,7 +495,7 @@ const HeroCircle = memo(function HeroCircle({ selectedItem, onQ onClick={onQuickGen} > - GET 同款 + GET 同款 @@ -507,9 +533,7 @@ const FilterSection = memo(function FilterSection({ activeTa transform: [{ skewX: '-6deg' }], }} > - - {label} - + {label} ) })} @@ -577,13 +601,13 @@ const GridItem = memo(function GridItem({ item, isSelected, itemW - + {item.id} - {item.likeCount} + {item.likeCount} diff --git a/app/(tabs)/my.tsx b/app/(tabs)/my.tsx index 941c878..173df4b 100644 --- a/app/(tabs)/my.tsx +++ b/app/(tabs)/my.tsx @@ -1,14 +1,15 @@ -import React, { useMemo, useState, useCallback, useEffect } from 'react' -import { Block, Text, Toast, ConfirmModal, VideoBox } from '@share/components' -import Img from '@share/components/Img' import { Ionicons } from '@expo/vector-icons' -import { Dimensions, ScrollView } from 'react-native' +import { Block, ConfirmModal, Text, Toast, VideoBox } from '@share/components' +import Img from '@share/components/Img' import { LinearGradient } from 'expo-linear-gradient' -import { useAuth } from '@/hooks/core/use-auth' -import { useTemplateGenerations, TemplateGeneration } from '@/hooks/data/use-template-generations' -import { useUserBalance } from '@/hooks/core/use-user-balance' -import { useTemplateActions } from '@/hooks/actions/use-template-actions' import { router } from 'expo-router' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { Dimensions, ScrollView } from 'react-native' + +import { useTemplateActions } from '@/hooks/actions/use-template-actions' +import { useAuth } from '@/hooks/core/use-auth' +import { useUserBalance } from '@/hooks/core/use-user-balance' +import { type TemplateGeneration, useTemplateGenerations } from '@/hooks/data/use-template-generations' const BACKGROUND_VIDEOS = [ 'https://cdn.roasmax.cn/material/b46f380532e14cf58dd350dbacc7c34a.mp4', @@ -39,23 +40,27 @@ export default function My() { }, [user?.id]) const generations = useMemo(() => generationsData?.data || [], [generationsData]) - const filteredPosts = activeFilter === 'my_gen' ? generations : generations.filter((g: TemplateGeneration) => g.status === 'completed') + const filteredPosts = + activeFilter === 'my_gen' ? generations : generations.filter((g: TemplateGeneration) => g.status === 'completed') const toggleSelectionMode = useCallback(() => { setIsSelectionMode((v) => !v) setSelectedIds(new Set()) }, []) - const handleItemClick = useCallback((generation: any) => { - if (isSelectionMode) { - setSelectedIds((prev) => { - const next = new Set(prev) - if (next.has(generation.id)) next.delete(generation.id) - else next.add(generation.id) - return next - }) - } - }, [isSelectionMode]) + const handleItemClick = useCallback( + (generation: any) => { + if (isSelectionMode) { + setSelectedIds((prev) => { + const next = new Set(prev) + if (next.has(generation.id)) next.delete(generation.id) + else next.add(generation.id) + return next + }) + } + }, + [isSelectionMode], + ) const handleDelete = useCallback(async () => { if (selectedIds.size === 0) return @@ -96,49 +101,50 @@ export default function My() { const { width: screenWidth } = Dimensions.get('window') const itemWidth = Math.floor((screenWidth - 24 - 12 * 2) / 3) - const renderBanner = useCallback(() => ( - - - - - ), [bgVideo]) + const renderBanner = useCallback( + () => ( + + + + + ), + [bgVideo], + ) const renderHeaderCard = useCallback(() => { const username = user?.name || user?.email || 'Guest' - const avatarUrl = user?.image || 'https://image.pollinations.ai/prompt/cool%20anime%20boy%20avatar%20hoodie?seed=123&nologo=true' + const avatarUrl = + user?.image || 'https://image.pollinations.ai/prompt/cool%20anime%20boy%20avatar%20hoodie?seed=123&nologo=true' const uid = user?.id?.slice(-6) || '000000' const generationCount = generations.length const completedCount = generations.filter((g: TemplateGeneration) => g.status === 'completed').length return ( - + - - - + + + - + {username} - + - + UID: {uid} - + CREDITS: {balance} - + TOTAL {generationCount} @@ -155,7 +161,7 @@ export default function My() { {balance > 100 && ( - + PRO )} @@ -164,46 +170,58 @@ export default function My() { ) }, [user, balance, generations]) - const renderActions = useCallback(() => ( - - {[ - { label: 'SHOP', color: '#4ADE80', icon: 'bag-outline' as const }, - { label: 'SYNC', color: '#FFE500', icon: 'watch-outline' as const }, - { label: 'PAY', color: '#e61e25', icon: 'card-outline' as const }, - ].map(({ label, color, icon }) => ( - - - {label} - - ))} - - ), []) + const renderActions = useCallback( + () => ( + + {[ + { label: 'SHOP', color: '#4ADE80', icon: 'bag-outline' as const }, + { label: 'SYNC', color: '#FFE500', icon: 'watch-outline' as const }, + { label: 'PAY', color: '#e61e25', icon: 'card-outline' as const }, + ].map(({ label, color, icon }) => ( + + + {label} + + ))} + + ), + [], + ) - const renderFilters = useCallback(() => ( - - - {['我的生成', '我的专辑'].map((label) => { - const target = label === '我的生成' ? 'my_gen' : 'my_album' - const isActive = activeFilter === (target as 'my_gen' | 'my_album') - return ( - setActiveFilter(target as 'my_gen' | 'my_album')} - className={`border-[2px] border-black px-[16px] py-[4px] ${isActive ? 'bg-black' : 'bg-white'} skew-x-[-12deg]`} - > - {label} - - ) - })} + const renderFilters = useCallback( + () => ( + + + {['我的生成', '我的专辑'].map((label) => { + const target = label === '我的生成' ? 'my_gen' : 'my_album' + const isActive = activeFilter === (target as 'my_gen' | 'my_album') + return ( + setActiveFilter(target as 'my_gen' | 'my_album')} + className={`border-2 border-black px-[16px] py-[4px] ${isActive ? 'bg-black' : 'bg-white'} -skew-x-12`} + > + + {label} + + + ) + })} + + + {isSelectionMode ? '取消' : '管理'} + - - {isSelectionMode ? '取消' : '管理'} - - - ), [activeFilter, isSelectionMode, toggleSelectionMode]) + ), + [activeFilter, isSelectionMode, toggleSelectionMode], + ) const renderGrid = useCallback(() => { if (filteredPosts.length === 0) { @@ -211,7 +229,7 @@ export default function My() { - + {activeFilter === 'my_gen' ? '暂无生成作品' : '暂无收藏'} @@ -230,7 +248,7 @@ export default function My() { return ( handleItemClick(generation)} className="relative"> @@ -238,33 +256,36 @@ export default function My() { isVideoUrl(imageUrl) ? ( ) : ( - + ) ) : ( - + )} - {isSelected && } + {isSelected && } {isSelectionMode && ( - + )} {!isSelectionMode && ( - + {statusRank} )} {!isSelectionMode && ( - + {generation.type} - + - + {new Date(generation.createdAt).toLocaleDateString()} @@ -297,26 +318,24 @@ export default function My() { const renderSelection = useCallback(() => { if (!isSelectionMode) return null return ( - - - - 已选: {selectedIds.size} + + + + 已选: {selectedIds.size} 全选 0 && !deleteLoading ? 'bg-[#e61e25]' : 'bg-gray-200'}`} + className={`font-900 skew-x-3 flex-row items-center gap-[8px] border-2 border-black px-[16px] py-[8px] text-[14px] shadow-medium-black${selectedIds.size > 0 && !deleteLoading ? 'bg-[#e61e25]' : 'bg-gray-200'}`} > {deleteLoading ? ( ) : ( )} - - {deleteLoading ? '删除中...' : '删除'} - + {deleteLoading ? '删除中...' : '删除'} @@ -326,9 +345,9 @@ export default function My() { return ( {renderBanner()} - + - 加载中... + 加载中... ) @@ -337,7 +356,7 @@ export default function My() { return ( {renderBanner()} - + {renderHeaderCard()} {renderActions()} {renderFilters()} diff --git a/app/(tabs)/sync.tsx b/app/(tabs)/sync.tsx index fbbf7a1..73372a8 100644 --- a/app/(tabs)/sync.tsx +++ b/app/(tabs)/sync.tsx @@ -3,6 +3,7 @@ import { Block, ConfirmModal, Text, Toast, VideoBox } from '@share/components' import Img from '@share/components/Img' import { FlashList } from '@shopify/flash-list' import * as ImagePicker from 'expo-image-picker' +import { router } from 'expo-router' import React, { memo, useCallback, useEffect, useMemo, useState } from 'react' import { ActivityIndicator, RefreshControl, ScrollView, View } from 'react-native' import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated' @@ -104,7 +105,7 @@ const Sync = () => { } }, [posts]) - console.log('selectedItem-----------', selectedItem) + // console.log('selectedItem-----------', selectedItem) // 事件处理函数 const handleConnectToggle = useCallback( @@ -154,7 +155,7 @@ const Sync = () => { > - 正在同步文件... + 正在同步文件... ), @@ -234,8 +235,7 @@ const Sync = () => { - 生成同款风格将消耗 2 Goo{' '} - 算力。 + 生成同款风格将消耗 2 Goo 算力。 } onCancel={() => Toast.hideModal()} @@ -273,7 +273,7 @@ const Sync = () => { > - 生成中... + 生成中... ), @@ -474,11 +474,11 @@ const DeviceItem = memo(({ device, onConnectToggle }: { device: any; onConnectTo - {name} + {name} {isConnected ? '已连接' : '未连接'} @@ -487,10 +487,10 @@ const DeviceItem = memo(({ device, onConnectToggle }: { device: any; onConnectTo onConnectToggle(device)} > - {isConnected ? '已连接' : '连接'} + {isConnected ? '已连接' : '连接'} @@ -529,7 +529,7 @@ const GridItem = memo( style={{ transform: [{ skewX: '6deg' }] }} > - 失败 + 失败 ) } @@ -541,7 +541,7 @@ const GridItem = memo( style={{ transform: [{ skewX: '6deg' }] }} > - 生成中 + 生成中 ) } @@ -589,7 +589,7 @@ const FilterButtons = memo( key={label} className={`-skew-x-3 border-2 border-black bg-black px-[16px] py-[4px] shadow-small-accent`} > - {label} + {label} ) })} @@ -598,7 +598,7 @@ const FilterButtons = memo( className={`border-2 border-black px-[16px] py-[6px] shadow-medium-black ${isSelectionMode ? 'bg-[#e61e25] text-white' : 'bg-accent text-black'}`} onClick={onToggleSelectionMode} > - {isSelectionMode ? '取消' : '管理'} + {isSelectionMode ? '取消' : '管理'} ) @@ -624,7 +624,7 @@ const SelectionBar = memo( - 已选: {selectedCount} + 已选: {selectedCount} 全选 @@ -655,7 +655,7 @@ const FABButtons = memo( > - 再次生成 + 再次生成 @@ -667,7 +667,7 @@ const FABButtons = memo( > - 同步 + 同步 @@ -679,6 +679,21 @@ const FABButtons = memo( // ============ 主要组件部分 ============ const HeaderBanner = memo(({ connectedDevice, onPick }: { connectedDevice: any; onPick: () => void }) => { + const { user, isAuthenticated, signOut } = useAuth() + + console.log('user-----------', user) + + const handleLogout = () => { + if (isAuthenticated) { + signOut().then(() => { + Toast.show({ title: '已登出' }) + }) + } else { + router.replace('/auth') + } + } + const loginText = isAuthenticated ? '登出' : '登录' + return ( @@ -687,12 +702,20 @@ const HeaderBanner = memo(({ connectedDevice, onPick }: { connectedDevice: any; {connectedDevice ? '设备已连接' : '设备离线'} - - - 上传本地 + + + {loginText} + + + + 上传本地 + ) @@ -728,7 +751,7 @@ const TopCircleSection = memo( name={connectedDevice ? 'wifi' : 'wifi-outline'} size={20} /> - {connectedDevice ? '已连接' : '选择设备'} + {connectedDevice ? '已连接' : '选择设备'} {/* */} - + 离线模式 @@ -758,7 +781,7 @@ const TopCircleSection = memo( const GalleryRenderer = memo(({ selectedItem }: { selectedItem: any }) => { const url = selectedItem?.url || selectedItem?.imageUrl - console.log('GalleryRenderer--------------', selectedItem) + // console.log('GalleryRenderer--------------', selectedItem) if (!url) return null @@ -804,7 +827,7 @@ const ManagerView = memo( > - 设备管理 + 设备管理 {/* 空占位符 */} diff --git a/app/auth.tsx b/app/auth.tsx index c424537..2775927 100644 --- a/app/auth.tsx +++ b/app/auth.tsx @@ -126,7 +126,7 @@ export default function Auth() { - LOOMART + LOOMART @@ -141,7 +141,7 @@ export default function Auth() { onClick={() => setMode(tabMode)} > {tabMode === 'login' ? '登录' : '注册'} @@ -159,7 +159,7 @@ export default function Auth() { {mode === 'login' ? ( - 账号 + 账号 - 邮箱 + 邮箱 - 用户名 + 用户名 - 密码 + 密码 - 确认密码 + 确认密码 )} - + {loading ? '处理中...' : mode === 'login' ? '登录' : '注册'} diff --git a/app/pointList.native.tsx b/app/pointList.native.tsx index 037571b..bd32a95 100644 --- a/app/pointList.native.tsx +++ b/app/pointList.native.tsx @@ -6,7 +6,7 @@ import Alipay from 'expo-native-alipay' import { Stack, useRouter } from 'expo-router' import ExpoWeChat from 'expo-wechat' import React, { useEffect, useState } from 'react' -import { Dimensions, ScrollView } from 'react-native' +import { Dimensions, Platform, ScrollView } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { Block, Img, Text } from '@/@share/components' @@ -43,9 +43,12 @@ export default function ChargePage() { const initPay = async () => { // 设置支付宝回调 URL Scheme(使用 App 的统一 scheme) - Alipay.setAlipayScheme(SCHEME) - Alipay.setAlipaySandbox(true) // 开启沙箱模式,测试环境使用 + if (Platform.OS === 'ios') { + Alipay.setAlipayScheme(SCHEME) + } else { + Alipay.setAlipaySandbox(true) // 开启沙箱模式,测试环境使用 + } console.log('-------alipay version:', await Alipay.getVersion()) @@ -105,7 +108,7 @@ export default function ChargePage() { const renderMyPoints = () => ( 我的积分 - {balanceRes?.balance} + {balanceRes?.balance} ) @@ -211,7 +214,7 @@ export default function ChargePage() { opacity={0.8} onClick={handlePay} > - 确认充值 + 确认充值 diff --git a/app/pointList.tsx b/app/pointList.tsx index 575d431..c1435ef 100644 --- a/app/pointList.tsx +++ b/app/pointList.tsx @@ -55,7 +55,7 @@ export default function ChargePage() { const renderMyPoints = () => ( 我的积分 - {balanceRes?.balance} + {balanceRes?.balance} ) @@ -155,7 +155,7 @@ export default function ChargePage() { opacity={0.8} onClick={handlePay} > - 确认充值 + 确认充值 已阅读并同意 diff --git a/components/auth-loading-screen.tsx b/components/auth-loading-screen.tsx index d6ee72b..9c23f5d 100644 --- a/components/auth-loading-screen.tsx +++ b/components/auth-loading-screen.tsx @@ -7,7 +7,7 @@ export function AuthLoadingScreen() { - 加载中... + 加载中... ) diff --git a/lib/fetch-logger.ts b/lib/fetch-logger.ts index c3f6a34..948ced8 100644 --- a/lib/fetch-logger.ts +++ b/lib/fetch-logger.ts @@ -1,12 +1,12 @@ import { router } from 'expo-router' -import * as SecureStore from 'expo-secure-store' -import { storage } from './storage'; + +import { storage } from './storage' interface FetchLoggerOptions { - enableLogging?: boolean; - logRequest?: boolean; - logResponse?: boolean; - logError?: boolean; + enableLogging?: boolean + logRequest?: boolean + logResponse?: boolean + logError?: boolean } const defaultOptions: FetchLoggerOptions = { @@ -14,89 +14,87 @@ const defaultOptions: FetchLoggerOptions = { logRequest: true, logResponse: true, logError: true, -}; +} -const originalFetch = global.fetch; +const originalFetch = global.fetch export const createFetchWithLogger = (options: FetchLoggerOptions = {}) => { - const config = { ...defaultOptions, ...options }; + const config = { ...defaultOptions, ...options } return async (input: RequestInfo | URL, init?: RequestInit): Promise => { - const startTime = Date.now(); - const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url; - const method = init?.method || 'GET'; + const startTime = Date.now() + const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url + const method = init?.method || 'GET' try { - const token = await storage.getItem('token'); + const token = await storage.getItem('token') if (token) { init = { ...init, headers: { ...init?.headers, - 'authorization': `Bearer ${token}`, + authorization: `Bearer ${token}`, }, - }; + } } - const response = await originalFetch(input, init); + const response = await originalFetch(input, init) if (response.status === 401) { console.warn('🔐 401 未授权,跳转到登录页') router.replace('/auth') } - return response; + return response } catch (error) { - const duration = Date.now() - startTime; + const duration = Date.now() - startTime if (config.enableLogging && config.logError) { - console.group(`❌ [FETCH 错误] ${method} ${url}`); - console.error('📍 URL:', url); - console.error('🔧 Method:', method); - console.error('⏱️ Duration:', `${duration}ms`); + console.group(`❌ [FETCH 错误] ${method} ${url}`) + console.error('📍 URL:', url) + console.error('🔧 Method:', method) + console.error('⏱️ Duration:', `${duration}ms`) if (init?.headers) { - console.error('📋 Request Headers:', JSON.stringify(init.headers, null, 2)); + console.error('📋 Request Headers:', JSON.stringify(init.headers, null, 2)) } if (init?.body) { try { - const bodyContent = typeof init.body === 'string' - ? init.body - : JSON.stringify(init.body); - console.error('📦 Request Body:', bodyContent); + const bodyContent = typeof init.body === 'string' ? init.body : JSON.stringify(init.body) + console.error('📦 Request Body:', bodyContent) } catch (e) { - console.error('📦 Request Body: [无法序列化]'); + console.error('📦 Request Body: [无法序列化]') } } - console.error('💥 Error Type:', error?.constructor?.name || 'Unknown'); - console.error('💥 Error Message:', error instanceof Error ? error.message : String(error)); + console.error('💥 Error Type:', error?.constructor?.name || 'Unknown') + console.error('💥 Error Message:', error instanceof Error ? error.message : String(error)) if (error instanceof Error && error.stack) { - console.error('📚 Stack Trace:', error.stack); + console.error('📚 Stack Trace:', error.stack) } if (error && typeof error === 'object') { - console.error('🔍 Error Details:', JSON.stringify(error, Object.getOwnPropertyNames(error), 2)); + console.error('🔍 Error Details:', JSON.stringify(error, Object.getOwnPropertyNames(error), 2)) } - console.groupEnd(); + console.groupEnd() } - throw error; + throw error } - }; -}; + } +} -export const fetchWithLogger = createFetchWithLogger(); +export const fetchWithLogger = createFetchWithLogger() export const setupGlobalFetchLogger = (options?: FetchLoggerOptions) => { - const loggedFetch = createFetchWithLogger(options); - global.fetch = loggedFetch as typeof fetch; + const loggedFetch = createFetchWithLogger(options) + global.fetch = loggedFetch as typeof fetch return () => { - global.fetch = originalFetch; - }; -}; + global.fetch = originalFetch + } +}