mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-28 17:29:44 +00:00
new framwork
This commit is contained in:
67
client/src/hooks/useConfirm.tsx
Normal file
67
client/src/hooks/useConfirm.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogContent,
|
||||
AlertDialogOverlay,
|
||||
useDisclosure,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
export const useConfirm = ({ title = '提示', content }: { title?: string; content: string }) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const cancelRef = useRef(null);
|
||||
const confirmCb = useRef<any>();
|
||||
const cancelCb = useRef<any>();
|
||||
|
||||
return {
|
||||
openConfirm: useCallback(
|
||||
(confirm?: any, cancel?: any) => {
|
||||
confirmCb.current = confirm;
|
||||
cancelCb.current = cancel;
|
||||
|
||||
return onOpen;
|
||||
},
|
||||
[onOpen]
|
||||
),
|
||||
ConfirmChild: useCallback(
|
||||
() => (
|
||||
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
|
||||
<AlertDialogOverlay>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||
{title}
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogBody>{content}</AlertDialogBody>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
typeof cancelCb.current === 'function' && cancelCb.current();
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
ml={4}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
typeof confirmCb.current === 'function' && confirmCb.current();
|
||||
}}
|
||||
>
|
||||
确认
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogOverlay>
|
||||
</AlertDialog>
|
||||
),
|
||||
[content, isOpen, onClose, title]
|
||||
)
|
||||
};
|
||||
};
|
91
client/src/hooks/useEditInfo.tsx
Normal file
91
client/src/hooks/useEditInfo.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
Input,
|
||||
useDisclosure,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
export const useEditInfo = ({
|
||||
title,
|
||||
placeholder = ''
|
||||
}: {
|
||||
title: string;
|
||||
placeholder?: string;
|
||||
}) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const onSuccessCb = useRef<(content: string) => void | Promise<void>>();
|
||||
const onErrorCb = useRef<(err: any) => void>();
|
||||
const defaultValue = useRef('');
|
||||
|
||||
const onOpenModal = useCallback(
|
||||
({
|
||||
defaultVal,
|
||||
onSuccess,
|
||||
onError
|
||||
}: {
|
||||
defaultVal: string;
|
||||
onSuccess: (content: string) => any;
|
||||
onError?: (err: any) => void;
|
||||
}) => {
|
||||
onOpen();
|
||||
onSuccessCb.current = onSuccess;
|
||||
onErrorCb.current = onError;
|
||||
defaultValue.current = defaultVal;
|
||||
},
|
||||
[onOpen]
|
||||
);
|
||||
|
||||
const onclickConfirm = useCallback(async () => {
|
||||
if (!inputRef.current) return;
|
||||
try {
|
||||
const val = inputRef.current.value;
|
||||
await onSuccessCb.current?.(val);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
onErrorCb.current?.(err);
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const EditModal = useCallback(
|
||||
() => (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
defaultValue={defaultValue.current}
|
||||
placeholder={placeholder}
|
||||
autoFocus
|
||||
maxLength={20}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} variant={'outline'} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={onclickConfirm}>确认</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
),
|
||||
[isOpen, onClose, onclickConfirm, placeholder, title]
|
||||
);
|
||||
|
||||
return {
|
||||
onOpenModal,
|
||||
EditModal
|
||||
};
|
||||
};
|
19
client/src/hooks/useLoading.tsx
Normal file
19
client/src/hooks/useLoading.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import LoadingComponent from '@/components/Loading';
|
||||
|
||||
export const useLoading = (props?: { defaultLoading: boolean }) => {
|
||||
const [isLoading, setIsLoading] = useState(props?.defaultLoading || false);
|
||||
|
||||
const Loading = useCallback(
|
||||
({ loading, fixed = true }: { loading?: boolean; fixed?: boolean }): JSX.Element | null => {
|
||||
return isLoading || loading ? <LoadingComponent fixed={fixed} /> : null;
|
||||
},
|
||||
[isLoading]
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
Loading
|
||||
};
|
||||
};
|
15
client/src/hooks/useMarkdown.ts
Normal file
15
client/src/hooks/useMarkdown.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
export const getMd = async (url: string) => {
|
||||
const response = await fetch(`/docs/${url}`);
|
||||
const textContent = await response.text();
|
||||
return textContent;
|
||||
};
|
||||
|
||||
export const useMarkdown = ({ url }: { url: string }) => {
|
||||
const { data = '' } = useQuery([url], () => getMd(url));
|
||||
|
||||
return {
|
||||
data
|
||||
};
|
||||
};
|
108
client/src/hooks/usePagination.tsx
Normal file
108
client/src/hooks/usePagination.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import type { PagingData } from '../types/index';
|
||||
import { IconButton, Flex, Box, Input } from '@chakra-ui/react';
|
||||
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useToast } from './useToast';
|
||||
|
||||
export const usePagination = <T = any,>({
|
||||
api,
|
||||
pageSize = 10,
|
||||
params = {},
|
||||
defaultRequest = true
|
||||
}: {
|
||||
api: (data: any) => any;
|
||||
pageSize?: number;
|
||||
params?: Record<string, any>;
|
||||
defaultRequest?: boolean;
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const [pageNum, setPageNum] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
const maxPage = useMemo(() => Math.ceil(total / pageSize) || 1, [pageSize, total]);
|
||||
|
||||
const { mutate, isLoading } = useMutation({
|
||||
mutationFn: async (num: number = pageNum) => {
|
||||
try {
|
||||
const res: PagingData<T> = await api({
|
||||
pageNum: num,
|
||||
pageSize,
|
||||
...params
|
||||
});
|
||||
setPageNum(num);
|
||||
res.total !== undefined && setTotal(res.total);
|
||||
setData(res.data);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: error?.message || '获取数据异常',
|
||||
status: 'error'
|
||||
});
|
||||
console.log(error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const Pagination = useCallback(() => {
|
||||
return (
|
||||
<Flex alignItems={'center'} justifyContent={'end'}>
|
||||
<IconButton
|
||||
isDisabled={pageNum === 1}
|
||||
icon={<ArrowBackIcon />}
|
||||
aria-label={'left'}
|
||||
size={'sm'}
|
||||
w={'28px'}
|
||||
h={'28px'}
|
||||
onClick={() => mutate(pageNum - 1)}
|
||||
/>
|
||||
<Flex mx={2} alignItems={'center'}>
|
||||
<Input
|
||||
defaultValue={pageNum}
|
||||
w={'50px'}
|
||||
size={'xs'}
|
||||
type={'number'}
|
||||
min={1}
|
||||
max={maxPage}
|
||||
onBlur={(e) => {
|
||||
const val = +e.target.value;
|
||||
if (val === pageNum) return;
|
||||
if (val >= maxPage) {
|
||||
mutate(maxPage);
|
||||
} else if (val < 1) {
|
||||
mutate(1);
|
||||
} else {
|
||||
mutate(+e.target.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box mx={2}>/</Box>
|
||||
{maxPage}
|
||||
</Flex>
|
||||
<IconButton
|
||||
isDisabled={pageNum === maxPage}
|
||||
icon={<ArrowForwardIcon />}
|
||||
aria-label={'left'}
|
||||
size={'sm'}
|
||||
w={'28px'}
|
||||
h={'28px'}
|
||||
onClick={() => mutate(pageNum + 1)}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}, [maxPage, mutate, pageNum]);
|
||||
|
||||
useEffect(() => {
|
||||
defaultRequest && mutate(1);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
pageNum,
|
||||
pageSize,
|
||||
total,
|
||||
data,
|
||||
isLoading,
|
||||
Pagination,
|
||||
getData: mutate
|
||||
};
|
||||
};
|
33
client/src/hooks/useRequest.tsx
Normal file
33
client/src/hooks/useRequest.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import type { UseMutationOptions } from '@tanstack/react-query';
|
||||
|
||||
interface Props extends UseMutationOptions<any, any, any, any> {
|
||||
successToast?: string;
|
||||
errorToast?: string;
|
||||
}
|
||||
|
||||
export const useRequest = ({ successToast, errorToast, onSuccess, onError, ...props }: Props) => {
|
||||
const { toast } = useToast();
|
||||
const mutation = useMutation<unknown, unknown, any, unknown>({
|
||||
...props,
|
||||
onSuccess(res, variables: void, context: unknown) {
|
||||
onSuccess?.(res, variables, context);
|
||||
successToast &&
|
||||
toast({
|
||||
title: successToast,
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
onError(err: any, variables: void, context: unknown) {
|
||||
onError?.(err, variables, context);
|
||||
errorToast &&
|
||||
toast({
|
||||
title: typeof err === 'string' ? err : err?.message || errorToast,
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
21
client/src/hooks/useScreen.ts
Normal file
21
client/src/hooks/useScreen.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMediaQuery } from '@chakra-ui/react';
|
||||
|
||||
interface Props {
|
||||
defaultIsPc?: boolean;
|
||||
}
|
||||
|
||||
export function useScreen(data?: Props) {
|
||||
const { defaultIsPc = false } = data || {};
|
||||
const [isPc] = useMediaQuery('(min-width: 900px)', {
|
||||
ssr: true,
|
||||
fallback: defaultIsPc
|
||||
});
|
||||
|
||||
return {
|
||||
isPc,
|
||||
mediaLgMd: useMemo(() => (isPc ? 'lg' : 'md'), [isPc]),
|
||||
mediaMdSm: useMemo(() => (isPc ? 'md' : 'sm'), [isPc]),
|
||||
media: (pc: any, phone: any) => (isPc ? pc : phone)
|
||||
};
|
||||
}
|
34
client/src/hooks/useSelectFile.tsx
Normal file
34
client/src/hooks/useSelectFile.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
export const useSelectFile = (props?: { fileType?: string; multiple?: boolean }) => {
|
||||
const { fileType = '*', multiple = false } = props || {};
|
||||
const SelectFileDom = useRef<HTMLInputElement>(null);
|
||||
|
||||
const File = useCallback(
|
||||
({ onSelect }: { onSelect: (e: File[]) => void }) => (
|
||||
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
|
||||
<input
|
||||
ref={SelectFileDom}
|
||||
type="file"
|
||||
accept={fileType}
|
||||
multiple={multiple}
|
||||
onChange={(e) => {
|
||||
if (!e.target.files || e.target.files?.length === 0) return;
|
||||
onSelect(Array.from(e.target.files));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
[fileType, multiple]
|
||||
);
|
||||
|
||||
const onOpen = useCallback(() => {
|
||||
SelectFileDom.current && SelectFileDom.current.click();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
File,
|
||||
onOpen
|
||||
};
|
||||
};
|
66
client/src/hooks/useSendCode.ts
Normal file
66
client/src/hooks/useSendCode.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { sendAuthCode } from '@/api/user';
|
||||
import { UserAuthTypeEnum } from '@/constants/common';
|
||||
let timer: any;
|
||||
import { useToast } from './useToast';
|
||||
import { getClientToken } from '@/utils/plugin/google';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
export const useSendCode = () => {
|
||||
const {
|
||||
initData: { googleVerKey }
|
||||
} = useGlobalStore();
|
||||
const { toast } = useToast();
|
||||
const [codeSending, setCodeSending] = useState(false);
|
||||
const [codeCountDown, setCodeCountDown] = useState(0);
|
||||
const sendCodeText = useMemo(() => {
|
||||
if (codeCountDown >= 10) {
|
||||
return `${codeCountDown}s后重新获取`;
|
||||
}
|
||||
if (codeCountDown > 0) {
|
||||
return `0${codeCountDown}s后重新获取`;
|
||||
}
|
||||
return '获取验证码';
|
||||
}, [codeCountDown]);
|
||||
|
||||
const sendCode = useCallback(
|
||||
async ({ username, type }: { username: string; type: `${UserAuthTypeEnum}` }) => {
|
||||
setCodeSending(true);
|
||||
try {
|
||||
await sendAuthCode({
|
||||
username,
|
||||
type,
|
||||
googleToken: await getClientToken(googleVerKey)
|
||||
});
|
||||
setCodeCountDown(60);
|
||||
timer = setInterval(() => {
|
||||
setCodeCountDown((val) => {
|
||||
if (val <= 0) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
return val - 1;
|
||||
});
|
||||
}, 1000);
|
||||
toast({
|
||||
title: '验证码已发送',
|
||||
status: 'success',
|
||||
position: 'top'
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: error.message || '发送验证码异常',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setCodeSending(false);
|
||||
},
|
||||
[googleVerKey, toast]
|
||||
);
|
||||
|
||||
return {
|
||||
codeSending,
|
||||
sendCode,
|
||||
sendCodeText,
|
||||
codeCountDown
|
||||
};
|
||||
};
|
13
client/src/hooks/useToast.ts
Normal file
13
client/src/hooks/useToast.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useToast as uToast, UseToastOptions } from '@chakra-ui/react';
|
||||
|
||||
export const useToast = (props?: UseToastOptions) => {
|
||||
const toast = uToast({
|
||||
position: 'top',
|
||||
duration: 2000,
|
||||
...props
|
||||
});
|
||||
|
||||
return {
|
||||
toast
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user