Browse Source

feat: 代理后台功能

lihao
nili 3 months ago
parent
commit
5a16f576d5
  1. 10
      config/routes.ts
  2. 1
      dist/_umi_route_preload_helper.4168bea5.js
  3. 1
      dist/_umi_route_preload_helper.d67da88f.js
  4. 4
      dist/index.html
  5. 1
      dist/p__AdvRecordListV2.516b9678.async.js
  6. 1
      dist/p__AdvRecordListV2.77bc346f.async.js
  7. 1
      dist/p__DeviceOwnerApp.c61abcd9.async.js
  8. 1
      dist/p__DeviceOwnerApp.ec53e5c4.async.js
  9. 1
      dist/p__Proxy__ProxyAdvRecordListV2.d373f52a.async.js
  10. 1
      dist/p__Proxy__ProxyUserManagement.7f03f17a.async.js
  11. 110
      dist/umi.6d712b5d.js
  12. 22
      src/app.tsx
  13. 8
      src/pages/AdvRecordListV2.tsx
  14. 15
      src/pages/DeviceOwnerApp.tsx
  15. 2
      src/pages/OSSUploadDragger.tsx
  16. 336
      src/pages/Proxy/ProxyAdvRecordListV2.tsx
  17. 112
      src/pages/Proxy/ProxyUserManagement.tsx
  18. 1
      src/services/matrix/typings.d.ts

10
config/routes.ts

@ -86,6 +86,16 @@ export default [
name: '应用管理',
component: './App/AppManagement',
},
{
name: '广告数据',
path: '/proxy/advListV2/:code',
component: './Proxy/ProxyAdvRecordListV2',
},
{
name: '用户列表',
path: '/proxy/user/:code',
component: './Proxy/ProxyUserManagement',
},
{
path: '/',
redirect: '/welcome',

1
dist/_umi_route_preload_helper.4168bea5.js

@ -0,0 +1 @@
!function(){"use strict";var t="/".replace(/([^/])$/,"$1/"),e=location.pathname,n=e.startsWith(t)&&decodeURI("/".concat(e.slice(t.length)));if(n){var a=document,c=a.head,r=a.createElement.bind(a),i=function(t,e,n){var a,c=e.r[t]||(null===(a=Object.entries(e.r).find((function(e){var n=e[0];return new RegExp("^".concat(n.replace(/\/:[^/]+/g,"/[^/]+").replace("/*","/.+"),"$")).test(t)})))||void 0===a?void 0:a[1]);return null==c?void 0:c.map((function(t){var a=e.f[t][1],c=e.f[t][0];return{type:c.split(".").pop(),url:"".concat(n.publicPath).concat(c),attrs:[["data-".concat(e.b),"".concat(e.p,":").concat(a)]]}}))}(n,{"p":"ant-design-pro","b":"webpack","f":[["51.a2ea1b3b.async.js",51],["134.6fc81d91.async.js",134],["169.57647c7d.async.js",169],["p__Welcome.61596037.async.js",185],["p__MoneyManagement.7ad34f8d.async.js",190],["247.3fe8fb10.async.js",247],["t__plugin-layout__Layout.6cae69f5.chunk.css",301],["t__plugin-layout__Layout.4594a64b.async.js",301],["p__User__Login__index.9d3ab92e.async.js",366],["p__DeviceOwnerApp.c61abcd9.async.js",371],["390.41467286.async.js",390],["393.38316f72.async.js",393],["397.fb5f72c1.async.js",397],["p__SuperAdmin.92290426.async.js",455],["p__Proxy__ProxyUserManagement.7f03f17a.async.js",526],["531.4c68f902.async.js",531],["539.49b7b156.async.js",539],["p__AdvRecordListV2.77bc346f.async.js",542],["p__Bind.b6ee068f.async.js",557],["559.016bfdbb.async.js",559],["p__404.0c100574.async.js",571],["635.20e45f05.async.js",635],["p__App__AppManagement.7b88d7c4.async.js",731],["p__AdminManagement.16f4328b.async.js",816],["822.4ba8fa04.async.js",822],["841.614ef4c0.async.js",841],["p__AdvRecordList.fc092f59.async.js",857],["p__UserManagement.5e827801.async.js",903],["905.6e225d1b.async.js",905],["930.fca7adbf.async.js",930],["942.561ac28f.async.js",942],["p__Proxy__ProxyAdvRecordListV2.d373f52a.async.js",990]],"r":{"/*":[20,28],"/":[1,6,7,19,21,28],"/welcome":[1,2,3,11,19,29,6,7,21,28],"/super":[0,1,2,5,13,15,16,24,28,29,6,7,19,21],"/adminList":[0,1,2,23,24,30,6,7,19,21,28],"/bind":[1,2,11,12,18,19,29,6,7,21,28],"/appList":[0,1,2,5,12,15,16,22,24,28,29,6,7,19,21],"/advList/:code":[0,1,2,15,16,19,24,25,26,28,29,6,7,21],"/advListV2/:code":[0,1,2,15,16,17,19,24,25,28,29,6,7,21],"/user/:code":[0,1,2,15,16,24,27,28,29,6,7,19,21],"/money/:code":[0,1,2,4,15,16,24,28,29,6,7,19,21],"/app/:code":[0,1,2,9,15,16,19,24,25,28,29,6,7,21],"/user/login":[1,2,8,15,24],"/proxy/advListV2/:code":[0,1,2,15,16,19,24,25,28,29,31,6,7,21],"/proxy/user/:code":[0,1,2,14,15,16,24,28,29,6,7,19,21]}},{publicPath:"/"});null==i||i.forEach((function(t){var e,n=t.type,a=t.url;if("js"===n)(e=r("script")).src=a,e.async=!0;else{if("css"!==n)return;(e=r("link")).href=a,e.rel="preload",e.as="style"}t.attrs.forEach((function(t){e.setAttribute(t[0],t[1]||"")})),c.appendChild(e)}))}}();

1
dist/_umi_route_preload_helper.d67da88f.js

@ -1 +0,0 @@
!function(){"use strict";var t="/".replace(/([^/])$/,"$1/"),e=location.pathname,n=e.startsWith(t)&&decodeURI("/".concat(e.slice(t.length)));if(n){var a=document,c=a.head,r=a.createElement.bind(a),i=function(t,e,n){var a,c=e.r[t]||(null===(a=Object.entries(e.r).find((function(e){var n=e[0];return new RegExp("^".concat(n.replace(/\/:[^/]+/g,"/[^/]+").replace("/*","/.+"),"$")).test(t)})))||void 0===a?void 0:a[1]);return null==c?void 0:c.map((function(t){var a=e.f[t][1],c=e.f[t][0];return{type:c.split(".").pop(),url:"".concat(n.publicPath).concat(c),attrs:[["data-".concat(e.b),"".concat(e.p,":").concat(a)]]}}))}(n,{"p":"ant-design-pro","b":"webpack","f":[["51.a2ea1b3b.async.js",51],["134.6fc81d91.async.js",134],["169.57647c7d.async.js",169],["p__Welcome.61596037.async.js",185],["p__MoneyManagement.7ad34f8d.async.js",190],["247.3fe8fb10.async.js",247],["t__plugin-layout__Layout.6cae69f5.chunk.css",301],["t__plugin-layout__Layout.4594a64b.async.js",301],["p__User__Login__index.9d3ab92e.async.js",366],["p__DeviceOwnerApp.ec53e5c4.async.js",371],["390.41467286.async.js",390],["393.38316f72.async.js",393],["397.fb5f72c1.async.js",397],["p__SuperAdmin.92290426.async.js",455],["531.4c68f902.async.js",531],["539.49b7b156.async.js",539],["p__AdvRecordListV2.516b9678.async.js",542],["p__Bind.b6ee068f.async.js",557],["559.016bfdbb.async.js",559],["p__404.0c100574.async.js",571],["635.20e45f05.async.js",635],["p__App__AppManagement.7b88d7c4.async.js",731],["p__AdminManagement.16f4328b.async.js",816],["822.4ba8fa04.async.js",822],["841.614ef4c0.async.js",841],["p__AdvRecordList.fc092f59.async.js",857],["p__UserManagement.5e827801.async.js",903],["905.6e225d1b.async.js",905],["930.fca7adbf.async.js",930],["942.561ac28f.async.js",942]],"r":{"/*":[19,27],"/":[1,6,7,18,20,27],"/welcome":[1,2,3,11,18,28,6,7,20,27],"/super":[0,1,2,5,13,14,15,23,27,28,6,7,18,20],"/adminList":[0,1,2,22,23,29,6,7,18,20,27],"/bind":[1,2,11,12,17,18,28,6,7,20,27],"/appList":[0,1,2,5,12,14,15,21,23,27,28,6,7,18,20],"/advList/:code":[0,1,2,14,15,18,23,24,25,27,28,6,7,20],"/advListV2/:code":[0,1,2,14,15,16,18,23,24,27,28,6,7,20],"/user/:code":[0,1,2,14,15,23,26,27,28,6,7,18,20],"/money/:code":[0,1,2,4,14,15,23,27,28,6,7,18,20],"/app/:code":[0,1,2,9,14,15,18,23,24,27,28,6,7,20],"/user/login":[1,2,8,14,23]}},{publicPath:"/"});null==i||i.forEach((function(t){var e,n=t.type,a=t.url;if("js"===n)(e=r("script")).src=a,e.async=!0;else{if("css"!==n)return;(e=r("link")).href=a,e.rel="preload",e.as="style"}t.attrs.forEach((function(t){e.setAttribute(t[0],t[1]||"")})),c.appendChild(e)}))}}();

4
dist/index.html

@ -7,10 +7,10 @@
<title>Ant Design Pro</title>
<link rel="stylesheet" href="/umi.1ca9308c.css">
<script async src="/scripts/loading.js"></script>
<script src="/_umi_route_preload_helper.d67da88f.js"></script>
<script src="/_umi_route_preload_helper.4168bea5.js"></script>
</head>
<body>
<div id="root"></div>
<script src="/umi.21e3ba7b.js"></script>
<script src="/umi.6d712b5d.js"></script>
</body>
</html>

1
dist/p__AdvRecordListV2.516b9678.async.js

File diff suppressed because one or more lines are too long

1
dist/p__AdvRecordListV2.77bc346f.async.js

File diff suppressed because one or more lines are too long

1
dist/p__DeviceOwnerApp.c61abcd9.async.js

File diff suppressed because one or more lines are too long

1
dist/p__DeviceOwnerApp.ec53e5c4.async.js

File diff suppressed because one or more lines are too long

1
dist/p__Proxy__ProxyAdvRecordListV2.d373f52a.async.js

File diff suppressed because one or more lines are too long

1
dist/p__Proxy__ProxyUserManagement.7f03f17a.async.js

File diff suppressed because one or more lines are too long

110
dist/umi.21e3ba7b.js → dist/umi.6d712b5d.js

File diff suppressed because one or more lines are too long

22
src/app.tsx

@ -4,13 +4,13 @@ import { LinkOutlined } from '@ant-design/icons';
import { MenuDataItem, SettingDrawer } from '@ant-design/pro-components';
import { Link, history } from '@umijs/max';
import { appList, current } from '@/services/matrix/admin';
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
import type { RunTimeLayoutConfig } from '@umijs/max';
import { JSXElementConstructor, ReactElement, ReactNode, ReactPortal } from 'react';
import defaultSettings from '../config/defaultSettings';
import access from './access';
import { errorConfig } from './requestErrorConfig';
import { appList, current } from './services/matrix/admin';
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';
@ -50,6 +50,24 @@ export async function getInitialState(): Promise<{
};
}
const proxyMenu = (app: API.MatrixAppBo): MenuDataItem => {
return {
name: app.name,
children: [
{
name: '广告数据',
path: '/proxy/advListV2/' + app.code,
component: './Proxy/ProxyAdvRecordListV2',
},
{
name: '用户管理',
path: '/proxy/user/' + app.code,
component: './Proxy/ProxyUserManagement',
},
],
};
};
const matrixMenu = (app: API.MatrixAppBo): MenuDataItem => {
if (!app.enableCash) {
return {
@ -99,6 +117,8 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
const role = initialState?.currentUser?.role || 100;
apps.data?.forEach((x: any) => {
if (x.hide === 1 && role <= 2) {
} else if (role === 5) {
menuData.push(proxyMenu(x));
} else {
menuData.push(matrixMenu(x));
}

8
src/pages/AdvRecordListV2.tsx

@ -111,6 +111,14 @@ const AdvRecordListV2: React.FC = () => {
return x / 100;
},
},
{
title: '金币(元)',
dataIndex: 'ecpmReal',
hideInSearch: true,
renderText: (x) => {
return x / 100;
},
},
{
title: '设备',
dataIndex: 'device',

15
src/pages/DeviceOwnerApp.tsx

@ -1,3 +1,4 @@
import { advList } from '@/services/matrix/admin';
import {
ColumnsState,
PageContainer,
@ -14,10 +15,15 @@ import TabPane from 'antd/es/tabs/TabPane';
import RcResizeObserver from 'rc-resize-observer';
import React, { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import { advList } from '../services/matrix/admin';
// import { RequestOptionsType, ProFieldRequestData } from "@ant-design/pro-utils";
import { deviceList, incomeDaily1, incomeOverview1, offline, online } from '@/services/matrix/device';
import {
deviceList,
incomeDaily1,
incomeOverview1,
offline,
online,
} from '@/services/matrix/device';
import { Column } from '@ant-design/charts';
import type { ActionType } from '@ant-design/pro-components';
import moment from 'moment';
@ -135,9 +141,12 @@ const DeviceOwnerApp: React.FC = () => {
},
{
title: 'ecpm-抽成后(元)',
dataIndex: 'deductEcpm',
dataIndex: 'ecpmAdmin',
hideInSearch: true,
renderText: (x, record) => {
if (x) {
return x / 100;
}
if (currentUser?.incomeRate && record.ecpm) {
return Math.floor((record.ecpm * currentUser.incomeRate) / 100) / 100;
}

2
src/pages/OSSUploadDragger.tsx

@ -1,8 +1,8 @@
import { getSts } from '@/services/matrix/admin';
import { toMD5 } from '@/utils/encryptUtil';
import OSS from 'ali-oss';
import { message, Upload, UploadFile, UploadProps } from 'antd';
import React, { useEffect, useState } from 'react';
import { getSts } from '../services/matrix/admin';
interface OSSUploadProps {
onUploadSuccess?: (fileUrl: string) => void;

336
src/pages/Proxy/ProxyAdvRecordListV2.tsx

@ -0,0 +1,336 @@
import { advList } from '@/services/matrix/admin';
import { incomeDaily1, incomeOverview1 } from '@/services/matrix/device';
import { Column } from '@ant-design/charts';
import {
ColumnsState,
PageContainer,
ProColumns,
ProFormDateRangePicker,
ProFormSelect,
ProTable,
QueryFilter,
StatisticCard,
} from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { Divider, Tabs } from 'antd';
import TabPane from 'antd/es/tabs/TabPane';
import moment from 'moment';
import RcResizeObserver from 'rc-resize-observer';
import React, { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import UserInfo from '../User/UserInfo';
// import { RequestOptionsType, ProFieldRequestData } from "@ant-design/pro-utils";
import type { ActionType } from '@ant-design/pro-components';
const ProxyAdvRecordListV2: React.FC = () => {
const actionRef = useRef<ActionType>();
const deviceRef = useRef<ActionType>();
const [responsive, setResponsive] = useState(false);
/**
* @en-US International configuration
* @zh-CN
* */
const intl = useIntl();
const { code } = useParams();
const [overview, setOverview] = useState<API.OverviewBo>();
const [daily, setDaily] = useState<API.DateIncome[]>([]);
const isMobile = () => {
return window.innerWidth <= 768;
};
const [columnsState, setColumnsState] = useState<Record<string, ColumnsState>>({
deviceId: { show: !isMobile() },
appName: { show: !isMobile() },
platform: { show: !isMobile() },
deviceBrand: { show: !isMobile() },
deviceName: { show: !isMobile() },
ip: { show: !isMobile() },
});
const columns: ProColumns<API.MatrixAdvRecordBo>[] = [
{
title: '用户id',
dataIndex: 'userId',
valueType: 'textarea',
hideInTable: true,
},
{
title: '设备id',
dataIndex: 'deviceId',
valueType: 'textarea',
ellipsis: true,
copyable: true,
hideInSearch: true,
},
{
title: '用户',
dataIndex: 'user',
hideInSearch: true,
width: 150,
renderText: (_, record) => {
if (!record.user) {
return;
}
return <UserInfo data={record.user} />;
},
},
{
title: '平台',
dataIndex: 'platform',
valueEnum: {
1: {
text: '穿山甲',
},
2: {
text: '腾讯',
},
3: {
text: '百度联盟',
},
4: {
text: 'Mintegral',
},
5: {
text: '快手',
},
6: {
text: '游可赢',
},
7: {
text: 'Sigmob',
},
8: {
text: 'Admob',
},
},
},
{
title: 'ecpm(元)',
dataIndex: 'ecpm',
hideInSearch: true,
renderText: (x) => {
return x / 100;
},
},
{
title: 'ecpm-代理归属(元)',
dataIndex: 'ecpmAdmin',
hideInSearch: true,
renderText: (x) => {
return x / 100;
},
},
{
title: '设备',
dataIndex: 'device',
hideInSearch: true,
renderText: (_, record) => {
return (
<>
<p>{record.deviceBrand}</p>
<p>{record.deviceName}</p>
</>
);
},
},
{
title: 'ip',
dataIndex: 'ip',
hideInSearch: true,
valueType: 'textarea',
},
{
title: '时间',
hideInSearch: true,
dataIndex: 'createdAt',
valueType: 'dateTime',
},
{
title: '时间',
hideInTable: true,
dataIndex: 'createdAt',
valueType: 'dateRange',
},
// {
// title: "应用名称",
// hideInTable: true,
// dataIndex: 'appId',
// valueType: "select",
// request: appNameMap
// }
];
const fetchData = async (params: any) => {
const res = await advList({ ...params, code: code });
return {
data: res.data?.data,
total: res.data?.total,
success: true,
};
};
const fetchOverview = async () => {
const res = await incomeOverview1({ appCode: code });
if (res.data) {
setOverview(res.data);
}
};
const fetchDaily = async (params: API.IncomeQuery | undefined) => {
const res = await incomeDaily1({ ...params, code: code });
if (res.data) {
setDaily(res.data);
}
};
useEffect(() => {
actionRef.current?.reload();
deviceRef.current?.reload();
fetchOverview();
fetchDaily(undefined);
}, [code]);
const formatIncome = (v: number | undefined) => {
if (!v) {
return 0;
}
return parseFloat((v / 1000_00).toFixed(2));
};
const processedChartData: { date: string | number; income: number }[] = daily.map((item) => ({
date: (() => {
const inputDateStr = item.date + '';
const year: string = inputDateStr.slice(0, 4);
const month: string = inputDateStr.slice(4, 6);
const day: string = inputDateStr.slice(6, 8);
return `${year}-${month}-${day}`;
})(),
income: formatIncome(item.income),
}));
const config = {
data: processedChartData,
xField: 'date',
yField: 'income',
tooltip: {
name: '收入',
field: 'income',
},
axis: {
date: {
title: '日期',
},
},
title:
'累计' +
formatIncome(daily.reduce((acc, data) => acc + (data.income ? data.income : 0), 0)) +
'元',
height: 400,
label: {
text: (d: any) => (d.income > 0 ? d.income : ''),
textBaseline: 'bottom',
},
};
return (
<PageContainer>
<Tabs defaultActiveKey="2" centered style={{ backgroundColor: 'white', padding: '20px' }}>
<TabPane tab="广告详情" key="2">
<ProTable<API.MatrixAdvRecordBo, API.AdvRecordQuery>
headerTitle={intl.formatMessage({
id: 'pages.searchTable.title',
defaultMessage: 'Enquiry form',
})}
actionRef={actionRef}
rowKey="id"
search={{
labelWidth: 120,
}}
request={fetchData}
columns={columns}
columnsState={{
value: columnsState,
onChange: (newState) => {
setColumnsState(newState);
},
}}
/>
</TabPane>
<TabPane tab="数据总览" key="3">
<RcResizeObserver
key="resize-observer"
onResize={(offset) => {
setResponsive(offset.width < 596);
}}
>
<StatisticCard>
<StatisticCard.Group direction={responsive ? 'column' : 'row'}>
<StatisticCard
statistic={{
title: '上月收入(元)',
value: formatIncome(overview?.lastMonthIncome),
}}
/>
<Divider type={responsive ? 'horizontal' : 'vertical'} />
<StatisticCard
statistic={{
title: '本月收入(元)',
value: formatIncome(overview?.thisMonthIncome),
}}
/>
<Divider type={responsive ? 'horizontal' : 'vertical'} />
<StatisticCard
statistic={{
title: '今日收入(元)',
value: formatIncome(overview?.todayIncome),
}}
/>
<Divider type={responsive ? 'horizontal' : 'vertical'} />
<StatisticCard
statistic={{
title: '昨日收入(元)',
value: formatIncome(overview?.yesterdayIncome),
}}
/>
</StatisticCard.Group>
</StatisticCard>
</RcResizeObserver>
<QueryFilter defaultCollapsed split onFinish={fetchDaily}>
<ProFormSelect
label="平台"
name="platform"
valueEnum={{
1: '穿山甲',
2: '腾讯',
3: '百度联盟',
4: 'Mintegral',
5: '快手',
6: '游可赢',
7: 'Sigmob',
8: 'Admob',
}}
/>
<ProFormDateRangePicker
fieldProps={{
disabledDate: (current: any) => current && current >= moment().startOf('day'),
}}
name="date"
label="时间"
/>
</QueryFilter>
{/* <Line {...config} /> */}
<Column {...config} />
</TabPane>
</Tabs>
</PageContainer>
);
};
export default ProxyAdvRecordListV2;

112
src/pages/Proxy/ProxyUserManagement.tsx

@ -0,0 +1,112 @@
import { list } from '@/services/matrix/matrixUserController';
import { cent2Yuan, formatIncome } from '@/utils/numberUtils';
import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
import { useParams } from '@umijs/max';
import { Tag } from 'antd';
import { useEffect, useRef } from 'react';
import UserInfo from '../User/UserInfo';
const ProxyUserManagement: React.FC = () => {
const { code } = useParams();
const actionRef = useRef<ActionType>();
useEffect(() => {
actionRef.current?.reload();
}, [code]);
const columns: ProColumns<API.UserBo>[] = [
{
title: '用户id',
dataIndex: 'userId',
valueType: 'textarea',
hideInTable: true,
},
{
title: 'id',
dataIndex: 'id',
valueType: 'textarea',
ellipsis: true,
copyable: true,
hideInSearch: true,
},
{
title: '用户',
hideInSearch: true,
dataIndex: 'nickname',
valueType: 'textarea',
renderText: (nickname: string, r: API.UserBo) => {
return <UserInfo data={r} />;
},
},
{
title: '支付宝名称',
dataIndex: 'name',
hideInSearch: true,
},
{
title: '支付宝账号',
dataIndex: 'aliPayAccount',
hideInSearch: true,
},
{
title: '微信',
dataIndex: 'wxOpenId',
hideInSearch: true,
renderText: (s: string | undefined) => {
if (s) {
return <Tag color="green"></Tag>;
}
return <Tag color="red"></Tag>;
},
},
{
title: '总收益(元)',
hideInSearch: true,
dataIndex: 'income',
renderText: (r: number) => {
return formatIncome(r);
},
},
{
title: '已提现(元)',
hideInSearch: true,
dataIndex: 'realMoney',
renderText: (r: number) => {
return cent2Yuan(r);
},
},
{
title: '注册时间',
hideInSearch: true,
dataIndex: 'createdAt',
valueType: 'dateTime',
},
{
title: '时间',
hideInTable: true,
dataIndex: 'createdAt',
valueType: 'dateRange',
hideInSearch: true,
},
];
return (
<PageContainer>
<ProTable<API.UserBo, API.UserQuery>
actionRef={actionRef}
request={async (params: API.UserQuery) => {
const res = await list({ ...params, appCode: code });
return {
data: res.data?.data,
total: res.data?.total,
success: true,
};
}}
columns={columns}
/>
</PageContainer>
);
};
export default ProxyUserManagement;

1
src/services/matrix/typings.d.ts

@ -194,6 +194,7 @@ declare namespace API {
appId?: number;
createdAt?: number;
id?: number;
ecpmReal?: number;
user?: UserBo;
adminName?: string;
};

Loading…
Cancel
Save