diff --git a/src/pages/AppManagement.tsx b/src/pages/AppManagement.tsx new file mode 100644 index 0000000..859ed09 --- /dev/null +++ b/src/pages/AppManagement.tsx @@ -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([]); + 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[] = [ + { + title: '应用名', + dataIndex: 'name', + }, + { + title: 'code', + dataIndex: 'code', + }, + { + title: '下载地址', + dataIndex: 'url', + renderText: (url: string) => ( + } + > + {url} + + ), + }, + ]; + + const columnsWithOperation: ProColumns[] = [ + ...columns, + { + title: 'secret', + dataIndex: 'secret', + ellipsis: true, + copyable: true, + }, + { + title: '操作', + width: 80, + renderText: (record: API.MatrixApp) => ( + handleEdit(record)}> + 编辑 + + ), + }, + ]; + + 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 ( + + {currentUser && currentUser.role && currentUser.role < 2 && ( + + )} + + + +
+ + + + + + + + + + + + +
+
+
+ ); +}; + +export default AppManagement; diff --git a/src/pages/Bind.tsx b/src/pages/Bind.tsx new file mode 100644 index 0000000..917dcb5 --- /dev/null +++ b/src/pages/Bind.tsx @@ -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 ( + + + + +

请使用游戏app扫码绑定设备

+
+
+ ); +}; + +export default Bind; diff --git a/src/pages/DeviceOwnerApp.tsx b/src/pages/DeviceOwnerApp.tsx new file mode 100644 index 0000000..7633c8f --- /dev/null +++ b/src/pages/DeviceOwnerApp.tsx @@ -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(); + const deviceRef = useRef(); + 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[] = [ + { + 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[] = [ + { + 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 handleSetOffline(record.deviceId)}>下线; + } + 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 ( + + + +

+ {'正常使用设备数:' + + usedDeviceCnt + + ',剩余可绑定设备数:' + + Math.max(0, (currentUser?.deviceCnt ? currentUser.deviceCnt : 0) - usedDeviceCnt)} +

+ + actionRef={deviceRef} + rowKey="id" + search={false} + request={fetchDevice} + columns={deviceColumns} + /> +
+ + + headerTitle={intl.formatMessage({ + id: 'pages.searchTable.title', + defaultMessage: 'Enquiry form', + })} + actionRef={actionRef} + rowKey="id" + search={{ + labelWidth: 120, + }} + request={fetchData} + columns={columns} + /> + +
+
+ ); +}; + +export default DeviceOwnerApp; diff --git a/src/services/matrix/device.ts b/src/services/matrix/device.ts new file mode 100644 index 0000000..c8163c8 --- /dev/null +++ b/src/services/matrix/device.ts @@ -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/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/admin/device/offline', { + method: 'POST', + params: { + ...params, + }, + ...(options || {}), + }); +}