Browse Source

Pending changes exported from your codespace

pull/11230/head
mmmww 2 years ago
parent
commit
fe8a2e2c59
  1. 20
      config/config.ts
  2. 4
      config/defaultSettings.ts
  3. 63
      config/routes.ts
  4. 10
      go.mod
  5. 6
      go.sum
  6. 2
      public/logo.svg
  7. 1
      public/logo2.svg
  8. 29
      src/app.tsx
  9. 30
      src/components/Footer/index.tsx
  10. 12
      src/components/RightContent/index.tsx
  11. 5
      src/components/index.ts
  12. 21
      src/global.tsx
  13. 2
      src/locales/bn-BD/pages.ts
  14. 2
      src/locales/en-US/pages.ts
  15. 2
      src/locales/id-ID/pages.ts
  16. 2
      src/locales/pt-BR/pages.ts
  17. 4
      src/locales/zh-CN/menu.ts
  18. 14
      src/locales/zh-CN/pages.ts
  19. 2
      src/locales/zh-TW/pages.ts
  20. 8
      src/pages/404.tsx
  21. 31
      src/pages/Admin.tsx
  22. 267
      src/pages/TableList/components/UpdateForm.tsx
  23. 145
      src/pages/TableList/group.tsx
  24. 465
      src/pages/TableList/index.tsx
  25. 353
      src/pages/TableList/list.tsx
  26. 150
      src/pages/User/Login/index.tsx
  27. 70
      src/pages/Welcome.tsx
  28. 84
      src/pages/dashboard.tsx
  29. 40
      src/services/ant-design-pro/api.ts
  30. 4
      src/services/ant-design-pro/typings.d.ts

20
config/config.ts

@ -4,9 +4,7 @@ import { join } from 'path';
import defaultSettings from './defaultSettings'; import defaultSettings from './defaultSettings';
import proxy from './proxy'; import proxy from './proxy';
import routes from './routes'; import routes from './routes';
const { REACT_APP_ENV = 'dev' } = process.env; const { REACT_APP_ENV = 'dev' } = process.env;
export default defineConfig({ export default defineConfig({
/** /**
* @name hash * @name hash
@ -14,7 +12,6 @@ export default defineConfig({
* @doc https://umijs.org/docs/api/config#hash * @doc https://umijs.org/docs/api/config#hash
*/ */
hash: true, hash: true,
/** /**
* @name * @name
* @description ie11 使 * @description ie11 使
@ -76,7 +73,7 @@ export default defineConfig({
* @name layout * @name layout
* @doc https://umijs.org/docs/max/layout-menu * @doc https://umijs.org/docs/max/layout-menu
*/ */
title: 'Ant Design Pro', title: '在线管理系统',
layout: { layout: {
locale: true, locale: true,
...defaultSettings, ...defaultSettings,
@ -93,15 +90,7 @@ export default defineConfig({
/** /**
* @name * @name
* @doc https://umijs.org/docs/max/i18n * @doc https://umijs.org/docs/max/i18n
*/ */ /**
locale: {
// default zh-CN
default: 'zh-CN',
antd: true,
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
},
/**
* @name antd * @name antd
* @description babel import * @description babel import
* @doc https://umijs.org/docs/max/antd#antd * @doc https://umijs.org/docs/max/antd#antd
@ -125,7 +114,10 @@ export default defineConfig({
*/ */
headScripts: [ headScripts: [
// 解决首次加载时白屏的问题 // 解决首次加载时白屏的问题
{ src: '/scripts/loading.js', async: true }, {
src: '/scripts/loading.js',
async: true,
},
], ],
//================ pro 插件配置 ================= //================ pro 插件配置 =================
presets: ['umi-presets-pro'], presets: ['umi-presets-pro'],

4
config/defaultSettings.ts

@ -15,9 +15,9 @@ const Settings: ProLayoutProps & {
fixedHeader: false, fixedHeader: false,
fixSiderbar: true, fixSiderbar: true,
colorWeak: false, colorWeak: false,
title: 'Ant Design Pro', title: '在线管理系统',
pwa: true, pwa: true,
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', logo: '/logo.svg',
iconfontUrl: '', iconfontUrl: '',
token: { token: {
// 参见ts声明,demo 见文档,通过token 修改样式 // 参见ts声明,demo 见文档,通过token 修改样式

63
config/routes.ts

@ -1,63 +1,26 @@
/** import component from "@/locales/bn-BD/component";
* @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
*/
export default [ export default [
{ {
path: '/user', path: '/user',
layout: false, layout: false,
routes: [ routes: [{ name: '登录', path: '/user/login', component: './User/Login' }],
{
name: 'login',
path: '/user/login',
component: './User/Login',
},
],
},
{
path: '/welcome',
name: 'welcome',
icon: 'smile',
component: './Welcome',
}, },
{ path: '/welcome', name: '欢迎', icon: 'smile', component: './Welcome' },
{ path: '/dashboard/workplace', name: '工作台', icon: 'dashboard', component: './dashboard'},
{ {
path: '/admin', path: '/admin',
name: 'admin', name: '管理页',
icon: 'crown', icon: 'crown',
access: 'canAdmin', access: 'canAdmin',
routes: [ routes: [
{ { path: '/admin', redirect: '/admin/sub-page' },
path: '/admin', { path: '/admin/sub-page', name: '二级管理页', component: './Admin' },
redirect: '/admin/sub-page',
},
{
path: '/admin/sub-page',
name: 'sub-page',
component: './Admin',
},
], ],
}, },
{ { name: '设备列表', icon: 'table', path: '/list/device-list', component: './TableList' },
name: 'list.table-list', { name: '设备分组', icon: 'table', path: '/list/group-list', component: './TableList/group' },
icon: 'table', { name: '记录列表', icon: 'table', path: '/list/basic-list', component: './TableList/list' },
path: '/list', { path: '/', redirect: '/welcome' },
component: './TableList', { path: '*', layout: false, component: './404' },
},
{
path: '/',
redirect: '/welcome',
},
{
path: '*',
layout: false,
component: './404',
},
]; ];

10
go.mod

@ -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

6
go.sum

@ -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=

2
public/logo.svg

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg> <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1714968135312" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4494" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#68CDBE" p-id="4495"></path><path d="M172.8 225.6c52.8-3.2 105.6-11.2 156.8-24C395.2 185.6 464 128 513.6 128c60.8 0 126.4 56 184 73.6 51.2 12.8 104 20.8 156.8 24v182.4c0 224-134.4 427.2-340.8 510.4-204.8-83.2-340.8-284.8-340.8-508.8V225.6z" fill="#FFFFFF" p-id="4496"></path><path d="M561.6 350.4c-72-52.8-174.4-36.8-227.2 35.2S297.6 560 369.6 612.8c65.6 48 153.6 40 209.6-14.4l80 59.2c11.2 8 28.8 6.4 36.8-6.4 8-11.2 6.4-28.8-6.4-36.8l-80-59.2c36.8-70.4 17.6-156.8-48-204.8z m-12.8 192c-33.6 46.4-99.2 56-144 22.4-46.4-33.6-56-97.6-22.4-144s99.2-56 144-22.4 56 97.6 22.4 144z" fill="#68CDBE" p-id="4497"></path></svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1017 B

1
public/logo2.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

29
src/app.tsx

@ -1,13 +1,12 @@
import { Footer, Question, SelectLang, AvatarDropdown, AvatarName } from '@/components'; import { AvatarDropdown, AvatarName, Footer, Question } from '@/components';
import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api';
import { LinkOutlined } from '@ant-design/icons'; import { LinkOutlined } from '@ant-design/icons';
import type { Settings as LayoutSettings } from '@ant-design/pro-components'; import type { Settings as LayoutSettings } from '@ant-design/pro-components';
import { SettingDrawer } from '@ant-design/pro-components'; import { SettingDrawer } from '@ant-design/pro-components';
import type { RunTimeLayoutConfig } from '@umijs/max'; import type { RunTimeLayoutConfig } from '@umijs/max';
import { history, Link } from '@umijs/max'; import { Link, history } from '@umijs/max';
import defaultSettings from '../config/defaultSettings'; import defaultSettings from '../config/defaultSettings';
import { errorConfig } from './requestErrorConfig'; import { errorConfig } from './requestErrorConfig';
import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api';
import React from 'react';
const isDev = process.env.NODE_ENV === 'development'; const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login'; const loginPath = '/user/login';
@ -50,7 +49,7 @@ export async function getInitialState(): Promise<{
// ProLayout 支持的api https://procomponents.ant.design/components/layout // ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => { export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
return { return {
actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />], actionsRender: () => [<Question key="doc" />],
avatarProps: { avatarProps: {
src: initialState?.currentUser?.avatar, src: initialState?.currentUser?.avatar,
title: <AvatarName />, title: <AvatarName />,
@ -131,6 +130,26 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
* axios ahooks useRequest * axios ahooks useRequest
* @doc https://umijs.org/docs/max/request#配置 * @doc https://umijs.org/docs/max/request#配置
*/ */
/**
*
* request进行运行时配置umi-request的全局配置
* umi中引入request或者useRequest直接使用便
*/
/** 请求拦截 */
const requestInterceptor = (url: any, options: any): any => {
const headers = {
...options.headers,
//此处需要修改一下逻辑判断
Authorization: localStorage.getItem('authToken') || '1213',
};
return {
url: url,
options: { ...options, headers: headers },
};
};
export const request = { export const request = {
...errorConfig, ...errorConfig,
requestInterceptors: [requestInterceptor], //请求拦截
}; };

30
src/components/Footer/index.tsx

@ -9,22 +9,22 @@ const Footer: React.FC = () => {
background: 'none', background: 'none',
}} }}
links={[ links={[
// {
// key: 'Pro',
// title: 'Pro',
// href: '',
// blankTarget: true,
// },
// {
// key: '@@@',
// title: <GithubOutlined />,
// href: '',
// blankTarget: true,
// },
{ {
key: 'Ant Design Pro', key: 'copyright',
title: 'Ant Design Pro', title: "2024",
href: 'https://pro.ant.design', href: '',
blankTarget: true,
},
{
key: 'github',
title: <GithubOutlined />,
href: 'https://github.com/ant-design/ant-design-pro',
blankTarget: true,
},
{
key: 'Ant Design',
title: 'Ant Design',
href: 'https://ant.design',
blankTarget: true, blankTarget: true,
}, },
]} ]}

12
src/components/RightContent/index.tsx

@ -1,9 +1,6 @@
import { QuestionCircleOutlined } from '@ant-design/icons'; import { QuestionCircleOutlined } from '@ant-design/icons';
import { SelectLang as UmiSelectLang } from '@umijs/max'; import '@umijs/max';
import React from 'react';
export type SiderTheme = 'light' | 'dark'; export type SiderTheme = 'light' | 'dark';
export const SelectLang = () => { export const SelectLang = () => {
return ( return (
<UmiSelectLang <UmiSelectLang
@ -13,7 +10,6 @@ export const SelectLang = () => {
/> />
); );
}; };
export const Question = () => { export const Question = () => {
return ( return (
<div <div
@ -21,9 +17,9 @@ export const Question = () => {
display: 'flex', display: 'flex',
height: 26, height: 26,
}} }}
onClick={() => { // onClick={() => {
window.open('https://pro.ant.design/docs/getting-started'); // window.open('https://pro.ant.design/docs/getting-started');
}} // }}
> >
<QuestionCircleOutlined /> <QuestionCircleOutlined />
</div> </div>

5
src/components/index.ts

@ -6,7 +6,6 @@
* *
*/ */
import Footer from './Footer'; import Footer from './Footer';
import { Question, SelectLang } from './RightContent'; import { Question } from './RightContent';
import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown'; import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown';
export { AvatarDropdown, AvatarName, Footer, Question };
export { Footer, Question, SelectLang, AvatarDropdown, AvatarName };

21
src/global.tsx

@ -1,10 +1,8 @@
import { useIntl } from '@umijs/max'; import '@umijs/max';
import { Button, message, notification } from 'antd'; import { Button, message, notification } from 'antd';
import defaultSettings from '../config/defaultSettings'; import defaultSettings from '../config/defaultSettings';
const { pwa } = defaultSettings; const { pwa } = defaultSettings;
const isHttps = document.location.protocol === 'https:'; const isHttps = document.location.protocol === 'https:';
const clearCache = () => { const clearCache = () => {
// remove all caches // remove all caches
if (window.caches) { if (window.caches) {
@ -23,7 +21,7 @@ const clearCache = () => {
if (pwa) { if (pwa) {
// Notify user if offline now // Notify user if offline now
window.addEventListener('sw.offline', () => { window.addEventListener('sw.offline', () => {
message.warning(useIntl().formatMessage({ id: 'app.pwa.offline' })); message.warning('当前处于离线状态');
}); });
// Pop up a prompt on the page asking the user if they want to use the latest version // Pop up a prompt on the page asking the user if they want to use the latest version
@ -46,9 +44,13 @@ if (pwa) {
resolve(msgEvent.data); resolve(msgEvent.data);
} }
}; };
worker.postMessage({ type: 'skip-waiting' }, [channel.port2]); worker.postMessage(
{
type: 'skip-waiting',
},
[channel.port2],
);
}); });
clearCache(); clearCache();
window.location.reload(); window.location.reload();
return true; return true;
@ -62,12 +64,12 @@ if (pwa) {
reloadSW(); reloadSW();
}} }}
> >
{useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })} {'刷新'}
</Button> </Button>
); );
notification.open({ notification.open({
message: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }), message: '有新内容',
description: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }), description: '请点击“刷新”按钮或者手动刷新页面',
btn, btn,
key, key,
onClose: async () => null, onClose: async () => null,
@ -86,6 +88,5 @@ if (pwa) {
serviceWorker.getRegistration().then((sw) => { serviceWorker.getRegistration().then((sw) => {
if (sw) sw.unregister(); if (sw) sw.unregister();
}); });
clearCache(); clearCache();
} }

2
src/locales/bn-BD/pages.ts

@ -5,7 +5,7 @@ export default {
'pages.login.accountLogin.errorMessage': 'ভুল ব্যবহারকারীর নাম/পাসওয়ার্ড(admin/ant.design)', 'pages.login.accountLogin.errorMessage': 'ভুল ব্যবহারকারীর নাম/পাসওয়ার্ড(admin/ant.design)',
'pages.login.failure': 'লগইন ব্যর্থ হয়েছে। আবার চেষ্টা করুন!', 'pages.login.failure': 'লগইন ব্যর্থ হয়েছে। আবার চেষ্টা করুন!',
'pages.login.success': 'সফল লগইন!', 'pages.login.success': 'সফল লগইন!',
'pages.login.username.placeholder': 'ব্যবহারকারীর নাম: admin or user', 'pages.login.username.placeholder': 'ব্যবহারকারীর নাম',
'pages.login.username.required': 'আপনার ব্যবহারকারীর নাম ইনপুট করুন!', 'pages.login.username.required': 'আপনার ব্যবহারকারীর নাম ইনপুট করুন!',
'pages.login.password.placeholder': 'পাসওয়ার্ড: ant.design', 'pages.login.password.placeholder': 'পাসওয়ার্ড: ant.design',
'pages.login.password.required': 'আপনার পাসওয়ার্ড ইনপুট করুন!', 'pages.login.password.required': 'আপনার পাসওয়ার্ড ইনপুট করুন!',

2
src/locales/en-US/pages.ts

@ -5,7 +5,7 @@ export default {
'pages.login.accountLogin.errorMessage': 'Incorrect username/password(admin/ant.design)', 'pages.login.accountLogin.errorMessage': 'Incorrect username/password(admin/ant.design)',
'pages.login.failure': 'Login failed, please try again!', 'pages.login.failure': 'Login failed, please try again!',
'pages.login.success': 'Login successful!', 'pages.login.success': 'Login successful!',
'pages.login.username.placeholder': 'Username: admin or user', 'pages.login.username.placeholder': 'Username',
'pages.login.username.required': 'Please input your username!', 'pages.login.username.required': 'Please input your username!',
'pages.login.password.placeholder': 'Password: ant.design', 'pages.login.password.placeholder': 'Password: ant.design',
'pages.login.password.required': 'Please input your password!', 'pages.login.password.required': 'Please input your password!',

2
src/locales/id-ID/pages.ts

@ -5,7 +5,7 @@ export default {
'pages.login.accountLogin.errorMessage': 'Nama pengguna dan kata sandi salah(admin/ant.design)', 'pages.login.accountLogin.errorMessage': 'Nama pengguna dan kata sandi salah(admin/ant.design)',
'pages.login.failure': 'Log masuk gagal, silakan coba lagi!', 'pages.login.failure': 'Log masuk gagal, silakan coba lagi!',
'pages.login.success': 'Login berhasil!', 'pages.login.success': 'Login berhasil!',
'pages.login.username.placeholder': 'nama pengguna: admin atau user', 'pages.login.username.placeholder': 'nama pengguna',
'pages.login.username.required': 'Nama pengguna harus diisi!', 'pages.login.username.required': 'Nama pengguna harus diisi!',
'pages.login.password.placeholder': 'kata sandi: ant.design', 'pages.login.password.placeholder': 'kata sandi: ant.design',
'pages.login.password.required': 'Kata sandi harus diisi!', 'pages.login.password.required': 'Kata sandi harus diisi!',

2
src/locales/pt-BR/pages.ts

@ -5,7 +5,7 @@ export default {
'pages.login.accountLogin.errorMessage': 'usuário/senha incorreto(admin/ant.design)', 'pages.login.accountLogin.errorMessage': 'usuário/senha incorreto(admin/ant.design)',
'pages.login.failure': 'Login falhou, por favor tente novamente!', 'pages.login.failure': 'Login falhou, por favor tente novamente!',
'pages.login.success': 'Login efetuado com sucesso!', 'pages.login.success': 'Login efetuado com sucesso!',
'pages.login.username.placeholder': 'Usuário: admin or user', 'pages.login.username.placeholder': 'Usuário',
'pages.login.username.required': 'Por favor insira seu usuário!', 'pages.login.username.required': 'Por favor insira seu usuário!',
'pages.login.password.placeholder': 'Senha: ant.design', 'pages.login.password.placeholder': 'Senha: ant.design',
'pages.login.password.required': 'Por favor insira sua senha!', 'pages.login.password.required': 'Por favor insira sua senha!',

4
src/locales/zh-CN/menu.ts

@ -22,8 +22,8 @@ export default {
'menu.form.step-form.result': '分步表单(完成)', 'menu.form.step-form.result': '分步表单(完成)',
'menu.form.advanced-form': '高级表单', 'menu.form.advanced-form': '高级表单',
'menu.list': '列表页', 'menu.list': '列表页',
'menu.list.table-list': '查询表格', 'menu.list.table-list': '设备列表',
'menu.list.basic-list': '标准列表', 'menu.list.basic-list': '记录列表',
'menu.list.card-list': '卡片列表', 'menu.list.card-list': '卡片列表',
'menu.list.search-list': '搜索列表', 'menu.list.search-list': '搜索列表',
'menu.list.search-list.articles': '搜索列表(文章)', 'menu.list.search-list.articles': '搜索列表(文章)',

14
src/locales/zh-CN/pages.ts

@ -1,12 +1,12 @@
export default { export default {
'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范', 'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范',
'pages.login.accountLogin.tab': '账户密码登录', 'pages.login.accountLogin.tab': '账户密码登录',
'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/ant.design)', 'pages.login.accountLogin.errorMessage': '错误的用户名和密码',
'pages.login.failure': '登录失败,请重试!', 'pages.login.failure': '登录失败,请重试!',
'pages.login.success': '登录成功!', 'pages.login.success': '登录成功!',
'pages.login.username.placeholder': '用户名: admin or user', 'pages.login.username.placeholder': '用户名',
'pages.login.username.required': '用户名是必填项!', 'pages.login.username.required': '用户名是必填项!',
'pages.login.password.placeholder': '密码: ant.design', 'pages.login.password.placeholder': '密码',
'pages.login.password.required': '密码是必填项!', 'pages.login.password.required': '密码是必填项!',
'pages.login.phoneLogin.tab': '手机号登录', 'pages.login.phoneLogin.tab': '手机号登录',
'pages.login.phoneLogin.errorMessage': '验证码错误', 'pages.login.phoneLogin.errorMessage': '验证码错误',
@ -31,7 +31,7 @@ export default {
'pages.searchTable.createForm.newRule': '新建规则', 'pages.searchTable.createForm.newRule': '新建规则',
'pages.searchTable.updateForm.ruleConfig': '规则配置', 'pages.searchTable.updateForm.ruleConfig': '规则配置',
'pages.searchTable.updateForm.basicConfig': '基本信息', 'pages.searchTable.updateForm.basicConfig': '基本信息',
'pages.searchTable.updateForm.ruleName.nameLabel': '规则名称', 'pages.searchTable.updateForm.ruleName.nameLabel': '名称',
'pages.searchTable.updateForm.ruleName.nameRules': '请输入规则名称!', 'pages.searchTable.updateForm.ruleName.nameRules': '请输入规则名称!',
'pages.searchTable.updateForm.ruleDesc.descLabel': '规则描述', 'pages.searchTable.updateForm.ruleDesc.descLabel': '规则描述',
'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '请输入至少五个字符', 'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '请输入至少五个字符',
@ -45,10 +45,10 @@ export default {
'pages.searchTable.updateForm.schedulingPeriod.timeRules': '请选择开始时间!', 'pages.searchTable.updateForm.schedulingPeriod.timeRules': '请选择开始时间!',
'pages.searchTable.titleDesc': '描述', 'pages.searchTable.titleDesc': '描述',
'pages.searchTable.ruleName': '规则名称为必填项', 'pages.searchTable.ruleName': '规则名称为必填项',
'pages.searchTable.titleCallNo': '服务调用次数', 'pages.searchTable.titleCallNo': '本机IP',
'pages.searchTable.titleStatus': '状态', 'pages.searchTable.titleStatus': '状态',
'pages.searchTable.nameStatus.default': '关闭', 'pages.searchTable.nameStatus.default': '离线',
'pages.searchTable.nameStatus.running': '运行中', 'pages.searchTable.nameStatus.running': '在线',
'pages.searchTable.nameStatus.online': '已上线', 'pages.searchTable.nameStatus.online': '已上线',
'pages.searchTable.nameStatus.abnormal': '异常', 'pages.searchTable.nameStatus.abnormal': '异常',
'pages.searchTable.titleUpdatedAt': '上次调度时间', 'pages.searchTable.titleUpdatedAt': '上次调度时间',

2
src/locales/zh-TW/pages.ts

@ -4,7 +4,7 @@ export default {
'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼(admin/ant.design)', 'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼(admin/ant.design)',
'pages.login.failure': '登錄失敗,請重試!', 'pages.login.failure': '登錄失敗,請重試!',
'pages.login.success': '登錄成功!', 'pages.login.success': '登錄成功!',
'pages.login.username.placeholder': '用戶名: admin or user', 'pages.login.username.placeholder': '用戶名',
'pages.login.username.required': '用戶名是必填項!', 'pages.login.username.required': '用戶名是必填項!',
'pages.login.password.placeholder': '密碼: ant.design', 'pages.login.password.placeholder': '密碼: ant.design',
'pages.login.password.required': '密碼是必填項!', 'pages.login.password.required': '密碼是必填項!',

8
src/pages/404.tsx

@ -1,18 +1,16 @@
import { history, useIntl } from '@umijs/max'; import { history } from '@umijs/max';
import { Button, Result } from 'antd'; import { Button, Result } from 'antd';
import React from 'react'; import React from 'react';
const NoFoundPage: React.FC = () => ( const NoFoundPage: React.FC = () => (
<Result <Result
status="404" status="404"
title="404" title="404"
subTitle={useIntl().formatMessage({ id: 'pages.404.subTitle' })} subTitle={'抱歉,您访问的页面不存在。'}
extra={ extra={
<Button type="primary" onClick={() => history.push('/')}> <Button type="primary" onClick={() => history.push('/')}>
{useIntl().formatMessage({ id: 'pages.404.buttonText' })} {'返回首页'}
</Button> </Button>
} }
/> />
); );
export default NoFoundPage; export default NoFoundPage;

31
src/pages/Admin.tsx

@ -1,24 +1,14 @@
import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons'; import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max'; import '@umijs/max';
import { Alert, Card, Typography } from 'antd'; import { Alert, Card, Typography } from 'antd';
import React from 'react'; import React from 'react';
const Admin: React.FC = () => { const Admin: React.FC = () => {
const intl = useIntl();
return ( return (
<PageContainer <PageContainer content={' 这个页面只有 admin 权限才能查看'}>
content={intl.formatMessage({
id: 'pages.admin.subPage.title',
defaultMessage: 'This page can only be viewed by admin',
})}
>
<Card> <Card>
<Alert <Alert
message={intl.formatMessage({ message={'更快更强的重型组件,已经发布。'}
id: 'pages.welcome.alertMessage',
defaultMessage: 'Faster and stronger heavy-duty components have been released.',
})}
type="success" type="success"
showIcon showIcon
banner banner
@ -27,11 +17,21 @@ const Admin: React.FC = () => {
marginBottom: 48, marginBottom: 48,
}} }}
/> />
<Typography.Title level={2} style={{ textAlign: 'center' }}> <Typography.Title
level={2}
style={{
textAlign: 'center',
}}
>
<SmileTwoTone /> Ant Design Pro <HeartTwoTone twoToneColor="#eb2f96" /> You <SmileTwoTone /> Ant Design Pro <HeartTwoTone twoToneColor="#eb2f96" /> You
</Typography.Title> </Typography.Title>
</Card> </Card>
<p style={{ textAlign: 'center', marginTop: 24 }}> <p
style={{
textAlign: 'center',
marginTop: 24,
}}
>
Want to add more pages? Please refer to{' '} Want to add more pages? Please refer to{' '}
<a href="https://pro.ant.design/docs/block-cn" target="_blank" rel="noopener noreferrer"> <a href="https://pro.ant.design/docs/block-cn" target="_blank" rel="noopener noreferrer">
use block use block
@ -41,5 +41,4 @@ const Admin: React.FC = () => {
</PageContainer> </PageContainer>
); );
}; };
export default Admin; export default Admin;

267
src/pages/TableList/components/UpdateForm.tsx

@ -1,13 +1,7 @@
import { import { ProFormText, ProFormTextArea, ProFormSelect, ModalForm } from '@ant-design/pro-components';
ProFormDateTimePicker, import '@umijs/max';
ProFormRadio, import { Button } from 'antd';
ProFormSelect, import { request } from '@umijs/max';
ProFormText,
ProFormTextArea,
StepsForm,
} from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max';
import { Modal } from 'antd';
import React from 'react'; import React from 'react';
export type FormValueType = { export type FormValueType = {
@ -16,6 +10,7 @@ export type FormValueType = {
type?: string; type?: string;
time?: string; time?: string;
frequency?: string; frequency?: string;
group?: number; // 分组选项的 ID
} & Partial<API.RuleListItem>; } & Partial<API.RuleListItem>;
export type UpdateFormProps = { export type UpdateFormProps = {
@ -26,183 +21,91 @@ export type UpdateFormProps = {
}; };
const UpdateForm: React.FC<UpdateFormProps> = (props) => { const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const intl = useIntl(); const { updateModalOpen, onCancel, onSubmit, values } = props;
const handleFinish = async (formValues: FormValueType) => {
await onSubmit(formValues);
};
return ( return (
<StepsForm <ModalForm<FormValueType>
stepsProps={{ title="修改名称"
size: 'small', width={640}
open={updateModalOpen}
modalProps={{
destroyOnClose: true,
onCancel: () => onCancel(),
}}
onFinish={handleFinish}
initialValues={{
name: values.name,
desc: values.desc,
group: values.group_id, // 初始化时设定分组选项
}} }}
stepsFormRender={(dom, submitter) => { submitter={{
return ( render: (_, dom) => (
<Modal <>
width={640} <Button key="cancel" onClick={() => onCancel()}>
bodyStyle={{ padding: '32px 40px 48px' }}
destroyOnClose </Button>
title={intl.formatMessage({ {dom.pop()}
id: 'pages.searchTable.updateForm.ruleConfig', </>
defaultMessage: '规则配置', ),
})}
open={props.updateModalOpen}
footer={submitter}
onCancel={() => {
props.onCancel();
}}
>
{dom}
</Modal>
);
}} }}
onFinish={props.onSubmit}
> >
<StepsForm.StepForm <ProFormText
initialValues={{ name="name"
name: props.values.name, label="名称"
desc: props.values.desc, rules={[
}} {
title={intl.formatMessage({ required: true,
id: 'pages.searchTable.updateForm.basicConfig', message: '请输入名称!',
defaultMessage: '基本信息', },
})} ]}
> // width="md"
<ProFormText />
name="name" <ProFormTextArea
label={intl.formatMessage({ name="desc"
id: 'pages.searchTable.updateForm.ruleName.nameLabel', label="描述"
defaultMessage: '规则名称', rules={[
})} {
width="md" required: true,
rules={[ message: '请输入至少五个字符的描述!',
{ min: 5,
required: true, },
message: ( ]}
<FormattedMessage // width="md"
id="pages.searchTable.updateForm.ruleName.nameRules" />
defaultMessage="请输入规则名称!" <ProFormSelect
/> name="group"
), label="分组"
}, request={async () => {
]} try {
/> const response = await request('https://867t766n6.zicp.fun/groups', {
<ProFormTextArea method: 'GET'
name="desc" }
width="md" );
label={intl.formatMessage({ const { data } = response;
id: 'pages.searchTable.updateForm.ruleDesc.descLabel',
defaultMessage: '规则描述', // 返回一个包含 { label, value } 键值对的数组
})} return data.map((group: { id: number; group_name: string }) => ({
placeholder={intl.formatMessage({ label: group.group_name,
id: 'pages.searchTable.updateForm.ruleDesc.descPlaceholder', value: group.id,
defaultMessage: '请输入至少五个字符', }));
})} } catch (error) {
rules={[ console.error('获取分组数据失败', error);
{ return [];
required: true, }
message: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleDesc.descRules"
defaultMessage="请输入至少五个字符的规则描述!"
/>
),
min: 5,
},
]}
/>
</StepsForm.StepForm>
<StepsForm.StepForm
initialValues={{
target: '0',
template: '0',
}}
title={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleProps.title',
defaultMessage: '配置规则属性',
})}
>
<ProFormSelect
name="target"
width="md"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.object',
defaultMessage: '监控对象',
})}
valueEnum={{
0: '表一',
1: '表二',
}}
/>
<ProFormSelect
name="template"
width="md"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleProps.templateLabel',
defaultMessage: '规则模板',
})}
valueEnum={{
0: '规则模板一',
1: '规则模板二',
}}
/>
<ProFormRadio.Group
name="type"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleProps.typeLabel',
defaultMessage: '规则类型',
})}
options={[
{
value: '0',
label: '强',
},
{
value: '1',
label: '弱',
},
]}
/>
</StepsForm.StepForm>
<StepsForm.StepForm
initialValues={{
type: '1',
frequency: 'month',
}} }}
title={intl.formatMessage({ // rules={[
id: 'pages.searchTable.updateForm.schedulingPeriod.title', // {
defaultMessage: '设定调度周期', // required: true,
})} // message: '请选择一个分组!',
> // },
<ProFormDateTimePicker // ]}
name="time" // width="md"
width="md" />
label={intl.formatMessage({ </ModalForm>
id: 'pages.searchTable.updateForm.schedulingPeriod.timeLabel',
defaultMessage: '开始时间',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.updateForm.schedulingPeriod.timeRules"
defaultMessage="请选择开始时间!"
/>
),
},
]}
/>
<ProFormSelect
name="frequency"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.object',
defaultMessage: '监控对象',
})}
width="md"
valueEnum={{
month: '月',
week: '周',
}}
/>
</StepsForm.StepForm>
</StepsForm>
); );
}; };

145
src/pages/TableList/group.tsx

@ -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;

465
src/pages/TableList/index.tsx

@ -1,20 +1,92 @@
import { addRule, removeRule, rule, updateRule } from '@/services/ant-design-pro/api'; import { addRule, removeRule, rule, updateRule } from '@/services/ant-design-pro/api';
import { PlusOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns, ProDescriptionsItemProps } from '@ant-design/pro-components'; import type { ActionType, ProColumns, ProDescriptionsItemProps } from '@ant-design/pro-components';
import { import {
FooterToolbar, FooterToolbar,
ModalForm, ModalForm,
PageContainer, PageContainer,
ProDescriptions, ProDescriptions,
ProFormSelect,
ProFormText, ProFormText,
ProFormTextArea, ProFormTextArea,
ProTable, ProTable,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max'; import '@umijs/max';
import { Button, Drawer, Input, message } from 'antd'; import { Button, Drawer, Image, Input, Modal, Popconfirm, Space, Tag, Tooltip, Typography, message } from 'antd';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import type { FormValueType } from './components/UpdateForm'; import type { FormValueType } from './components/UpdateForm';
import UpdateForm from './components/UpdateForm'; import UpdateForm from './components/UpdateForm';
import { request } from '@umijs/max';
import { DeleteOutlined } from '@ant-design/icons';
const { Text, Link } = Typography;
const HistoryModalTable = ({ visible, guid, onCancel }) => {
return (
<Modal title="查看记录" open={visible} onCancel={onCancel} footer={null} width={1000} destroyOnClose>
<ProTable
columns={[
{
title: '时间',
dataIndex: 'timestamp',
valueType: 'dateTime',
},
{
title: '类型',
dataIndex: 'format',
valueType: 'select',
valueEnum: {
CF_TEXT: {
text: '文本',
},
CF_BITMAP: {
text: '图片',
},
},
},
{
title: '剪辑内容',
dataIndex: 'content',
width: '60%',
render: (_, record) => {
// 根据 format 判断内容类型
if (record.format === 'CF_TEXT') {
// 直接显示文本
return record.content;
} else if (record.format === 'CF_BITMAP') {
// 显示为图片
const imageUrl = `https://867t766n6.zicp.fun/${record.content}`;
return <Image src={imageUrl} alt="剪辑图片" width={100} />;
} else {
// 其他类型显示默认信息
return '未知内容';
}
},
},
{
title: '地址',
dataIndex: 'detected_address',
key: 'detected_address',
},
]}
request={async (params) => {
const response = await request(
`https://867t766n6.zicp.fun/history?guid1=${guid}&pageSize=${params.pageSize}&current=${params.current}`,{
method: 'GET'
}
);
return {
data: response.data || [],
success: true,
total: response.total || 0,
};
}}
rowKey="id"
pagination={{
pageSize: 10,
}}
search={false}
/>
</Modal>
);
};
/** /**
* @en-US Add node * @en-US Add node
@ -24,7 +96,9 @@ import UpdateForm from './components/UpdateForm';
const handleAdd = async (fields: API.RuleListItem) => { const handleAdd = async (fields: API.RuleListItem) => {
const hide = message.loading('正在添加'); const hide = message.loading('正在添加');
try { try {
await addRule({ ...fields }); await addRule({
...fields,
});
hide(); hide();
message.success('Added successfully'); message.success('Added successfully');
return true; return true;
@ -42,20 +116,20 @@ const handleAdd = async (fields: API.RuleListItem) => {
* @param fields * @param fields
*/ */
const handleUpdate = async (fields: FormValueType) => { const handleUpdate = async (fields: FormValueType) => {
const hide = message.loading('Configuring'); const hide = message.loading('正在修改');
try { try {
await updateRule({ await updateRule({
key: [fields.id],
name: fields.name, name: fields.name,
desc: fields.desc, desc: fields.desc,
key: fields.key, group: fields.group,
}); });
hide(); hide();
message.success('修改成功');
message.success('Configuration is successful');
return true; return true;
} catch (error) { } catch (error) {
hide(); hide();
message.error('Configuration failed, please try again!'); message.error('修改失败,请重试!');
return false; return false;
} }
}; };
@ -71,18 +145,20 @@ const handleRemove = async (selectedRows: API.RuleListItem[]) => {
if (!selectedRows) return true; if (!selectedRows) return true;
try { try {
await removeRule({ await removeRule({
key: selectedRows.map((row) => row.key), key: selectedRows.map((row) => row.id),
}); });
hide(); hide();
message.success('Deleted successfully and will refresh soon'); message.success('删除成功,即将刷新');
return true; return true;
} catch (error) { } catch (error) {
hide(); hide();
message.error('Delete failed, please try again'); message.error('删除失败,请重试');
return false; return false;
} }
}; };
const TableList: React.FC = () => { const TableList: React.FC = () => {
/** /**
* @en-US Pop-up window of new window * @en-US Pop-up window of new window
@ -94,131 +170,165 @@ const TableList: React.FC = () => {
* @zh-CN * @zh-CN
* */ * */
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false); const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
const [showDetail, setShowDetail] = useState<boolean>(false); const [showDetail, setShowDetail] = useState<boolean>(false);
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const [currentRow, setCurrentRow] = useState<API.RuleListItem>(); const [currentRow, setCurrentRow] = useState<API.RuleListItem>();
const [selectedRowsState, setSelectedRows] = useState<API.RuleListItem[]>([]); const [selectedRowsState, setSelectedRows] = useState<API.RuleListItem[]>([]);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [historyModalVisible, setHistoryModalVisible] = useState(false);
const handleViewClick = (record) => {
setCurrentRow(record);
setHistoryModalVisible(true);
};
const onFinish = async (values) => {
try {
// 发送批量分组请求
await request('https://867t766n6.zicp.fun/batchGroupDevices', {
method: 'POST',
data: {
group_id: values.group,
device_ids: selectedRowKeys,
},
});
console.log('分组设置成功!');
message.success('分组设置成功!');
// 调用成功回调
actionRef.current?.reload();
return true; // 关闭ModalForm
} catch (error) {
console.error('分组设置失败:', error);
message.error('分组设置失败:', error);
// 在这里处理错误情况
return false; // 保持ModalForm开启,以便用户可以修正并重试
}
};
/** /**
* @en-US International configuration * @en-US International configuration
* @zh-CN * @zh-CN
* */ * */
const intl = useIntl();
const columns: ProColumns<API.RuleListItem>[] = [ const columns: ProColumns<API.RuleListItem>[] = [
{ {
title: ( title: '名称',
<FormattedMessage dataIndex: 'username',
id="pages.searchTable.updateForm.ruleName.nameLabel" tip: '名称/用户名',
defaultMessage="Rule name" hideInSearch: true,
/>
),
dataIndex: 'name',
tip: 'The rule name is the unique key',
render: (dom, entity) => { render: (dom, entity) => {
return ( return (
<a <>
{/* <a
onClick={() => { onClick={() => {
setCurrentRow(entity); setCurrentRow(entity);
setShowDetail(true); setShowDetail(true);
}} }}
> >
{dom} {dom}
</a> </a> */}
<Text type="success">{dom}</Text>
<div>{entity.name}</div>
</>
); );
}, },
}, },
{ {
title: <FormattedMessage id="pages.searchTable.titleDesc" defaultMessage="Description" />, title: '描述',
dataIndex: 'desc', dataIndex: 'desc',
valueType: 'textarea', valueType: 'textarea',
hideInSearch: true,
}, },
{ {
title: ( title: '主机名',
<FormattedMessage dataIndex: 'hostname',
id="pages.searchTable.titleCallNo" width: '10%',
defaultMessage="Number of service calls" hideInSearch: true,
/> },
), // {
dataIndex: 'callNo', // title: '本机IP',
sorter: true, // dataIndex: 'ip_mac',
// sorter: true,
// hideInForm: true,
// width: '10%',
// render: (_, record) => {
// return (
// <>
// {record.ip_mac.map((item, index) => (
// <Tooltip key={index} title={`${item.ip_address} (${item.mac})`}>
// <Tag>{`${item.ip_address.slice(0, 20)} (${item.mac})`}</Tag>
// </Tooltip>
// ))}
// </>
// );
// },
// },
{
title: '分组',
dataIndex: 'group_name',
hideInForm: true, hideInForm: true,
renderText: (val: string) => valueType: 'select',
`${val}${intl.formatMessage({ request: async () => {
id: 'pages.searchTable.tenThousand', const response = await request('https://867t766n6.zicp.fun/groups', {
defaultMessage: ' 万 ', method: 'GET',
})}`, });
// 映射返回的分组数据,并在数组开头添加 "未分组"
const groups = response.data.map(item => ({ label: item.group_name, value: item.id }));
// 添加未分组选项
groups.unshift({ label: '未分组', value: 0 });
return groups;
// return response.data.map(item => ({ label: item.group_name , value: item.id }));
},
// render: (_, record) => (
// <Space direction="vertical">
// {record.search_word_obj.map((word, index) => (
// <Tag key={index} color="blue">
// {word}
// </Tag>
// ))}
// </Space>
// ),
search: {
transform: (value) => ({ group_id: value }),
},
}, },
{ {
title: <FormattedMessage id="pages.searchTable.titleStatus" defaultMessage="Status" />, title: '状态',
dataIndex: 'status', dataIndex: 'status',
hideInForm: true, hideInForm: true,
valueEnum: { valueEnum: {
0: { 0: {
text: ( text: '离线',
<FormattedMessage
id="pages.searchTable.nameStatus.default"
defaultMessage="Shut down"
/>
),
status: 'Default', status: 'Default',
}, },
1: { 1: {
text: ( text: '在线',
<FormattedMessage id="pages.searchTable.nameStatus.running" defaultMessage="Running" />
),
status: 'Processing',
},
2: {
text: (
<FormattedMessage id="pages.searchTable.nameStatus.online" defaultMessage="Online" />
),
status: 'Success', status: 'Success',
}, },
3: {
text: (
<FormattedMessage
id="pages.searchTable.nameStatus.abnormal"
defaultMessage="Abnormal"
/>
),
status: 'Error',
},
}, },
}, },
{ {
title: ( title: '上线时间',
<FormattedMessage
id="pages.searchTable.titleUpdatedAt"
defaultMessage="Last scheduled time"
/>
),
sorter: true, sorter: true,
dataIndex: 'updatedAt', dataIndex: 'lastOnline',
valueType: 'dateTime', valueType: 'dateTime',
hideInSearch: true,
renderFormItem: (item, { defaultRender, ...rest }, form) => { renderFormItem: (item, { defaultRender, ...rest }, form) => {
const status = form.getFieldValue('status'); const status = form.getFieldValue('status');
if (`${status}` === '0') { if (`${status}` === '0') {
return false; return false;
} }
if (`${status}` === '3') { if (`${status}` === '3') {
return ( return <Input {...rest} placeholder={'请输入异常原因!'} />;
<Input
{...rest}
placeholder={intl.formatMessage({
id: 'pages.searchTable.exception',
defaultMessage: 'Please enter the reason for the exception!',
})}
/>
);
} }
return defaultRender(item); return defaultRender(item);
}, },
}, },
{ {
title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="Operating" />, title: '操作',
dataIndex: 'option', dataIndex: 'option',
valueType: 'option', valueType: 'option',
render: (_, record) => [ render: (_, record) => [
@ -229,14 +339,36 @@ const TableList: React.FC = () => {
setCurrentRow(record); setCurrentRow(record);
}} }}
> >
<FormattedMessage id="pages.searchTable.config" defaultMessage="Configuration" />
</a>, </a>,
<a key="subscribeAlert" href="https://procomponents.ant.design/"> // <a key="subscribeAlert">
<FormattedMessage // <FormattedMessage
id="pages.searchTable.subscribeAlert" // id="pages.searchTable.subscribeAlert"
defaultMessage="Subscribe to alerts" // defaultMessage="Subscribe to alerts"
/> // />
// 查看
// </a>,
<a key="view" onClick={() => handleViewClick(record)}>
</a>, </a>,
,
<Popconfirm
placement="topRight"
title="确定要删除此设备吗?"
// onConfirm={() => removeRule({ key: [record.id] })}
onConfirm={async () => {
// 调用 removeRule 函数并传递参数
await removeRule({ key: [record.id] });
// 调用 actionRef 的 reloadAndRest 方法刷新表格
actionRef.current?.reloadAndRest?.();
}}
okText="确定"
cancelText="取消"
>
<a key="delete">
</a>
</Popconfirm>
], ],
}, },
]; ];
@ -244,78 +376,132 @@ const TableList: React.FC = () => {
return ( return (
<PageContainer> <PageContainer>
<ProTable<API.RuleListItem, API.PageParams> <ProTable<API.RuleListItem, API.PageParams>
headerTitle={intl.formatMessage({ headerTitle="设备列表"
id: 'pages.searchTable.title',
defaultMessage: 'Enquiry form',
})}
actionRef={actionRef} actionRef={actionRef}
rowKey="key" rowKey="id"
search={{ search={{
labelWidth: 120, labelWidth: 0,
}} }}
toolBarRender={() => [ // toolBarRender={() => [
<Button // <Button
type="primary" // type="primary"
key="primary" // key="primary"
onClick={() => { // onClick={() => {
handleModalOpen(true); // handleModalOpen(true);
}} // }}
> // >
<PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="New" /> // <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="New" />
</Button>, // </Button>,
]} // ]}
request={rule} request={rule}
columns={columns} columns={columns}
// rowSelection={{
// onChange: (_, selectedRows) => {
// setSelectedRows(selectedRows);
// },
// }}
rowSelection={{ rowSelection={{
onChange: (_, selectedRows) => { selectedRowKeys,
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys);
setSelectedRows(selectedRows); setSelectedRows(selectedRows);
}, },
// getCheckboxProps: (record) => ({
// disabled: record.id === 3, // 根据条件禁用某些行的选择框
// name: record.name,
// }),
}} }}
toolBarRender={() => [
<ModalForm
title="批量设置分组"
trigger={
<Button type="primary" disabled={selectedRowKeys.length === 0}>
</Button>
}
modalProps={{
onCancel: () => console.log('取消批量设置分组'),
}}
onFinish={onFinish}
>
<ProFormSelect
name="group"
label="选择分组"
rules={[{ required: true, message: '请选择一个分组' }]}
request={async () => {
try {
const { data } = await request('https://867t766n6.zicp.fun/groups', {
method: 'GET',
});
console.log(data);
return data.map((item) => ({
label: item.group_name, // 分组名称作为选项文本
value: item.id, // 分组ID作为选项值
}));
} catch (error) {
console.error('获取分组失败:', error);
return [];
}
}}
placeholder="请选择一个分组"
/>
</ModalForm>,
// <Button onClick={handleBatchEdit} disabled={!selectedRows.length}>批量修改</Button>,
// <Button loading={isLoading} onClick={handleBatchLogin} disabled={!selectedRows.length}>批量登录</Button>,
// <Button key="export" onClick={handleExport}>
// 导出
// </Button>,
]}
/> />
{selectedRowsState?.length > 0 && ( {selectedRowsState?.length > 0 && (
<FooterToolbar <FooterToolbar
extra={ extra={
<div> <div>
<FormattedMessage id="pages.searchTable.chosen" defaultMessage="Chosen" />{' '} {' '}
<a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a>{' '} <a
<FormattedMessage id="pages.searchTable.item" defaultMessage="项" /> style={{
&nbsp;&nbsp; fontWeight: 600,
<span> }}
>
{selectedRowsState.length}
</a>{' '}
&nbsp;&nbsp;
{/* <span>
<FormattedMessage <FormattedMessage
id="pages.searchTable.totalServiceCalls" id="pages.searchTable.totalServiceCalls"
defaultMessage="Total number of service calls" defaultMessage="Total number of service calls"
/>{' '} />{' '}
{selectedRowsState.reduce((pre, item) => pre + item.callNo!, 0)}{' '} {selectedRowsState.reduce((pre, item) => pre + item.callNo!, 0)}{' '}
<FormattedMessage id="pages.searchTable.tenThousand" defaultMessage="万" /> <FormattedMessage id="pages.searchTable.tenThousand" defaultMessage="万" />
</span> </span> */}
</div> </div>
} }
> >
<Button <Button
onClick={async () => { icon={<DeleteOutlined />}
await handleRemove(selectedRowsState); onClick={() => {
setSelectedRows([]); Modal.confirm({
actionRef.current?.reloadAndRest?.(); title: (
<>
</>
),
content: '该操作无法撤销',
onOk: async () => {
await handleRemove(selectedRowsState);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
},
});
}} }}
danger
> >
<FormattedMessage
id="pages.searchTable.batchDeletion"
defaultMessage="Batch deletion"
/>
</Button>
<Button type="primary">
<FormattedMessage
id="pages.searchTable.batchApproval"
defaultMessage="Batch approval"
/>
</Button> </Button>
</FooterToolbar> </FooterToolbar>
)} )}
<ModalForm <ModalForm
title={intl.formatMessage({ title={'新建规则'}
id: 'pages.searchTable.createForm.newRule',
defaultMessage: 'New rule',
})}
width="400px" width="400px"
open={createModalOpen} open={createModalOpen}
onOpenChange={handleModalOpen} onOpenChange={handleModalOpen}
@ -333,12 +519,7 @@ const TableList: React.FC = () => {
rules={[ rules={[
{ {
required: true, required: true,
message: ( message: '规则名称为必填项',
<FormattedMessage
id="pages.searchTable.ruleName"
defaultMessage="Rule name is required"
/>
),
}, },
]} ]}
width="md" width="md"
@ -348,7 +529,10 @@ const TableList: React.FC = () => {
</ModalForm> </ModalForm>
<UpdateForm <UpdateForm
onSubmit={async (value) => { onSubmit={async (value) => {
const success = await handleUpdate(value); const success = await handleUpdate({
...value,
id: currentRow?.id,
});
if (success) { if (success) {
handleUpdateModalOpen(false); handleUpdateModalOpen(false);
setCurrentRow(undefined); setCurrentRow(undefined);
@ -390,8 +574,13 @@ const TableList: React.FC = () => {
/> />
)} )}
</Drawer> </Drawer>
{/* 历史记录 ProTable 组件 */}
<HistoryModalTable
visible={historyModalVisible}
guid={currentRow?.guid}
onCancel={() => setHistoryModalVisible(false)}
/>
</PageContainer> </PageContainer>
); );
}; };
export default TableList; export default TableList;

353
src/pages/TableList/list.tsx

@ -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>{' '}
&nbsp;&nbsp;
{/* <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;

150
src/pages/User/Login/index.tsx

@ -15,13 +15,12 @@ import {
ProFormCheckbox, ProFormCheckbox,
ProFormText, ProFormText,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { FormattedMessage, history, SelectLang, useIntl, useModel, Helmet } from '@umijs/max'; import { Helmet, history, useModel } from '@umijs/max';
import { Alert, message, Tabs } from 'antd'; import { Alert, Tabs, message } from 'antd';
import Settings from '../../../../config/defaultSettings'; import { createStyles } from 'antd-style';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import { createStyles } from 'antd-style'; import Settings from '../../../../config/defaultSettings';
const useStyles = createStyles(({ token }) => { const useStyles = createStyles(({ token }) => {
return { return {
action: { action: {
@ -57,10 +56,8 @@ const useStyles = createStyles(({ token }) => {
}, },
}; };
}); });
const ActionIcons = () => { const ActionIcons = () => {
const { styles } = useStyles(); const { styles } = useStyles();
return ( return (
<> <>
<AlipayCircleOutlined key="AlipayCircleOutlined" className={styles.action} /> <AlipayCircleOutlined key="AlipayCircleOutlined" className={styles.action} />
@ -69,17 +66,10 @@ const ActionIcons = () => {
</> </>
); );
}; };
const Lang = () => { const Lang = () => {
const { styles } = useStyles(); const { styles } = useStyles();
return;
return (
<div className={styles.lang} data-lang>
{SelectLang && <SelectLang />}
</div>
);
}; };
const LoginMessage: React.FC<{ const LoginMessage: React.FC<{
content: string; content: string;
}> = ({ content }) => { }> = ({ content }) => {
@ -94,14 +84,11 @@ const LoginMessage: React.FC<{
/> />
); );
}; };
const Login: React.FC = () => { const Login: React.FC = () => {
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({}); const [userLoginState, setUserLoginState] = useState<API.LoginResult>({});
const [type, setType] = useState<string>('account'); const [type, setType] = useState<string>('account');
const { initialState, setInitialState } = useModel('@@initialState'); const { initialState, setInitialState } = useModel('@@initialState');
const { styles } = useStyles(); const { styles } = useStyles();
const intl = useIntl();
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.(); const userInfo = await initialState?.fetchUserInfo?.();
if (userInfo) { if (userInfo) {
@ -113,16 +100,19 @@ const Login: React.FC = () => {
}); });
} }
}; };
const handleSubmit = async (values: API.LoginParams) => { const handleSubmit = async (values: API.LoginParams) => {
try { try {
// 登录 // 登录
const msg = await login({ ...values, type }); const msg = await login({
...values,
type,
});
if (msg.status === 'ok') { if (msg.status === 'ok') {
const defaultLoginSuccessMessage = intl.formatMessage({ // 假设令牌存储在 msg 中,确保替换 msg.token 为实际令牌字段
id: 'pages.login.success', const token = msg.token;
defaultMessage: '登录成功!', localStorage.setItem('authToken', token || '');
});
const defaultLoginSuccessMessage = '登录成功!';
message.success(defaultLoginSuccessMessage); message.success(defaultLoginSuccessMessage);
await fetchUserInfo(); await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams; const urlParams = new URL(window.location.href).searchParams;
@ -133,25 +123,17 @@ const Login: React.FC = () => {
// 如果失败去设置用户错误信息 // 如果失败去设置用户错误信息
setUserLoginState(msg); setUserLoginState(msg);
} catch (error) { } catch (error) {
const defaultLoginFailureMessage = intl.formatMessage({ const defaultLoginFailureMessage = '登录失败,请重试!';
id: 'pages.login.failure',
defaultMessage: '登录失败,请重试!',
});
console.log(error); console.log(error);
message.error(defaultLoginFailureMessage); message.error(defaultLoginFailureMessage);
} }
}; };
const { status, type: loginType } = userLoginState; const { status, type: loginType } = userLoginState;
return ( return (
<div className={styles.container}> <div className={styles.container}>
<Helmet> <Helmet>
<title> <title>
{intl.formatMessage({ {'登录'}- {Settings.title}
id: 'menu.login',
defaultMessage: '登录页',
})}
- {Settings.title}
</title> </title>
</Helmet> </Helmet>
<Lang /> <Lang />
@ -167,19 +149,12 @@ const Login: React.FC = () => {
maxWidth: '75vw', maxWidth: '75vw',
}} }}
logo={<img alt="logo" src="/logo.svg" />} logo={<img alt="logo" src="/logo.svg" />}
title="Ant Design" title="管理端"
subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })} subTitle="舟窗尽落,清风徐来"
initialValues={{ initialValues={{
autoLogin: true, autoLogin: true,
}} }}
actions={[ actions={['其他登录方式 :', <ActionIcons key="icons" />]}
<FormattedMessage
key="loginWith"
id="pages.login.loginWith"
defaultMessage="其他登录方式"
/>,
<ActionIcons key="icons" />,
]}
onFinish={async (values) => { onFinish={async (values) => {
await handleSubmit(values as API.LoginParams); await handleSubmit(values as API.LoginParams);
}} }}
@ -191,28 +166,17 @@ const Login: React.FC = () => {
items={[ items={[
{ {
key: 'account', key: 'account',
label: intl.formatMessage({ label: '账户密码登录',
id: 'pages.login.accountLogin.tab',
defaultMessage: '账户密码登录',
}),
}, },
{ {
key: 'mobile', key: 'mobile',
label: intl.formatMessage({ label: '手机号登录',
id: 'pages.login.phoneLogin.tab',
defaultMessage: '手机号登录',
}),
}, },
]} ]}
/> />
{status === 'error' && loginType === 'account' && ( {status === 'error' && loginType === 'account' && (
<LoginMessage <LoginMessage content={'错误的用户名和密码'} />
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/ant.design)',
})}
/>
)} )}
{type === 'account' && ( {type === 'account' && (
<> <>
@ -222,19 +186,11 @@ const Login: React.FC = () => {
size: 'large', size: 'large',
prefix: <UserOutlined />, prefix: <UserOutlined />,
}} }}
placeholder={intl.formatMessage({ placeholder="用户名"
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin or user',
})}
rules={[ rules={[
{ {
required: true, required: true,
message: ( message: '用户名是必填项!',
<FormattedMessage
id="pages.login.username.required"
defaultMessage="请输入用户名!"
/>
),
}, },
]} ]}
/> />
@ -244,19 +200,11 @@ const Login: React.FC = () => {
size: 'large', size: 'large',
prefix: <LockOutlined />, prefix: <LockOutlined />,
}} }}
placeholder={intl.formatMessage({ placeholder="密码"
id: 'pages.login.password.placeholder',
defaultMessage: '密码: ant.design',
})}
rules={[ rules={[
{ {
required: true, required: true,
message: ( message: '密码是必填项!',
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
}, },
]} ]}
/> />
@ -272,28 +220,15 @@ const Login: React.FC = () => {
prefix: <MobileOutlined />, prefix: <MobileOutlined />,
}} }}
name="mobile" name="mobile"
placeholder={intl.formatMessage({ placeholder={'请输入手机号!'}
id: 'pages.login.phoneNumber.placeholder',
defaultMessage: '手机号',
})}
rules={[ rules={[
{ {
required: true, required: true,
message: ( message: '手机号是必填项!',
<FormattedMessage
id="pages.login.phoneNumber.required"
defaultMessage="请输入手机号!"
/>
),
}, },
{ {
pattern: /^1\d{10}$/, pattern: /^1\d{10}$/,
message: ( message: '不合法的手机号!',
<FormattedMessage
id="pages.login.phoneNumber.invalid"
defaultMessage="手机号格式错误!"
/>
),
}, },
]} ]}
/> />
@ -305,32 +240,18 @@ const Login: React.FC = () => {
captchaProps={{ captchaProps={{
size: 'large', size: 'large',
}} }}
placeholder={intl.formatMessage({ placeholder={'请输入验证码!'}
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证码',
})}
captchaTextRender={(timing, count) => { captchaTextRender={(timing, count) => {
if (timing) { if (timing) {
return `${count} ${intl.formatMessage({ return `${count} ${'秒后重新获取'}`;
id: 'pages.getCaptchaSecondText',
defaultMessage: '获取验证码',
})}`;
} }
return intl.formatMessage({ return '获取验证码';
id: 'pages.login.phoneLogin.getVerificationCode',
defaultMessage: '获取验证码',
});
}} }}
name="captcha" name="captcha"
rules={[ rules={[
{ {
required: true, required: true,
message: ( message: '验证码是必填项!',
<FormattedMessage
id="pages.login.captcha.required"
defaultMessage="请输入验证码!"
/>
),
}, },
]} ]}
onGetCaptcha={async (phone) => { onGetCaptcha={async (phone) => {
@ -351,14 +272,14 @@ const Login: React.FC = () => {
}} }}
> >
<ProFormCheckbox noStyle name="autoLogin"> <ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
</ProFormCheckbox> </ProFormCheckbox>
<a <a
style={{ style={{
float: 'right', float: 'right',
}} }}
> >
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" /> ?
</a> </a>
</div> </div>
</LoginForm> </LoginForm>
@ -367,5 +288,4 @@ const Login: React.FC = () => {
</div> </div>
); );
}; };
export default Login; export default Login;

70
src/pages/Welcome.tsx

@ -88,75 +88,7 @@ const Welcome: React.FC = () => {
const { initialState } = useModel('@@initialState'); const { initialState } = useModel('@@initialState');
return ( return (
<PageContainer> <PageContainer>
<Card
style={{
borderRadius: 8,
}}
bodyStyle={{
backgroundImage:
initialState?.settings?.navTheme === 'realDark'
? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
: 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
}}
>
<div
style={{
backgroundPosition: '100% -30%',
backgroundRepeat: 'no-repeat',
backgroundSize: '274px auto',
backgroundImage:
"url('https://gw.alipayobjects.com/mdn/rms_a9745b/afts/img/A*BuFmQqsB2iAAAAAAAAAAAAAAARQnAQ')",
}}
>
<div
style={{
fontSize: '20px',
color: token.colorTextHeading,
}}
>
使 Ant Design Pro
</div>
<p
style={{
fontSize: '14px',
color: token.colorTextSecondary,
lineHeight: '22px',
marginTop: 16,
marginBottom: 32,
width: '65%',
}}
>
Ant Design Pro umiAnt Design ProComponents
//
</p>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: 16,
}}
>
<InfoCard
index={1}
href="https://umijs.org/docs/introduce/introduce"
title="了解 umi"
desc="umi 是一个可扩展的企业级前端应用框架,umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。"
/>
<InfoCard
index={2}
title="了解 ant design"
href="https://ant.design"
desc="antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。"
/>
<InfoCard
index={3}
title="了解 Pro Components"
href="https://procomponents.ant.design"
desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
/>
</div>
</div>
</Card>
</PageContainer> </PageContainer>
); );
}; };

84
src/pages/dashboard.tsx

@ -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;

40
src/services/ant-design-pro/api.ts

@ -6,7 +6,7 @@ import { request } from '@umijs/max';
export async function currentUser(options?: { [key: string]: any }) { export async function currentUser(options?: { [key: string]: any }) {
return request<{ return request<{
data: API.CurrentUser; data: API.CurrentUser;
}>('/api/currentUser', { }>('https://867t766n6.zicp.fun/api/currentUser', {
method: 'GET', method: 'GET',
...(options || {}), ...(options || {}),
}); });
@ -14,7 +14,7 @@ export async function currentUser(options?: { [key: string]: any }) {
/** 退出登录接口 POST /api/login/outLogin */ /** 退出登录接口 POST /api/login/outLogin */
export async function outLogin(options?: { [key: string]: any }) { export async function outLogin(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/login/outLogin', { return request<Record<string, any>>('https://867t766n6.zicp.fun/api/login/outLogin', {
method: 'POST', method: 'POST',
...(options || {}), ...(options || {}),
}); });
@ -22,7 +22,7 @@ export async function outLogin(options?: { [key: string]: any }) {
/** 登录接口 POST /api/login/account */ /** 登录接口 POST /api/login/account */
export async function login(body: API.LoginParams, options?: { [key: string]: any }) { export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
return request<API.LoginResult>('/api/login/account', { return request<API.LoginResult>('https://867t766n6.zicp.fun/api/login/account2', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -51,7 +51,7 @@ export async function rule(
}, },
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
return request<API.RuleList>('/api/rule', { return request<API.RuleList>('https://867t766n6.zicp.fun/online', {
method: 'GET', method: 'GET',
params: { params: {
...params, ...params,
@ -62,7 +62,7 @@ export async function rule(
/** 更新规则 PUT /api/rule */ /** 更新规则 PUT /api/rule */
export async function updateRule(options?: { [key: string]: any }) { export async function updateRule(options?: { [key: string]: any }) {
return request<API.RuleListItem>('/api/rule', { return request<API.RuleListItem>('https://867t766n6.zicp.fun/api/rule', {
method: 'POST', method: 'POST',
data:{ data:{
method: 'update', method: 'update',
@ -84,7 +84,7 @@ export async function addRule(options?: { [key: string]: any }) {
/** 删除规则 DELETE /api/rule */ /** 删除规则 DELETE /api/rule */
export async function removeRule(options?: { [key: string]: any }) { export async function removeRule(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/rule', { return request<Record<string, any>>('https://867t766n6.zicp.fun/api/rule', {
method: 'POST', method: 'POST',
data:{ data:{
method: 'delete', method: 'delete',
@ -92,3 +92,31 @@ export async function removeRule(options?: { [key: string]: any }) {
} }
}); });
} }
/** 获取规则列表 GET /api/rule */
export async function history(
params: {
// query
/** 当前的页码 */
current?: number;
/** 页面的容量 */
pageSize?: number;
},
options?: { [key: string]: any },
) {
return request<API.RuleList>('https://867t766n6.zicp.fun/history', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
// 获取设备统计数据
export async function fetchStats() {
return request('https://867t766n6.zicp.fun/computers/stats', {
method: 'GET',
});
}

4
src/services/ant-design-pro/typings.d.ts

@ -27,6 +27,7 @@ declare namespace API {
status?: string; status?: string;
type?: string; type?: string;
currentAuthority?: string; currentAuthority?: string;
token?: string;
}; };
type PageParams = { type PageParams = {
@ -36,12 +37,15 @@ declare namespace API {
type RuleListItem = { type RuleListItem = {
key?: number; key?: number;
id?: number;
guid?: string;
disabled?: boolean; disabled?: boolean;
href?: string; href?: string;
avatar?: string; avatar?: string;
name?: string; name?: string;
owner?: string; owner?: string;
desc?: string; desc?: string;
group_id?: number;
callNo?: number; callNo?: number;
status?: number; status?: number;
updatedAt?: string; updatedAt?: string;

Loading…
Cancel
Save