feat: 滚动加载组件

This commit is contained in:
archer
2023-03-25 13:55:53 +08:00
parent 3db690773f
commit 4eaf3a1be0
9 changed files with 131 additions and 47 deletions

View File

@@ -27,6 +27,7 @@
"hyperdown": "^2.4.29", "hyperdown": "^2.4.29",
"immer": "^9.0.19", "immer": "^9.0.19",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"lodash": "^4.17.21",
"mammoth": "^1.5.1", "mammoth": "^1.5.1",
"mongoose": "^6.10.0", "mongoose": "^6.10.0",
"nanoid": "^4.0.1", "nanoid": "^4.0.1",
@@ -51,6 +52,7 @@
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@types/formidable": "^2.0.5", "@types/formidable": "^2.0.5",
"@types/jsonwebtoken": "^9.0.1", "@types/jsonwebtoken": "^9.0.1",
"@types/lodash": "^4.14.191",
"@types/node": "18.14.0", "@types/node": "18.14.0",
"@types/nodemailer": "^6.4.7", "@types/nodemailer": "^6.4.7",
"@types/react": "18.0.28", "@types/react": "18.0.28",

5
pnpm-lock.yaml generated
View File

@@ -10,6 +10,7 @@ specifiers:
'@tanstack/react-query': ^4.24.10 '@tanstack/react-query': ^4.24.10
'@types/formidable': ^2.0.5 '@types/formidable': ^2.0.5
'@types/jsonwebtoken': ^9.0.1 '@types/jsonwebtoken': ^9.0.1
'@types/lodash': ^4.14.191
'@types/node': 18.14.0 '@types/node': 18.14.0
'@types/nodemailer': ^6.4.7 '@types/nodemailer': ^6.4.7
'@types/nprogress': ^0.2.0 '@types/nprogress': ^0.2.0
@@ -31,6 +32,7 @@ specifiers:
immer: ^9.0.19 immer: ^9.0.19
jsonwebtoken: ^9.0.0 jsonwebtoken: ^9.0.0
lint-staged: ^13.1.2 lint-staged: ^13.1.2
lodash: ^4.17.21
mammoth: ^1.5.1 mammoth: ^1.5.1
mongoose: ^6.10.0 mongoose: ^6.10.0
nanoid: ^4.0.1 nanoid: ^4.0.1
@@ -70,6 +72,7 @@ dependencies:
hyperdown: registry.npmmirror.com/hyperdown/2.4.29 hyperdown: registry.npmmirror.com/hyperdown/2.4.29
immer: registry.npmmirror.com/immer/9.0.19 immer: registry.npmmirror.com/immer/9.0.19
jsonwebtoken: registry.npmmirror.com/jsonwebtoken/9.0.0 jsonwebtoken: registry.npmmirror.com/jsonwebtoken/9.0.0
lodash: registry.npmmirror.com/lodash/4.17.21
mammoth: registry.npmmirror.com/mammoth/1.5.1 mammoth: registry.npmmirror.com/mammoth/1.5.1
mongoose: registry.npmmirror.com/mongoose/6.10.0 mongoose: registry.npmmirror.com/mongoose/6.10.0
nanoid: registry.npmmirror.com/nanoid/4.0.1 nanoid: registry.npmmirror.com/nanoid/4.0.1
@@ -94,6 +97,7 @@ devDependencies:
'@svgr/webpack': registry.npmmirror.com/@svgr/webpack/6.5.1 '@svgr/webpack': registry.npmmirror.com/@svgr/webpack/6.5.1
'@types/formidable': registry.npmmirror.com/@types/formidable/2.0.5 '@types/formidable': registry.npmmirror.com/@types/formidable/2.0.5
'@types/jsonwebtoken': registry.npmmirror.com/@types/jsonwebtoken/9.0.1 '@types/jsonwebtoken': registry.npmmirror.com/@types/jsonwebtoken/9.0.1
'@types/lodash': registry.npmmirror.com/@types/lodash/4.14.191
'@types/node': registry.npmmirror.com/@types/node/18.14.0 '@types/node': registry.npmmirror.com/@types/node/18.14.0
'@types/nodemailer': registry.npmmirror.com/@types/nodemailer/6.4.7 '@types/nodemailer': registry.npmmirror.com/@types/nodemailer/6.4.7
'@types/react': registry.npmmirror.com/@types/react/18.0.28 '@types/react': registry.npmmirror.com/@types/react/18.0.28
@@ -4794,7 +4798,6 @@ packages:
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.191.tgz} resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.191.tgz}
name: '@types/lodash' name: '@types/lodash'
version: 4.14.191 version: 4.14.191
dev: false
registry.npmmirror.com/@types/mdast/3.0.10: registry.npmmirror.com/@types/mdast/3.0.10:
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.10.tgz} resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.10.tgz}

View File

@@ -37,7 +37,7 @@ function checkRes(data: ResponseDataType) {
console.log('error->', data, 'data is empty'); console.log('error->', data, 'data is empty');
return Promise.reject('服务器异常'); return Promise.reject('服务器异常');
} else if (data.code < 200 || data.code >= 400) { } else if (data.code < 200 || data.code >= 400) {
return Promise.reject(data.message); return Promise.reject(data);
} }
return data.data; return data.data;
} }

View File

@@ -62,8 +62,8 @@ const Layout = ({ children }: { children: JSX.Element }) => {
<Box h={'100%'} position={'fixed'} left={0} top={0} w={'80px'}> <Box h={'100%'} position={'fixed'} left={0} top={0} w={'80px'}>
<Navbar navbarList={navbarList} /> <Navbar navbarList={navbarList} />
</Box> </Box>
<Box ml={'80px'} p={7} h={'100%'}> <Box ml={'80px'} h={'100%'}>
<Box maxW={'1100px'} m={'auto'} h={'100%'}> <Box maxW={'1100px'} m={'auto'} h={'100%'} p={7} overflowY={'auto'}>
<Auth>{children}</Auth> <Auth>{children}</Auth>
</Box> </Box>
</Box> </Box>

View File

@@ -1,16 +1,63 @@
import React from 'react'; import React, { useRef, useEffect, useMemo } from 'react';
import type { BoxProps } from '@chakra-ui/react'; import type { BoxProps } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { throttle } from 'lodash';
interface Props extends BoxProps { interface Props extends BoxProps {
nextPage: () => void; nextPage: () => void;
isLoadAll: boolean;
requesting: boolean;
children: React.ReactNode; children: React.ReactNode;
} }
const ScrollData = ({ children, nextPage, ...props }: Props) => { const ScrollData = ({ children, nextPage, isLoadAll, requesting, ...props }: Props) => {
const elementRef = useRef<HTMLDivElement>(null);
const loadText = useMemo(() => {
if (requesting) return '请求中……';
if (isLoadAll) return '已加载全部';
return '点击加载更多';
}, [isLoadAll, requesting]);
useEffect(() => {
if (!elementRef.current) return;
const scrolling = throttle((e: Event) => {
const element = e.target as HTMLDivElement;
if (!element) return;
// 当前滚动位置
const scrollTop = element.scrollTop;
// 可视高度
const clientHeight = element.clientHeight;
// 内容总高度
const scrollHeight = element.scrollHeight;
// 判断是否滚动到底部
if (scrollTop + clientHeight + 100 >= scrollHeight) {
nextPage();
}
}, 100);
elementRef.current.addEventListener('scroll', scrolling);
return () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
elementRef.current?.removeEventListener('scroll', scrolling);
};
}, [elementRef, nextPage]);
return ( return (
<Box {...props} overflow={'auto'}> <Box {...props} ref={elementRef} overflow={'auto'}>
{children} {children}
<Box
mt={2}
fontSize={'xs'}
color={'blackAlpha.500'}
textAlign={'center'}
cursor={loadText === '点击加载更多' ? 'pointer' : 'default'}
onClick={() => {
if (loadText !== '点击加载更多') return;
nextPage();
}}
>
{loadText}
</Box>
</Box> </Box>
); );
}; };

View File

@@ -37,6 +37,7 @@ export const usePaging = <T = any>({
setIsLoadAll(true); setIsLoadAll(true);
} }
setTotal(res.total); setTotal(res.total);
setPageNum(num);
return data; return data;
}); });
} catch (error: any) { } catch (error: any) {
@@ -53,15 +54,18 @@ export const usePaging = <T = any>({
[api, isLoadAll, pageSize, params, requesting, toast] [api, isLoadAll, pageSize, params, requesting, toast]
); );
useQuery(['init', pageNum], () => getData(pageNum, pageNum === 1)); const nextPage = useCallback(() => getData(pageNum + 1), [getData, pageNum]);
useQuery(['init'], () => getData(1, true));
return { return {
pageNum, pageNum,
pageSize, pageSize,
setPageNum,
total, total,
data, data,
getData, getData,
requesting requesting,
isLoadAll,
nextPage
}; };
}; };

View File

@@ -4,6 +4,7 @@ import axios from 'axios';
import { connectToDatabase, User, Pay } from '@/service/mongo'; import { connectToDatabase, User, Pay } from '@/service/mongo';
import { authToken } from '@/service/utils/tools'; import { authToken } from '@/service/utils/tools';
import { PaySchema } from '@/types/mongoSchema'; import { PaySchema } from '@/types/mongoSchema';
import dayjs from 'dayjs';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@@ -28,6 +29,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
`https://sif268.laf.dev/wechat-order-query?order_number=${payOrder.orderId}&api_key=${process.env.WXPAYCODE}` `https://sif268.laf.dev/wechat-order-query?order_number=${payOrder.orderId}&api_key=${process.env.WXPAYCODE}`
); );
// 校验下是否超过一天
const orderTime = dayjs(payOrder.createTime);
const diffInHours = dayjs().diff(orderTime, 'hours');
if (data.trade_state === 'SUCCESS') { if (data.trade_state === 'SUCCESS') {
// 订单已支付 // 订单已支付
try { try {
@@ -47,7 +52,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
$inc: { balance: payOrder.price } $inc: { balance: payOrder.price }
}); });
jsonRes(res, { jsonRes(res, {
data: 'success' data: '支付成功'
}); });
} }
} catch (error) { } catch (error) {
@@ -56,17 +61,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}); });
console.log(error); console.log(error);
} }
} else if (data.trade_state === 'CLOSED') { } else if (data.trade_state === 'CLOSED' || diffInHours > 24) {
// 订单已关闭 // 订单已关闭
await Pay.findByIdAndUpdate(payId, { await Pay.findByIdAndUpdate(payId, {
status: 'CLOSED' status: 'CLOSED'
}); });
jsonRes(res, {
data: '订单已过期'
});
} else { } else {
throw new Error(data.trade_state_desc); throw new Error(data.trade_state_desc);
} }
throw new Error('订单已过期');
} catch (err) { } catch (err) {
console.log(err); // console.log(err);
jsonRes(res, { jsonRes(res, {
code: 500, code: 500,
error: err error: err

View File

@@ -28,8 +28,9 @@ const ImportDataModal = dynamic(() => import('./components/ImportDataModal'));
const DataList = () => { const DataList = () => {
const { const {
setPageNum, nextPage,
pageNum, isLoadAll,
requesting,
data: dataList, data: dataList,
getData getData
} = usePaging<DataListItem>({ } = usePaging<DataListItem>({
@@ -75,7 +76,7 @@ const DataList = () => {
</Card> </Card>
{/* 数据表 */} {/* 数据表 */}
<Card mt={3} flex={'1 0 0'} h={['auto', '0']} px={6} py={4}> <Card mt={3} flex={'1 0 0'} h={['auto', '0']} px={6} py={4}>
<ScrollData h={'100%'} nextPage={() => setPageNum(pageNum + 1)}> <ScrollData h={'100%'} nextPage={nextPage} isLoadAll={isLoadAll} requesting={requesting}>
<TableContainer> <TableContainer>
<Table> <Table>
<Thead> <Thead>

View File

@@ -32,6 +32,7 @@ import { PaySchema } from '@/types/mongoSchema';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { formatPrice } from '@/utils/user'; import { formatPrice } from '@/utils/user';
import WxConcat from '@/components/WxConcat'; import WxConcat from '@/components/WxConcat';
import ScrollData from '@/components/ScrollData';
const PayModal = dynamic(() => import('./components/PayModal')); const PayModal = dynamic(() => import('./components/PayModal'));
@@ -52,7 +53,12 @@ const NumberSetting = () => {
control, control,
name: 'accounts' name: 'accounts'
}); });
const { setPageNum, data: bills } = usePaging<UserBillType>({ const {
nextPage,
isLoadAll,
requesting,
data: bills
} = usePaging<UserBillType>({
api: getUserBills, api: getUserBills,
pageSize: 30 pageSize: 30
}); });
@@ -84,9 +90,14 @@ const NumberSetting = () => {
const handleRefreshPayOrder = useCallback( const handleRefreshPayOrder = useCallback(
async (payId: string) => { async (payId: string) => {
setLoading(true);
try { try {
setLoading(true); const data = await checkPayResult(payId);
await checkPayResult(payId); toast({
title: data,
status: 'info'
});
const res = await getPayOrders(); const res = await getPayOrders();
setPayOrders(res); setPayOrders(res);
} catch (error: any) { } catch (error: any) {
@@ -96,6 +107,7 @@ const NumberSetting = () => {
}); });
console.log(error); console.log(error);
} }
setLoading(false); setLoading(false);
}, },
[setLoading, toast] [setLoading, toast]
@@ -196,8 +208,8 @@ const NumberSetting = () => {
</Table> </Table>
</TableContainer> </TableContainer>
</Card> </Card>
<Card mt={6} px={6} py={4}> <Card mt={6} py={4}>
<Flex alignItems={'flex-end'}> <Flex alignItems={'flex-end'} px={6} mb={1}>
<Box fontSize={'xl'} fontWeight={'bold'}> <Box fontSize={'xl'} fontWeight={'bold'}>
</Box> </Box>
@@ -205,7 +217,7 @@ const NumberSetting = () => {
wx联系 wx联系
</Button> </Button>
</Flex> </Flex>
<TableContainer maxH={'400px'} overflowY={'auto'}> <TableContainer maxH={'400px'} overflowY={'auto'} px={6}>
<Table> <Table>
<Thead> <Thead>
<Tr> <Tr>
@@ -240,32 +252,40 @@ const NumberSetting = () => {
</Table> </Table>
</TableContainer> </TableContainer>
</Card> </Card>
<Card mt={6} px={6} py={4}> <Card mt={6} py={4}>
<Box fontSize={'xl'} fontWeight={'bold'}> <Box fontSize={'xl'} fontWeight={'bold'} px={6} mb={1}>
使(30) 使
</Box> </Box>
<TableContainer maxH={'400px'} overflowY={'auto'}> <ScrollData
<Table> maxH={'400px'}
<Thead> px={6}
<Tr> isLoadAll={isLoadAll}
<Th></Th> requesting={requesting}
<Th></Th> nextPage={nextPage}
<Th></Th> >
</Tr> <TableContainer>
</Thead> <Table>
<Tbody fontSize={'sm'}> <Thead>
{bills.map((item) => ( <Tr>
<Tr key={item.id}> <Th></Th>
<Td>{item.time}</Td> <Th></Th>
<Td whiteSpace="pre-wrap" wordBreak={'break-all'}> <Th></Th>
{item.textLen}
</Td>
<Td>{item.price}</Td>
</Tr> </Tr>
))} </Thead>
</Tbody> <Tbody fontSize={'sm'}>
</Table> {bills.map((item) => (
</TableContainer> <Tr key={item.id}>
<Td>{item.time}</Td>
<Td whiteSpace="pre-wrap" wordBreak={'break-all'}>
{item.textLen}
</Td>
<Td>{item.price}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</ScrollData>
</Card> </Card>
{showPay && <PayModal onClose={() => setShowPay(false)} />} {showPay && <PayModal onClose={() => setShowPay(false)} />}
{/* wx 联系 */} {/* wx 联系 */}