feat: inform
This commit is contained in:
parent
941549ff04
commit
55d0ed9de6
@ -6,6 +6,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
# Install dependencies based on the preferred package manager
|
# Install dependencies based on the preferred package manager
|
||||||
COPY package.json pnpm-lock.yaml* ./
|
COPY package.json pnpm-lock.yaml* ./
|
||||||
|
RUN pnpm config set registry https://registry.npmmirror.com/
|
||||||
RUN \
|
RUN \
|
||||||
[ -f pnpm-lock.yaml ] && pnpm install || \
|
[ -f pnpm-lock.yaml ] && pnpm install || \
|
||||||
(echo "Lockfile not found." && exit 1)
|
(echo "Lockfile not found." && exit 1)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ResLogin, PromotionRecordType } from './response/user';
|
|||||||
import { UserAuthTypeEnum } from '@/constants/common';
|
import { UserAuthTypeEnum } from '@/constants/common';
|
||||||
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
|
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
|
||||||
import type { PagingData, RequestPaging } from '@/types';
|
import type { PagingData, RequestPaging } from '@/types';
|
||||||
import { PaySchema } from '@/types/mongoSchema';
|
import { informSchema, PaySchema } from '@/types/mongoSchema';
|
||||||
|
|
||||||
export const sendAuthCode = (data: {
|
export const sendAuthCode = (data: {
|
||||||
username: string;
|
username: string;
|
||||||
@ -81,3 +81,9 @@ export const checkPayResult = (payId: string) => GET<number>(`/user/checkPayResu
|
|||||||
/* promotion records */
|
/* promotion records */
|
||||||
export const getPromotionRecords = (data: RequestPaging) =>
|
export const getPromotionRecords = (data: RequestPaging) =>
|
||||||
GET<PromotionRecordType>(`/user/promotion/getPromotions?${Obj2Query(data)}`);
|
GET<PromotionRecordType>(`/user/promotion/getPromotions?${Obj2Query(data)}`);
|
||||||
|
|
||||||
|
export const getInforms = (data: RequestPaging) =>
|
||||||
|
POST<PagingData<informSchema>>(`/user/inform/list`, data);
|
||||||
|
|
||||||
|
export const getUnreadCount = () => GET<number>(`/user/inform/countUnread`);
|
||||||
|
export const readInform = (id: string) => GET(`/user/inform/read`, { id });
|
||||||
|
|||||||
42
src/components/Badge/index.tsx
Normal file
42
src/components/Badge/index.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const Badge = ({
|
||||||
|
children,
|
||||||
|
isDot = false,
|
||||||
|
max = 99,
|
||||||
|
count = 0
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
isDot?: boolean;
|
||||||
|
max?: number;
|
||||||
|
count?: number;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Box position={'relative'}>
|
||||||
|
{children}
|
||||||
|
{count > 0 && (
|
||||||
|
<Box position={'absolute'} right={0} top={0} transform={'translate(70%,-50%)'}>
|
||||||
|
{isDot ? (
|
||||||
|
<Box w={'5px'} h={'5px'} bg={'myRead.600'} borderRadius={'20px'}></Box>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
color={'white'}
|
||||||
|
bg={'myRead.600'}
|
||||||
|
lineHeight={0.9}
|
||||||
|
borderRadius={'100px'}
|
||||||
|
px={'4px'}
|
||||||
|
py={'2px'}
|
||||||
|
fontSize={'12px'}
|
||||||
|
border={'1px solid white'}
|
||||||
|
>
|
||||||
|
{count > max ? `${max}+` : count}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge;
|
||||||
1
src/components/Icon/icons/inform.svg
Normal file
1
src/components/Icon/icons/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="1686042262954" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3245" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M510.1 928h5.5c52.6-0.7 96.7-38.4 103.3-88.5H406.8c6.6 50.1 50.7 87.9 103.3 88.5zM771.7 598.5V410.9c0.6-105.3-70.9-197-172.2-220.8v-4.5c0.8-31.7-15.5-61.4-42.5-77.6-27.1-16.1-60.6-16.1-87.7 0s-43.3 45.8-42.5 77.6v4.5C325.2 213.7 253.4 305.5 254 410.9v187.6c-51.9 41.3-83.2 103.5-85.9 170.2h689.5c-2.6-66.7-34-128.9-85.9-170.2z" p-id="3246"></path></svg>
|
||||||
|
After Width: | Height: | Size: 663 B |
@ -28,7 +28,8 @@ const map = {
|
|||||||
kb: require('./icons/kb.svg').default,
|
kb: require('./icons/kb.svg').default,
|
||||||
appStore: require('./icons/appStore.svg').default,
|
appStore: require('./icons/appStore.svg').default,
|
||||||
menu: require('./icons/menu.svg').default,
|
menu: require('./icons/menu.svg').default,
|
||||||
edit: require('./icons/edit.svg').default
|
edit: require('./icons/edit.svg').default,
|
||||||
|
inform: require('./icons/inform.svg').default
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IconName = keyof typeof map;
|
export type IconName = keyof typeof map;
|
||||||
|
|||||||
@ -7,6 +7,9 @@ import { throttle } from 'lodash';
|
|||||||
import Auth from './auth';
|
import Auth from './auth';
|
||||||
import Navbar from './navbar';
|
import Navbar from './navbar';
|
||||||
import NavbarPhone from './navbarPhone';
|
import NavbarPhone from './navbarPhone';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useUserStore } from '@/store/user';
|
||||||
|
import { getUnreadCount } from '@/api/user';
|
||||||
|
|
||||||
const pcUnShowLayoutRoute: Record<string, boolean> = {
|
const pcUnShowLayoutRoute: Record<string, boolean> = {
|
||||||
'/': true,
|
'/': true,
|
||||||
@ -24,6 +27,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
|||||||
const { colorMode, setColorMode } = useColorMode();
|
const { colorMode, setColorMode } = useColorMode();
|
||||||
const { Loading } = useLoading();
|
const { Loading } = useLoading();
|
||||||
const { loading, setScreenWidth, isPc } = useGlobalStore();
|
const { loading, setScreenWidth, isPc } = useGlobalStore();
|
||||||
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
const isChatPage = useMemo(
|
const isChatPage = useMemo(
|
||||||
() => router.pathname === '/chat' && Object.values(router.query).join('').length !== 0,
|
() => router.pathname === '/chat' && Object.values(router.query).join('').length !== 0,
|
||||||
@ -49,6 +53,11 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
|||||||
};
|
};
|
||||||
}, [setScreenWidth]);
|
}, [setScreenWidth]);
|
||||||
|
|
||||||
|
const { data: unread = 0 } = useQuery(['getUnreadCount'], getUnreadCount, {
|
||||||
|
enabled: !!userInfo,
|
||||||
|
refetchInterval: 5000
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -61,7 +70,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Box h={'100%'} position={'fixed'} left={0} top={0} w={'60px'}>
|
<Box h={'100%'} position={'fixed'} left={0} top={0} w={'60px'}>
|
||||||
<Navbar />
|
<Navbar unread={unread} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box h={'100%'} ml={'60px'} overflow={'overlay'}>
|
<Box h={'100%'} ml={'60px'} overflow={'overlay'}>
|
||||||
<Auth>{children}</Auth>
|
<Auth>{children}</Auth>
|
||||||
@ -76,7 +85,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
|||||||
<Auth>{children}</Auth>
|
<Auth>{children}</Auth>
|
||||||
</Box>
|
</Box>
|
||||||
<Box h={'50px'} borderTop={'1px solid rgba(0,0,0,0.1)'}>
|
<Box h={'50px'} borderTop={'1px solid rgba(0,0,0,0.1)'}>
|
||||||
<NavbarPhone />
|
<NavbarPhone unread={unread} />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Box, Flex, Tooltip } from '@chakra-ui/react';
|
import { Box, Flex, Tooltip, Link } from '@chakra-ui/react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import MyIcon from '../Icon';
|
import MyIcon from '../Icon';
|
||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
import { useChatStore } from '@/store/chat';
|
import { useChatStore } from '@/store/chat';
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import { HUMAN_ICON } from '@/constants/chat';
|
import { HUMAN_ICON } from '@/constants/chat';
|
||||||
|
import NextLink from 'next/link';
|
||||||
|
import Badge from '../Badge';
|
||||||
|
|
||||||
export enum NavbarTypeEnum {
|
export enum NavbarTypeEnum {
|
||||||
normal = 'normal',
|
normal = 'normal',
|
||||||
small = 'small'
|
small = 'small'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Navbar = () => {
|
const Navbar = ({ unread }: { unread: number }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { userInfo, lastModelId } = useUserStore();
|
const { userInfo, lastModelId } = useUserStore();
|
||||||
const { lastChatModelId, lastChatId } = useChatStore();
|
const { lastChatModelId, lastChatId } = useChatStore();
|
||||||
@ -58,6 +60,20 @@ const Navbar = () => {
|
|||||||
[lastChatId, lastChatModelId, lastModelId]
|
[lastChatId, lastChatModelId, lastModelId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const itemStyles: any = {
|
||||||
|
mb: 3,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
w: '60px',
|
||||||
|
h: '45px',
|
||||||
|
_hover: {
|
||||||
|
color: '#ffffff'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
flexDirection={'column'}
|
flexDirection={'column'}
|
||||||
@ -90,21 +106,10 @@ const Navbar = () => {
|
|||||||
openDelay={100}
|
openDelay={100}
|
||||||
gutter={-10}
|
gutter={-10}
|
||||||
>
|
>
|
||||||
<Flex
|
<Link
|
||||||
mb={3}
|
as={NextLink}
|
||||||
flexDirection={'column'}
|
href={item.link}
|
||||||
alignItems={'center'}
|
{...itemStyles}
|
||||||
justifyContent={'center'}
|
|
||||||
onClick={() => {
|
|
||||||
if (item.link === router.asPath) return;
|
|
||||||
router.push(item.link);
|
|
||||||
}}
|
|
||||||
cursor={'pointer'}
|
|
||||||
w={'60px'}
|
|
||||||
h={'45px'}
|
|
||||||
_hover={{
|
|
||||||
color: '#ffffff'
|
|
||||||
}}
|
|
||||||
{...(item.activeLink.includes(router.pathname)
|
{...(item.activeLink.includes(router.pathname)
|
||||||
? {
|
? {
|
||||||
color: '#ffffff ',
|
color: '#ffffff ',
|
||||||
@ -116,27 +121,29 @@ const Navbar = () => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<MyIcon name={item.icon as any} width={'22px'} height={'22px'} />
|
<MyIcon name={item.icon as any} width={'22px'} height={'22px'} />
|
||||||
</Flex>
|
</Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
{unread > 0 && (
|
||||||
|
<Box>
|
||||||
|
<Link as={NextLink} {...itemStyles} href={`/number?type=inform`} mb={0} color={'#9096a5'}>
|
||||||
|
<Badge count={unread}>
|
||||||
|
<MyIcon name={'inform'} width={'22px'} height={'22px'} />
|
||||||
|
</Badge>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<Box>
|
<Box>
|
||||||
<Flex
|
<Link
|
||||||
mb={3}
|
as={NextLink}
|
||||||
flexDirection={'column'}
|
href="https://github.com/c121914yu/FastGPT"
|
||||||
alignItems={'center'}
|
target={'_blank'}
|
||||||
justifyContent={'center'}
|
{...itemStyles}
|
||||||
cursor={'pointer'}
|
|
||||||
w={'60px'}
|
|
||||||
h={'45px'}
|
|
||||||
color={'#9096a5'}
|
color={'#9096a5'}
|
||||||
_hover={{
|
|
||||||
color: '#ffffff'
|
|
||||||
}}
|
|
||||||
onClick={() => window.open('https://github.com/c121914yu/FastGPT')}
|
|
||||||
>
|
>
|
||||||
<MyIcon name={'git'} width={'22px'} height={'22px'} />
|
<MyIcon name={'git'} width={'22px'} height={'22px'} />
|
||||||
</Flex>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,8 +3,9 @@ import { useRouter } from 'next/router';
|
|||||||
import MyIcon from '../Icon';
|
import MyIcon from '../Icon';
|
||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
import { useChatStore } from '@/store/chat';
|
import { useChatStore } from '@/store/chat';
|
||||||
|
import Badge from '../Badge';
|
||||||
|
|
||||||
const NavbarPhone = () => {
|
const NavbarPhone = ({ unread }: { unread: number }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { lastChatModelId, lastChatId } = useChatStore();
|
const { lastChatModelId, lastChatId } = useChatStore();
|
||||||
const navbarList = useMemo(
|
const navbarList = useMemo(
|
||||||
@ -12,25 +13,29 @@ const NavbarPhone = () => {
|
|||||||
{
|
{
|
||||||
icon: 'tabbarChat',
|
icon: 'tabbarChat',
|
||||||
link: `/chat?modelId=${lastChatModelId}&chatId=${lastChatId}`,
|
link: `/chat?modelId=${lastChatModelId}&chatId=${lastChatId}`,
|
||||||
activeLink: ['/chat']
|
activeLink: ['/chat'],
|
||||||
|
unread: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'tabbarModel',
|
icon: 'tabbarModel',
|
||||||
link: `/model`,
|
link: `/model`,
|
||||||
activeLink: ['/model']
|
activeLink: ['/model'],
|
||||||
|
unread: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'tabbarMore',
|
icon: 'tabbarMore',
|
||||||
link: '/tools',
|
link: '/tools',
|
||||||
activeLink: ['/tools']
|
activeLink: ['/tools'],
|
||||||
|
unread: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'tabbarMe',
|
icon: 'tabbarMe',
|
||||||
link: '/number',
|
link: '/number',
|
||||||
activeLink: ['/number']
|
activeLink: ['/number'],
|
||||||
|
unread
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[lastChatId, lastChatModelId]
|
[lastChatId, lastChatModelId, unread]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -82,7 +87,9 @@ const NavbarPhone = () => {
|
|||||||
router.push(item.link);
|
router.push(item.link);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MyIcon name={item.icon as any} width={'20px'} height={'20px'} />
|
<Badge isDot count={item.unread}>
|
||||||
|
<MyIcon name={item.icon as any} width={'20px'} height={'20px'} />
|
||||||
|
</Badge>
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -203,6 +203,9 @@ export const theme = extendTheme({
|
|||||||
800: '#2152d9',
|
800: '#2152d9',
|
||||||
900: '#1237b3',
|
900: '#1237b3',
|
||||||
1000: '#07228c'
|
1000: '#07228c'
|
||||||
|
},
|
||||||
|
myRead: {
|
||||||
|
600: '#ff4d4f'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fonts: {
|
fonts: {
|
||||||
|
|||||||
@ -30,3 +30,13 @@ export const PromotionTypeMap = {
|
|||||||
[PromotionEnum.shareModel]: '应用分享',
|
[PromotionEnum.shareModel]: '应用分享',
|
||||||
[PromotionEnum.withdraw]: '提现'
|
[PromotionEnum.withdraw]: '提现'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum InformTypeEnum {
|
||||||
|
system = 'system'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InformTypeMap = {
|
||||||
|
[InformTypeEnum.system]: {
|
||||||
|
label: '系统通知'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
31
src/pages/api/user/inform/countUnread.ts
Normal file
31
src/pages/api/user/inform/countUnread.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, Inform } from '@/service/mongo';
|
||||||
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
if (!req.headers.cookie) {
|
||||||
|
return jsonRes(res, {
|
||||||
|
data: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
|
const data = await Inform.countDocuments({
|
||||||
|
userId,
|
||||||
|
read: false
|
||||||
|
});
|
||||||
|
|
||||||
|
jsonRes(res, {
|
||||||
|
data
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
jsonRes(res, {
|
||||||
|
data: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/pages/api/user/inform/list.ts
Normal file
40
src/pages/api/user/inform/list.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, Inform } from '@/service/mongo';
|
||||||
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
|
const { pageNum, pageSize = 10 } = req.body as {
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
|
const [informs, total] = await Promise.all([
|
||||||
|
Inform.find({ userId })
|
||||||
|
.sort({ time: -1 }) // 按照创建时间倒序排列
|
||||||
|
.skip((pageNum - 1) * pageSize)
|
||||||
|
.limit(pageSize),
|
||||||
|
Inform.countDocuments({ userId })
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonRes(res, {
|
||||||
|
data: {
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
data: informs,
|
||||||
|
total
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
jsonRes(res, {
|
||||||
|
code: 500,
|
||||||
|
error: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/pages/api/user/inform/read.ts
Normal file
29
src/pages/api/user/inform/read.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, Inform } from '@/service/mongo';
|
||||||
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
|
const { id } = req.query as { id: string };
|
||||||
|
|
||||||
|
await Inform.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: id,
|
||||||
|
userId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
read: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
jsonRes(res);
|
||||||
|
} catch (err) {
|
||||||
|
jsonRes(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/pages/api/user/inform/send.ts
Normal file
61
src/pages/api/user/inform/send.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, Inform, User } from '@/service/mongo';
|
||||||
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
import { InformTypeEnum } from '@/constants/user';
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
type: `${InformTypeEnum}`;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
userId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
await authUser({ req, authRoot: true });
|
||||||
|
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
|
jsonRes(res, {
|
||||||
|
data: await sendInform(req.body),
|
||||||
|
message: '发送通知成功'
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
jsonRes(res, {
|
||||||
|
code: 500,
|
||||||
|
error: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendInform({ type, title, content, userId }: Props) {
|
||||||
|
if (!type || !title || !content) {
|
||||||
|
return Promise.reject('参数错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
await Inform.create({
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send to all user
|
||||||
|
const users = await User.find({}, '_id');
|
||||||
|
await Inform.insertMany(
|
||||||
|
users.map(({ _id }) => ({
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
userId: _id
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
|
import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex, Box } from '@chakra-ui/react';
|
||||||
import { BillTypeMap } from '@/constants/user';
|
import { BillTypeMap } from '@/constants/user';
|
||||||
import { getUserBills } from '@/api/user';
|
import { getUserBills } from '@/api/user';
|
||||||
import type { UserBillType } from '@/types/user';
|
import type { UserBillType } from '@/types/user';
|
||||||
import { usePagination } from '@/hooks/usePagination';
|
import { usePagination } from '@/hooks/usePagination';
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
|
||||||
const BillTable = () => {
|
const BillTable = () => {
|
||||||
const { Loading } = useLoading();
|
const { Loading } = useLoading();
|
||||||
@ -13,7 +14,9 @@ const BillTable = () => {
|
|||||||
const {
|
const {
|
||||||
data: bills,
|
data: bills,
|
||||||
isLoading,
|
isLoading,
|
||||||
Pagination
|
Pagination,
|
||||||
|
pageSize,
|
||||||
|
total
|
||||||
} = usePagination<UserBillType>({
|
} = usePagination<UserBillType>({
|
||||||
api: getUserBills
|
api: getUserBills
|
||||||
});
|
});
|
||||||
@ -48,9 +51,20 @@ const BillTable = () => {
|
|||||||
|
|
||||||
<Loading loading={isLoading} fixed={false} />
|
<Loading loading={isLoading} fixed={false} />
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
<Flex w={'100%'} mt={4} justifyContent={'flex-end'}>
|
|
||||||
<Pagination />
|
{!isLoading && bills.length === 0 && (
|
||||||
</Flex>
|
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'200px'}>
|
||||||
|
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||||
|
<Box mt={2} color={'myGray.500'}>
|
||||||
|
无使用记录~
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{total > pageSize && (
|
||||||
|
<Flex w={'100%'} mt={4} justifyContent={'flex-end'}>
|
||||||
|
<Pagination />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
91
src/pages/number/components/InformTable.tsx
Normal file
91
src/pages/number/components/InformTable.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Accordion,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionButton,
|
||||||
|
AccordionPanel,
|
||||||
|
AccordionIcon
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { getInforms, readInform } from '@/api/user';
|
||||||
|
import { usePagination } from '@/hooks/usePagination';
|
||||||
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
|
import type { informSchema } from '@/types/mongoSchema';
|
||||||
|
import { formatTimeToChatTime } from '@/utils/tools';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
|
||||||
|
const BillTable = () => {
|
||||||
|
const { Loading } = useLoading();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: informs,
|
||||||
|
isLoading,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
Pagination,
|
||||||
|
getData,
|
||||||
|
pageNum
|
||||||
|
} = usePagination<informSchema>({
|
||||||
|
api: getInforms
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Accordion defaultIndex={[0, 1, 2]} allowMultiple>
|
||||||
|
{informs.map((item) => (
|
||||||
|
<AccordionItem
|
||||||
|
key={item._id}
|
||||||
|
onClick={async () => {
|
||||||
|
if (!item.read) {
|
||||||
|
await readInform(item._id);
|
||||||
|
getData(pageNum);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
{!isLoading && informs.length === 0 && (
|
||||||
|
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'200px'}>
|
||||||
|
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||||
|
<Box mt={2} color={'myGray.500'}>
|
||||||
|
暂无通知~
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{total > pageSize && (
|
||||||
|
<Flex w={'100%'} mt={4} justifyContent={'flex-end'}>
|
||||||
|
<Pagination />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
<Loading loading={isLoading && informs.length === 0} fixed={false} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BillTable;
|
||||||
@ -1,5 +1,16 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import { Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
import {
|
||||||
|
Button,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tbody,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Td,
|
||||||
|
TableContainer,
|
||||||
|
Flex,
|
||||||
|
Box
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { getPayOrders, checkPayResult } from '@/api/user';
|
import { getPayOrders, checkPayResult } from '@/api/user';
|
||||||
import { PaySchema } from '@/types/mongoSchema';
|
import { PaySchema } from '@/types/mongoSchema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -7,15 +18,17 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { formatPrice } from '@/utils/user';
|
import { formatPrice } from '@/utils/user';
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
|
||||||
const PayRecordTable = () => {
|
const PayRecordTable = () => {
|
||||||
|
const { Loading, setIsLoading } = useLoading();
|
||||||
const [payOrders, setPayOrders] = useState<PaySchema[]>([]);
|
const [payOrders, setPayOrders] = useState<PaySchema[]>([]);
|
||||||
const { setLoading } = useGlobalStore();
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const handleRefreshPayOrder = useCallback(
|
const handleRefreshPayOrder = useCallback(
|
||||||
async (payId: string) => {
|
async (payId: string) => {
|
||||||
setLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await checkPayResult(payId);
|
const data = await checkPayResult(payId);
|
||||||
@ -33,50 +46,61 @@ const PayRecordTable = () => {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setIsLoading(false);
|
||||||
},
|
},
|
||||||
[setLoading, toast]
|
[setIsLoading, toast]
|
||||||
);
|
);
|
||||||
|
|
||||||
useQuery(['initPayOrder'], getPayOrders, {
|
const { isInitialLoading } = useQuery(['initPayOrder'], getPayOrders, {
|
||||||
onSuccess(res) {
|
onSuccess(res) {
|
||||||
setPayOrders(res);
|
setPayOrders(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<>
|
||||||
<Table>
|
<TableContainer>
|
||||||
<Thead>
|
<Table>
|
||||||
<Tr>
|
<Thead>
|
||||||
<Th>订单号</Th>
|
<Tr>
|
||||||
<Th>时间</Th>
|
<Th>订单号</Th>
|
||||||
<Th>金额</Th>
|
<Th>时间</Th>
|
||||||
<Th>状态</Th>
|
<Th>金额</Th>
|
||||||
<Th></Th>
|
<Th>状态</Th>
|
||||||
</Tr>
|
<Th></Th>
|
||||||
</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>
|
</Tr>
|
||||||
))}
|
</Thead>
|
||||||
</Tbody>
|
<Tbody fontSize={'sm'}>
|
||||||
</Table>
|
{payOrders.map((item) => (
|
||||||
</TableContainer>
|
<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={'200px'}>
|
||||||
|
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||||
|
<Box mt={2} color={'myGray.500'}>
|
||||||
|
无支付记录~
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
<Loading loading={isInitialLoading} fixed={false} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Flex, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
import { Flex, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Box } from '@chakra-ui/react';
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getPromotionRecords } from '@/api/user';
|
import { getPromotionRecords } from '@/api/user';
|
||||||
import { usePagination } from '@/hooks/usePagination';
|
import { usePagination } from '@/hooks/usePagination';
|
||||||
import { PromotionRecordType } from '@/api/response/user';
|
import { PromotionRecordType } from '@/api/response/user';
|
||||||
import { PromotionTypeMap } from '@/constants/user';
|
import { PromotionTypeMap } from '@/constants/user';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
|
||||||
const OpenApi = () => {
|
const OpenApi = () => {
|
||||||
const { Loading } = useLoading();
|
const { Loading } = useLoading();
|
||||||
@ -13,6 +14,8 @@ const OpenApi = () => {
|
|||||||
const {
|
const {
|
||||||
data: promotionRecords,
|
data: promotionRecords,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
Pagination
|
Pagination
|
||||||
} = usePagination<PromotionRecordType>({
|
} = usePagination<PromotionRecordType>({
|
||||||
api: getPromotionRecords
|
api: getPromotionRecords
|
||||||
@ -44,9 +47,20 @@ const OpenApi = () => {
|
|||||||
|
|
||||||
<Loading loading={isLoading} fixed={false} />
|
<Loading loading={isLoading} fixed={false} />
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
<Flex mt={4} justifyContent={'flex-end'}>
|
|
||||||
<Pagination />
|
{!isLoading && promotionRecords.length === 0 && (
|
||||||
</Flex>
|
<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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -43,6 +43,10 @@ const PromotionTable = dynamic(() => import('./components/PromotionTable'), {
|
|||||||
loading: () => <Loading fixed={false} />,
|
loading: () => <Loading fixed={false} />,
|
||||||
ssr: false
|
ssr: false
|
||||||
});
|
});
|
||||||
|
const InformTable = dynamic(() => import('./components/InformTable'), {
|
||||||
|
loading: () => <Loading fixed={false} />,
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
const PayModal = dynamic(() => import('./components/PayModal'), {
|
const PayModal = dynamic(() => import('./components/PayModal'), {
|
||||||
loading: () => <Loading fixed={false} />,
|
loading: () => <Loading fixed={false} />,
|
||||||
ssr: false
|
ssr: false
|
||||||
@ -55,14 +59,16 @@ const WxConcat = dynamic(() => import('@/components/WxConcat'), {
|
|||||||
enum TableEnum {
|
enum TableEnum {
|
||||||
'bill' = 'bill',
|
'bill' = 'bill',
|
||||||
'pay' = 'pay',
|
'pay' = 'pay',
|
||||||
'promotion' = 'promotion'
|
'promotion' = 'promotion',
|
||||||
|
'inform' = 'inform'
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberSetting = () => {
|
const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
||||||
const tableType = useRef([
|
const tableList = useRef([
|
||||||
{ label: '账单详情', value: TableEnum.bill, Component: BilTable },
|
{ label: '账单', value: TableEnum.bill, Component: BilTable },
|
||||||
{ label: '充值记录', value: TableEnum.pay, Component: PayRecordTable },
|
{ label: '充值', value: TableEnum.pay, Component: PayRecordTable },
|
||||||
{ label: '佣金记录', value: TableEnum.pay, Component: PromotionTable }
|
{ label: '佣金', value: TableEnum.promotion, Component: PromotionTable },
|
||||||
|
{ label: '通知', value: TableEnum.inform, Component: InformTable }
|
||||||
]);
|
]);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { copyData } = useCopyData();
|
const { copyData } = useCopyData();
|
||||||
@ -232,21 +238,26 @@ const NumberSetting = () => {
|
|||||||
colorScheme={'myBlue'}
|
colorScheme={'myBlue'}
|
||||||
onClick={onOpenWxConcat}
|
onClick={onOpenWxConcat}
|
||||||
>
|
>
|
||||||
提现
|
{residueAmount < 50 ? '50元起提' : '提现'}
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Card mt={4} px={[3, 6]} py={4}>
|
<Card mt={4} px={[3, 6]} py={4}>
|
||||||
<Tabs variant="unstyled" isLazy>
|
<Tabs
|
||||||
|
variant="unstyled"
|
||||||
|
isLazy
|
||||||
|
defaultIndex={tableList.current.findIndex((item) => item.value === tableType)}
|
||||||
|
onChange={(i) => router.replace(`/number?type=${tableList.current[i].value}`)}
|
||||||
|
>
|
||||||
<TabList whiteSpace={'nowrap'}>
|
<TabList whiteSpace={'nowrap'}>
|
||||||
{tableType.current.map((item) => (
|
{tableList.current.map((item) => (
|
||||||
<Tab
|
<Tab
|
||||||
key={item.value}
|
key={item.value}
|
||||||
py={1}
|
py={'2px'}
|
||||||
px={3}
|
px={4}
|
||||||
borderRadius={'sm'}
|
borderRadius={'sm'}
|
||||||
mr={1}
|
mr={2}
|
||||||
transition={'none'}
|
transition={'none'}
|
||||||
_selected={{ color: 'white', bg: 'myBlue.600' }}
|
_selected={{ color: 'white', bg: 'myBlue.600' }}
|
||||||
>
|
>
|
||||||
@ -255,7 +266,7 @@ const NumberSetting = () => {
|
|||||||
))}
|
))}
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
{tableType.current.map((Item) => (
|
{tableList.current.map((Item) => (
|
||||||
<TabPanel minH={'550px'} key={Item.value}>
|
<TabPanel minH={'550px'} key={Item.value}>
|
||||||
<Item.Component />
|
<Item.Component />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@ -272,3 +283,9 @@ const NumberSetting = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default NumberSetting;
|
export default NumberSetting;
|
||||||
|
|
||||||
|
NumberSetting.getInitialProps = ({ query, req }: any) => {
|
||||||
|
return {
|
||||||
|
tableType: query?.type || TableEnum.bill
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
40
src/service/models/inform.ts
Normal file
40
src/service/models/inform.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Schema, model, models, Model } from 'mongoose';
|
||||||
|
import { informSchema } from '@/types/mongoSchema';
|
||||||
|
import { InformTypeMap } from '@/constants/user';
|
||||||
|
|
||||||
|
const InformSchema = new Schema({
|
||||||
|
userId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'user',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
type: Date,
|
||||||
|
default: () => new Date()
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: Object.keys(InformTypeMap)
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
InformSchema.index({ time: -1 });
|
||||||
|
InformSchema.index({ userId: 1 });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Inform: Model<informSchema> = models['inform'] || model('inform', InformSchema);
|
||||||
@ -68,3 +68,4 @@ export * from './models/promotionRecord';
|
|||||||
export * from './models/collection';
|
export * from './models/collection';
|
||||||
export * from './models/shareChat';
|
export * from './models/shareChat';
|
||||||
export * from './models/kb';
|
export * from './models/kb';
|
||||||
|
export * from './models/inform';
|
||||||
|
|||||||
12
src/types/mongoSchema.d.ts
vendored
12
src/types/mongoSchema.d.ts
vendored
@ -7,7 +7,7 @@ import {
|
|||||||
EmbeddingModelType
|
EmbeddingModelType
|
||||||
} from '@/constants/model';
|
} from '@/constants/model';
|
||||||
import type { DataType } from './data';
|
import type { DataType } from './data';
|
||||||
import { BillTypeEnum } from '@/constants/user';
|
import { BillTypeEnum, InformTypeEnum } from '@/constants/user';
|
||||||
import { TrainingModeEnum } from '@/constants/plugin';
|
import { TrainingModeEnum } from '@/constants/plugin';
|
||||||
|
|
||||||
export interface UserModelSchema {
|
export interface UserModelSchema {
|
||||||
@ -155,3 +155,13 @@ export interface kbSchema {
|
|||||||
name: string;
|
name: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface informSchema {
|
||||||
|
_id: string;
|
||||||
|
userId: string;
|
||||||
|
time: Date;
|
||||||
|
type: `${InformTypeEnum}`;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
read: boolean;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user