活动页

dev-v2
Joe 2 months ago
parent 26f4721989
commit 2295f9ffbf

@ -332,6 +332,7 @@ export default [
name: '广告设置', name: '广告设置',
path: '/ad', path: '/ad',
icon: 'ad', icon: 'ad',
access: 'adScreen',
routes: [ routes: [
{ {
name: '开屏广告', name: '开屏广告',
@ -385,6 +386,32 @@ export default [
}, },
], ],
}, },
{
name: '活动管理',
path: '/activity',
icon: 'GiftOutlined',
// access: 'activityQuery',
routes: [
{
path: 'statistics',
name: '活动统计',
// access: 'activityStatisticsQuery',
component: './Activity/statistics',
},
{
path: 'sign-in',
name: '打卡签到活动',
// access: 'activitySignInQuery',
component: './Activity/sign-in',
},
{
path: 'lottery',
name: '抽奖明细查看',
// access: 'activityLotteryQuery',
component: './Activity/lottery',
},
],
},
{ path: '/' }, { path: '/' },
{ path: '*', layout: false, component: './404' }, { path: '*', layout: false, component: './404' },
]; ];

@ -77,6 +77,10 @@ export default function access(initialState: { currentUser?: API.PbcUsersVO | un
trainingClasses: false, trainingClasses: false,
trainingClassesQuery: false, trainingClassesQuery: false,
trainingClassesCategoryQuery: false, trainingClassesCategoryQuery: false,
activityQuery: false,
activityStatisticsQuery: false,
activitySignInQuery: false,
activityLotteryQuery: false,
}; };
for (let i = 0; i < currentUser?.currentAuthority.length; i++) { for (let i = 0; i < currentUser?.currentAuthority.length; i++) {
const element = currentUser?.currentAuthority[i]; const element = currentUser?.currentAuthority[i];
@ -99,6 +103,13 @@ export default function access(initialState: { currentUser?: API.PbcUsersVO | un
params.trainingClasses = params.trainingClasses =
params.trainingClassesQuery || params.trainingClassesQuery ||
params.trainingClassesCategoryQuery; params.trainingClassesCategoryQuery;
params.adScreen =
params.adScreenQuery ||
params.adBannerQuery;
params.activityQuery =
params.activityStatisticsQuery ||
params.activitySignInQuery ||
params.activityLotteryQuery;
return params; return params;
} }
return {}; return {};

@ -0,0 +1,292 @@
import React, { useEffect, useRef, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
import { ProTable } from '@ant-design/pro-components';
import { Button, Card, message } from 'antd';
import Constants from '@/constants';
import {
exportPageByAgentUsingPost,
getActivityPageByAgentUsingPost,
getSignActivityPageUsingPost,
} from '@/services/pop-b2b2c/pbcActivityController';
type QueryParams = API.PbcActivityActionRecordDTO;
const actionTypeText: Record<number, string> = {
4: '打卡签到',
5: '下单活动',
};
const buildParams = (params: Record<string, any>): QueryParams => {
const { current, pageSize, orderTimeRange, orderFinishTimeRange, ...rest } = params;
const payload: QueryParams = {
...rest,
current,
pageSize,
pbcActionType: 5,
};
if (Array.isArray(orderTimeRange) && orderTimeRange.length === 2) {
payload.pbcActionStartTime = `${orderTimeRange[0]} 00:00:00`;
payload.pbcActionEndTime = `${orderTimeRange[1]} 23:59:59`;
}
if (Array.isArray(orderFinishTimeRange) && orderFinishTimeRange.length === 2) {
payload.pbcOrderFinishStartTime = `${orderFinishTimeRange[0]} 00:00:00`;
payload.pbcOrderFinishEndTime = `${orderFinishTimeRange[1]} 23:59:59`;
}
return payload;
};
const LotteryDetail: React.FC = () => {
const summaryActionRef = useRef<ActionType>();
const detailActionRef = useRef<ActionType>();
const summaryFormRef = useRef<ProFormInstance<QueryParams>>();
const [selectedAgent, setSelectedAgent] = useState<API.PurchaseAgentStatsVO>();
const filtersRef = useRef<QueryParams>({});
const summaryColumns: ProColumns<API.PurchaseAgentStatsVO>[] = [
{
title: '#',
dataIndex: 'index',
valueType: 'indexBorder',
width: 60,
search: false,
},
{
title: '采购员',
dataIndex: 'pbcPurchaseAgentName',
},
{
title: '抽奖总次数',
dataIndex: 'totalCount',
search: false,
renderText: (value) => `${value ?? 0}`,
},
{
title: '剩余次数',
dataIndex: 'notPrizeCount',
search: false,
renderText: (value) => `${value ?? 0}`,
},
{
title: '操作',
valueType: 'option',
search: false,
render: (_, record) => [
<a
key="view"
onClick={() => {
setSelectedAgent(record);
}}
>
</a>,
],
},
{
title: '商家名称',
dataIndex: 'pbcBusinessName',
hideInTable: true,
},
{
title: '订单状态',
dataIndex: 'pbcOrderState',
valueType: 'select',
hideInTable: true,
valueEnum: {
1: { text: Constants.orderStateEnum['1']?.text || '预订单' },
2: { text: Constants.orderStateEnum['2']?.text || '已配货' },
3: { text: Constants.orderStateEnum['3']?.text || '已取消' },
4: { text: Constants.orderStateEnum['4']?.text || '已完成' },
},
},
{
title: '下单时间',
dataIndex: 'orderTimeRange',
valueType: 'dateRange',
hideInTable: true,
},
{
title: '下单完成时间',
dataIndex: 'orderFinishTimeRange',
valueType: 'dateRange',
hideInTable: true,
},
];
const detailColumns: ProColumns<API.PbcActivityActionRecord_>[] = [
{
title: '#',
dataIndex: 'index',
valueType: 'indexBorder',
width: 60,
search: false,
},
{
title: '采购员',
dataIndex: 'pbcPurchaseAgentName',
search: false,
},
{
title: '抽奖类型',
dataIndex: 'pbcActionType',
search: false,
renderText: (value) => actionTypeText[value as number] || '其它',
},
{
title: '抽奖次数',
dataIndex: 'pbcId',
search: false,
render: () => '1次',
},
{
title: '操作时间',
dataIndex: 'pbcActionTime',
valueType: 'dateTime',
search: false,
},
{
title: '操作',
valueType: 'option',
search: false,
render: () => [
<a
key="detail"
onClick={() => {
message.info('查看功能即将上线');
}}
>
</a>,
],
},
];
const handleExport = async () => {
const values = summaryFormRef.current?.getFieldsValue() || {};
const payload = buildParams({ ...values, current: 1, pageSize: 9999 });
const hide = message.loading('正在处理', 0);
try {
const res = await exportPageByAgentUsingPost(payload);
hide();
if (res.retcode && typeof res.data === 'string' && res.data) {
window.open(res.data);
} else {
message.error(res.retmsg || '导出失败,请重试');
}
} catch (error) {
hide();
message.error('导出失败,请重试');
}
};
const summaryRequest = async (params: Record<string, any>) => {
const payload = buildParams(params);
filtersRef.current = { ...payload, current: undefined, pageSize: undefined };
const res = await getActivityPageByAgentUsingPost(payload);
const records = res.data?.records || [];
if (records.length) {
setSelectedAgent((prev) => {
if (!prev) {
return records[0];
}
const exists = records.find(
(item) => item.pbcPurchaseAgentId === prev.pbcPurchaseAgentId,
);
return exists ? prev : records[0];
});
} else {
setSelectedAgent(undefined);
}
return {
data: records,
total: res.data?.total || 0,
success: Boolean(res.retcode),
};
};
const detailRequest = async (params: Record<string, any>) => {
if (!selectedAgent) {
return {
data: [],
total: 0,
success: true,
};
}
const payload: QueryParams = {
...filtersRef.current,
...params,
pbcPurchaseAgentId: selectedAgent.pbcPurchaseAgentId,
pbcPurchaseAgentName: selectedAgent.pbcPurchaseAgentName,
};
const res = await getSignActivityPageUsingPost(payload);
return {
data: res.data?.records || [],
total: res.data?.total || 0,
success: Boolean(res.retcode),
};
};
useEffect(() => {
detailActionRef.current?.reload();
}, [selectedAgent]);
return (
<PageContainer header={{ title: '' }}>
<Card bordered={false}>
<ProTable<API.PurchaseAgentStatsVO>
actionRef={summaryActionRef}
formRef={summaryFormRef}
columns={summaryColumns}
rowKey="pbcPurchaseAgentId"
request={summaryRequest}
bordered
size="small"
search={{
labelWidth: 'auto',
span: 6,
}}
pagination={{
defaultPageSize: 10,
showSizeChanger: true,
}}
dateFormatter="string"
scroll={{
y: 'calc(50vh - 200px)',
}}
options={false}
toolBarRender={() => [
<Button key="export" type="primary" onClick={handleExport}>
</Button>,
]}
/>
</Card>
<Card bordered={false} style={{ marginTop: 24 }} title="抽奖明细">
<ProTable<API.PbcActivityActionRecord_>
actionRef={detailActionRef}
columns={detailColumns}
rowKey="pbcId"
request={detailRequest}
bordered
size="small"
search={false}
pagination={{
defaultPageSize: 10,
showSizeChanger: true,
}}
dateFormatter="string"
options={false}
scroll={{
y: 'calc(50vh - 200px)',
}}
/>
</Card>
</PageContainer>
);
};
export default LotteryDetail;

@ -0,0 +1,132 @@
import React, { useRef } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
import { ProTable } from '@ant-design/pro-components';
import { Button, message } from 'antd';
import {
exportPageByAgentUsingPost,
getSignActivityPageUsingPost,
} from '@/services/pop-b2b2c/pbcActivityController';
type QueryParams = API.PbcActivityActionRecordDTO;
const formatParams = (params: Record<string, any>): QueryParams => {
const { current, pageSize, pbcActionTimeRange, ...rest } = params;
let start: string | undefined;
let end: string | undefined;
if (Array.isArray(pbcActionTimeRange) && pbcActionTimeRange.length === 2) {
start = `${pbcActionTimeRange[0]} 00:00:00`;
end = `${pbcActionTimeRange[1]} 23:59:59`;
}
return {
...rest,
current,
pageSize,
pbcActionStartTime: start,
pbcActionEndTime: end,
pbcActionType: 4,
};
};
const SignInActivity: React.FC = () => {
const actionRef = useRef<ActionType>();
const formRef = useRef<ProFormInstance<QueryParams>>();
const columns: ProColumns<API.PbcActivityActionRecord_>[] = [
{
title: '#',
dataIndex: 'index',
valueType: 'indexBorder',
width: 60,
search: false,
},
{
title: '抽奖编码',
dataIndex: 'pbcOrderNo',
},
{
title: '采购员',
dataIndex: 'pbcPurchaseAgentName',
},
{
title: '商家名称',
dataIndex: 'pbcBusinessName',
},
{
title: '打卡时间',
dataIndex: 'pbcActionTime',
valueType: 'dateTime',
search: false,
},
{
title: '打卡时间',
dataIndex: 'pbcActionTimeRange',
valueType: 'dateRange',
hideInTable: true,
},
];
const request = async (params: Record<string, any>) => {
const payload = formatParams(params);
const res = await getSignActivityPageUsingPost(payload);
return {
data: res.data?.records || [],
total: res.data?.total || 0,
success: Boolean(res.retcode),
};
};
const handleExport = async () => {
const values = formRef.current?.getFieldsValue() || {};
const payload = formatParams({ ...values, current: 1, pageSize: 9999 });
const hide = message.loading('正在处理', 0);
try {
const res = await exportPageByAgentUsingPost(payload);
hide();
if (res.retcode && typeof res.data === 'string' && res.data) {
window.open(res.data);
} else {
message.error(res.retmsg || '导出失败,请重试');
}
} catch (error) {
hide();
message.error('导出失败,请重试');
}
};
return (
<PageContainer header={{ title: '' }}>
<ProTable<API.PbcActivityActionRecord_>
actionRef={actionRef}
formRef={formRef}
columns={columns}
rowKey="pbcId"
request={request}
bordered
size="small"
search={{
labelWidth: 'auto',
span: 6,
}}
pagination={{
defaultPageSize: 10,
showSizeChanger: true,
}}
scroll={{
y: 'calc(100vh - 320px)',
}}
options={false}
dateFormatter="string"
toolBarRender={() => [
<Button key="export" type="primary" onClick={handleExport}>
</Button>,
]}
/>
</PageContainer>
);
};
export default SignInActivity;

@ -0,0 +1,331 @@
import React, { useMemo, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Card, Col, Row, Segmented, Typography } from 'antd';
import type { PieConfig } from '@ant-design/plots';
import { Pie } from '@ant-design/plots';
const { Title, Paragraph, Text } = Typography;
type RangeKey = 'day' | 'month' | 'all';
type ChartDatum = {
label: string;
value: number;
};
type DataMap = Record<RangeKey, ChartDatum[]>;
const signMerchantData: DataMap = {
day: [
{ label: 'A商家', value: 35 },
{ label: 'B商家', value: 25 },
{ label: 'C商家', value: 18 },
{ label: 'D商家', value: 12 },
{ label: 'E商家', value: 10 },
{ label: 'F商家', value: 8 },
{ label: 'G商家', value: 6 },
{ label: 'H商家', value: 5 },
{ label: 'I商家', value: 4 },
{ label: 'J商家', value: 3 },
],
month: [
{ label: 'A商家', value: 260 },
{ label: 'B商家', value: 200 },
{ label: 'C商家', value: 180 },
{ label: 'D商家', value: 140 },
{ label: 'E商家', value: 120 },
{ label: 'F商家', value: 100 },
{ label: 'G商家', value: 80 },
{ label: 'H商家', value: 60 },
{ label: 'I商家', value: 45 },
{ label: 'J商家', value: 30 },
],
all: [],
};
const signAgentData: DataMap = {
day: [
{ label: 'A采购员', value: 40 },
{ label: 'B采购员', value: 30 },
{ label: 'C采购员', value: 20 },
{ label: 'D采购员', value: 15 },
{ label: 'E采购员', value: 12 },
{ label: 'F采购员', value: 10 },
{ label: 'G采购员', value: 8 },
{ label: 'H采购员', value: 6 },
{ label: 'I采购员', value: 5 },
{ label: 'J采购员', value: 4 },
],
month: [
{ label: 'A采购员', value: 320 },
{ label: 'B采购员', value: 230 },
{ label: 'C采购员', value: 180 },
{ label: 'D采购员', value: 150 },
{ label: 'E采购员', value: 130 },
{ label: 'F采购员', value: 110 },
{ label: 'G采购员', value: 95 },
{ label: 'H采购员', value: 80 },
{ label: 'I采购员', value: 70 },
{ label: 'J采购员', value: 55 },
],
all: [],
};
const orderMerchantData: DataMap = {
day: [
{ label: 'A商家', value: 30 },
{ label: 'B商家', value: 27 },
{ label: 'C商家', value: 19 },
{ label: 'D商家', value: 14 },
{ label: 'E商家', value: 12 },
{ label: 'F商家', value: 9 },
{ label: 'G商家', value: 7 },
{ label: 'H商家', value: 6 },
{ label: 'I商家', value: 5 },
{ label: 'J商家', value: 3 },
],
month: [
{ label: 'A商家', value: 280 },
{ label: 'B商家', value: 230 },
{ label: 'C商家', value: 200 },
{ label: 'D商家', value: 180 },
{ label: 'E商家', value: 150 },
{ label: 'F商家', value: 120 },
{ label: 'G商家', value: 100 },
{ label: 'H商家', value: 85 },
{ label: 'I商家', value: 70 },
{ label: 'J商家', value: 55 },
],
all: [
{ label: 'A商家', value: 680 },
{ label: 'B商家', value: 620 },
{ label: 'C商家', value: 550 },
{ label: 'D商家', value: 480 },
{ label: 'E商家', value: 440 },
{ label: 'F商家', value: 360 },
{ label: 'G商家', value: 310 },
{ label: 'H商家', value: 290 },
{ label: 'I商家', value: 240 },
{ label: 'J商家', value: 200 },
],
};
const orderAgentData: DataMap = {
day: [
{ label: 'A采购员', value: 28 },
{ label: 'B采购员', value: 24 },
{ label: 'C采购员', value: 20 },
{ label: 'D采购员', value: 16 },
{ label: 'E采购员', value: 12 },
{ label: 'F采购员', value: 10 },
{ label: 'G采购员', value: 8 },
{ label: 'H采购员', value: 6 },
{ label: 'I采购员', value: 5 },
{ label: 'J采购员', value: 4 },
],
month: [
{ label: 'A采购员', value: 260 },
{ label: 'B采购员', value: 240 },
{ label: 'C采购员', value: 220 },
{ label: 'D采购员', value: 180 },
{ label: 'E采购员', value: 160 },
{ label: 'F采购员', value: 140 },
{ label: 'G采购员', value: 120 },
{ label: 'H采购员', value: 95 },
{ label: 'I采购员', value: 80 },
{ label: 'J采购员', value: 70 },
],
all: [
{ label: 'A采购员', value: 620 },
{ label: 'B采购员', value: 580 },
{ label: 'C采购员', value: 520 },
{ label: 'D采购员', value: 480 },
{ label: 'E采购员', value: 430 },
{ label: 'F采购员', value: 360 },
{ label: 'G采购员', value: 320 },
{ label: 'H采购员', value: 280 },
{ label: 'I采购员', value: 240 },
{ label: 'J采购员', value: 220 },
],
};
const buildPieConfig = (data: ChartDatum[]): PieConfig => ({
data,
angleField: 'value',
colorField: 'label',
radius: 0.8,
legend: false,
label: {
type: 'inner',
offset: '-30%',
content: ({ percent }) => `${Math.round(Number(percent) * 100)}%`,
style: {
fontSize: 12,
},
},
statistic: {
title: false,
content: {
formatter: () => 'Top 10',
style: {
fontSize: '16px',
},
},
},
});
const renderRanking = (data: ChartDatum[]) => {
const total = data.reduce((sum, item) => sum + item.value, 0);
return (
<div style={{ paddingLeft: 16 }}>
{data.map((item, index) => {
const percent = total ? Math.round((item.value / total) * 100) : 0;
return (
<Paragraph key={item.label} style={{ marginBottom: 8 }}>
<Text strong style={{ marginRight: 8 }}>
{index + 1}
</Text>
<Text style={{ marginRight: 8 }}>{item.label}</Text>
<Text type="secondary">
{item.value} | {percent}%
</Text>
</Paragraph>
);
})}
</div>
);
};
const ActivityStatistics: React.FC = () => {
const [signRange, setSignRange] = useState<RangeKey>('day');
const [signAgentRange, setSignAgentRange] = useState<RangeKey>('day');
const [orderRange, setOrderRange] = useState<RangeKey>('day');
const [orderAgentRange, setOrderAgentRange] = useState<RangeKey>('day');
const signMerchant = useMemo(() => signMerchantData[signRange], [signRange]);
const signAgent = useMemo(() => signAgentData[signAgentRange], [signAgentRange]);
const orderMerchant = useMemo(() => orderMerchantData[orderRange], [orderRange]);
const orderAgent = useMemo(() => orderAgentData[orderAgentRange], [orderAgentRange]);
return (
<PageContainer header={{ title: '' }}>
<Card bordered={false} style={{ marginBottom: 24 }}>
<Row justify="space-between" align="middle" style={{ marginBottom: 16 }}>
<Col>
<Title level={4} style={{ margin: 0 }}>
</Title>
</Col>
</Row>
<Row gutter={24} style={{ marginBottom: 24 }}>
<Col span={12}>
<Card
title="商家打卡前10统计"
extra={
<Segmented
options={[{ label: '当日', value: 'day' }, { label: '当月', value: 'month' }]}
value={signRange}
onChange={(value) => setSignRange(value as RangeKey)}
size="small"
/>
}
bordered={false}
>
<Row gutter={16}>
<Col span={12}>
<Pie {...buildPieConfig(signMerchant)} />
</Col>
<Col span={12}>{renderRanking(signMerchant)}</Col>
</Row>
</Card>
</Col>
<Col span={12}>
<Card
title="采购员打卡前10统计"
extra={
<Segmented
options={[{ label: '当日', value: 'day' }, { label: '当月', value: 'month' }]}
value={signAgentRange}
onChange={(value) => setSignAgentRange(value as RangeKey)}
size="small"
/>
}
bordered={false}
>
<Row gutter={16}>
<Col span={12}>
<Pie {...buildPieConfig(signAgent)} />
</Col>
<Col span={12}>{renderRanking(signAgent)}</Col>
</Row>
</Card>
</Col>
</Row>
</Card>
<Card bordered={false}>
<Row justify="space-between" align="middle" style={{ marginBottom: 16 }}>
<Col>
<Title level={4} style={{ margin: 0 }}>
</Title>
</Col>
</Row>
<Row gutter={24} style={{ marginBottom: 24 }}>
<Col span={12}>
<Card
title="商家下单前10统计"
extra={
<Segmented
options={[
{ label: '当日', value: 'day' },
{ label: '当月', value: 'month' },
{ label: '全部', value: 'all' },
]}
value={orderRange}
onChange={(value) => setOrderRange(value as RangeKey)}
size="small"
/>
}
bordered={false}
>
<Row gutter={16}>
<Col span={12}>
<Pie {...buildPieConfig(orderMerchant)} />
</Col>
<Col span={12}>{renderRanking(orderMerchant)}</Col>
</Row>
</Card>
</Col>
<Col span={12}>
<Card
title="采购员下单前10统计"
extra={
<Segmented
options={[
{ label: '当日', value: 'day' },
{ label: '当月', value: 'month' },
{ label: '全部', value: 'all' },
]}
value={orderAgentRange}
onChange={(value) => setOrderAgentRange(value as RangeKey)}
size="small"
/>
}
bordered={false}
>
<Row gutter={16}>
<Col span={12}>
<Pie {...buildPieConfig(orderAgent)} />
</Col>
<Col span={12}>{renderRanking(orderAgent)}</Col>
</Row>
</Card>
</Col>
</Row>
</Card>
</PageContainer>
);
};
export default ActivityStatistics;
Loading…
Cancel
Save