mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
user ui
This commit is contained in:
1
client/src/components/Icon/icons/light/billRecord.svg
Normal file
1
client/src/components/Icon/icons/light/billRecord.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689854058748" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3614" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M803.84 934.4H238.08c-52.736 0-96.256-43.008-96.256-96.256V165.376c0-52.736 43.008-96.256 96.256-96.256H803.84c52.736 0 96.256 43.008 96.256 96.256v673.28c0 52.736-43.52 95.744-96.256 95.744zM232.96 128c-17.408 0-32.256 14.336-32.256 32.256v683.52c0 17.408 14.336 32.256 32.256 32.256H808.96c17.408 0 32.256-14.336 32.256-32.256V160.256c0-17.408-14.336-32.256-32.256-32.256H232.96z m256 646.656c-8.192 0-15.872-3.072-22.528-9.728l-48.128-46.592-24.064 15.872c-4.608 4.608-11.264 6.656-17.408 6.656h-48.128c-22.016-5.12-31.744-13.312-31.744-27.136 0-17.408 9.216-32.256 31.744-32.256h38.4l36.864-30.72c12.8-9.728 30.208-8.192 41.472 3.072l48.128 46.592 25.6-17.408c4.608-3.072 11.264-6.656 17.408-6.656h79.872c17.408 0 32.256 14.336 32.256 32.256 0 17.408-14.336 32.256-32.256 32.256h-70.656l-39.936 27.136c-4.096 5.12-10.24 6.656-16.896 6.656z" p-id="3615"></path><path d="M718.336 385.536H316.416c-16.384 0-29.184-13.312-29.184-29.184 0-16.384 13.312-29.184 29.184-29.184h401.92c16.384 0 29.184 13.312 29.184 29.184 0.512 15.872-12.8 29.184-29.184 29.184zM718.336 535.04H316.416c-16.384 0-29.184-13.312-29.184-29.184 0-16.384 13.312-29.696 29.184-29.696h401.92c16.384 0 29.184 13.312 29.184 29.696 0.512 16.384-12.8 29.184-29.184 29.184z" p-id="3616"></path></svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
client/src/components/Icon/icons/light/inform.svg
Normal file
1
client/src/components/Icon/icons/light/inform.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689854044643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3460" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M892.1 805L781.9 621.3V458c0-71.8-28.1-139.5-79.3-190.6-36.2-36.2-80.7-60.8-129-72.2v-22.7c0-34-27.6-61.6-61.6-61.6s-61.6 27.6-61.6 61.6v22.7c-48.4 11.3-92.9 36-129 72.2-51.1 51.1-79.3 118.8-79.3 190.6v163.3L131.9 805h257.7c7.6 60.8 59.5 108 122.3 108s114.8-47.2 122.3-108h257.9zM496.4 172.6c0-8.6 7-15.6 15.6-15.6s15.6 7 15.6 15.6v16c-5.2-0.3-10.4-0.5-15.6-0.5s-10.4 0.2-15.6 0.5v-16zM288.1 634.1V458c0-123.4 100.4-223.9 223.9-223.9 123.4 0 223.9 100.4 223.9 223.9v176.1L810.8 759H213.2l74.9-124.9zM512 867c-37.4 0-68.6-26.7-75.8-62h151.5c-7.1 35.3-38.3 62-75.7 62z" p-id="3461"></path></svg>
|
After Width: | Height: | Size: 924 B |
1
client/src/components/Icon/icons/light/loginout.svg
Normal file
1
client/src/components/Icon/icons/light/loginout.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689855121257" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3135" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M952.7 492.1c-1.4-1.8-3.1-3.4-4.8-4.9l-179-178.9c-12.5-12.5-32.9-12.5-45.4 0s-12.5 32.9 0 45.4l126 126H421.3h-0.1c-18.2 0-32.9 14.8-32.9 33s14.7 33 32.9 33c0.3 0.1 0.5 0 0.7 0h427.8l-126 126c-12.3 12.3-12.3 32.4 0 44.7l0.7 0.7c12.3 12.3 32.4 12.3 44.7 0l182-182c11.7-11.7 12.3-30.6 1.6-43z" fill="#515151" p-id="3136"></path><path d="M562.3 799c-18 0-32.7 14.7-32.7 32.7v63.8H129.2V128.7h400.4v63.1c0 18 14.7 32.7 32.7 32.7s32.7-14.7 32.7-32.7V96.3c0-3.5-0.6-6.8-1.6-10-4.2-13.3-16.6-23-31.2-23H96.6c-18 0-32.7 14.7-32.7 32.7v831.9c0 14.2 9.2 26.3 21.8 30.8 3.6 1.4 7.5 2.1 11.5 2.1h463.2c0.6 0 1.3 0.1 1.9 0.1 18 0 32.7-14.7 32.7-32.7v-96.5c0-18-14.7-32.7-32.7-32.7z" fill="#515151" p-id="3137"></path><path d="M256.8 512.7a32.9 33 0 1 0 65.8 0 32.9 33 0 1 0-65.8 0Z" fill="#515151" p-id="3138"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
client/src/components/Icon/icons/light/payRecord.svg
Normal file
1
client/src/components/Icon/icons/light/payRecord.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 12 KiB |
@@ -63,7 +63,11 @@ const map = {
|
||||
qaImport: require('./icons/file/qaImport.svg').default,
|
||||
uploadFile: require('./icons/file/uploadFile.svg').default,
|
||||
closeLight: require('./icons/light/close.svg').default,
|
||||
customTitle: require('./icons/light/customTitle.svg').default
|
||||
customTitle: require('./icons/light/customTitle.svg').default,
|
||||
billRecordLight: require('./icons/light/billRecord.svg').default,
|
||||
informLight: require('./icons/light/inform.svg').default,
|
||||
payRecordLight: require('./icons/light/payRecord.svg').default,
|
||||
loginoutLight: require('./icons/light/loginout.svg').default
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof map;
|
||||
|
@@ -1,21 +0,0 @@
|
||||
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: false,
|
||||
fallback: defaultIsPc
|
||||
});
|
||||
|
||||
return {
|
||||
isPc,
|
||||
mediaLgMd: useMemo(() => (isPc ? 'lg' : 'md'), [isPc]),
|
||||
mediaMdSm: useMemo(() => (isPc ? 'md' : 'sm'), [isPc]),
|
||||
media: (pc: any, phone: any) => (isPc ? pc : phone)
|
||||
};
|
||||
}
|
@@ -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([
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
139
client/src/pages/number/components/Info.tsx
Normal file
139
client/src/pages/number/components/Info.tsx
Normal 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;
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -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} />
|
||||
</>
|
||||
|
@@ -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;
|
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user