This commit is contained in:
archer
2023-07-20 20:38:14 +08:00
parent 9fefaa8e18
commit e0b6860706
13 changed files with 307 additions and 344 deletions

View File

@@ -6,8 +6,8 @@ import { useForm } from 'react-hook-form';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { KbItemType } from '@/types/plugin';
import { useScreen } from '@/hooks/useScreen';
import { getErrText } from '@/utils/tools';
import { useGlobalStore } from '@/store/global';
import { type ComponentRef } from './components/Info';
import Tabs from '@/components/Tabs';
import dynamic from 'next/dynamic';
@@ -37,7 +37,7 @@ const Detail = ({ kbId, currentTab }: { kbId: string; currentTab: `${TabEnum}` }
const theme = useTheme();
const { toast } = useToast();
const router = useRouter();
const { isPc } = useScreen();
const { isPc } = useGlobalStore();
const { kbDetail, getKbDetail } = useUserStore();
const tabList = useRef([

View File

@@ -21,6 +21,7 @@ import MyIcon from '@/components/Icon';
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
import { addDays } from 'date-fns';
import dynamic from 'next/dynamic';
import { useGlobalStore } from '@/store/global';
const BillDetail = dynamic(() => import('./BillDetail'));
@@ -30,6 +31,7 @@ const BillTable = () => {
from: addDays(new Date(), -7),
to: new Date()
});
const { isPc } = useGlobalStore();
const {
data: bills,
@@ -38,6 +40,7 @@ const BillTable = () => {
getData
} = usePagination<UserBillType>({
api: getUserBills,
pageSize: isPc ? 20 : 10,
params: {
dateStart: new Date(dateRange.from || new Date()).setHours(0, 0, 0, 0),
dateEnd: new Date(dateRange.to || new Date()).setHours(23, 59, 59, 999)
@@ -47,8 +50,8 @@ const BillTable = () => {
const [billDetail, setBillDetail] = useState<UserBillType>();
return (
<>
<TableContainer position={'relative'} minH={'100px'}>
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
<Table>
<Thead>
<Tr>
@@ -78,27 +81,27 @@ const BillTable = () => {
</TableContainer>
{!isLoading && bills.length === 0 && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'}>
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
使~
</Box>
</Flex>
)}
<Flex w={'100%'} mt={4} justifyContent={'flex-end'} flexWrap={'wrap'}>
<Flex w={'100%'} mt={4} px={[3, 8]} alignItems={'center'} justifyContent={'flex-end'}>
<DateRangePicker
defaultDate={dateRange}
position="top"
onChange={setDateRange}
onSuccess={() => getData(1)}
/>
<Box ml={[0, 2]} mt={[3, 0]} w={['100%', 'auto']}>
<Box ml={3}>
<Pagination />
</Box>
</Flex>
<Loading loading={isLoading} fixed={false} />
{!!billDetail && <BillDetail bill={billDetail} onClose={() => setBillDetail(undefined)} />}
</>
</Flex>
);
};

View File

@@ -0,0 +1,139 @@
import React, { useCallback } from 'react';
import { Box, Flex, Button, useDisclosure } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { putUserInfo } from '@/api/user';
import { useToast } from '@/hooks/useToast';
import { useGlobalStore } from '@/store/global';
import { useUserStore } from '@/store/user';
import { UserType } from '@/types/user';
import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useSelectFile } from '@/hooks/useSelectFile';
import { compressImg } from '@/utils/file';
import { getErrText } from '@/utils/tools';
import { feConfigs } from '@/store/static';
import Loading from '@/components/Loading';
import Avatar from '@/components/Avatar';
const PayModal = dynamic(() => import('./PayModal'), {
loading: () => <Loading fixed={false} />,
ssr: false
});
const UserInfo = () => {
const router = useRouter();
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
const { setLoading } = useGlobalStore();
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
const { toast } = useToast();
const {
isOpen: isOpenPayModal,
onClose: onClosePayModal,
onOpen: onOpenPayModal
} = useDisclosure();
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
multiple: false
});
const onclickSave = useCallback(
async (data: UserUpdateParams) => {
setLoading(true);
try {
await putUserInfo({
avatar: data.avatar
});
updateUserInfo({
avatar: data.avatar
});
reset(data);
toast({
title: '更新数据成功',
status: 'success'
});
} catch (error) {
toast({
title: getErrText(error),
status: 'error'
});
}
setLoading(false);
},
[reset, setLoading, toast, updateUserInfo]
);
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file) return;
try {
const src = await compressImg({
file,
maxW: 100,
maxH: 100
});
onclickSave({
...userInfo,
avatar: src
});
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : '头像选择异常',
status: 'warning'
});
}
},
[onclickSave, toast, userInfo]
);
useQuery(['init'], initUserInfo, {
onSuccess(res) {
reset(res);
}
});
return (
<Flex flexDirection={'column'} alignItems={'center'} py={[0, 8]} fontSize={['lg', 'xl']}>
<Flex mt={6} alignItems={'center'} w={'260px'}>
<Box flex={'0 0 50px'}>:</Box>
<Box flex={1} pl={10}>
<Avatar
src={userInfo?.avatar}
w={['34px', '44px']}
h={['34px', '44px']}
cursor={'pointer'}
title={'点击切换头像'}
onClick={onOpenSelectFile}
/>
</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={'260px'}>
<Box flex={'0 0 50px'}>:</Box>
<Box>{userInfo?.username}</Box>
</Flex>
{feConfigs?.show_userDetail && (
<Box mt={6} w={'260px'} whiteSpace={'nowrap'}>
<Flex alignItems={'center'}>
<Box flex={'0 0 50px'}>:</Box>
<Box>
<strong>{userInfo?.balance}</strong>
</Box>
<Button size={['xs', 'sm']} w={['70px', '80px']} ml={5} onClick={onOpenPayModal}>
</Button>
</Flex>
</Box>
)}
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
<File onSelect={onSelectFile} />
</Flex>
);
};
export default UserInfo;

View File

@@ -1,13 +1,5 @@
import React from 'react';
import {
Box,
Flex,
Accordion,
AccordionItem,
AccordionButton,
AccordionPanel,
AccordionIcon
} from '@chakra-ui/react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { getInforms, readInform } from '@/api/user';
import { usePagination } from '@/hooks/usePagination';
import { useLoading } from '@/hooks/useLoading';
@@ -16,8 +8,8 @@ import { formatTimeToChatTime } from '@/utils/tools';
import MyIcon from '@/components/Icon';
const BillTable = () => {
const theme = useTheme();
const { Loading } = useLoading();
const {
data: informs,
isLoading,
@@ -31,11 +23,17 @@ const BillTable = () => {
});
return (
<Box mt={2}>
<Accordion defaultIndex={[0, 1, 2]} allowMultiple>
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
<Box px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
{informs.map((item) => (
<AccordionItem
<Box
key={item._id}
border={theme.borders.md}
py={2}
px={4}
borderRadius={'md'}
cursor={item.read ? 'default' : 'pointer'}
position={'relative'}
onClick={async () => {
if (!item.read) {
await readInform(item._id);
@@ -43,35 +41,31 @@ const BillTable = () => {
}
}}
>
<AccordionButton>
<Flex alignItems={'center'} flex="1" textAlign="left">
<Box fontWeight={'bold'} position={'relative'}>
{!item.read && (
<Box
w={'5px'}
h={'5px'}
borderRadius={'10px'}
bg={'myRead.600'}
position={'absolute'}
top={1}
left={'-5px'}
></Box>
)}
{item.title}
</Box>
<Box ml={2} color={'myGray.500'}>
{formatTimeToChatTime(item.time)}
</Box>
</Flex>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4}>{item.content}</AccordionPanel>
</AccordionItem>
<Flex alignItems={'center'} justifyContent={'space-between'}>
<Box>{item.title}</Box>
<Box ml={2} color={'myGray.500'}>
{formatTimeToChatTime(item.time)}
</Box>
</Flex>
<Box fontSize={'sm'} color={'myGray.600'}>
{item.content}
</Box>
{!item.read && (
<Box
w={'5px'}
h={'5px'}
borderRadius={'10px'}
bg={'myRead.600'}
position={'absolute'}
bottom={'8px'}
right={'8px'}
></Box>
)}
</Box>
))}
</Accordion>
</Box>
{!isLoading && informs.length === 0 && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'100px'}>
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'} pt={'-48px'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
~
@@ -79,12 +73,12 @@ const BillTable = () => {
</Flex>
)}
{total > pageSize && (
<Flex w={'100%'} mt={4} justifyContent={'flex-end'}>
<Flex w={'100%'} mt={4} px={[3, 8]} justifyContent={'flex-end'}>
<Pagination />
</Flex>
)}
<Loading loading={isLoading && informs.length === 0} fixed={false} />
</Box>
</Flex>
);
};

View File

@@ -59,45 +59,46 @@ const PayRecordTable = () => {
return (
<>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{payOrders.map((item) => (
<Tr key={item._id}>
<Td>{item.orderId}</Td>
<Td>
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
</Td>
<Td>{formatPrice(item.price)}</Td>
<Td>{item.status}</Td>
<Td>
{item.status === 'NOTPAY' && (
<Button onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
</Button>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{!isInitialLoading && payOrders.length === 0 && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'100px'}>
{!isInitialLoading && payOrders.length === 0 ? (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} justifyContent={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
~
</Box>
</Flex>
) : (
<TableContainer py={[0, 5]} px={[3, 8]} h={'100%'} overflow={'overlay'}>
<Table>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{payOrders.map((item) => (
<Tr key={item._id}>
<Td>{item.orderId}</Td>
<Td>
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
</Td>
<Td>{formatPrice(item.price)}</Td>
<Td>{item.status}</Td>
<Td>
{item.status === 'NOTPAY' && (
<Button onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
</Button>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
)}
<Loading loading={isInitialLoading} fixed={false} />
</>

View File

@@ -1,67 +0,0 @@
import React from 'react';
import { Flex, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Box } from '@chakra-ui/react';
import { useLoading } from '@/hooks/useLoading';
import dayjs from 'dayjs';
import { getPromotionRecords } from '@/api/user';
import { usePagination } from '@/hooks/usePagination';
import { PromotionRecordType } from '@/api/response/user';
import { PromotionTypeMap } from '@/constants/user';
import MyIcon from '@/components/Icon';
const OpenApi = () => {
const { Loading } = useLoading();
const {
data: promotionRecords,
isLoading,
total,
pageSize,
Pagination
} = usePagination<PromotionRecordType>({
api: getPromotionRecords
});
return (
<>
<TableContainer position={'relative'} overflow={'hidden'} minH={'100px'}>
<Table>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{promotionRecords.map((item) => (
<Tr key={item._id}>
<Td>
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
</Td>
<Td>{PromotionTypeMap[item.type]}</Td>
<Td>{item.amount}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{!isLoading && promotionRecords.length === 0 && (
<Flex flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
~
</Box>
</Flex>
)}
{total > pageSize && (
<Flex mt={4} justifyContent={'flex-end'}>
<Pagination />
</Flex>
)}
<Loading loading={isLoading} fixed={false} />
</>
);
};
export default OpenApi;

View File

@@ -1,212 +1,118 @@
import React, { useCallback, useRef } from 'react';
import { Card, Box, Flex, Button, Grid, useDisclosure } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { putUserInfo } from '@/api/user';
import { useToast } from '@/hooks/useToast';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { useGlobalStore } from '@/store/global';
import { useUserStore } from '@/store/user';
import { UserType } from '@/types/user';
import { clearCookie } from '@/utils/user';
import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useSelectFile } from '@/hooks/useSelectFile';
import { compressImg } from '@/utils/file';
import { getErrText } from '@/utils/tools';
import { feConfigs } from '@/store/static';
import { clearCookie } from '@/utils/user';
import PageContainer from '@/components/PageContainer';
import SideTabs from '@/components/SideTabs';
import Loading from '@/components/Loading';
import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import Tabs from '@/components/Tabs';
import BillTable from './components/BillTable';
import UserInfo from './components/Info';
import { useUserStore } from '@/store/user';
const BillTable = dynamic(() => import('./components/BillTable'), {
ssr: false
});
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'), {
ssr: false
});
const InformTable = dynamic(() => import('./components/InformTable'), {
ssr: false
});
const PayModal = dynamic(() => import('./components/PayModal'), {
loading: () => <Loading fixed={false} />,
ssr: false
});
const WxConcat = dynamic(() => import('@/components/WxConcat'), {
loading: () => <Loading fixed={false} />,
ssr: false
});
enum TableEnum {
enum TabEnum {
'info' = 'info',
'bill' = 'bill',
'pay' = 'pay',
'promotion' = 'promotion',
'inform' = 'inform'
'inform' = 'inform',
'loginout' = 'loginout'
}
const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
const tableList = useRef([
{ label: '账单', id: TableEnum.bill, Component: <BillTable /> },
{ label: '充值', id: TableEnum.pay, Component: <PayRecordTable /> },
{ label: '通知', id: TableEnum.inform, Component: <InformTable /> }
const NumberSetting = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const tabList = useRef([
{ icon: 'meLight', label: '个人信息', id: TabEnum.info, Component: <BillTable /> },
{ icon: 'billRecordLight', label: '消费记录', id: TabEnum.bill, Component: <BillTable /> },
{ icon: 'payRecordLight', label: '充值记录', id: TabEnum.pay, Component: <PayRecordTable /> },
{ icon: 'informLight', label: '通知', id: TabEnum.inform, Component: <InformTable /> },
{ icon: 'loginoutLight', label: '登出', id: TabEnum.loginout, Component: () => <></> }
]);
const router = useRouter();
const { userInfo, updateUserInfo, initUserInfo, setUserInfo } = useUserStore();
const { setLoading } = useGlobalStore();
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
const { toast } = useToast();
const {
isOpen: isOpenPayModal,
onClose: onClosePayModal,
onOpen: onOpenPayModal
} = useDisclosure();
const theme = useTheme();
const { isPc } = useGlobalStore();
const { setUserInfo } = useUserStore();
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
multiple: false
});
const onclickSave = useCallback(
async (data: UserUpdateParams) => {
setLoading(true);
try {
await putUserInfo({
avatar: data.avatar
});
updateUserInfo({
avatar: data.avatar
});
reset(data);
toast({
title: '更新数据成功',
status: 'success'
});
} catch (error) {
toast({
title: getErrText(error),
status: 'error'
});
}
setLoading(false);
},
[reset, setLoading, toast, updateUserInfo]
);
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file) return;
try {
const src = await compressImg({
file,
maxW: 100,
maxH: 100
});
onclickSave({
...userInfo,
avatar: src
});
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : '头像选择异常',
status: 'warning'
const setCurrentTab = useCallback(
(tab: string) => {
if (tab === TabEnum.loginout) {
clearCookie();
setUserInfo(null);
router.replace('/login');
} else {
router.replace({
query: {
currentTab: tab
}
});
}
},
[onclickSave, toast, userInfo]
[router, setUserInfo]
);
const onclickLogOut = useCallback(() => {
clearCookie();
setUserInfo(null);
router.replace('/login');
}, [router, setUserInfo]);
useQuery(['init'], initUserInfo, {
onSuccess(res) {
reset(res);
}
});
return (
<Box h={'100%'} overflow={'overlay'}>
<Box py={[5, 10]} px={'5vw'}>
<Grid gridTemplateColumns={['1fr', '3fr 300px']} gridGap={4}>
<Card px={6} py={4}>
<Flex justifyContent={'space-between'}>
<Box fontSize={'xl'} fontWeight={'bold'}>
</Box>
<Button variant={'base'} size={'xs'} onClick={onclickLogOut}>
退
</Button>
</Flex>
<Flex mt={6} alignItems={'center'}>
<Box flex={'0 0 50px'}>:</Box>
<Avatar
src={userInfo?.avatar}
w={['28px', '36px']}
h={['28px', '36px']}
cursor={'pointer'}
title={'点击切换头像'}
onClick={onOpenSelectFile}
/>
</Flex>
<Flex mt={6} alignItems={'center'}>
<Box flex={'0 0 50px'}>:</Box>
<Box>{userInfo?.username}</Box>
</Flex>
{feConfigs?.show_userDetail && (
<Box mt={6}>
<Flex alignItems={'center'}>
<Box flex={'0 0 50px'}>:</Box>
<Box>
<strong>{userInfo?.balance}</strong>
</Box>
<Button size={['xs', 'sm']} w={['70px', '80px']} ml={5} onClick={onOpenPayModal}>
</Button>
</Flex>
</Box>
)}
</Card>
</Grid>
{feConfigs?.show_userDetail && (
<Card mt={4} px={[3, 6]} py={4}>
<PageContainer>
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
{isPc ? (
<Flex
flexDirection={'column'}
p={4}
h={'100%'}
flex={'0 0 200px'}
borderRight={theme.borders.base}
>
<SideTabs
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList.current}
activeId={currentTab}
onChange={setCurrentTab}
/>
</Flex>
) : (
<Box mb={3}>
<Tabs
m={'auto'}
w={'200px'}
list={tableList.current}
activeId={tableType}
size={'sm'}
onChange={(id: any) => router.replace(`/number?type=${id}`)}
w={'90%'}
size={isPc ? 'md' : 'sm'}
list={tabList.current.map((item) => ({
id: item.id,
label: item.label
}))}
activeId={currentTab}
onChange={setCurrentTab}
/>
<Box minH={'300px'}>
{(() => {
const item = tableList.current.find((item) => item.id === tableType);
return item ? item.Component : null;
})()}
</Box>
</Card>
</Box>
)}
</Box>
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
<File onSelect={onSelectFile} />
</Box>
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
{currentTab === TabEnum.info && <UserInfo />}
{currentTab === TabEnum.bill && <BillTable />}
{currentTab === TabEnum.pay && <PayRecordTable />}
{currentTab === TabEnum.inform && <InformTable />}
</Box>
</Flex>
</PageContainer>
);
};
export async function getServerSideProps({ query }: any) {
return {
props: {
tableType: query?.type || TableEnum.bill
currentTab: query?.currentTab || TabEnum.info
}
};
}