From 2295f9ffbf6707ff19856f1cb31c4d1d924d6862 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 29 Oct 2025 17:33:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.ts | 27 ++ src/access.ts | 11 + src/pages/Activity/lottery/index.tsx | 292 +++++++++++++++++++++ src/pages/Activity/sign-in/index.tsx | 132 ++++++++++ src/pages/Activity/statistics/index.tsx | 331 ++++++++++++++++++++++++ 5 files changed, 793 insertions(+) create mode 100644 src/pages/Activity/lottery/index.tsx create mode 100644 src/pages/Activity/sign-in/index.tsx create mode 100644 src/pages/Activity/statistics/index.tsx diff --git a/config/routes.ts b/config/routes.ts index bff9923..34a61ab 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -332,6 +332,7 @@ export default [ name: '广告设置', path: '/ad', icon: 'ad', + access: 'adScreen', routes: [ { 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: '*', layout: false, component: './404' }, ]; diff --git a/src/access.ts b/src/access.ts index f72de5c..87a91fa 100644 --- a/src/access.ts +++ b/src/access.ts @@ -77,6 +77,10 @@ export default function access(initialState: { currentUser?: API.PbcUsersVO | un trainingClasses: false, trainingClassesQuery: false, trainingClassesCategoryQuery: false, + activityQuery: false, + activityStatisticsQuery: false, + activitySignInQuery: false, + activityLotteryQuery: false, }; for (let i = 0; i < currentUser?.currentAuthority.length; i++) { const element = currentUser?.currentAuthority[i]; @@ -99,6 +103,13 @@ export default function access(initialState: { currentUser?: API.PbcUsersVO | un params.trainingClasses = params.trainingClassesQuery || params.trainingClassesCategoryQuery; + params.adScreen = + params.adScreenQuery || + params.adBannerQuery; + params.activityQuery = + params.activityStatisticsQuery || + params.activitySignInQuery || + params.activityLotteryQuery; return params; } return {}; diff --git a/src/pages/Activity/lottery/index.tsx b/src/pages/Activity/lottery/index.tsx new file mode 100644 index 0000000..d20c596 --- /dev/null +++ b/src/pages/Activity/lottery/index.tsx @@ -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 = { + 4: '打卡签到', + 5: '下单活动', +}; + +const buildParams = (params: Record): 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(); + const detailActionRef = useRef(); + const summaryFormRef = useRef>(); + const [selectedAgent, setSelectedAgent] = useState(); + const filtersRef = useRef({}); + + const summaryColumns: ProColumns[] = [ + { + 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) => [ + { + setSelectedAgent(record); + }} + > + 查看 + , + ], + }, + { + 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[] = [ + { + 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: () => [ + { + message.info('查看功能即将上线'); + }} + > + 查看 + , + ], + }, + ]; + + 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) => { + 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) => { + 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 ( + + + + 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={() => [ + , + ]} + /> + + + + + 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)', + }} + /> + + + ); +}; + +export default LotteryDetail; diff --git a/src/pages/Activity/sign-in/index.tsx b/src/pages/Activity/sign-in/index.tsx new file mode 100644 index 0000000..e706ed0 --- /dev/null +++ b/src/pages/Activity/sign-in/index.tsx @@ -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): 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(); + const formRef = useRef>(); + + const columns: ProColumns[] = [ + { + 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) => { + 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 ( + + + 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={() => [ + , + ]} + /> + + ); +}; + +export default SignInActivity; diff --git a/src/pages/Activity/statistics/index.tsx b/src/pages/Activity/statistics/index.tsx new file mode 100644 index 0000000..1eebc0f --- /dev/null +++ b/src/pages/Activity/statistics/index.tsx @@ -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; + +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 ( +
+ {data.map((item, index) => { + const percent = total ? Math.round((item.value / total) * 100) : 0; + return ( + + + {index + 1} + + {item.label} + + {item.value}次 | {percent}% + + + ); + })} +
+ ); +}; + +const ActivityStatistics: React.FC = () => { + const [signRange, setSignRange] = useState('day'); + const [signAgentRange, setSignAgentRange] = useState('day'); + const [orderRange, setOrderRange] = useState('day'); + const [orderAgentRange, setOrderAgentRange] = useState('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 ( + + + + + + 打卡签到活动 + + + + + + setSignRange(value as RangeKey)} + size="small" + /> + } + bordered={false} + > + + + + + {renderRanking(signMerchant)} + + + + + setSignAgentRange(value as RangeKey)} + size="small" + /> + } + bordered={false} + > + + + + + {renderRanking(signAgent)} + + + + + + + + + + + 下单活动 + + + + + + setOrderRange(value as RangeKey)} + size="small" + /> + } + bordered={false} + > + + + + + {renderRanking(orderMerchant)} + + + + + setOrderAgentRange(value as RangeKey)} + size="small" + /> + } + bordered={false} + > + + + + + {renderRanking(orderAgent)} + + + + + + + ); +}; + +export default ActivityStatistics;