perf: promotion
This commit is contained in:
@@ -14,14 +14,6 @@ export const sendAuthCode = (data: {
|
|||||||
|
|
||||||
export const getTokenLogin = () => GET<UserType>('/user/tokenLogin');
|
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 = ({
|
export const postRegister = ({
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
@@ -78,10 +70,6 @@ export const getPayCode = (amount: number) =>
|
|||||||
|
|
||||||
export const checkPayResult = (payId: string) => GET<number>(`/user/checkPayResult?payId=${payId}`);
|
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) =>
|
export const getInforms = (data: RequestPaging) =>
|
||||||
POST<PagingData<informSchema>>(`/user/inform/list`, data);
|
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 { PaySchema } from '@/types/mongoSchema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getPayResult } from '@/service/utils/wxpay';
|
import { getPayResult } from '@/service/utils/wxpay';
|
||||||
import { pushPromotionRecord } from '@/service/utils/promotion';
|
|
||||||
import { PRICE_SCALE } from '@/constants/common';
|
|
||||||
import { startQueue } from '@/service/utils/tools';
|
import { startQueue } from '@/service/utils/tools';
|
||||||
|
|
||||||
/* 校验支付结果 */
|
/* 校验支付结果 */
|
||||||
@@ -33,13 +31,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('找不到用户');
|
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);
|
const payRes = await getPayResult(payOrder.orderId);
|
||||||
|
|
||||||
@@ -65,16 +56,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
await User.findByIdAndUpdate(userId, {
|
await User.findByIdAndUpdate(userId, {
|
||||||
$inc: { balance: payOrder.price }
|
$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);
|
unlockTask(userId);
|
||||||
return jsonRes(res, {
|
return jsonRes(res, {
|
||||||
data: '支付成功'
|
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 { Card, Box, Flex, Button, Grid, useDisclosure } from '@chakra-ui/react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { UserUpdateParams } from '@/types/user';
|
import { UserUpdateParams } from '@/types/user';
|
||||||
import { putUserInfo, getPromotionInitData } from '@/api/user';
|
import { putUserInfo } from '@/api/user';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
@@ -13,7 +13,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useSelectFile } from '@/hooks/useSelectFile';
|
import { useSelectFile } from '@/hooks/useSelectFile';
|
||||||
import { compressImg } from '@/utils/file';
|
import { compressImg } from '@/utils/file';
|
||||||
import { getErrText, useCopyData } from '@/utils/tools';
|
import { getErrText } from '@/utils/tools';
|
||||||
import { feConfigs } from '@/store/static';
|
import { feConfigs } from '@/store/static';
|
||||||
|
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
@@ -25,9 +25,7 @@ import BillTable from './components/BillTable';
|
|||||||
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'), {
|
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'), {
|
||||||
ssr: false
|
ssr: false
|
||||||
});
|
});
|
||||||
const PromotionTable = dynamic(() => import('./components/PromotionTable'), {
|
|
||||||
ssr: false
|
|
||||||
});
|
|
||||||
const InformTable = dynamic(() => import('./components/InformTable'), {
|
const InformTable = dynamic(() => import('./components/InformTable'), {
|
||||||
ssr: false
|
ssr: false
|
||||||
});
|
});
|
||||||
@@ -51,12 +49,10 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
|||||||
const tableList = useRef([
|
const tableList = useRef([
|
||||||
{ label: '账单', id: TableEnum.bill, Component: <BillTable /> },
|
{ label: '账单', id: TableEnum.bill, Component: <BillTable /> },
|
||||||
{ label: '充值', id: TableEnum.pay, Component: <PayRecordTable /> },
|
{ label: '充值', id: TableEnum.pay, Component: <PayRecordTable /> },
|
||||||
{ label: '佣金', id: TableEnum.promotion, Component: <PromotionTable /> },
|
|
||||||
{ label: '通知', id: TableEnum.inform, Component: <InformTable /> }
|
{ label: '通知', id: TableEnum.inform, Component: <InformTable /> }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { copyData } = useCopyData();
|
|
||||||
const { userInfo, updateUserInfo, initUserInfo, setUserInfo } = useUserStore();
|
const { userInfo, updateUserInfo, initUserInfo, setUserInfo } = useUserStore();
|
||||||
const { setLoading } = useGlobalStore();
|
const { setLoading } = useGlobalStore();
|
||||||
const { reset } = useForm<UserUpdateParams>({
|
const { reset } = useForm<UserUpdateParams>({
|
||||||
@@ -68,11 +64,6 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
|||||||
onClose: onClosePayModal,
|
onClose: onClosePayModal,
|
||||||
onOpen: onOpenPayModal
|
onOpen: onOpenPayModal
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
const {
|
|
||||||
isOpen: isOpenWxConcat,
|
|
||||||
onClose: onCloseWxConcat,
|
|
||||||
onOpen: onOpenWxConcat
|
|
||||||
} = useDisclosure();
|
|
||||||
|
|
||||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||||
fileType: '.jpg,.png',
|
fileType: '.jpg,.png',
|
||||||
@@ -141,10 +132,6 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
|||||||
reset(res);
|
reset(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const { data: { invitedAmount = 0, historyAmount = 0, residueAmount = 0 } = {} } = useQuery(
|
|
||||||
['getPromotionInitData'],
|
|
||||||
getPromotionInitData
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box h={'100%'} overflow={'overlay'}>
|
<Box h={'100%'} overflow={'overlay'}>
|
||||||
@@ -188,50 +175,6 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</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>
|
</Grid>
|
||||||
|
|
||||||
{feConfigs?.show_userDetail && (
|
{feConfigs?.show_userDetail && (
|
||||||
@@ -255,7 +198,6 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||||
{isOpenWxConcat && <WxConcat onClose={onCloseWxConcat} />}
|
|
||||||
<File onSelect={onSelectFile} />
|
<File onSelect={onSelectFile} />
|
||||||
</Box>
|
</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