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