perf: 懒加载和动态加载优化

This commit is contained in:
archer 2023-03-05 21:16:19 +08:00
parent 78903baefa
commit 52a752dab5
14 changed files with 203 additions and 191 deletions

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="12" viewBox="0 0 17 12" fill="none"><g opacity="1" transform="translate(0.70001220703125 0.2001953125) rotate(0 7.5 5.5)"><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(0 5) rotate(0 7.5 0.5)" d="M0,0.5L15,0.5 " /><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(0 0) rotate(0 7.5 0.5)" d="M0,0.5L15,0.5 " /><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(7 10) rotate(0 4 0.5)" d="M0,0.5L8,0.5 " /></g></svg>

Before

Width:  |  Height:  |  Size: 728 B

View File

@ -25,10 +25,10 @@ const Auth = ({ children }: { children: JSX.Element }) => {
useQuery( useQuery(
[router.pathname, userInfo], [router.pathname, userInfo],
() => { () => {
setLoading(true);
if (unAuthPage[router.pathname] === true || userInfo) { if (unAuthPage[router.pathname] === true || userInfo) {
return setLoading(false); return setLoading(false);
} else { } else {
setLoading(true);
return getTokenLogin(); return getTokenLogin();
} }
}, },

View File

@ -43,15 +43,13 @@ const navbarList = [
const Layout = ({ children }: { children: JSX.Element }) => { const Layout = ({ children }: { children: JSX.Element }) => {
const { isPc } = useScreen(); const { isPc } = useScreen();
const router = useRouter(); const router = useRouter();
const { Loading } = useLoading({ const { Loading } = useLoading({ defaultLoading: true });
defaultLoading: true
});
const { loading } = useGlobalStore(); const { loading } = useGlobalStore();
return ( return (
<> <>
{!unShowLayoutRoute[router.pathname] ? ( {!unShowLayoutRoute[router.pathname] ? (
<Box data-test="ss" h={'100%'} backgroundColor={'gray.100'} overflow={'auto'}> <Box h={'100%'} backgroundColor={'gray.100'} overflow={'auto'}>
{isPc ? ( {isPc ? (
<> <>
<Box h={'100%'} position={'fixed'} left={0} top={0} w={'80px'}> <Box h={'100%'} position={'fixed'} left={0} top={0} w={'80px'}>

View File

@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, memo } from 'react';
import { Spinner, Flex } from '@chakra-ui/react'; import { Spinner, Flex } from '@chakra-ui/react';
export const useLoading = (props?: { defaultLoading: boolean }) => { export const useLoading = (props?: { defaultLoading: boolean }) => {
@ -31,6 +31,6 @@ export const useLoading = (props?: { defaultLoading: boolean }) => {
return { return {
isLoading, isLoading,
setIsLoading, setIsLoading,
Loading Loading: memo(Loading)
}; };
}; };

View File

@ -11,6 +11,6 @@ export function useScreen() {
isPc, isPc,
mediaLgMd: useMemo(() => (isPc ? 'lg' : 'md'), [isPc]), mediaLgMd: useMemo(() => (isPc ? 'lg' : 'md'), [isPc]),
mediaMdSm: useMemo(() => (isPc ? 'md' : 'sm'), [isPc]), mediaMdSm: useMemo(() => (isPc ? 'md' : 'sm'), [isPc]),
media: (pc: number | string, phone: number | string) => (isPc ? pc : phone) media: (pc: any, phone: any) => (isPc ? pc : phone)
}; };
} }

View File

@ -38,6 +38,7 @@ export default function App({ Component, pageProps }: AppProps) {
/> />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Script src="/iconfont.js" strategy="afterInteractive"></Script>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}> <ChakraProvider theme={theme}>
<Layout> <Layout>
@ -45,7 +46,6 @@ export default function App({ Component, pageProps }: AppProps) {
</Layout> </Layout>
</ChakraProvider> </ChakraProvider>
</QueryClientProvider> </QueryClientProvider>
<Script src="/iconfont.js"></Script>
</> </>
); );
} }

View File

@ -13,10 +13,13 @@ import { Textarea, Box, Flex, Button } from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import Icon from '@/components/Icon'; import Icon from '@/components/Icon';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import Markdown from '@/components/Markdown';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading'; import { useLoading } from '@/hooks/useLoading';
import { OpenAiModelEnum } from '@/constants/model'; import { OpenAiModelEnum } from '@/constants/model';
import dynamic from 'next/dynamic';
import { useGlobalStore } from '@/store/global';
const Markdown = dynamic(() => import('@/components/Markdown'));
const textareaMinH = '22px'; const textareaMinH = '22px';
@ -34,7 +37,7 @@ const Chat = () => {
const isChatting = useMemo(() => chatList[chatList.length - 1]?.status === 'loading', [chatList]); const isChatting = useMemo(() => chatList[chatList.length - 1]?.status === 'loading', [chatList]);
const lastWordHuman = useMemo(() => chatList[chatList.length - 1]?.obj === 'Human', [chatList]); const lastWordHuman = useMemo(() => chatList[chatList.length - 1]?.obj === 'Human', [chatList]);
const { Loading, setIsLoading } = useLoading({ defaultLoading: true }); const { setLoading } = useGlobalStore();
// 滚动到底部 // 滚动到底部
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {
@ -49,32 +52,40 @@ const Chat = () => {
}, []); }, []);
// 初始化聊天框 // 初始化聊天框
useQuery([chatId, windowId], () => (chatId ? getInitChatSiteInfo(chatId, windowId) : null), { useQuery(
cacheTime: 5 * 60 * 1000, [chatId, windowId],
onSuccess(res) { () => {
if (!res) return; if (!chatId) return null;
router.replace(`/chat?chatId=${chatId}&windowId=${res.windowId}`); setLoading(true);
return getInitChatSiteInfo(chatId, windowId);
setChatSiteData(res.chatSite);
setChatList(
res.history.map((item) => ({
...item,
status: 'finish'
}))
);
scrollToBottom();
setIsLoading(false);
}, },
onError() { {
toast({ cacheTime: 5 * 60 * 1000,
title: '初始化异常,请刷新', onSuccess(res) {
status: 'error', if (!res) return;
isClosable: true, router.replace(`/chat?chatId=${chatId}&windowId=${res.windowId}`);
duration: 5000
}); setChatSiteData(res.chatSite);
setIsLoading(false); setChatList(
res.history.map((item) => ({
...item,
status: 'finish'
}))
);
scrollToBottom();
setLoading(false);
},
onError() {
toast({
title: '初始化异常,请刷新',
status: 'error',
isClosable: true,
duration: 5000
});
setLoading(false);
}
} }
}); );
// gpt3 方法 // gpt3 方法
const gpt3ChatPrompt = useCallback( const gpt3ChatPrompt = useCallback(
@ -293,7 +304,7 @@ const Chat = () => {
alt="/imgs/modelAvatar.png" alt="/imgs/modelAvatar.png"
width={30} width={30}
height={30} height={30}
></Image> />
</Box> </Box>
<Box flex={'1 0 0'} w={0} overflowX={'auto'}> <Box flex={'1 0 0'} w={0} overflowX={'auto'}>
{item.obj === 'AI' ? ( {item.obj === 'AI' ? (
@ -393,7 +404,6 @@ const Chat = () => {
</Box> </Box>
)} )}
</Box> </Box>
<Loading />
</Flex> </Flex>
); );
}; };

View File

@ -1,12 +1,9 @@
import React, { useEffect } from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { Card } from '@chakra-ui/react';
import { Card, Text, Box, Heading, Flex } from '@chakra-ui/react';
import Markdown from '@/components/Markdown'; import Markdown from '@/components/Markdown';
import { introPage } from '@/constants/common'; import { introPage } from '@/constants/common';
const Home = () => { const Home = () => {
const router = useRouter();
return ( return (
<Card p={5} lineHeight={2}> <Card p={5} lineHeight={2}>
<Markdown source={introPage} isChatting={false} /> <Markdown source={introPage} isChatting={false} />

View File

@ -1,15 +1,17 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback, useMemo } from 'react';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { Box, Flex, Image } from '@chakra-ui/react'; import { Box, Flex, Image } from '@chakra-ui/react';
import { PageTypeEnum } from '@/constants/user'; import { PageTypeEnum } from '@/constants/user';
import LoginForm from './components/LoginForm';
import RegisterForm from './components/RegisterForm';
import ForgetPasswordForm from './components/ForgetPasswordForm';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import type { ResLogin } from '@/api/response/user'; import type { ResLogin } from '@/api/response/user';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
import dynamic from 'next/dynamic';
const LoginForm = dynamic(() => import('./components/LoginForm'));
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
const Login = () => { const Login = () => {
const router = useRouter(); const router = useRouter();
const { isPc } = useScreen(); const { isPc } = useScreen();
@ -24,20 +26,17 @@ const Login = () => {
[router, setUserInfo] [router, setUserInfo]
); );
const map = { function DynamicComponent({ type }: { type: `${PageTypeEnum}` }) {
[PageTypeEnum.login]: { const TypeMap = {
Component: <LoginForm setPageType={setPageType} loginSuccess={loginSuccess} />, [PageTypeEnum.login]: LoginForm,
img: '/icon/loginLeft.svg' [PageTypeEnum.register]: RegisterForm,
}, [PageTypeEnum.forgetPassword]: ForgetPasswordForm
[PageTypeEnum.register]: { };
Component: <RegisterForm setPageType={setPageType} loginSuccess={loginSuccess} />,
img: '/icon/loginLeft.svg' const Component = TypeMap[type];
},
[PageTypeEnum.forgetPassword]: { return <Component setPageType={setPageType} loginSuccess={loginSuccess} />;
Component: <ForgetPasswordForm setPageType={setPageType} loginSuccess={loginSuccess} />, }
img: '/icon/loginLeft.svg'
}
};
return ( return (
<Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}> <Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}>
@ -54,7 +53,7 @@ const Login = () => {
> >
{isPc && ( {isPc && (
<Image <Image
src={map[pageType].img} src={'/icon/loginLeft.svg'}
order={pageType === PageTypeEnum.login ? 0 : 2} order={pageType === PageTypeEnum.login ? 0 : 2}
flex={'1 0 0'} flex={'1 0 0'}
w="0" w="0"
@ -76,7 +75,7 @@ const Login = () => {
px={10} px={10}
borderRadius={isPc ? 'md' : 'none'} borderRadius={isPc ? 'md' : 'none'}
> >
{map[pageType].Component} <DynamicComponent type={pageType} />
</Box> </Box>
</Flex> </Flex>
</Box> </Box>

View File

@ -25,11 +25,9 @@ interface CreateFormType {
} }
const CreateModel = ({ const CreateModel = ({
isOpen,
setCreateModelOpen, setCreateModelOpen,
onSuccess onSuccess
}: { }: {
isOpen: boolean;
setCreateModelOpen: Dispatch<boolean>; setCreateModelOpen: Dispatch<boolean>;
onSuccess: Dispatch<ModelType>; onSuccess: Dispatch<ModelType>;
}) => { }) => {
@ -72,7 +70,7 @@ const CreateModel = ({
return ( return (
<> <>
<Modal isOpen={isOpen} onClose={() => setCreateModelOpen(false)}> <Modal isOpen={true} onClose={() => setCreateModelOpen(false)}>
<ModalOverlay /> <ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader></ModalHeader> <ModalHeader></ModalHeader>

View File

@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { Grid, Box, Card, Flex, Button, FormControl, Input, Textarea } from '@chakra-ui/react'; import { Grid, Box, Card, Flex, Button, FormControl, Input, Textarea } from '@chakra-ui/react';
import type { ModelType } from '@/types/model'; import type { ModelType } from '@/types/model';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@ -7,17 +7,17 @@ import { putModelById } from '@/api/model';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
const ModelEditForm = ({ model }: { model: ModelType }) => { const ModelEditForm = ({ model }: { model?: ModelType }) => {
const isInit = useRef(false);
const { const {
register, register,
handleSubmit, handleSubmit,
reset,
formState: { errors } formState: { errors }
} = useForm<ModelType>({ } = useForm<ModelType>();
defaultValues: model
});
const { setLoading } = useGlobalStore(); const { setLoading } = useGlobalStore();
const { toast } = useToast(); const { toast } = useToast();
const { isPc } = useScreen(); const { media } = useScreen();
const onclickSave = useCallback( const onclickSave = useCallback(
async (data: ModelType) => { async (data: ModelType) => {
@ -61,8 +61,16 @@ const ModelEditForm = ({ model }: { model: ModelType }) => {
}); });
}, [errors, toast]); }, [errors, toast]);
/* model 只会改变一次 */
useEffect(() => {
if (model && !isInit.current) {
reset(model);
isInit.current = true;
}
}, [model, reset]);
return ( return (
<Grid gridTemplateColumns={isPc ? '1fr 1fr' : '1fr'} gridGap={5}> <Grid gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
<Card p={4}> <Card p={4}>
<Flex justifyContent={'space-between'} alignItems={'center'}> <Flex justifyContent={'space-between'} alignItems={'center'}>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
@ -83,7 +91,7 @@ const ModelEditForm = ({ model }: { model: ModelType }) => {
<FormControl mt={5}> <FormControl mt={5}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 80px'}>:</Box> <Box flex={'0 0 80px'}>:</Box>
<Box>{model.service.modelName}</Box> <Box>{model?.service.modelName}</Box>
</Flex> </Flex>
</FormControl> </FormControl>
<FormControl mt={5}> <FormControl mt={5}>

View File

@ -1,5 +1,5 @@
import React, { useEffect, useCallback, useState } from 'react'; import React, { useEffect, useCallback, useState } from 'react';
import { Box, Card, TableContainer, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react'; import { Box, TableContainer, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react';
import { ModelType } from '@/types/model'; import { ModelType } from '@/types/model';
import { getModelTrainings } from '@/api/model'; import { getModelTrainings } from '@/api/model';
import type { TrainingItemType } from '@/types/training'; import type { TrainingItemType } from '@/types/training';
@ -38,7 +38,7 @@ const Training = ({ model }: { model: ModelType }) => {
}, [loadTrainingRecords, model]); }, [loadTrainingRecords, model]);
return ( return (
<Card p={4} h={'100%'}> <>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
: {model.trainingTimes} : {model.trainingTimes}
</Box> </Box>
@ -63,7 +63,7 @@ const Training = ({ model }: { model: ModelType }) => {
</Tbody> </Tbody>
</Table> </Table>
</TableContainer> </TableContainer>
</Card> </>
); );
}; };

View File

@ -11,12 +11,14 @@ import { useGlobalStore } from '@/store/global';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import ModelEditForm from './components/ModelEditForm'; import ModelEditForm from './components/ModelEditForm';
import Icon from '@/components/Icon'; import Icon from '@/components/Icon';
import Training from './components/Training'; import dynamic from 'next/dynamic';
const Training = dynamic(() => import('./components/Training'));
const ModelDetail = () => { const ModelDetail = () => {
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter(); const router = useRouter();
const { isPc } = useScreen(); const { isPc, media } = useScreen();
const { setLoading } = useGlobalStore(); const { setLoading } = useGlobalStore();
const { openConfirm, ConfirmChild } = useConfirm({ const { openConfirm, ConfirmChild } = useConfirm({
content: '确认删除该模型?' content: '确认删除该模型?'
@ -128,114 +130,114 @@ const ModelDetail = () => {
return ( return (
<> <>
{!!model && ( {/* 头部 */}
<> <Card px={6} py={3}>
{/* 头部 */} {isPc ? (
<Card px={6} py={3}> <Flex alignItems={'center'}>
{isPc ? ( <Box fontSize={'xl'} fontWeight={'bold'}>
<Flex alignItems={'center'}> {model?.name || '模型'}
<Box fontSize={'xl'} fontWeight={'bold'}> </Box>
{model.name} {!!model && (
</Box> <Tag
<Tag ml={2}
ml={2} variant="solid"
variant="solid" colorScheme={formatModelStatus[model.status].colorTheme}
colorScheme={formatModelStatus[model.status].colorTheme} cursor={model.status === ModelStatusEnum.training ? 'pointer' : 'default'}
cursor={model.status === ModelStatusEnum.training ? 'pointer' : 'default'} onClick={handleClickUpdateStatus}
onClick={handleClickUpdateStatus} >
> {formatModelStatus[model.status].text}
</Tag>
)}
<Box flex={1} />
<Button variant={'outline'} onClick={handlePreviewChat}>
</Button>
</Flex>
) : (
<>
<Flex alignItems={'center'}>
<Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}>
{model?.name || '模型'}
</Box>
{!!model && (
<Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}>
{formatModelStatus[model.status].text} {formatModelStatus[model.status].text}
</Tag> </Tag>
<Box flex={1} /> )}
<Button variant={'outline'} onClick={handlePreviewChat}> </Flex>
<Box mt={4} textAlign={'right'}>
</Button> <Button variant={'outline'} onClick={handlePreviewChat}>
</Flex>
) : ( </Button>
<> </Box>
<Flex alignItems={'center'}> </>
<Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}> )}
{model.name} </Card>
</Box> {/* 基本信息编辑 */}
<Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}> <Box mt={5}>
{formatModelStatus[model.status].text} <ModelEditForm model={model} />
</Tag> </Box>
</Flex> {/* 其他配置 */}
<Box mt={4} textAlign={'right'}> <Grid mt={5} gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
<Button variant={'outline'} onClick={handlePreviewChat}> <Card p={4}>{!!model && <Training model={model} />}</Card>
<Card p={4}>
</Button> <Box fontWeight={'bold'} fontSize={'lg'}>
</Box>
</>
)}
</Card>
{/* 基本信息编辑 */}
<Box mt={5}>
<ModelEditForm model={model} />
</Box> </Box>
{/* 其他配置 */} <Flex mt={5} alignItems={'center'}>
<Grid mt={5} gridTemplateColumns={isPc ? '1fr 1fr' : '1fr'} gridGap={5}> <Box flex={'0 0 80px'}>:</Box>
<Training model={model} /> <Button
<Card h={'100%'} p={4}> size={'sm'}
<Box fontWeight={'bold'} fontSize={'lg'}> onClick={() => {
SelectFileDom.current?.click();
</Box> }}
<Flex mt={5} alignItems={'center'}> title={!canTrain ? '' : '模型不支持微调'}
<Box flex={'0 0 80px'}>:</Box> isDisabled={!canTrain}
<Button >
size={'sm'}
onClick={() => { </Button>
SelectFileDom.current?.click(); <Flex
}} as={'a'}
title={!canTrain ? '' : '模型不支持微调'} href="/TrainingTemplate.jsonl"
isDisabled={!canTrain} download
> ml={5}
cursor={'pointer'}
</Button> alignItems={'center'}
<Flex color={'blue.500'}
as={'a'} >
href="/TrainingTemplate.jsonl" <Icon name={'icon-yunxiazai'} color={'#3182ce'} />
download
ml={5} </Flex>
cursor={'pointer'} </Flex>
alignItems={'center'} {/* 提示 */}
color={'blue.500'} <Box mt={3} py={3} color={'blackAlpha.500'}>
> <Box as={'li'} lineHeight={1.9}>
<Icon name={'icon-yunxiazai'} color={'#3182ce'} /> prompt completion
</Box>
</Flex> <Box as={'li'} lineHeight={1.9}>
</Flex> prompt \n\n###\n\n prompt
{/* 提示 */}
<Box mt={3} py={3} color={'blackAlpha.500'}> </Box>
<Box as={'li'} lineHeight={1.9}> <Box as={'li'} lineHeight={1.9}>
prompt completion completion ###
</Box> </Box>
<Box as={'li'} lineHeight={1.9}> </Box>
prompt \n\n###\n\n prompt <Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 80px'}>:</Box>
</Box> <Button
<Box as={'li'} lineHeight={1.9}> colorScheme={'red'}
completion ### size={'sm'}
</Box> onClick={() => {
</Box> openConfirm(() => {
<Flex mt={5} alignItems={'center'}> handleDelModel();
<Box flex={'0 0 80px'}>:</Box> });
<Button }}
colorScheme={'red'} >
size={'sm'}
onClick={() => { </Button>
openConfirm(() => { </Flex>
handleDelModel(); </Card>
}); </Grid>
}}
>
</Button>
</Flex>
</Card>
</Grid>
</>
)}
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}> <Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
<input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} /> <input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} />
</Box> </Box>

View File

@ -1,15 +1,17 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { Box, Button, Flex, Card } from '@chakra-ui/react'; import { Box, Button, Flex, Card } from '@chakra-ui/react';
import { getMyModels } from '@/api/model'; import { getMyModels } from '@/api/model';
import { getChatSiteId } from '@/api/chat'; import { getChatSiteId } from '@/api/chat';
import { ModelType } from '@/types/model'; import { ModelType } from '@/types/model';
import CreateModel from './components/CreateModel';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import ModelTable from './components/ModelTable'; import ModelTable from './components/ModelTable';
import ModelPhoneList from './components/ModelPhoneList'; import ModelPhoneList from './components/ModelPhoneList';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading'; import { useLoading } from '@/hooks/useLoading';
import dynamic from 'next/dynamic';
const CreateModel = dynamic(() => import('./components/CreateModel'));
const ModelList = () => { const ModelList = () => {
const { isPc } = useScreen(); const { isPc } = useScreen();
@ -72,11 +74,10 @@ const ModelList = () => {
)} )}
</Box> </Box>
{/* 创建弹窗 */} {/* 创建弹窗 */}
<CreateModel {openCreateModel && (
isOpen={openCreateModel} <CreateModel setCreateModelOpen={setOpenCreateModel} onSuccess={createModelSuccess} />
setCreateModelOpen={setOpenCreateModel} )}
onSuccess={createModelSuccess}
/>
<Loading loading={isLoading} /> <Loading loading={isLoading} />
</Box> </Box>
); );