invite url

This commit is contained in:
archer
2023-08-20 20:35:48 +08:00
parent c20fba11ba
commit 7a231c6501
20 changed files with 345 additions and 43 deletions

View File

@@ -119,17 +119,26 @@
}, },
"user": { "user": {
"Account": "Account", "Account": "Account",
"Amount of earnings": "Earnings",
"Amount of inviter": "Inviter",
"Application Name": "Application Name", "Application Name": "Application Name",
"Avatar": "Avatar", "Avatar": "Avatar",
"Balance": "Balance", "Balance": "Balance",
"Bill Detail": "Bill Detail", "Bill Detail": "Bill Detail",
"Change": "Change", "Change": "Change",
"Copy invite url": "Copy invitation link",
"Invite Url": "Invite Url",
"Invite url tip": "Friends who register through this link will be permanently bound to you, and you will get a certain balance reward when they recharge. In addition, when friends register with their mobile phone number, you will get 5 yuan reward immediately.",
"Notice": "Notice", "Notice": "Notice",
"Old password is error": "Old password is error", "Old password is error": "Old password is error",
"OpenAI Account Setting": "OpenAI Account Setting", "OpenAI Account Setting": "OpenAI Account Setting",
"Password": "Password", "Password": "Password",
"Pay": "Pay", "Pay": "Pay",
"Personal Information": "Personal", "Personal Information": "Personal",
"Promotion": "Promotion",
"Promotion Rate": "Promotion Rate",
"Promotion Record": "Promotion",
"Promotion rate tip": "You will be rewarded with a percentage of the balance when your friends top up",
"Recharge Record": "Recharge", "Recharge Record": "Recharge",
"Replace": "Replace", "Replace": "Replace",
"Set OpenAI Account Failed": "Set OpenAI account failed", "Set OpenAI Account Failed": "Set OpenAI account failed",

View File

@@ -119,17 +119,26 @@
}, },
"user": { "user": {
"Account": "账号", "Account": "账号",
"Amount of earnings": "收益(¥)",
"Amount of inviter": "累计邀请人数",
"Application Name": "应用名", "Application Name": "应用名",
"Avatar": "头像", "Avatar": "头像",
"Balance": "余额", "Balance": "余额",
"Bill Detail": "账单详情", "Bill Detail": "账单详情",
"Change": "变更", "Change": "变更",
"Copy invite url": "复制邀请链接",
"Invite Url": "邀请链接",
"Invite url tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n此外好友使用手机号注册时你将立即获得 5 元奖励。",
"Notice": "通知", "Notice": "通知",
"Old password is error": "旧密码错误", "Old password is error": "旧密码错误",
"OpenAI Account Setting": "OpenAI 账号配置", "OpenAI Account Setting": "OpenAI 账号配置",
"Password": "密码", "Password": "密码",
"Pay": "充值", "Pay": "充值",
"Personal Information": "个人信息", "Personal Information": "个人信息",
"Promotion": "",
"Promotion Rate": "返现比例",
"Promotion Record": "推广记录",
"Promotion rate tip": "好友充值时你将获得一定比例的余额奖励",
"Recharge Record": "充值记录", "Recharge Record": "充值记录",
"Replace": "更换", "Replace": "更换",
"Set OpenAI Account Failed": "设置 OpenAI 账号异常", "Set OpenAI Account Failed": "设置 OpenAI 账号异常",
@@ -140,6 +149,10 @@
"Update Password": "修改密码", "Update Password": "修改密码",
"Update password failed": "修改密码异常", "Update password failed": "修改密码异常",
"Update password succseful": "修改密码成功", "Update password succseful": "修改密码成功",
"Usage Record": "使用记录" "Usage Record": "使用记录",
"promption": {
"register": "好友注册",
"pay": "好友充值"
}
} }
} }

View File

@@ -1,6 +1,6 @@
import { GET, POST, PUT } from './request'; import { GET, POST, PUT } from './request';
import { createHashPassword } from '@/utils/tools'; import { createHashPassword } from '@/utils/tools';
import { ResLogin } from './response/user'; import type { 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';
@@ -13,7 +13,8 @@ export const sendAuthCode = (data: {
}) => POST('/user/sendAuthCode', data); }) => POST('/user/sendAuthCode', data);
export const getTokenLogin = () => GET<UserType>('/user/account/tokenLogin'); export const getTokenLogin = () => GET<UserType>('/user/account/tokenLogin');
export const gitLogin = (code: string) => GET<ResLogin>('/user/account/gitLogin', { code }); export const gitLogin = (params: { code: string; inviterId?: string }) =>
GET<ResLogin>('/user/account/gitLogin', params);
export const postRegister = ({ export const postRegister = ({
username, username,
@@ -82,3 +83,14 @@ export const getInforms = (data: RequestPaging) =>
export const getUnreadCount = () => GET<number>(`/user/inform/countUnread`); export const getUnreadCount = () => GET<number>(`/user/inform/countUnread`);
export const readInform = (id: string) => GET(`/user/inform/read`, { id }); export const readInform = (id: string) => GET(`/user/inform/read`, { id });
/* get promotion init data */
export const getPromotionInitData = () =>
GET<{
invitedAmount: number;
earningsAmount: number;
}>('/user/promotion/getPromotionData');
/* promotion records */
export const getPromotionRecords = (data: RequestPaging) =>
POST<PromotionRecordType>(`/user/promotion/getPromotions`, data);

View File

@@ -0,0 +1,8 @@
<?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="1692522401431"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4063"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path
d="M855 789.1V127.4L633 274.9H169V641h81l59.5 255.7H508L448.4 641H633l222 148.1z m-46-86l-146-97.5V310.2l146-97v489.9zM450 850.6H346L297.1 641h104L450 850.6zM239.2 595H215V320.9h274V595H239.2z m377.8 0h-82V320.9h82V595z"
p-id="4064"></path>
</svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@@ -75,7 +75,8 @@ const map = {
outlink_iframe: require('./icons/outlink/iframe.svg').default, outlink_iframe: require('./icons/outlink/iframe.svg').default,
addCircle: require('./icons/circle/add.svg').default, addCircle: require('./icons/circle/add.svg').default,
playFill: require('./icons/fill/play.svg').default, playFill: require('./icons/fill/play.svg').default,
courseLight: require('./icons/light/course.svg').default courseLight: require('./icons/light/course.svg').default,
promotionLight: require('./icons/light/promotion.svg').default
}; };
export type IconName = keyof typeof map; export type IconName = keyof typeof map;

View File

@@ -1,8 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import type { GridProps } from '@chakra-ui/react'; import type { GridProps } from '@chakra-ui/react';
import MyIcon, { type IconName } from '../Icon'; import MyIcon, { type IconName } from '../Icon';
import { useTranslation } from 'next-i18next';
// @ts-ignore // @ts-ignore
export interface Props extends GridProps { export interface Props extends GridProps {
@@ -13,7 +12,6 @@ export interface Props extends GridProps {
} }
const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => { const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
const { t } = useTranslation();
const sizeMap = useMemo(() => { const sizeMap = useMemo(() => {
switch (size) { switch (size) {
case 'sm': case 'sm':
@@ -63,7 +61,7 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
}} }}
> >
<MyIcon mr={2} name={item.icon as IconName} w={'16px'} /> <MyIcon mr={2} name={item.icon as IconName} w={'16px'} />
{t(item.label)} {item.label}
</Flex> </Flex>
))} ))}
</Box> </Box>

View File

@@ -42,6 +42,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
p={sizeMap.outP} p={sizeMap.outP}
borderRadius={'sm'} borderRadius={'sm'}
fontSize={sizeMap.fontSize} fontSize={sizeMap.fontSize}
overflowX={'auto'}
{...props} {...props}
> >
{list.map((item) => ( {list.map((item) => (
@@ -50,6 +51,8 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
py={sizeMap.inlineP} py={sizeMap.inlineP}
textAlign={'center'} textAlign={'center'}
borderBottom={'2px solid transparent'} borderBottom={'2px solid transparent'}
px={3}
whiteSpace={'nowrap'}
{...(activeId === item.id {...(activeId === item.id
? { ? {
color: 'myBlue.700', color: 'myBlue.700',

View File

@@ -16,17 +16,10 @@ export const BillSourceMap: Record<`${BillSourceEnum}`, string> = {
}; };
export enum PromotionEnum { export enum PromotionEnum {
invite = 'invite', register = 'register',
shareModel = 'shareModel', pay = 'pay'
withdraw = 'withdraw'
} }
export const PromotionTypeMap = {
[PromotionEnum.invite]: '好友充值',
[PromotionEnum.shareModel]: '应用分享',
[PromotionEnum.withdraw]: '提现'
};
export enum InformTypeEnum { export enum InformTypeEnum {
system = 'system' system = 'system'
} }

View File

@@ -35,6 +35,7 @@ const queryClient = new QueryClient({
function App({ Component, pageProps }: AppProps) { function App({ Component, pageProps }: AppProps) {
const router = useRouter(); const router = useRouter();
const { hiId } = router.query as { hiId?: string };
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const [scripts, setScripts] = useState<FeConfigsType['scripts']>([]); const [scripts, setScripts] = useState<FeConfigsType['scripts']>([]);
@@ -50,6 +51,10 @@ function App({ Component, pageProps }: AppProps) {
})(); })();
}, []); }, []);
useEffect(() => {
hiId && localStorage.setItem('inviterId', hiId);
}, [hiId]);
useEffect(() => { useEffect(() => {
const lang = getLangStore() || 'zh'; const lang = getLangStore() || 'zh';
i18n?.changeLanguage?.(lang); i18n?.changeLanguage?.(lang);

View File

@@ -0,0 +1,148 @@
import React from 'react';
import {
Grid,
Box,
Flex,
BoxProps,
useTheme,
Button,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { getPromotionInitData, getPromotionRecords } from '@/api/user';
import { useUserStore } from '@/store/user';
import { useLoading } from '@/hooks/useLoading';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useCopyData } from '@/utils/tools';
import { usePagination } from '@/hooks/usePagination';
import { PromotionRecordType } from '@/api/response/user';
import MyIcon from '@/components/Icon';
import dayjs from 'dayjs';
const Promotion = () => {
const { t } = useTranslation();
const theme = useTheme();
const { copyData } = useCopyData();
const { userInfo } = useUserStore();
const { Loading } = useLoading();
const {
data: promotionRecords,
isLoading,
total,
pageSize,
Pagination
} = usePagination<PromotionRecordType>({
api: getPromotionRecords
});
const { data: { invitedAmount = 0, earningsAmount = 0 } = {} } = useQuery(
['getPromotionInitData'],
getPromotionInitData
);
const statisticsStyles: BoxProps = {
p: [4, 5],
border: theme.borders.base,
textAlign: 'center',
fontSize: ['md', 'xl'],
borderRadius: 'md'
};
const titleStyles: BoxProps = {
mt: 2,
fontSize: ['lg', '28px'],
fontWeight: 'bold'
};
return (
<Flex flexDirection={'column'} py={[0, 5]} px={5} h={'100%'} position={'relative'}>
<Grid gridTemplateColumns={['1fr 1fr', 'repeat(2,1fr)', 'repeat(4,1fr)']} gridGap={5}>
<Box {...statisticsStyles}>
<Box>{t('user.Amount of inviter')}</Box>
<Box {...titleStyles}>{invitedAmount}</Box>
</Box>
<Box {...statisticsStyles}>
<Box>{t('user.Amount of earnings')}</Box>
<Box {...titleStyles}>{earningsAmount}</Box>
</Box>
<Box {...statisticsStyles}>
<Flex alignItems={'center'} justifyContent={'center'}>
<Box>{t('user.Promotion Rate')}</Box>
<MyTooltip label={t('user.Promotion rate tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Box {...titleStyles}>{userInfo?.promotionRate || 15}%</Box>
</Box>
<Box {...statisticsStyles}>
<Flex alignItems={'center'} justifyContent={'center'}>
<Box>{t('user.Invite Url')}</Box>
<MyTooltip label={t('user.Invite url tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Button
mt={4}
variant={'base'}
fontSize={'sm'}
onClick={() => {
copyData(`${location.origin}/?hiId=${userInfo?._id}`);
}}
>
{t('user.Copy invite url')}
</Button>
</Box>
</Grid>
<Box mt={5}>
<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>{t(`user.promotion.${item.type}`)}</Td>
<Td>{item.amount}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{!isLoading && promotionRecords.length === 0 && (
<Flex mt={'10vh'} 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} />
</Box>
</Flex>
);
};
export default Promotion;

View File

@@ -12,7 +12,11 @@ import Tabs from '@/components/Tabs';
import UserInfo from './components/Info'; import UserInfo from './components/Info';
import { serviceSideProps } from '@/utils/i18n'; import { serviceSideProps } from '@/utils/i18n';
import { feConfigs } from '@/store/static'; import { feConfigs } from '@/store/static';
import { useTranslation } from 'react-i18next';
const Promotion = dynamic(() => import('./components/Promotion'), {
ssr: false
});
const BillTable = dynamic(() => import('./components/BillTable'), { const BillTable = dynamic(() => import('./components/BillTable'), {
ssr: false ssr: false
}); });
@@ -25,6 +29,7 @@ const InformTable = dynamic(() => import('./components/InformTable'), {
enum TabEnum { enum TabEnum {
'info' = 'info', 'info' = 'info',
'promotion' = 'promotion',
'bill' = 'bill', 'bill' = 'bill',
'pay' = 'pay', 'pay' = 'pay',
'inform' = 'inform', 'inform' = 'inform',
@@ -32,40 +37,42 @@ enum TabEnum {
} }
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { t } = useTranslation();
const tabList = useRef([ const tabList = useRef([
{ {
icon: 'meLight', icon: 'meLight',
label: 'user.Personal Information', label: t('user.Personal Information'),
id: TabEnum.info, id: TabEnum.info
Component: <BillTable />
}, },
{ {
icon: 'billRecordLight', icon: 'billRecordLight',
label: 'user.Usage Record', label: t('user.Usage Record'),
id: TabEnum.bill, id: TabEnum.bill
Component: <BillTable />
}, },
...(feConfigs?.show_userDetail ...(feConfigs?.show_userDetail
? [ ? [
{
icon: 'promotionLight',
label: t('user.Promotion Record'),
id: TabEnum.promotion
},
{ {
icon: 'payRecordLight', icon: 'payRecordLight',
label: 'user.Recharge Record', label: t('user.Recharge Record'),
id: TabEnum.pay, id: TabEnum.pay
Component: <PayRecordTable />
} }
] ]
: []), : []),
{ {
icon: 'informLight', icon: 'informLight',
label: 'user.Notice', label: t('user.Notice'),
id: TabEnum.inform, id: TabEnum.inform
Component: <InformTable />
}, },
{ {
icon: 'loginoutLight', icon: 'loginoutLight',
label: 'user.Sign Out', label: t('user.Sign Out'),
id: TabEnum.loginout, id: TabEnum.loginout
Component: () => <></>
} }
]); ]);
@@ -122,7 +129,6 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<Box mb={3}> <Box mb={3}>
<Tabs <Tabs
m={'auto'} m={'auto'}
w={'90%'}
size={isPc ? 'md' : 'sm'} size={isPc ? 'md' : 'sm'}
list={tabList.current.map((item) => ({ list={tabList.current.map((item) => ({
id: item.id, id: item.id,
@@ -136,6 +142,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}> <Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
{currentTab === TabEnum.info && <UserInfo />} {currentTab === TabEnum.info && <UserInfo />}
{currentTab === TabEnum.promotion && <Promotion />}
{currentTab === TabEnum.bill && <BillTable />} {currentTab === TabEnum.bill && <BillTable />}
{currentTab === TabEnum.pay && <PayRecordTable />} {currentTab === TabEnum.pay && <PayRecordTable />}
{currentTab === TabEnum.inform && <InformTable />} {currentTab === TabEnum.inform && <InformTable />}

View File

@@ -24,7 +24,7 @@ type GithubUserType = {
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
const { code } = req.query as { code: string }; const { code, inviterId } = req.query as { code: string; inviterId?: string };
const { data: gitAccessToken } = await axios.post<string>( const { data: gitAccessToken } = await axios.post<string>(
`https://github.com/login/oauth/access_token?client_id=${global.feConfigs.gitLoginKey}&client_secret=${global.systemEnv.gitLoginSecret}&code=${code}` `https://github.com/login/oauth/access_token?client_id=${global.feConfigs.gitLoginKey}&client_secret=${global.systemEnv.gitLoginSecret}&code=${code}`
@@ -51,7 +51,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
} catch (err: any) { } catch (err: any) {
if (err?.code === 500) { if (err?.code === 500) {
jsonRes(res, { jsonRes(res, {
data: await registerUser({ username, avatar: avatar_url, res }) data: await registerUser({ username, avatar: avatar_url, res, inviterId })
}); });
return; return;
} }
@@ -88,17 +88,21 @@ export async function loginByUsername({
export async function registerUser({ export async function registerUser({
username, username,
avatar, avatar,
inviterId,
res res
}: { }: {
username: string; username: string;
avatar?: string; avatar?: string;
inviterId?: string;
res: NextApiResponse; res: NextApiResponse;
}) { }) {
const response = await User.create({ const response = await User.create({
username, username,
avatar, avatar,
password: nanoid() password: nanoid(),
inviterId
}); });
console.log(response, '-=-=-=');
// 根据 id 获取用户信息 // 根据 id 获取用户信息
const user = await User.findById(response._id); const user = await User.findById(response._id);

View File

@@ -0,0 +1,51 @@
// 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, User, promotionRecord } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import mongoose from 'mongoose';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
const invitedAmount = await User.countDocuments({
inviterId: userId
});
// 计算累计合
const countHistory: { totalAmount: number }[] = await promotionRecord.aggregate([
{
$match: {
userId: new mongoose.Types.ObjectId(userId),
amount: { $gt: 0 }
}
},
{
$group: {
_id: null, // 分组条件,这里使用 null 表示不分组
totalAmount: { $sum: '$amount' } // 计算 amount 字段的总和
}
},
{
$project: {
_id: false, // 排除 _id 字段
totalAmount: true // 只返回 totalAmount 字段
}
}
]);
jsonRes(res, {
data: {
invitedAmount,
historyAmount: countHistory[0]?.totalAmount || 0
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,44 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, promotionRecord } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
let { pageNum = 1, pageSize = 10 } = req.body as {
pageNum: number;
pageSize: number;
};
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const data = await promotionRecord
.find(
{
userId
},
'_id createTime type amount'
)
.sort({ _id: -1 })
.skip((pageNum - 1) * pageSize)
.limit(pageSize);
jsonRes(res, {
data: {
pageNum,
pageSize,
data,
total: await promotionRecord.countDocuments({
userId
})
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -24,7 +24,6 @@ interface RegisterType {
} }
const RegisterForm = ({ setPageType, loginSuccess }: Props) => { const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
const { inviterId = '' } = useRouter().query as { inviterId: string };
const { toast } = useToast(); const { toast } = useToast();
const { const {
register, register,
@@ -58,7 +57,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
username, username,
code, code,
password, password,
inviterId: inviterId || localStorage.getItem('inviterId') || '' inviterId: localStorage.getItem('inviterId') || ''
}) })
); );
toast({ toast({
@@ -81,7 +80,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
} }
setRequesting(false); setRequesting(false);
}, },
[inviterId, loginSuccess, toast] [loginSuccess, toast]
); );
return ( return (

View File

@@ -44,7 +44,10 @@ const provider = ({ code }: { code: string }) => {
try { try {
const res = await (async () => { const res = await (async () => {
if (loginStore.provider === 'git') { if (loginStore.provider === 'git') {
return gitLogin(code); return gitLogin({
code,
inviterId: localStorage.getItem('inviterId') || ''
});
} }
return null; return null;
})(); })();

View File

@@ -19,7 +19,7 @@ const PromotionRecordSchema = new Schema({
type: { type: {
type: String, type: String,
required: true, required: true,
enum: ['invite', 'shareModel', 'withdraw'] enum: ['invite', 'register']
}, },
amount: { amount: {
type: Number, type: Number,

View File

@@ -26,7 +26,6 @@ const UserSchema = new Schema({
default: '/icon/human.png' default: '/icon/human.png'
}, },
balance: { balance: {
// 平台余额,不可提现
type: Number, type: Number,
default: 2 * PRICE_SCALE default: 2 * PRICE_SCALE
}, },
@@ -35,6 +34,10 @@ const UserSchema = new Schema({
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'user' ref: 'user'
}, },
promotionRate: {
type: Number,
default: 15
},
limit: { limit: {
exportKbTime: { exportKbTime: {
// Every half hour // Every half hour

View File

@@ -13,8 +13,8 @@ export interface UserModelSchema {
password: string; password: string;
avatar: string; avatar: string;
balance: number; balance: number;
promotionRate: number;
inviterId?: string; inviterId?: string;
promotionAmount: number;
openaiKey: string; openaiKey: string;
createTime: number; createTime: number;
openaiAccount?: { openaiAccount?: {

View File

@@ -5,6 +5,7 @@ export interface UserType {
username: string; username: string;
avatar: string; avatar: string;
balance: number; balance: number;
promotionRate: UserModelSchema['promotionRate'];
openaiAccount: UserModelSchema['openaiAccount']; openaiAccount: UserModelSchema['openaiAccount'];
} }