30 changed files with 1195 additions and 653 deletions
@ -1,63 +1,26 @@ |
|||
/** |
|||
* @name umi 的路由配置 |
|||
* @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置 |
|||
* @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。 |
|||
* @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。 |
|||
* @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。 |
|||
* @param redirect 配置路由跳转 |
|||
* @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验 |
|||
* @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题 |
|||
* @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 <StepBackwardOutlined /> 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 <UserOutlined /> 则取值应为 user 或者 User
|
|||
* @doc https://umijs.org/docs/guides/routes
|
|||
*/ |
|||
import component from "@/locales/bn-BD/component"; |
|||
|
|||
export default [ |
|||
{ |
|||
path: '/user', |
|||
layout: false, |
|||
routes: [ |
|||
{ |
|||
name: 'login', |
|||
path: '/user/login', |
|||
component: './User/Login', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: '/welcome', |
|||
name: 'welcome', |
|||
icon: 'smile', |
|||
component: './Welcome', |
|||
routes: [{ name: '登录', path: '/user/login', component: './User/Login' }], |
|||
}, |
|||
{ path: '/welcome', name: '欢迎', icon: 'smile', component: './Welcome' }, |
|||
{ path: '/dashboard/workplace', name: '工作台', icon: 'dashboard', component: './dashboard'}, |
|||
{ |
|||
path: '/admin', |
|||
name: 'admin', |
|||
name: '管理页', |
|||
icon: 'crown', |
|||
access: 'canAdmin', |
|||
routes: [ |
|||
{ |
|||
path: '/admin', |
|||
redirect: '/admin/sub-page', |
|||
}, |
|||
{ |
|||
path: '/admin/sub-page', |
|||
name: 'sub-page', |
|||
component: './Admin', |
|||
}, |
|||
{ path: '/admin', redirect: '/admin/sub-page' }, |
|||
{ path: '/admin/sub-page', name: '二级管理页', component: './Admin' }, |
|||
], |
|||
}, |
|||
{ |
|||
name: 'list.table-list', |
|||
icon: 'table', |
|||
path: '/list', |
|||
component: './TableList', |
|||
}, |
|||
{ |
|||
path: '/', |
|||
redirect: '/welcome', |
|||
}, |
|||
{ |
|||
path: '*', |
|||
layout: false, |
|||
component: './404', |
|||
}, |
|||
{ name: '设备列表', icon: 'table', path: '/list/device-list', component: './TableList' }, |
|||
{ name: '设备分组', icon: 'table', path: '/list/group-list', component: './TableList/group' }, |
|||
{ name: '记录列表', icon: 'table', path: '/list/basic-list', component: './TableList/list' }, |
|||
{ path: '/', redirect: '/welcome' }, |
|||
{ path: '*', layout: false, component: './404' }, |
|||
]; |
|||
|
|||
@ -0,0 +1,10 @@ |
|||
module app |
|||
|
|||
go 1.22.2 |
|||
|
|||
require ( |
|||
github.com/gorilla/websocket v1.5.1 |
|||
golang.org/x/sys v0.19.0 |
|||
) |
|||
|
|||
require golang.org/x/net v0.17.0 // indirect |
|||
@ -0,0 +1,6 @@ |
|||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= |
|||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= |
|||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= |
|||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= |
|||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= |
|||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
|||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1017 B |
|
After Width: | Height: | Size: 3.8 KiB |
@ -1,18 +1,16 @@ |
|||
import { history, useIntl } from '@umijs/max'; |
|||
import { history } from '@umijs/max'; |
|||
import { Button, Result } from 'antd'; |
|||
import React from 'react'; |
|||
|
|||
const NoFoundPage: React.FC = () => ( |
|||
<Result |
|||
status="404" |
|||
title="404" |
|||
subTitle={useIntl().formatMessage({ id: 'pages.404.subTitle' })} |
|||
subTitle={'抱歉,您访问的页面不存在。'} |
|||
extra={ |
|||
<Button type="primary" onClick={() => history.push('/')}> |
|||
{useIntl().formatMessage({ id: 'pages.404.buttonText' })} |
|||
{'返回首页'} |
|||
</Button> |
|||
} |
|||
/> |
|||
); |
|||
|
|||
export default NoFoundPage; |
|||
|
|||
@ -0,0 +1,145 @@ |
|||
import React, { useRef } from 'react'; |
|||
import { ActionType, ModalForm, ProColumns, ProFormText, ProTable } from '@ant-design/pro-components'; |
|||
import { Button, Popconfirm, message } from 'antd'; |
|||
import { request } from '@umijs/max'; |
|||
|
|||
// 定义一个接口来描述分组的形状
|
|||
interface Tag { |
|||
id: number; |
|||
TagKey: string; |
|||
TagValue: string; |
|||
} |
|||
|
|||
const TagsTable: React.FC = () => { |
|||
// 创建一个actionRef来控制ProTable
|
|||
const actionRef = useRef<ActionType>(); |
|||
|
|||
const onFinish = async (values: { group_name: string, description: string }) => { |
|||
try { |
|||
// 使用@umijs/max的request方法发送POST请求
|
|||
const { success, message: apiMessage, group_id } = await request('https://867t766n6.zicp.fun/groups', { |
|||
method: 'POST', |
|||
data: { |
|||
group_name: values.group_name, |
|||
description: values.description, // 假设对于新分组TagValue是空的
|
|||
}, |
|||
// 注意:@umijs/max的request默认使用'application/json',并自动转换body为JSON字符串
|
|||
}); |
|||
|
|||
// 根据接口返回的success字段判断操作是否成功
|
|||
if (success) { |
|||
message.success(apiMessage || '分组创建成功!'); |
|||
actionRef.current?.reload(); // 刷新表格以显示新创建的分组
|
|||
return true; |
|||
} else { |
|||
message.error(apiMessage || '分组创建失败。'); |
|||
return false; |
|||
} |
|||
} catch (error) { |
|||
// 处理请求过程中出现的错误
|
|||
console.error('创建操作失败', error); |
|||
message.error('操作异常,请稍后重试。'); |
|||
return false; |
|||
} |
|||
}; |
|||
|
|||
// 使用ProColumns<Tag>来确保columns与Tag接口一致
|
|||
const columns: ProColumns<Tag>[] = [ |
|||
{ title: 'ID', dataIndex: 'id', key: 'id' }, |
|||
{ title: '分组名称', dataIndex: 'group_name', key: 'group_name' }, |
|||
{ title: '分组描述', dataIndex: 'description', key: 'description' }, |
|||
{ |
|||
title: '操作', |
|||
key: 'action', |
|||
render: (_, record: Tag) => ( |
|||
<Popconfirm |
|||
placement="topRight" |
|||
title="确定要删除此分组吗?" |
|||
onConfirm={() => deleteTag(record)} |
|||
okText="确定" |
|||
cancelText="取消" |
|||
> |
|||
<Button danger> |
|||
删除 |
|||
</Button> |
|||
</Popconfirm> |
|||
), |
|||
}, |
|||
]; |
|||
|
|||
const deleteTag = async (record: Tag) => { |
|||
try { |
|||
// 使用@umijs/max的request方法发送DELETE请求
|
|||
const { success, message: apiMessage } = await request(`https://867t766n6.zicp.fun/groups/${record.id}`, { |
|||
method: 'DELETE', |
|||
}); |
|||
|
|||
// 根据返回的success字段判断操作是否成功
|
|||
if (success) { |
|||
message.success(apiMessage || '分组删除成功。'); |
|||
} else { |
|||
message.error(apiMessage || '分组删除失败。'); |
|||
} |
|||
|
|||
// 如果你有引用ProTable的actionRef来刷新数据,也可以在这里调用
|
|||
actionRef.current?.reload(); |
|||
} catch (error) { |
|||
// 处理请求过程中出现的错误
|
|||
console.error('删除操作失败', error); |
|||
message.error('操作异常,请稍后重试。'); |
|||
} |
|||
}; |
|||
|
|||
return <ProTable<Tag> |
|||
columns={columns} |
|||
actionRef={actionRef} |
|||
request={async (params, sorter, filter) => { |
|||
// 使用 @umijs/max 的 request 方法请求数据
|
|||
const response = await request('https://867t766n6.zicp.fun/groups', { |
|||
method: 'GET', // 根据实际请求调整
|
|||
// 可以在这里传递查询参数(如分页信息),或者设置请求头部等
|
|||
// params: { ...params },
|
|||
}); |
|||
|
|||
// 直接从response中解构出data
|
|||
const { data } = response; |
|||
|
|||
return { |
|||
data: data, // 实际的数据数组
|
|||
success: true, // 请求是否成功
|
|||
total: data.length, // 数据总数,用于分页
|
|||
}; |
|||
}} |
|||
|
|||
rowKey="id" |
|||
pagination={false} |
|||
search={false} |
|||
toolBarRender={() => [ |
|||
<ModalForm |
|||
title="新建分组" |
|||
trigger={ |
|||
<Button type="primary"> |
|||
新建分组 |
|||
</Button> |
|||
} |
|||
onFinish={onFinish} |
|||
modalProps={{ |
|||
onCancel: () => console.log('取消新建分组'), |
|||
}} |
|||
> |
|||
<ProFormText |
|||
name="group_name" |
|||
label="名称" |
|||
rules={[{ required: true, message: '请输入分组名称' }]} |
|||
/> |
|||
<ProFormText |
|||
name="description" |
|||
label="描述" |
|||
rules={[{ required: true, message: '请输入分组描述' }]} |
|||
/> |
|||
</ModalForm>, |
|||
]} |
|||
/>; |
|||
}; |
|||
|
|||
export default TagsTable; |
|||
@ -0,0 +1,353 @@ |
|||
import { addRule, history, removeRule, updateRule } from '@/services/ant-design-pro/api'; |
|||
import { DeleteOutlined, ExportOutlined, PlusOutlined } from '@ant-design/icons'; |
|||
import type { ActionType, ProColumns, ProDescriptionsItemProps } from '@ant-design/pro-components'; |
|||
import { |
|||
FooterToolbar, |
|||
ModalForm, |
|||
PageContainer, |
|||
ProDescriptions, |
|||
ProFormText, |
|||
ProFormTextArea, |
|||
ProTable, |
|||
} from '@ant-design/pro-components'; |
|||
import '@umijs/max'; |
|||
import { Button, Drawer, Image, Modal, Popconfirm, Typography, message } from 'antd'; |
|||
import React, { useRef, useState } from 'react'; |
|||
import type { FormValueType } from './components/UpdateForm'; |
|||
import UpdateForm from './components/UpdateForm'; |
|||
import { request } from '@umijs/max'; |
|||
|
|||
const { Text, Link } = Typography; |
|||
|
|||
|
|||
|
|||
/** |
|||
* @en-US Add node |
|||
* @zh-CN 添加节点 |
|||
* @param fields |
|||
*/ |
|||
const handleAdd = async (fields: API.RuleListItem) => { |
|||
const hide = message.loading('正在添加'); |
|||
try { |
|||
await addRule({ |
|||
...fields, |
|||
}); |
|||
hide(); |
|||
message.success('Added successfully'); |
|||
return true; |
|||
} catch (error) { |
|||
hide(); |
|||
message.error('Adding failed, please try again!'); |
|||
return false; |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* @en-US Update node |
|||
* @zh-CN 更新节点 |
|||
* |
|||
* @param fields |
|||
*/ |
|||
const handleUpdate = async (fields: FormValueType) => { |
|||
const hide = message.loading('Configuring'); |
|||
try { |
|||
await updateRule({ |
|||
name: fields.name, |
|||
desc: fields.desc, |
|||
key: fields.guid, |
|||
}); |
|||
hide(); |
|||
message.success('Configuration is successful'); |
|||
return true; |
|||
} catch (error) { |
|||
hide(); |
|||
message.error('Configuration failed, please try again!'); |
|||
return false; |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* Delete node |
|||
* @zh-CN 删除节点 |
|||
* |
|||
* @param selectedRows |
|||
*/ |
|||
const handleRemove = async (selectedRows: API.RuleListItem[]) => { |
|||
const hide = message.loading('正在删除'); |
|||
if (!selectedRows) return true; |
|||
try { |
|||
await removeRule({ |
|||
key: selectedRows.map((row) => row.key), |
|||
}); |
|||
hide(); |
|||
message.success('删除成功,即将刷新'); |
|||
return true; |
|||
} catch (error) { |
|||
hide(); |
|||
message.error('删除失败,请重试'); |
|||
return false; |
|||
} |
|||
}; |
|||
|
|||
const handleExport = async () => { |
|||
try { |
|||
const response = await request('https://867t766n6.zicp.fun/api/exportAccount', { |
|||
responseType: 'blob', |
|||
}); |
|||
const blob = new Blob([response], { type: 'text/csv' }); // 使用 response 而不是 response.data
|
|||
const url = window.URL.createObjectURL(blob); |
|||
const link = document.createElement('a'); |
|||
link.href = url; |
|||
link.setAttribute('download', 'export.csv'); |
|||
document.body.appendChild(link); |
|||
link.click(); |
|||
document.body.removeChild(link); // 移除创建的链接
|
|||
} catch (error) { |
|||
console.error('Error exporting file:', error); |
|||
} |
|||
}; |
|||
|
|||
const TableList: React.FC = () => { |
|||
/** |
|||
* @en-US Pop-up window of new window |
|||
* @zh-CN 新建窗口的弹窗 |
|||
* */ |
|||
const [createModalOpen, handleModalOpen] = useState<boolean>(false); |
|||
/** |
|||
* @en-US The pop-up window of the distribution update window |
|||
* @zh-CN 分布更新窗口的弹窗 |
|||
* */ |
|||
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false); |
|||
const [showDetail, setShowDetail] = useState<boolean>(false); |
|||
const actionRef = useRef<ActionType>(); |
|||
const [currentRow, setCurrentRow] = useState<API.RuleListItem>(); |
|||
const [selectedRowKeys, setSelectedRowKeys] = useState([]); |
|||
const [selectedRowsState, setSelectedRows] = useState<API.RuleListItem[]>([]); |
|||
const [historyModalVisible, setHistoryModalVisible] = useState(false); |
|||
const handleViewClick = (record) => { |
|||
setCurrentRow(record); |
|||
setHistoryModalVisible(true); |
|||
}; |
|||
|
|||
/** |
|||
* @en-US International configuration |
|||
* @zh-CN 国际化配置 |
|||
* */ |
|||
|
|||
const columns: ProColumns<API.RuleListItem>[] = [ |
|||
{ |
|||
title: '名称', |
|||
dataIndex: 'user_name', |
|||
render: (dom, entity) => { |
|||
return ( |
|||
<> |
|||
{/* <a |
|||
onClick={() => { |
|||
setCurrentRow(entity); |
|||
setShowDetail(true); |
|||
}} |
|||
> |
|||
{dom} |
|||
</a> */} |
|||
<Text type="success">{dom}</Text> |
|||
<div>{entity.name}</div> |
|||
</> |
|||
); |
|||
}, |
|||
}, |
|||
{ |
|||
title: '类型', |
|||
dataIndex: 'format', |
|||
valueType: 'select', |
|||
valueEnum: { |
|||
CF_TEXT: { |
|||
text: '文本', |
|||
}, |
|||
CF_BITMAP: { |
|||
text: '图片', |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
title: '内容', |
|||
dataIndex: 'content', |
|||
sorter: true, |
|||
hideInForm: true, |
|||
width: '60%', |
|||
}, |
|||
{ |
|||
title: '地址', |
|||
dataIndex: 'detected_address', |
|||
hideInSearch: true, |
|||
}, |
|||
{ |
|||
title: '时间', |
|||
sorter: true, |
|||
dataIndex: 'timestamp', |
|||
valueType: 'dateTime', |
|||
}, |
|||
{ |
|||
title: '操作', |
|||
dataIndex: 'option', |
|||
valueType: 'option', |
|||
render: (_, record) => [ |
|||
<Popconfirm |
|||
key={record.id} |
|||
title="确定要删除该行吗?" |
|||
onConfirm={async () => { |
|||
await handleRemove(record.id); // 直接传递代理ID
|
|||
actionRef.current?.reload(); |
|||
}} |
|||
okText="确定" |
|||
cancelText="取消" |
|||
> |
|||
<Button danger icon={<DeleteOutlined />} /> |
|||
</Popconfirm>, |
|||
], |
|||
}, |
|||
]; |
|||
return ( |
|||
<PageContainer> |
|||
<ProTable<API.RuleListItem, API.PageParams> |
|||
headerTitle="记录" |
|||
actionRef={actionRef} |
|||
rowKey="id" |
|||
// search={{
|
|||
// labelWidth: 120,
|
|||
// }}
|
|||
search={false} |
|||
toolBarRender={() => [ |
|||
<Button |
|||
type="primary" |
|||
key="export" |
|||
onClick={() => { |
|||
handleExport(); |
|||
}} |
|||
> |
|||
<ExportOutlined /> 导出 |
|||
</Button>, |
|||
]} |
|||
request={history} |
|||
columns={columns} |
|||
rowSelection={{ |
|||
preserveSelectedRowKeys: true, |
|||
selectedRowKeys, |
|||
onChange: (keys, selectedRows) => { |
|||
setSelectedRowKeys(keys); |
|||
setSelectedRows(selectedRows); |
|||
}, |
|||
}} |
|||
/> |
|||
{selectedRowsState?.length > 0 && ( |
|||
<FooterToolbar |
|||
extra={ |
|||
<div> |
|||
已选择{' '} |
|||
<a |
|||
style={{ |
|||
fontWeight: 600, |
|||
}} |
|||
> |
|||
{selectedRowsState.length} |
|||
</a>{' '} |
|||
项 |
|||
{/* <span> |
|||
<FormattedMessage |
|||
id="pages.searchTable.totalServiceCalls" |
|||
defaultMessage="Total number of service calls" |
|||
/>{' '} |
|||
{selectedRowsState.reduce((pre, item) => pre + item.callNo!, 0)}{' '} |
|||
<FormattedMessage id="pages.searchTable.tenThousand" defaultMessage="万" /> |
|||
</span> */} |
|||
</div> |
|||
} |
|||
> |
|||
<Button |
|||
onClick={async () => { |
|||
await handleRemove(selectedRowsState); |
|||
setSelectedRows([]); |
|||
actionRef.current?.reloadAndRest?.(); |
|||
}} |
|||
> |
|||
批量删除 |
|||
</Button> |
|||
</FooterToolbar> |
|||
)} |
|||
<ModalForm |
|||
title={'新建规则'} |
|||
width="400px" |
|||
open={createModalOpen} |
|||
onOpenChange={handleModalOpen} |
|||
onFinish={async (value) => { |
|||
const success = await handleAdd(value as API.RuleListItem); |
|||
if (success) { |
|||
handleModalOpen(false); |
|||
if (actionRef.current) { |
|||
actionRef.current.reload(); |
|||
} |
|||
} |
|||
}} |
|||
> |
|||
<ProFormText |
|||
rules={[ |
|||
{ |
|||
required: true, |
|||
message: '规则名称为必填项', |
|||
}, |
|||
]} |
|||
width="md" |
|||
name="name" |
|||
/> |
|||
<ProFormTextArea width="md" name="desc" /> |
|||
</ModalForm> |
|||
<UpdateForm |
|||
onSubmit={async (value) => { |
|||
const success = await handleUpdate({ |
|||
...value, |
|||
guid: currentRow?.guid, |
|||
}); |
|||
if (success) { |
|||
handleUpdateModalOpen(false); |
|||
setCurrentRow(undefined); |
|||
if (actionRef.current) { |
|||
actionRef.current.reload(); |
|||
} |
|||
} |
|||
}} |
|||
onCancel={() => { |
|||
handleUpdateModalOpen(false); |
|||
if (!showDetail) { |
|||
setCurrentRow(undefined); |
|||
} |
|||
}} |
|||
updateModalOpen={updateModalOpen} |
|||
values={currentRow || {}} |
|||
/> |
|||
|
|||
<Drawer |
|||
width={600} |
|||
open={showDetail} |
|||
onClose={() => { |
|||
setCurrentRow(undefined); |
|||
setShowDetail(false); |
|||
}} |
|||
closable={false} |
|||
> |
|||
{currentRow?.name && ( |
|||
<ProDescriptions<API.RuleListItem> |
|||
column={2} |
|||
title={currentRow?.name} |
|||
request={async () => ({ |
|||
data: currentRow || {}, |
|||
})} |
|||
params={{ |
|||
id: currentRow?.name, |
|||
}} |
|||
columns={columns as ProDescriptionsItemProps<API.RuleListItem>[]} |
|||
/> |
|||
)} |
|||
</Drawer> |
|||
</PageContainer> |
|||
); |
|||
}; |
|||
export default TableList; |
|||
@ -0,0 +1,84 @@ |
|||
import { fetchStats } from '@/services/ant-design-pro/api'; |
|||
import React, { useEffect, useState } from 'react'; |
|||
import { ProCard, StatisticCard } from '@ant-design/pro-components'; |
|||
import { Button, message } from 'antd'; |
|||
import axios from 'axios'; |
|||
|
|||
interface StatsData { |
|||
online: number; |
|||
offline: number; |
|||
total: number; |
|||
} |
|||
|
|||
const StatsPage: React.FC = () => { |
|||
const [stats, setStats] = useState<StatsData | null>(null); |
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
// 通过 request 属性模拟远程请求
|
|||
// const fetchStats = async () => {
|
|||
// try {
|
|||
// setLoading(true);
|
|||
// const response = await axios.get<StatsData>('https://867t766n6.zicp.fun/computers/stats'); // 更新为实际的 API 地址
|
|||
// setStats(response.data);
|
|||
// setLoading(false);
|
|||
// } catch (error) {
|
|||
// message.error('无法获取统计数据,请稍后再试。');
|
|||
// setLoading(false);
|
|||
// }
|
|||
// };
|
|||
|
|||
// 获取统计数据
|
|||
const getStats = async () => { |
|||
setLoading(true); |
|||
try { |
|||
const response = await fetchStats(); |
|||
setStats(response); |
|||
} catch (error) { |
|||
message.error('无法获取统计数据,请稍后再试。'); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
// 初始化时获取统计数据
|
|||
useEffect(() => { |
|||
getStats(); |
|||
}, []); |
|||
|
|||
return ( |
|||
<div style={{ padding: 24 }}> |
|||
<Button type="primary" onClick={fetchStats} style={{ marginBottom: 16 }}> |
|||
刷新统计数据 |
|||
</Button> |
|||
<ProCard |
|||
gutter={[16, 16]} |
|||
loading={loading} |
|||
title="设备状态统计" |
|||
headerBordered |
|||
> |
|||
<StatisticCard |
|||
statistic={{ |
|||
title: '在线设备数量', |
|||
value: stats ? stats.online : 0, |
|||
valueStyle: { color: '#3f8600' }, |
|||
}} |
|||
/> |
|||
<StatisticCard |
|||
statistic={{ |
|||
title: '离线设备数量', |
|||
value: stats ? stats.offline : 0, |
|||
valueStyle: { color: '#cf1322' }, |
|||
}} |
|||
/> |
|||
<StatisticCard |
|||
statistic={{ |
|||
title: '总设备数量', |
|||
value: stats ? stats.total : 0, |
|||
}} |
|||
/> |
|||
</ProCard> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default StatsPage; |
|||
Loading…
Reference in new issue