new framwork

This commit is contained in:
archer
2023-06-09 12:57:42 +08:00
parent d9450bd7ee
commit ba9d9c3d5f
263 changed files with 12269 additions and 11599 deletions

View 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]
)
};
};

View 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
};
};

View 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
};
};

View 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
};
};

View 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
};
};

View 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;
};

View 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)
};
}

View 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
};
};

View 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
};
};

View 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
};
};