mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
perf: promotion
This commit is contained in:
@@ -14,14 +14,6 @@ export const sendAuthCode = (data: {
|
||||
|
||||
export const getTokenLogin = () => GET<UserType>('/user/tokenLogin');
|
||||
|
||||
/* get promotion init data */
|
||||
export const getPromotionInitData = () =>
|
||||
GET<{
|
||||
invitedAmount: number;
|
||||
historyAmount: number;
|
||||
residueAmount: number;
|
||||
}>('/user/promotion/getPromotionData');
|
||||
|
||||
export const postRegister = ({
|
||||
username,
|
||||
password,
|
||||
@@ -78,10 +70,6 @@ export const getPayCode = (amount: number) =>
|
||||
|
||||
export const checkPayResult = (payId: string) => GET<number>(`/user/checkPayResult?payId=${payId}`);
|
||||
|
||||
/* promotion records */
|
||||
export const getPromotionRecords = (data: RequestPaging) =>
|
||||
GET<PromotionRecordType>(`/user/promotion/getPromotions?${Obj2Query(data)}`);
|
||||
|
||||
export const getInforms = (data: RequestPaging) =>
|
||||
POST<PagingData<informSchema>>(`/user/inform/list`, data);
|
||||
|
||||
|
47
client/src/pages/api/admin/initPromotion.ts
Normal file
47
client/src/pages/api/admin/initPromotion.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authUser } from '@/service/utils/auth';
|
||||
import { connectToDatabase, TrainingData, User, promotionRecord } from '@/service/mongo';
|
||||
import { PRICE_SCALE } from '@/constants/common';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await authUser({ req, authRoot: true });
|
||||
await connectToDatabase();
|
||||
|
||||
// 计算剩余金额
|
||||
const countResidue: { userId: string; totalAmount: number }[] = await promotionRecord.aggregate(
|
||||
[
|
||||
{
|
||||
$group: {
|
||||
_id: '$userId', // Group by userId
|
||||
totalAmount: { $sum: '$amount' } // Calculate the sum of amount field
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: false, // Exclude _id field
|
||||
userId: '$_id', // Include userId field
|
||||
totalAmount: true // Include totalAmount field
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
countResidue.map((item) =>
|
||||
User.findByIdAndUpdate(item.userId, {
|
||||
$inc: { balance: item.totalAmount * PRICE_SCALE }
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
jsonRes(res, { data: countResidue });
|
||||
} catch (error) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
@@ -5,8 +5,6 @@ import { authUser } from '@/service/utils/auth';
|
||||
import { PaySchema } from '@/types/mongoSchema';
|
||||
import dayjs from 'dayjs';
|
||||
import { getPayResult } from '@/service/utils/wxpay';
|
||||
import { pushPromotionRecord } from '@/service/utils/promotion';
|
||||
import { PRICE_SCALE } from '@/constants/common';
|
||||
import { startQueue } from '@/service/utils/tools';
|
||||
|
||||
/* 校验支付结果 */
|
||||
@@ -33,13 +31,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
if (!user) {
|
||||
throw new Error('找不到用户');
|
||||
}
|
||||
// 获取邀请者
|
||||
const inviter = await (async () => {
|
||||
if (user.inviterId) {
|
||||
return User.findById(user.inviterId, '_id promotion');
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
|
||||
const payRes = await getPayResult(payOrder.orderId);
|
||||
|
||||
@@ -65,16 +56,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await User.findByIdAndUpdate(userId, {
|
||||
$inc: { balance: payOrder.price }
|
||||
});
|
||||
// 推广佣金发放
|
||||
if (inviter) {
|
||||
pushPromotionRecord({
|
||||
userId: inviter._id,
|
||||
objUId: userId,
|
||||
type: 'invite',
|
||||
// amount 单位为元,需要除以缩放比例,最后乘比例
|
||||
amount: (payOrder.price / PRICE_SCALE) * inviter.promotion.rate * 0.01
|
||||
});
|
||||
}
|
||||
|
||||
unlockTask(userId);
|
||||
return jsonRes(res, {
|
||||
data: '支付成功'
|
||||
|
@@ -1,69 +0,0 @@
|
||||
// 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 {
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
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 字段
|
||||
}
|
||||
}
|
||||
]);
|
||||
// 计算剩余金额
|
||||
const countResidue: { totalAmount: number }[] = await promotionRecord.aggregate([
|
||||
{ $match: { userId: new mongoose.Types.ObjectId(userId) } },
|
||||
{
|
||||
$group: {
|
||||
_id: null, // 分组条件,这里使用 null 表示不分组
|
||||
totalAmount: { $sum: '$amount' } // 计算 amount 字段的总和
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: false, // 排除 _id 字段
|
||||
totalAmount: true // 只返回 totalAmount 字段
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
jsonRes(res, {
|
||||
data: {
|
||||
invitedAmount,
|
||||
historyAmount: countHistory[0]?.totalAmount || 0,
|
||||
residueAmount: countResidue[0]?.totalAmount || 0
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
// 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, 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.query as {
|
||||
pageNum: string;
|
||||
pageSize: string;
|
||||
};
|
||||
pageNum = +pageNum;
|
||||
pageSize = +pageSize;
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@ 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, getPromotionInitData } from '@/api/user';
|
||||
import { putUserInfo } from '@/api/user';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useUserStore } from '@/store/user';
|
||||
@@ -13,7 +13,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSelectFile } from '@/hooks/useSelectFile';
|
||||
import { compressImg } from '@/utils/file';
|
||||
import { getErrText, useCopyData } from '@/utils/tools';
|
||||
import { getErrText } from '@/utils/tools';
|
||||
import { feConfigs } from '@/store/static';
|
||||
|
||||
import Loading from '@/components/Loading';
|
||||
@@ -25,9 +25,7 @@ import BillTable from './components/BillTable';
|
||||
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'), {
|
||||
ssr: false
|
||||
});
|
||||
const PromotionTable = dynamic(() => import('./components/PromotionTable'), {
|
||||
ssr: false
|
||||
});
|
||||
|
||||
const InformTable = dynamic(() => import('./components/InformTable'), {
|
||||
ssr: false
|
||||
});
|
||||
@@ -51,12 +49,10 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
||||
const tableList = useRef([
|
||||
{ label: '账单', id: TableEnum.bill, Component: <BillTable /> },
|
||||
{ label: '充值', id: TableEnum.pay, Component: <PayRecordTable /> },
|
||||
{ label: '佣金', id: TableEnum.promotion, Component: <PromotionTable /> },
|
||||
{ label: '通知', id: TableEnum.inform, Component: <InformTable /> }
|
||||
]);
|
||||
|
||||
const router = useRouter();
|
||||
const { copyData } = useCopyData();
|
||||
const { userInfo, updateUserInfo, initUserInfo, setUserInfo } = useUserStore();
|
||||
const { setLoading } = useGlobalStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
@@ -68,11 +64,6 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
||||
onClose: onClosePayModal,
|
||||
onOpen: onOpenPayModal
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenWxConcat,
|
||||
onClose: onCloseWxConcat,
|
||||
onOpen: onOpenWxConcat
|
||||
} = useDisclosure();
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
@@ -141,10 +132,6 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
||||
reset(res);
|
||||
}
|
||||
});
|
||||
const { data: { invitedAmount = 0, historyAmount = 0, residueAmount = 0 } = {} } = useQuery(
|
||||
['getPromotionInitData'],
|
||||
getPromotionInitData
|
||||
);
|
||||
|
||||
return (
|
||||
<Box h={'100%'} overflow={'overlay'}>
|
||||
@@ -188,50 +175,6 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
{feConfigs?.show_userDetail && (
|
||||
<Card px={6} py={4}>
|
||||
<Box fontSize={'xl'} fontWeight={'bold'}>
|
||||
我的邀请
|
||||
</Box>
|
||||
{[
|
||||
{ label: '佣金比例', value: `${userInfo?.promotion.rate || 15}%` },
|
||||
{ label: '已注册用户数', value: `${invitedAmount}人` },
|
||||
{ label: '可用佣金', value: `¥${residueAmount}` }
|
||||
].map((item) => (
|
||||
<Flex
|
||||
key={item.label}
|
||||
alignItems={'center'}
|
||||
mt={4}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Box w={'120px'}>{item.label}</Box>
|
||||
<Box fontWeight={'bold'}>{item.value}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
<Button
|
||||
mt={4}
|
||||
variant={'base'}
|
||||
w={'100%'}
|
||||
onClick={() =>
|
||||
copyData(`${location.origin}/?inviterId=${userInfo?._id}`, '已复制邀请链接')
|
||||
}
|
||||
>
|
||||
复制邀请链接
|
||||
</Button>
|
||||
<Button
|
||||
mt={4}
|
||||
leftIcon={<MyIcon name="withdraw" w={'22px'} />}
|
||||
px={4}
|
||||
title={residueAmount < 50 ? '最低提现额度为50元' : ''}
|
||||
isDisabled={residueAmount < 50}
|
||||
variant={'base'}
|
||||
colorScheme={'myBlue'}
|
||||
onClick={onOpenWxConcat}
|
||||
>
|
||||
{residueAmount < 50 ? '50元起提' : '提现'}
|
||||
</Button>
|
||||
</Card>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{feConfigs?.show_userDetail && (
|
||||
@@ -255,7 +198,6 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
||||
)}
|
||||
</Box>
|
||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||
{isOpenWxConcat && <WxConcat onClose={onCloseWxConcat} />}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
|
@@ -1,36 +0,0 @@
|
||||
import { promotionRecord } from '../mongo';
|
||||
|
||||
export const pushPromotionRecord = async ({
|
||||
userId,
|
||||
objUId,
|
||||
type,
|
||||
amount
|
||||
}: {
|
||||
userId: string;
|
||||
objUId: string;
|
||||
type: 'invite' | 'shareModel';
|
||||
amount: number;
|
||||
}) => {
|
||||
try {
|
||||
await promotionRecord.create({
|
||||
userId,
|
||||
objUId,
|
||||
type,
|
||||
amount
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('创建推广记录异常', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const withdrawRecord = async ({ userId, amount }: { userId: string; amount: number }) => {
|
||||
try {
|
||||
await promotionRecord.create({
|
||||
userId,
|
||||
type: 'withdraw',
|
||||
amount
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('提现记录异常', error);
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user