nili
9 months ago
4 changed files with 443 additions and 0 deletions
@ -0,0 +1,130 @@ |
|||||
|
import { PageContainer, ProColumns, ProTable } from '@ant-design/pro-components'; |
||||
|
import { useModel } from '@umijs/max'; |
||||
|
import { Button, Form, Input, Modal, Popover, QRCode } from 'antd'; |
||||
|
import { useEffect, useState } from 'react'; |
||||
|
|
||||
|
import { appList, saveApp } from '../services/matrix/admin'; |
||||
|
|
||||
|
const AppManagement = () => { |
||||
|
const [visible, setVisible] = useState(false); |
||||
|
const [form] = Form.useForm(); |
||||
|
const [appArr, setAppArr] = useState<API.MatrixApp[]>([]); |
||||
|
const { initialState } = useModel('@@initialState'); |
||||
|
const currentUser = initialState?.currentUser; |
||||
|
const canUpdate = currentUser && currentUser.role && currentUser.role < 2; |
||||
|
|
||||
|
const handleEdit = (record: API.MatrixApp) => { |
||||
|
form.setFieldsValue(record); |
||||
|
setVisible(true); |
||||
|
}; |
||||
|
|
||||
|
const columns: ProColumns<API.MatrixApp>[] = [ |
||||
|
{ |
||||
|
title: '应用名', |
||||
|
dataIndex: 'name', |
||||
|
}, |
||||
|
{ |
||||
|
title: 'code', |
||||
|
dataIndex: 'code', |
||||
|
}, |
||||
|
{ |
||||
|
title: '下载地址', |
||||
|
dataIndex: 'url', |
||||
|
renderText: (url: string) => ( |
||||
|
<Popover |
||||
|
overlayInnerStyle={{ padding: 0 }} |
||||
|
content={<QRCode value={url || ''} bordered={false} />} |
||||
|
> |
||||
|
<a>{url}</a> |
||||
|
</Popover> |
||||
|
), |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const columnsWithOperation: ProColumns<API.MatrixApp>[] = [ |
||||
|
...columns, |
||||
|
{ |
||||
|
title: 'secret', |
||||
|
dataIndex: 'secret', |
||||
|
ellipsis: true, |
||||
|
copyable: true, |
||||
|
}, |
||||
|
{ |
||||
|
title: '操作', |
||||
|
width: 80, |
||||
|
renderText: (record: API.MatrixApp) => ( |
||||
|
<a key="edit" onClick={() => handleEdit(record)}> |
||||
|
编辑 |
||||
|
</a> |
||||
|
), |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const handleOk = () => { |
||||
|
form.submit(); |
||||
|
}; |
||||
|
|
||||
|
const handleCancel = () => { |
||||
|
setVisible(false); |
||||
|
}; |
||||
|
|
||||
|
const fetchApp = async () => { |
||||
|
const res = await appList(); |
||||
|
if (res.data) { |
||||
|
setAppArr(res.data); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
useEffect(() => { |
||||
|
fetchApp(); |
||||
|
}, []); |
||||
|
|
||||
|
const handleNew = () => { |
||||
|
form.resetFields(); // 重置表单字段
|
||||
|
setVisible(true); |
||||
|
}; |
||||
|
|
||||
|
const handleSaveApp = async (values: API.MatrixApp) => { |
||||
|
try { |
||||
|
await saveApp(values); |
||||
|
} catch (e) { |
||||
|
return; |
||||
|
} |
||||
|
setVisible(false); |
||||
|
fetchApp(); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<PageContainer> |
||||
|
{currentUser && currentUser.role && currentUser.role < 2 && ( |
||||
|
<Button style={{ marginBottom: '20px' }} onClick={handleNew}> |
||||
|
新建应用 |
||||
|
</Button> |
||||
|
)} |
||||
|
<ProTable |
||||
|
search={false} |
||||
|
columns={canUpdate ? columnsWithOperation : columns} |
||||
|
dataSource={appArr} |
||||
|
/> |
||||
|
|
||||
|
<Modal title="编辑" visible={visible} onOk={handleOk} onCancel={handleCancel}> |
||||
|
<Form form={form} onFinish={handleSaveApp}> |
||||
|
<Form.Item name="id" style={{ display: 'none' }}> |
||||
|
<Input type="hidden" /> |
||||
|
</Form.Item> |
||||
|
<Form.Item name="code" style={{ display: 'none' }}> |
||||
|
<Input type="hidden" /> |
||||
|
</Form.Item> |
||||
|
<Form.Item label="应用名" name="name"> |
||||
|
<Input /> |
||||
|
</Form.Item> |
||||
|
<Form.Item label="下载地址" name="url"> |
||||
|
<Input /> |
||||
|
</Form.Item> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
</PageContainer> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default AppManagement; |
@ -0,0 +1,19 @@ |
|||||
|
import { PageContainer } from '@ant-design/pro-components'; |
||||
|
import { useModel } from '@umijs/max'; |
||||
|
import { Card, QRCode } from 'antd'; |
||||
|
|
||||
|
const Bind: React.FC = () => { |
||||
|
const { initialState } = useModel('@@initialState'); |
||||
|
const currentUser = initialState?.currentUser; |
||||
|
return ( |
||||
|
<PageContainer> |
||||
|
<Card> |
||||
|
<QRCode style={{ margin: 'auto' }} value={currentUser?.name || ''} /> |
||||
|
|
||||
|
<p style={{ textAlign: 'center', marginTop: '20px' }}>请使用游戏app扫码绑定设备</p> |
||||
|
</Card> |
||||
|
</PageContainer> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Bind; |
@ -0,0 +1,261 @@ |
|||||
|
import { advList } from '@/services/matrix/admin'; |
||||
|
import { PageContainer, ProColumns, ProTable } from '@ant-design/pro-components'; |
||||
|
import { useIntl, useModel } from '@umijs/max'; |
||||
|
import { Tabs } from 'antd'; |
||||
|
import TabPane from 'antd/es/tabs/TabPane'; |
||||
|
import React, { useEffect, useRef, useState } from 'react'; |
||||
|
import { useParams } from 'react-router-dom'; |
||||
|
|
||||
|
// import { RequestOptionsType, ProFieldRequestData } from "@ant-design/pro-utils";
|
||||
|
|
||||
|
import { deviceList, offline } from '@/services/matrix/device'; |
||||
|
import type { ActionType } from '@ant-design/pro-components'; |
||||
|
const DeviceOwnerApp: React.FC = () => { |
||||
|
const actionRef = useRef<ActionType>(); |
||||
|
const deviceRef = useRef<ActionType>(); |
||||
|
const [usedDeviceCnt, setUsedDeviceCnt] = useState(0); |
||||
|
/** |
||||
|
* @en-US International configuration |
||||
|
* @zh-CN 国际化配置 |
||||
|
* */ |
||||
|
const intl = useIntl(); |
||||
|
const { code } = useParams(); |
||||
|
|
||||
|
const { initialState } = useModel('@@initialState'); |
||||
|
const currentUser = initialState?.currentUser; |
||||
|
|
||||
|
const fetchDevice = async () => { |
||||
|
const res = await deviceList({ appCode: code ? code : '' }); |
||||
|
if (res.data) { |
||||
|
setUsedDeviceCnt(res.data?.filter((device) => device.status === 0).length); |
||||
|
} |
||||
|
return { |
||||
|
data: res.data, |
||||
|
total: res.data?.length, |
||||
|
success: true, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
const handleSetOffline = async (deviceId: string | undefined) => { |
||||
|
if (!deviceId) { |
||||
|
return; |
||||
|
} |
||||
|
await offline({ deviceId: deviceId }); |
||||
|
deviceRef.current?.reload(); |
||||
|
}; |
||||
|
|
||||
|
const columns: ProColumns<API.MatrixAdvRecordBo>[] = [ |
||||
|
{ |
||||
|
title: '设备id', |
||||
|
dataIndex: 'deviceId', |
||||
|
valueType: 'textarea', |
||||
|
ellipsis: true, |
||||
|
copyable: true, |
||||
|
}, |
||||
|
{ |
||||
|
title: '应用名称', |
||||
|
hideInSearch: true, |
||||
|
dataIndex: 'appName', |
||||
|
valueType: 'textarea', |
||||
|
}, |
||||
|
{ |
||||
|
title: '平台', |
||||
|
dataIndex: 'platform', |
||||
|
valueEnum: { |
||||
|
1: { |
||||
|
text: '穿山甲', |
||||
|
}, |
||||
|
2: { |
||||
|
text: '腾讯', |
||||
|
}, |
||||
|
3: { |
||||
|
text: '百度联盟', |
||||
|
}, |
||||
|
4: { |
||||
|
text: 'Mintegral', |
||||
|
}, |
||||
|
5: { |
||||
|
text: '快手', |
||||
|
}, |
||||
|
6: { |
||||
|
text: '游可赢', |
||||
|
}, |
||||
|
7: { |
||||
|
text: 'Sigmob', |
||||
|
}, |
||||
|
8: { |
||||
|
text: 'Admob', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: '广告类型', |
||||
|
dataIndex: 'advType', |
||||
|
valueEnum: { |
||||
|
1: { |
||||
|
text: '横幅', |
||||
|
status: 'Default', |
||||
|
}, |
||||
|
2: { |
||||
|
text: '插页', |
||||
|
status: 'Processing', |
||||
|
}, |
||||
|
3: { |
||||
|
text: '激励视频', |
||||
|
status: 'Success', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: 'ecpm(元)', |
||||
|
dataIndex: 'ecpm', |
||||
|
hideInSearch: true, |
||||
|
renderText: (x) => { |
||||
|
return x / 100; |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: 'ecpm-抽成后(元)', |
||||
|
dataIndex: 'deductEcpm', |
||||
|
hideInSearch: true, |
||||
|
renderText: (x, record) => { |
||||
|
if (currentUser?.incomeRate && record.ecpm) { |
||||
|
return Math.floor((record.ecpm * currentUser.incomeRate) / 100) / 100; |
||||
|
} |
||||
|
return '-'; |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: '设备品牌', |
||||
|
dataIndex: 'deviceBrand', |
||||
|
hideInSearch: true, |
||||
|
valueType: 'textarea', |
||||
|
}, |
||||
|
{ |
||||
|
title: '设备名', |
||||
|
dataIndex: 'deviceName', |
||||
|
hideInSearch: true, |
||||
|
valueType: 'textarea', |
||||
|
}, |
||||
|
{ |
||||
|
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 deviceColumns: ProColumns<API.MatrixAdminDevice>[] = [ |
||||
|
{ |
||||
|
title: '设备id', |
||||
|
dataIndex: 'deviceId', |
||||
|
valueType: 'text', |
||||
|
ellipsis: true, |
||||
|
width: 400, |
||||
|
copyable: true, |
||||
|
}, |
||||
|
{ |
||||
|
title: '状态', |
||||
|
dataIndex: 'status', |
||||
|
valueEnum: { |
||||
|
0: { |
||||
|
text: '正常', |
||||
|
}, |
||||
|
'-1': { |
||||
|
text: '已下线', |
||||
|
}, |
||||
|
}, |
||||
|
filters: true, |
||||
|
onFilter: true, |
||||
|
}, |
||||
|
{ |
||||
|
title: '绑定时间', |
||||
|
hideInSearch: true, |
||||
|
dataIndex: 'createdAt', |
||||
|
valueType: 'dateTime', |
||||
|
}, |
||||
|
{ |
||||
|
title: '操作', |
||||
|
dataIndex: 'operation', |
||||
|
hideInSearch: true, |
||||
|
render: (_, record) => { |
||||
|
if (record.status === 0) { |
||||
|
return <a onClick={() => handleSetOffline(record.deviceId)}>下线</a>; |
||||
|
} |
||||
|
return null; |
||||
|
}, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const fetchData = async (params: any) => { |
||||
|
const res = await advList({ ...params, code: code }); |
||||
|
return { |
||||
|
data: res.data?.data, |
||||
|
total: res.data?.total, |
||||
|
success: true, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
useEffect(() => { |
||||
|
actionRef.current?.reload(); |
||||
|
deviceRef.current?.reload(); |
||||
|
}, [code]); |
||||
|
|
||||
|
return ( |
||||
|
<PageContainer> |
||||
|
<Tabs defaultActiveKey="1" centered style={{ backgroundColor: 'white', padding: '20px' }}> |
||||
|
<TabPane tab="设备列表" key="1"> |
||||
|
<p style={{ textAlign: 'center' }}> |
||||
|
{'正常使用设备数:' + |
||||
|
usedDeviceCnt + |
||||
|
',剩余可绑定设备数:' + |
||||
|
Math.max(0, (currentUser?.deviceCnt ? currentUser.deviceCnt : 0) - usedDeviceCnt)} |
||||
|
</p> |
||||
|
<ProTable<API.MatrixAdminDevice> |
||||
|
actionRef={deviceRef} |
||||
|
rowKey="id" |
||||
|
search={false} |
||||
|
request={fetchDevice} |
||||
|
columns={deviceColumns} |
||||
|
/> |
||||
|
</TabPane> |
||||
|
<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} |
||||
|
/> |
||||
|
</TabPane> |
||||
|
</Tabs> |
||||
|
</PageContainer> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default DeviceOwnerApp; |
@ -0,0 +1,33 @@ |
|||||
|
// @ts-ignore
|
||||
|
/* eslint-disable */ |
||||
|
import { request } from '@umijs/max'; |
||||
|
|
||||
|
/** 此处后端没有提供注释 GET /api/admin/device/list */ |
||||
|
export async function deviceList( |
||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
params: API.deviceListParams, |
||||
|
options?: { [key: string]: any }, |
||||
|
) { |
||||
|
return request<API.RListMatrixAdminDevice>('/api/admin/device/list', { |
||||
|
method: 'GET', |
||||
|
params: { |
||||
|
...params, |
||||
|
}, |
||||
|
...(options || {}), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** 此处后端没有提供注释 POST /api/admin/device/offline */ |
||||
|
export async function offline( |
||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
params: API.offlineParams, |
||||
|
options?: { [key: string]: any }, |
||||
|
) { |
||||
|
return request<API.RString>('/api/admin/device/offline', { |
||||
|
method: 'POST', |
||||
|
params: { |
||||
|
...params, |
||||
|
}, |
||||
|
...(options || {}), |
||||
|
}); |
||||
|
} |
Loading…
Reference in new issue