From f62e07d62b2e8df21df04c5ff5a482aafd024a74 Mon Sep 17 00:00:00 2001 From: DVK Date: Mon, 22 Jan 2024 21:10:55 +0800 Subject: [PATCH] feat: init web client (#43) --- .gitignore | 1 - web/.editorconfig | 16 + web/.eslintignore | 8 + web/.eslintrc.js | 8 + web/.gitignore | 40 ++ web/.prettierignore | 23 + web/.prettierrc.js | 5 + web/.stylelintrc.js | 3 + web/README.md | 3 + web/config/config.dev.ts | 15 + web/config/config.ts | 76 +++ web/config/defaultSettings.ts | 21 + web/config/oneapi.json | 593 ++++++++++++++++++ web/config/proxy.ts | 34 + web/config/routes.ts | 74 +++ web/jest.config.js | 9 + web/jsconfig.json | 11 + web/mock/listTableList.ts | 174 +++++ web/mock/notices.ts | 107 ++++ web/mock/route.ts | 5 + web/mock/user.ts | 203 ++++++ web/package.json | 95 +++ web/playwright.config.ts | 22 + web/public/favicon.ico | Bin 0 -> 270398 bytes web/public/icon.svg | 55 ++ web/public/icon_black.svg | 55 ++ web/public/icons/icon-128x128.png | Bin 0 -> 1329 bytes web/public/icons/icon-192x192.png | Bin 0 -> 1856 bytes web/public/icons/icon-512x512.png | Bin 0 -> 5082 bytes web/public/logo.png | Bin 0 -> 43889 bytes web/src/access.ts | 9 + web/src/app.tsx | 101 +++ web/src/components/CodePreview/index.less | 8 + web/src/components/CodePreview/index.tsx | 13 + web/src/components/Footer/index.tsx | 14 + web/src/components/HeaderDropdown/index.less | 16 + web/src/components/HeaderDropdown/index.tsx | 17 + web/src/components/HeaderSearch/index.less | 25 + web/src/components/HeaderSearch/index.tsx | 101 +++ web/src/components/NoticeIcon/NoticeIcon.tsx | 126 ++++ web/src/components/NoticeIcon/NoticeList.less | 103 +++ web/src/components/NoticeIcon/NoticeList.tsx | 112 ++++ web/src/components/NoticeIcon/index.less | 35 ++ web/src/components/NoticeIcon/index.tsx | 152 +++++ .../RightContent/AvatarDropdown.tsx | 111 ++++ web/src/components/RightContent/index.less | 84 +++ web/src/components/RightContent/index.tsx | 44 ++ web/src/components/ShieldList/index.tsx | 15 + web/src/components/index.md | 271 ++++++++ web/src/e2e/baseLayout.e2e.spec.ts | 45 ++ web/src/global.less | 60 ++ web/src/global.tsx | 91 +++ web/src/locales/bn-BD.ts | 26 + web/src/locales/bn-BD/component.ts | 5 + web/src/locales/bn-BD/globalHeader.ts | 17 + web/src/locales/bn-BD/menu.ts | 52 ++ web/src/locales/bn-BD/pages.ts | 68 ++ web/src/locales/bn-BD/pwa.ts | 7 + web/src/locales/bn-BD/settingDrawer.ts | 31 + web/src/locales/bn-BD/settings.ts | 59 ++ web/src/locales/en-US.ts | 25 + web/src/locales/en-US/component.ts | 5 + web/src/locales/en-US/globalHeader.ts | 17 + web/src/locales/en-US/menu.ts | 52 ++ web/src/locales/en-US/pages.ts | 68 ++ web/src/locales/en-US/pwa.ts | 6 + web/src/locales/en-US/settingDrawer.ts | 31 + web/src/locales/en-US/settings.ts | 60 ++ web/src/locales/fa-IR.ts | 24 + web/src/locales/fa-IR/component.ts | 5 + web/src/locales/fa-IR/globalHeader.ts | 17 + web/src/locales/fa-IR/menu.ts | 52 ++ web/src/locales/fa-IR/pages.ts | 65 ++ web/src/locales/fa-IR/pwa.ts | 7 + web/src/locales/fa-IR/settingDrawer.ts | 32 + web/src/locales/fa-IR/settings.ts | 60 ++ web/src/locales/id-ID.ts | 25 + web/src/locales/id-ID/component.ts | 5 + web/src/locales/id-ID/globalHeader.ts | 17 + web/src/locales/id-ID/menu.ts | 52 ++ web/src/locales/id-ID/pages.ts | 68 ++ web/src/locales/id-ID/pwa.ts | 7 + web/src/locales/id-ID/settingDrawer.ts | 32 + web/src/locales/id-ID/settings.ts | 60 ++ web/src/locales/ja-JP.ts | 24 + web/src/locales/ja-JP/component.ts | 5 + web/src/locales/ja-JP/globalHeader.ts | 17 + web/src/locales/ja-JP/menu.ts | 52 ++ web/src/locales/ja-JP/pages.ts | 65 ++ web/src/locales/ja-JP/pwa.ts | 7 + web/src/locales/ja-JP/settingDrawer.ts | 31 + web/src/locales/ja-JP/settings.ts | 59 ++ web/src/locales/pt-BR.ts | 22 + web/src/locales/pt-BR/component.ts | 5 + web/src/locales/pt-BR/globalHeader.ts | 18 + web/src/locales/pt-BR/menu.ts | 52 ++ web/src/locales/pt-BR/pages.ts | 68 ++ web/src/locales/pt-BR/pwa.ts | 7 + web/src/locales/pt-BR/settingDrawer.ts | 32 + web/src/locales/pt-BR/settings.ts | 60 ++ web/src/locales/zh-CN.ts | 25 + web/src/locales/zh-CN/component.ts | 5 + web/src/locales/zh-CN/globalHeader.ts | 17 + web/src/locales/zh-CN/menu.ts | 52 ++ web/src/locales/zh-CN/pages.ts | 65 ++ web/src/locales/zh-CN/pwa.ts | 6 + web/src/locales/zh-CN/settingDrawer.ts | 31 + web/src/locales/zh-CN/settings.ts | 55 ++ web/src/locales/zh-TW.ts | 20 + web/src/locales/zh-TW/component.ts | 5 + web/src/locales/zh-TW/globalHeader.ts | 17 + web/src/locales/zh-TW/menu.ts | 52 ++ web/src/locales/zh-TW/pwa.ts | 6 + web/src/locales/zh-TW/settingDrawer.ts | 31 + web/src/locales/zh-TW/settings.ts | 55 ++ web/src/manifest.json | 22 + web/src/pages/404.tsx | 18 + web/src/pages/Admin.tsx | 45 ++ web/src/pages/Dashboard.less | 0 web/src/pages/Dashboard.tsx | 14 + .../pages/TableList/components/UpdateForm.tsx | 209 ++++++ web/src/pages/TableList/index.tsx | 397 ++++++++++++ web/src/pages/application/index.tsx | 0 web/src/pages/document.ejs | 237 +++++++ .../network/Proxy/components/CreateForm.tsx | 0 web/src/pages/network/Proxy/index.tsx | 80 +++ web/src/pages/setting/Server/index.tsx | 10 + web/src/pages/setting/System/index.tsx | 17 + web/src/pages/user/Login/index.less | 77 +++ web/src/pages/user/Login/index.tsx | 152 +++++ web/src/service-worker.js | 65 ++ web/src/services/ant-design-pro/api.ts | 85 +++ web/src/services/ant-design-pro/index.ts | 10 + web/src/services/ant-design-pro/login.ts | 21 + web/src/services/ant-design-pro/typings.d.ts | 101 +++ web/src/services/swagger/index.ts | 12 + web/src/services/swagger/pet.ts | 166 +++++ web/src/services/swagger/store.ts | 54 ++ web/src/services/swagger/typings.d.ts | 52 ++ web/src/services/swagger/user.ts | 114 ++++ web/src/typings.d.ts | 24 + web/tests/run-tests.js | 47 ++ web/tests/setupTests.js | 10 + web/tsconfig.json | 42 ++ 144 files changed, 7312 insertions(+), 1 deletion(-) create mode 100644 web/.editorconfig create mode 100644 web/.eslintignore create mode 100644 web/.eslintrc.js create mode 100644 web/.gitignore create mode 100644 web/.prettierignore create mode 100644 web/.prettierrc.js create mode 100644 web/.stylelintrc.js create mode 100644 web/README.md create mode 100644 web/config/config.dev.ts create mode 100644 web/config/config.ts create mode 100644 web/config/defaultSettings.ts create mode 100644 web/config/oneapi.json create mode 100644 web/config/proxy.ts create mode 100644 web/config/routes.ts create mode 100644 web/jest.config.js create mode 100644 web/jsconfig.json create mode 100644 web/mock/listTableList.ts create mode 100644 web/mock/notices.ts create mode 100644 web/mock/route.ts create mode 100644 web/mock/user.ts create mode 100644 web/package.json create mode 100644 web/playwright.config.ts create mode 100644 web/public/favicon.ico create mode 100644 web/public/icon.svg create mode 100644 web/public/icon_black.svg create mode 100644 web/public/icons/icon-128x128.png create mode 100644 web/public/icons/icon-192x192.png create mode 100644 web/public/icons/icon-512x512.png create mode 100644 web/public/logo.png create mode 100644 web/src/access.ts create mode 100644 web/src/app.tsx create mode 100644 web/src/components/CodePreview/index.less create mode 100644 web/src/components/CodePreview/index.tsx create mode 100644 web/src/components/Footer/index.tsx create mode 100644 web/src/components/HeaderDropdown/index.less create mode 100644 web/src/components/HeaderDropdown/index.tsx create mode 100644 web/src/components/HeaderSearch/index.less create mode 100644 web/src/components/HeaderSearch/index.tsx create mode 100644 web/src/components/NoticeIcon/NoticeIcon.tsx create mode 100755 web/src/components/NoticeIcon/NoticeList.less create mode 100644 web/src/components/NoticeIcon/NoticeList.tsx create mode 100644 web/src/components/NoticeIcon/index.less create mode 100644 web/src/components/NoticeIcon/index.tsx create mode 100644 web/src/components/RightContent/AvatarDropdown.tsx create mode 100644 web/src/components/RightContent/index.less create mode 100644 web/src/components/RightContent/index.tsx create mode 100644 web/src/components/ShieldList/index.tsx create mode 100644 web/src/components/index.md create mode 100644 web/src/e2e/baseLayout.e2e.spec.ts create mode 100644 web/src/global.less create mode 100644 web/src/global.tsx create mode 100644 web/src/locales/bn-BD.ts create mode 100644 web/src/locales/bn-BD/component.ts create mode 100644 web/src/locales/bn-BD/globalHeader.ts create mode 100644 web/src/locales/bn-BD/menu.ts create mode 100644 web/src/locales/bn-BD/pages.ts create mode 100644 web/src/locales/bn-BD/pwa.ts create mode 100644 web/src/locales/bn-BD/settingDrawer.ts create mode 100644 web/src/locales/bn-BD/settings.ts create mode 100644 web/src/locales/en-US.ts create mode 100644 web/src/locales/en-US/component.ts create mode 100644 web/src/locales/en-US/globalHeader.ts create mode 100644 web/src/locales/en-US/menu.ts create mode 100644 web/src/locales/en-US/pages.ts create mode 100644 web/src/locales/en-US/pwa.ts create mode 100644 web/src/locales/en-US/settingDrawer.ts create mode 100644 web/src/locales/en-US/settings.ts create mode 100644 web/src/locales/fa-IR.ts create mode 100644 web/src/locales/fa-IR/component.ts create mode 100644 web/src/locales/fa-IR/globalHeader.ts create mode 100644 web/src/locales/fa-IR/menu.ts create mode 100644 web/src/locales/fa-IR/pages.ts create mode 100644 web/src/locales/fa-IR/pwa.ts create mode 100644 web/src/locales/fa-IR/settingDrawer.ts create mode 100644 web/src/locales/fa-IR/settings.ts create mode 100644 web/src/locales/id-ID.ts create mode 100644 web/src/locales/id-ID/component.ts create mode 100644 web/src/locales/id-ID/globalHeader.ts create mode 100644 web/src/locales/id-ID/menu.ts create mode 100644 web/src/locales/id-ID/pages.ts create mode 100644 web/src/locales/id-ID/pwa.ts create mode 100644 web/src/locales/id-ID/settingDrawer.ts create mode 100644 web/src/locales/id-ID/settings.ts create mode 100644 web/src/locales/ja-JP.ts create mode 100644 web/src/locales/ja-JP/component.ts create mode 100644 web/src/locales/ja-JP/globalHeader.ts create mode 100644 web/src/locales/ja-JP/menu.ts create mode 100644 web/src/locales/ja-JP/pages.ts create mode 100644 web/src/locales/ja-JP/pwa.ts create mode 100644 web/src/locales/ja-JP/settingDrawer.ts create mode 100644 web/src/locales/ja-JP/settings.ts create mode 100644 web/src/locales/pt-BR.ts create mode 100644 web/src/locales/pt-BR/component.ts create mode 100644 web/src/locales/pt-BR/globalHeader.ts create mode 100644 web/src/locales/pt-BR/menu.ts create mode 100644 web/src/locales/pt-BR/pages.ts create mode 100644 web/src/locales/pt-BR/pwa.ts create mode 100644 web/src/locales/pt-BR/settingDrawer.ts create mode 100644 web/src/locales/pt-BR/settings.ts create mode 100644 web/src/locales/zh-CN.ts create mode 100644 web/src/locales/zh-CN/component.ts create mode 100644 web/src/locales/zh-CN/globalHeader.ts create mode 100644 web/src/locales/zh-CN/menu.ts create mode 100644 web/src/locales/zh-CN/pages.ts create mode 100644 web/src/locales/zh-CN/pwa.ts create mode 100644 web/src/locales/zh-CN/settingDrawer.ts create mode 100644 web/src/locales/zh-CN/settings.ts create mode 100644 web/src/locales/zh-TW.ts create mode 100644 web/src/locales/zh-TW/component.ts create mode 100644 web/src/locales/zh-TW/globalHeader.ts create mode 100644 web/src/locales/zh-TW/menu.ts create mode 100644 web/src/locales/zh-TW/pwa.ts create mode 100644 web/src/locales/zh-TW/settingDrawer.ts create mode 100644 web/src/locales/zh-TW/settings.ts create mode 100644 web/src/manifest.json create mode 100644 web/src/pages/404.tsx create mode 100644 web/src/pages/Admin.tsx create mode 100644 web/src/pages/Dashboard.less create mode 100644 web/src/pages/Dashboard.tsx create mode 100644 web/src/pages/TableList/components/UpdateForm.tsx create mode 100644 web/src/pages/TableList/index.tsx create mode 100644 web/src/pages/application/index.tsx create mode 100644 web/src/pages/document.ejs create mode 100644 web/src/pages/network/Proxy/components/CreateForm.tsx create mode 100644 web/src/pages/network/Proxy/index.tsx create mode 100644 web/src/pages/setting/Server/index.tsx create mode 100644 web/src/pages/setting/System/index.tsx create mode 100644 web/src/pages/user/Login/index.less create mode 100644 web/src/pages/user/Login/index.tsx create mode 100644 web/src/service-worker.js create mode 100644 web/src/services/ant-design-pro/api.ts create mode 100644 web/src/services/ant-design-pro/index.ts create mode 100644 web/src/services/ant-design-pro/login.ts create mode 100644 web/src/services/ant-design-pro/typings.d.ts create mode 100644 web/src/services/swagger/index.ts create mode 100644 web/src/services/swagger/pet.ts create mode 100644 web/src/services/swagger/store.ts create mode 100644 web/src/services/swagger/typings.d.ts create mode 100644 web/src/services/swagger/user.ts create mode 100644 web/src/typings.d.ts create mode 100644 web/tests/run-tests.js create mode 100644 web/tests/setupTests.js create mode 100644 web/tsconfig.json diff --git a/.gitignore b/.gitignore index c037d28..ee2a61c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ *.key dist/ -web/ *.log *.toml .config diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 0000000..7e3649a --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/web/.eslintignore b/web/.eslintignore new file mode 100644 index 0000000..8336e93 --- /dev/null +++ b/web/.eslintignore @@ -0,0 +1,8 @@ +/lambda/ +/scripts +/config +.history +public +dist +.umi +mock \ No newline at end of file diff --git a/web/.eslintrc.js b/web/.eslintrc.js new file mode 100644 index 0000000..b882c20 --- /dev/null +++ b/web/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + extends: [require.resolve('@umijs/fabric/dist/eslint')], + globals: { + ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, + page: true, + REACT_APP_ENV: true, + }, +}; diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..21ab9fb --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +**/node_modules +# roadhog-api-doc ignore +/src/utils/request-temp.js +_roadhog-api-doc + +# production +/dist + +# misc +.DS_Store +npm-debug.log* +yarn-error.log + +/coverage +.idea +yarn.lock +package-lock.json +pnpm-lock.yaml +*bak + + +# visual studio code +.history +*.log +functions/* +.temp/** + +# umi +.umi +.umi-production + +# screenshot +screenshot +.firebase +.eslintcache + +build diff --git a/web/.prettierignore b/web/.prettierignore new file mode 100644 index 0000000..d17efb4 --- /dev/null +++ b/web/.prettierignore @@ -0,0 +1,23 @@ +**/*.svg +package.json +.umi +.umi-production +/dist +.dockerignore +.DS_Store +.eslintignore +*.png +*.toml +docker +.editorconfig +Dockerfile* +.gitignore +.prettierignore +LICENSE +.eslintcache +*.lock +yarn-error.log +.history +CNAME +/build +/public \ No newline at end of file diff --git a/web/.prettierrc.js b/web/.prettierrc.js new file mode 100644 index 0000000..7b597d7 --- /dev/null +++ b/web/.prettierrc.js @@ -0,0 +1,5 @@ +const fabric = require('@umijs/fabric'); + +module.exports = { + ...fabric.prettier, +}; diff --git a/web/.stylelintrc.js b/web/.stylelintrc.js new file mode 100644 index 0000000..a1184de --- /dev/null +++ b/web/.stylelintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@umijs/fabric/dist/stylelint')], +}; diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..a7c8143 --- /dev/null +++ b/web/README.md @@ -0,0 +1,3 @@ +# SeaMoon Web Control + +客户端 web 页面, 基于 ant design pro 5.2.0 diff --git a/web/config/config.dev.ts b/web/config/config.dev.ts new file mode 100644 index 0000000..ab0e590 --- /dev/null +++ b/web/config/config.dev.ts @@ -0,0 +1,15 @@ +// https://umijs.org/config/ +import { defineConfig } from 'umi'; + +export default defineConfig({ + plugins: [ + // https://github.com/zthxxx/react-dev-inspector + 'react-dev-inspector/plugins/umi/react-inspector', + ], + // https://github.com/zthxxx/react-dev-inspector#inspector-loader-props + inspectorConfig: { + exclude: [], + babelPlugins: [], + babelOptions: {}, + }, +}); diff --git a/web/config/config.ts b/web/config/config.ts new file mode 100644 index 0000000..d288354 --- /dev/null +++ b/web/config/config.ts @@ -0,0 +1,76 @@ +// https://umijs.org/config/ +import { defineConfig } from 'umi'; +import { join } from 'path'; + +import defaultSettings from './defaultSettings'; +import proxy from './proxy'; +import routes from './routes'; + +const { REACT_APP_ENV } = process.env; + +export default defineConfig({ + hash: true, + antd: {}, + dva: { + hmr: true, + }, + layout: { + // https://umijs.org/zh-CN/plugins/plugin-layout + locale: true, + siderWidth: 208, + ...defaultSettings, + }, + // https://umijs.org/zh-CN/plugins/plugin-locale + locale: { + // default zh-CN + default: 'zh-CN', + antd: true, + // default true, when it is true, will use `navigator.language` overwrite default + baseNavigator: true, + }, + dynamicImport: { + loading: '@ant-design/pro-layout/es/PageLoading', + }, + targets: { + ie: 11, + }, + // umi routes: https://umijs.org/docs/routing + routes, + access: {}, + // Theme for antd: https://ant.design/docs/react/customize-theme-cn + theme: { + // 如果不想要 configProvide 动态设置主题需要把这个设置为 default + // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 + // https://ant.design/docs/react/customize-theme-variable-cn + 'root-entry-name': 'variable', + }, + // esbuild is father build tools + // https://umijs.org/plugins/plugin-esbuild + esbuild: {}, + title: false, + ignoreMomentLocale: true, + proxy: proxy[REACT_APP_ENV || 'dev'], + manifest: { + basePath: '/', + }, + // Fast Refresh 热更新 + fastRefresh: {}, + openAPI: [ + { + requestLibPath: "import { request } from 'umi'", + // 或者使用在线的版本 + // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json" + schemaPath: join(__dirname, 'oneapi.json'), + mock: false, + }, + { + requestLibPath: "import { request } from 'umi'", + schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json', + projectName: 'swagger', + }, + ], + nodeModulesTransform: { type: 'none' }, + mfsu: {}, + webpack5: {}, + exportStatic: {}, +}); diff --git a/web/config/defaultSettings.ts b/web/config/defaultSettings.ts new file mode 100644 index 0000000..476d4d2 --- /dev/null +++ b/web/config/defaultSettings.ts @@ -0,0 +1,21 @@ +import {Settings as LayoutSettings} from '@ant-design/pro-components'; + +const Settings: LayoutSettings & { + pwa?: boolean; + logo?: string; +} = { + navTheme: 'realDark', + // 拂晓蓝 + primaryColor: '#76b39d', + layout: 'mix', + contentWidth: 'Fluid', + fixedHeader: false, + fixSiderbar: true, + colorWeak: false, + title: 'SeaMoon', + pwa: false, + logo: 'icon.svg', + iconfontUrl: '', +}; + +export default Settings; diff --git a/web/config/oneapi.json b/web/config/oneapi.json new file mode 100644 index 0000000..c77d988 --- /dev/null +++ b/web/config/oneapi.json @@ -0,0 +1,593 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Ant Design Pro", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:8000/" + }, + { + "url": "https://localhost:8000/" + } + ], + "paths": { + "/api/currentUser": { + "get": { + "tags": ["api"], + "description": "获取当前的用户", + "operationId": "currentUser", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CurrentUser" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "x-swagger-router-controller": "api" + }, + "/api/login/captcha": { + "post": { + "description": "发送验证码", + "operationId": "getFakeCaptcha", + "tags": ["login"], + "parameters": [ + { + "name": "phone", + "in": "query", + "description": "手机号", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FakeCaptcha" + } + } + } + } + } + } + }, + "/api/login/outLogin": { + "post": { + "description": "登录接口", + "operationId": "outLogin", + "tags": ["login"], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "x-swagger-router-controller": "api" + }, + "/api/login/account": { + "post": { + "tags": ["login"], + "description": "登录接口", + "operationId": "login", + "requestBody": { + "description": "登录系统", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginParams" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginResult" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-codegen-request-body-name": "body" + }, + "x-swagger-router-controller": "api" + }, + "/api/notices": { + "summary": "getNotices", + "description": "NoticeIconItem", + "get": { + "tags": ["api"], + "operationId": "getNotices", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NoticeIconList" + } + } + } + } + } + } + }, + "/api/rule": { + "get": { + "tags": ["rule"], + "description": "获取规则列表", + "operationId": "rule", + "parameters": [ + { + "name": "current", + "in": "query", + "description": "当前的页码", + "schema": { + "type": "number" + } + }, + { + "name": "pageSize", + "in": "query", + "description": "页面的容量", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleList" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": ["rule"], + "description": "新建规则", + "operationId": "addRule", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleListItem" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "put": { + "tags": ["rule"], + "description": "新建规则", + "operationId": "updateRule", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleListItem" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "delete": { + "tags": ["rule"], + "description": "删除规则", + "operationId": "removeRule", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "x-swagger-router-controller": "api" + }, + "/swagger": { + "x-swagger-pipe": "swagger_raw" + } + }, + "components": { + "schemas": { + "CurrentUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "userid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "title": { + "type": "string" + }, + "group": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "label": { + "type": "string" + } + } + } + }, + "notifyCount": { + "type": "integer", + "format": "int32" + }, + "unreadCount": { + "type": "integer", + "format": "int32" + }, + "country": { + "type": "string" + }, + "access": { + "type": "string" + }, + "geographic": { + "type": "object", + "properties": { + "province": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "key": { + "type": "string" + } + } + }, + "city": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + } + }, + "address": { + "type": "string" + }, + "phone": { + "type": "string" + } + } + }, + "LoginResult": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "currentAuthority": { + "type": "string" + } + } + }, + "PageParams": { + "type": "object", + "properties": { + "current": { + "type": "number" + }, + "pageSize": { + "type": "number" + } + } + }, + "RuleListItem": { + "type": "object", + "properties": { + "key": { + "type": "integer", + "format": "int32" + }, + "disabled": { + "type": "boolean" + }, + "href": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "callNo": { + "type": "integer", + "format": "int32" + }, + "status": { + "type": "integer", + "format": "int32" + }, + "updatedAt": { + "type": "string", + "format": "datetime" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "progress": { + "type": "integer", + "format": "int32" + } + } + }, + "RuleList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RuleListItem" + } + }, + "total": { + "type": "integer", + "description": "列表的内容总数", + "format": "int32" + }, + "success": { + "type": "boolean" + } + } + }, + "FakeCaptcha": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "status": { + "type": "string" + } + } + }, + "LoginParams": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "autoLogin": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, + "ErrorResponse": { + "required": ["errorCode"], + "type": "object", + "properties": { + "errorCode": { + "type": "string", + "description": "业务约定的错误码" + }, + "errorMessage": { + "type": "string", + "description": "业务上的错误信息" + }, + "success": { + "type": "boolean", + "description": "业务上的请求是否成功" + } + } + }, + "NoticeIconList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NoticeIconItem" + } + }, + "total": { + "type": "integer", + "description": "列表的内容总数", + "format": "int32" + }, + "success": { + "type": "boolean" + } + } + }, + "NoticeIconItemType": { + "title": "NoticeIconItemType", + "description": "已读未读列表的枚举", + "type": "string", + "properties": {}, + "enum": ["notification", "message", "event"] + }, + "NoticeIconItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "extra": { + "type": "string", + "format": "any" + }, + "key": { "type": "string" }, + "read": { + "type": "boolean" + }, + "avatar": { + "type": "string" + }, + "title": { + "type": "string" + }, + "status": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date" + }, + "description": { + "type": "string" + }, + "type": { + "extensions": { + "x-is-enum": true + }, + "$ref": "#/components/schemas/NoticeIconItemType" + } + } + } + } + } +} diff --git a/web/config/proxy.ts b/web/config/proxy.ts new file mode 100644 index 0000000..a8194b7 --- /dev/null +++ b/web/config/proxy.ts @@ -0,0 +1,34 @@ +/** + * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 + * ------------------------------- + * The agent cannot take effect in the production environment + * so there is no configuration of the production environment + * For details, please see + * https://pro.ant.design/docs/deploy + */ +export default { + dev: { + // localhost:8000/api/** -> https://preview.pro.ant.design/api/** + '/api/': { + // 要代理的地址 + target: 'https://preview.pro.ant.design', + // 配置了这个可以从 http 代理到 https + // 依赖 origin 的功能可能需要这个,比如 cookie + changeOrigin: true, + }, + }, + test: { + '/api/': { + target: 'https://proapi.azurewebsites.net', + changeOrigin: true, + pathRewrite: { '^': '' }, + }, + }, + pre: { + '/api/': { + target: 'your pre url', + changeOrigin: true, + pathRewrite: { '^': '' }, + }, + }, +}; diff --git a/web/config/routes.ts b/web/config/routes.ts new file mode 100644 index 0000000..1e9a6c4 --- /dev/null +++ b/web/config/routes.ts @@ -0,0 +1,74 @@ +export default [ + { + path: '/user', + layout: false, + routes: [ + { + name: 'login', + path: '/user/login', + component: './user/Login', + }, + { + component: './404', + }, + ], + }, + { + path: '/dashboard', + name: '仪表盘', + icon: 'dashboard', + component: './Dashboard', + }, + { + path: '/network', + name: '网络', + icon: 'cluster', + routes: [ + { + path: 'proxy', + name: '节点', + component: './network/Proxy', + }, + { + component: './404', + }, + ], + }, + { + path: '/application', + name: '应用', + icon: 'AppstoreAdd', + routes: [ + { + component: './404', + }, + ], + }, + { + path: '/setting', + name: '配置', + icon: 'setting', + routes: [ + { + path: 'system', + name: '系统', // 本地代理状态、web相关选项、管理员账户 + component: './setting/System', + }, + { + path: 'server', + name: '服务', // 当前服务运行状态 + component: './setting/Server', + }, + { + component: './404', + }, + ] + }, + { + path: '/', + redirect: '/dashboard', + }, + { + component: './404', + }, +]; diff --git a/web/jest.config.js b/web/jest.config.js new file mode 100644 index 0000000..4729573 --- /dev/null +++ b/web/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + testURL: 'http://localhost:8000', + verbose: false, + extraSetupFiles: ['./tests/setupTests.js'], + globals: { + ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, + localStorage: null, + }, +}; diff --git a/web/jsconfig.json b/web/jsconfig.json new file mode 100644 index 0000000..197bee5 --- /dev/null +++ b/web/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/web/mock/listTableList.ts b/web/mock/listTableList.ts new file mode 100644 index 0000000..08ed86d --- /dev/null +++ b/web/mock/listTableList.ts @@ -0,0 +1,174 @@ +import { Request, Response } from 'express'; +import moment from 'moment'; +import { parse } from 'url'; + +// mock tableListDataSource +const genList = (current: number, pageSize: number) => { + const tableListDataSource: API.RuleListItem[] = []; + + for (let i = 0; i < pageSize; i += 1) { + const index = (current - 1) * 10 + i; + tableListDataSource.push({ + key: index, + disabled: i % 6 === 0, + href: 'https://ant.design', + avatar: [ + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + ][i % 2], + name: `TradeCode ${index}`, + owner: '曲丽丽', + desc: '这是一段描述', + callNo: Math.floor(Math.random() * 1000), + status: Math.floor(Math.random() * 10) % 4, + updatedAt: moment().format('YYYY-MM-DD'), + createdAt: moment().format('YYYY-MM-DD'), + progress: Math.ceil(Math.random() * 100), + }); + } + tableListDataSource.reverse(); + return tableListDataSource; +}; + +let tableListDataSource = genList(1, 100); + +function getRule(req: Request, res: Response, u: string) { + let realUrl = u; + if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { + realUrl = req.url; + } + const { current = 1, pageSize = 10 } = req.query; + const params = parse(realUrl, true).query as unknown as API.PageParams & + API.RuleListItem & { + sorter: any; + filter: any; + }; + + let dataSource = [...tableListDataSource].slice( + ((current as number) - 1) * (pageSize as number), + (current as number) * (pageSize as number), + ); + if (params.sorter) { + const sorter = JSON.parse(params.sorter); + dataSource = dataSource.sort((prev, next) => { + let sortNumber = 0; + Object.keys(sorter).forEach((key) => { + if (sorter[key] === 'descend') { + if (prev[key] - next[key] > 0) { + sortNumber += -1; + } else { + sortNumber += 1; + } + return; + } + if (prev[key] - next[key] > 0) { + sortNumber += 1; + } else { + sortNumber += -1; + } + }); + return sortNumber; + }); + } + if (params.filter) { + const filter = JSON.parse(params.filter as any) as { + [key: string]: string[]; + }; + if (Object.keys(filter).length > 0) { + dataSource = dataSource.filter((item) => { + return Object.keys(filter).some((key) => { + if (!filter[key]) { + return true; + } + if (filter[key].includes(`${item[key]}`)) { + return true; + } + return false; + }); + }); + } + } + + if (params.name) { + dataSource = dataSource.filter((data) => data?.name?.includes(params.name || '')); + } + const result = { + data: dataSource, + total: tableListDataSource.length, + success: true, + pageSize, + current: parseInt(`${params.current}`, 10) || 1, + }; + + return res.json(result); +} + +function postRule(req: Request, res: Response, u: string, b: Request) { + let realUrl = u; + if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { + realUrl = req.url; + } + + const body = (b && b.body) || req.body; + const { method, name, desc, key } = body; + + switch (method) { + /* eslint no-case-declarations:0 */ + case 'delete': + tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1); + break; + case 'post': + (() => { + const i = Math.ceil(Math.random() * 10000); + const newRule: API.RuleListItem = { + key: tableListDataSource.length, + href: 'https://ant.design', + avatar: [ + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + ][i % 2], + name, + owner: '曲丽丽', + desc, + callNo: Math.floor(Math.random() * 1000), + status: Math.floor(Math.random() * 10) % 2, + updatedAt: moment().format('YYYY-MM-DD'), + createdAt: moment().format('YYYY-MM-DD'), + progress: Math.ceil(Math.random() * 100), + }; + tableListDataSource.unshift(newRule); + return res.json(newRule); + })(); + return; + + case 'update': + (() => { + let newRule = {}; + tableListDataSource = tableListDataSource.map((item) => { + if (item.key === key) { + newRule = { ...item, desc, name }; + return { ...item, desc, name }; + } + return item; + }); + return res.json(newRule); + })(); + return; + default: + break; + } + + const result = { + list: tableListDataSource, + pagination: { + total: tableListDataSource.length, + }, + }; + + res.json(result); +} + +export default { + 'GET /api/rule': getRule, + 'POST /api/rule': postRule, +}; diff --git a/web/mock/notices.ts b/web/mock/notices.ts new file mode 100644 index 0000000..732dd58 --- /dev/null +++ b/web/mock/notices.ts @@ -0,0 +1,107 @@ +import { Request, Response } from 'express'; + +const getNotices = (req: Request, res: Response) => { + res.json({ + data: [ + { + id: '000000001', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', + title: '你收到了 14 份新周报', + datetime: '2017-08-09', + type: 'notification', + }, + { + id: '000000002', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', + title: '你推荐的 曲妮妮 已通过第三轮面试', + datetime: '2017-08-08', + type: 'notification', + }, + { + id: '000000003', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', + title: '这种模板可以区分多种通知类型', + datetime: '2017-08-07', + read: true, + type: 'notification', + }, + { + id: '000000004', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + datetime: '2017-08-07', + type: 'notification', + }, + { + id: '000000005', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', + title: '内容不要超过两行字,超出时自动截断', + datetime: '2017-08-07', + type: 'notification', + }, + { + id: '000000006', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '曲丽丽 评论了你', + description: '描述信息描述信息描述信息', + datetime: '2017-08-07', + type: 'message', + clickClose: true, + }, + { + id: '000000007', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '朱偏右 回复了你', + description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', + datetime: '2017-08-07', + type: 'message', + clickClose: true, + }, + { + id: '000000008', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '标题', + description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', + datetime: '2017-08-07', + type: 'message', + clickClose: true, + }, + { + id: '000000009', + title: '任务名称', + description: '任务需要在 2017-01-12 20:00 前启动', + extra: '未开始', + status: 'todo', + type: 'event', + }, + { + id: '000000010', + title: '第三方紧急代码变更', + description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', + extra: '马上到期', + status: 'urgent', + type: 'event', + }, + { + id: '000000011', + title: '信息安全考试', + description: '指派竹尔于 2017-01-09 前完成更新并发布', + extra: '已耗时 8 天', + status: 'doing', + type: 'event', + }, + { + id: '000000012', + title: 'ABCD 版本发布', + description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', + extra: '进行中', + status: 'processing', + type: 'event', + }, + ], + }); +}; + +export default { + 'GET /api/notices': getNotices, +}; diff --git a/web/mock/route.ts b/web/mock/route.ts new file mode 100644 index 0000000..418d10f --- /dev/null +++ b/web/mock/route.ts @@ -0,0 +1,5 @@ +export default { + '/api/auth_routes': { + '/form/advanced-form': { authority: ['admin', 'user'] }, + }, +}; diff --git a/web/mock/user.ts b/web/mock/user.ts new file mode 100644 index 0000000..75edd34 --- /dev/null +++ b/web/mock/user.ts @@ -0,0 +1,203 @@ +import { Request, Response } from 'express'; + +const waitTime = (time: number = 100) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, time); + }); +}; + +async function getFakeCaptcha(req: Request, res: Response) { + await waitTime(2000); + return res.json('captcha-xxx'); +} + +const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env; + +/** + * 当前用户的权限,如果为空代表没登录 + * current user access, if is '', user need login + * 如果是 pro 的预览,默认是有权限的 + */ +let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : ''; + +const getAccess = () => { + return access; +}; + +// 代码中会兼容本地 service mock 以及部署站点的静态数据 +export default { + // 支持值为 Object 和 Array + 'GET /api/currentUser': (req: Request, res: Response) => { + if (!getAccess()) { + res.status(401).send({ + data: { + isLogin: false, + }, + errorCode: '401', + errorMessage: '请先登录!', + success: true, + }); + return; + } + res.send({ + success: true, + data: { + name: 'Serati Ma', + avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', + userid: '00000001', + email: 'antdesign@alipay.com', + signature: '海纳百川,有容乃大', + title: '交互专家', + group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', + tags: [ + { + key: '0', + label: '很有想法的', + }, + { + key: '1', + label: '专注设计', + }, + { + key: '2', + label: '辣~', + }, + { + key: '3', + label: '大长腿', + }, + { + key: '4', + label: '川妹子', + }, + { + key: '5', + label: '海纳百川', + }, + ], + notifyCount: 12, + unreadCount: 11, + country: 'China', + access: getAccess(), + geographic: { + province: { + label: '浙江省', + key: '330000', + }, + city: { + label: '杭州市', + key: '330100', + }, + }, + address: '西湖区工专路 77 号', + phone: '0752-268888888', + }, + }); + }, + // GET POST 可省略 + 'GET /api/users': [ + { + key: '1', + name: 'John Brown', + age: 32, + address: 'New York No. 1 Lake Park', + }, + { + key: '2', + name: 'Jim Green', + age: 42, + address: 'London No. 1 Lake Park', + }, + { + key: '3', + name: 'Joe Black', + age: 32, + address: 'Sidney No. 1 Lake Park', + }, + ], + 'POST /api/login/account': async (req: Request, res: Response) => { + const { password, username, type } = req.body; + await waitTime(2000); + if (password === 'ant.design' && username === 'admin') { + res.send({ + status: 'ok', + type, + currentAuthority: 'admin', + }); + access = 'admin'; + return; + } + if (password === 'ant.design' && username === 'user') { + res.send({ + status: 'ok', + type, + currentAuthority: 'user', + }); + access = 'user'; + return; + } + if (type === 'mobile') { + res.send({ + status: 'ok', + type, + currentAuthority: 'admin', + }); + access = 'admin'; + return; + } + + res.send({ + status: 'error', + type, + currentAuthority: 'guest', + }); + access = 'guest'; + }, + 'POST /api/login/outLogin': (req: Request, res: Response) => { + access = ''; + res.send({ data: {}, success: true }); + }, + 'POST /api/register': (req: Request, res: Response) => { + res.send({ status: 'ok', currentAuthority: 'user', success: true }); + }, + 'GET /api/500': (req: Request, res: Response) => { + res.status(500).send({ + timestamp: 1513932555104, + status: 500, + error: 'error', + message: 'error', + path: '/base/category/list', + }); + }, + 'GET /api/404': (req: Request, res: Response) => { + res.status(404).send({ + timestamp: 1513932643431, + status: 404, + error: 'Not Found', + message: 'No message available', + path: '/base/category/list/2121212', + }); + }, + 'GET /api/403': (req: Request, res: Response) => { + res.status(403).send({ + timestamp: 1513932555104, + status: 403, + error: 'Forbidden', + message: 'Forbidden', + path: '/base/category/list', + }); + }, + 'GET /api/401': (req: Request, res: Response) => { + res.status(401).send({ + timestamp: 1513932555104, + status: 401, + error: 'Unauthorized', + message: 'Unauthorized', + path: '/base/category/list', + }); + }, + + 'GET /api/login/captcha': getFakeCaptcha, +}; diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..7332efd --- /dev/null +++ b/web/package.json @@ -0,0 +1,95 @@ +{ + "name": "ant-design-pro", + "version": "5.2.0", + "private": true, + "description": "An out-of-box UI solution for enterprise applications", + "scripts": { + "analyze": "cross-env ANALYZE=1 umi build", + "build": "umi build", + "deploy": "npm run build && npm run gh-pages", + "dev": "npm run start:dev", + "gh-pages": "gh-pages -d dist", + "i18n-remove": "pro i18n-remove --locale=zh-CN --write", + "postinstall": "umi g tmp", + "lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier && npm run tsc", + "lint-staged": "lint-staged", + "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ", + "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style", + "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src", + "lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto", + "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less", + "openapi": "umi openapi", + "playwright": "playwright install && playwright test", + "prepare": "husky install", + "prettier": "prettier -c --write \"src/**/*\"", + "serve": "umi-serve", + "start": "cross-env UMI_ENV=dev umi dev", + "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev", + "start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev", + "start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev", + "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev", + "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev", + "test": "umi test", + "test:component": "umi test ./src/components", + "test:e2e": "node ./tests/run-tests.js", + "tsc": "tsc --noEmit" + }, + "lint-staged": { + "**/*.less": "stylelint --syntax less", + "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js", + "**/*.{js,jsx,tsx,ts,less,md,json}": ["prettier --write"] + }, + "browserslist": ["> 1%", "last 2 versions", "not ie <= 10"], + "dependencies": { + "@ant-design/icons": "^4.7.0", + "@ant-design/pro-components": "1.1.5", + "@umijs/route-utils": "^2.0.0", + "antd": "^4.20.0", + "classnames": "^2.3.0", + "lodash": "^4.17.0", + "moment": "^2.29.0", + "omit.js": "^2.0.2", + "rc-menu": "^9.1.0", + "rc-util": "^5.16.0", + "react": "^17.0.0", + "react-dev-inspector": "^1.7.0", + "react-dom": "^17.0.0", + "react-helmet-async": "^1.2.0", + "umi": "^3.5.0" + }, + "devDependencies": { + "@ant-design/pro-cli": "^2.1.0", + "@playwright/test": "^1.17.0", + "@types/classnames": "^2.3.1", + "@types/express": "^4.17.0", + "@types/history": "^4.7.0", + "@types/jest": "^26.0.0", + "@types/lodash": "^4.14.0", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "@types/react-helmet": "^6.1.0", + "@umijs/fabric": "^2.11.1", + "@umijs/openapi": "^1.6.0", + "@umijs/plugin-blocks": "^2.2.0", + "@umijs/plugin-esbuild": "^1.4.0", + "@umijs/plugin-openapi": "^1.3.3", + "@umijs/preset-ant-design-pro": "^1.3.0", + "@umijs/preset-dumi": "^1.1.0", + "@umijs/preset-react": "^2.1.0", + "cross-env": "^7.0.0", + "cross-port-killer": "^1.3.0", + "detect-installer": "^1.0.0", + "eslint": "^7.32.0", + "gh-pages": "^3.2.0", + "husky": "^7.0.4", + "jsdom-global": "^3.0.0", + "lint-staged": "^10.0.0", + "mockjs": "^1.1.0", + "prettier": "^2.5.0", + "stylelint": "^13.0.0", + "swagger-ui-dist": "^4.12.0", + "typescript": "^4.5.0", + "umi-serve": "^1.9.10" + }, + "engines": { "node": ">=12.0.0" } +} diff --git a/web/playwright.config.ts b/web/playwright.config.ts new file mode 100644 index 0000000..ec1b31d --- /dev/null +++ b/web/playwright.config.ts @@ -0,0 +1,22 @@ +// playwright.config.ts +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + use: { + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + ], +}; +export default config; diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..106c293ab616faf4e3ec8af0ce013319c4414d6e GIT binary patch literal 270398 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 + Created with Snap + + + + + + + Created with Snap + + + + + + + Created with Snap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/public/icon_black.svg b/web/public/icon_black.svg new file mode 100644 index 0000000..d12f2aa --- /dev/null +++ b/web/public/icon_black.svg @@ -0,0 +1,55 @@ + + Created with Snap + + + + + + + Created with Snap + + + + + + + Created with Snap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/public/icons/icon-128x128.png b/web/public/icons/icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..48d0e2339a60a637b94319c65e8654289b4f4b6c GIT binary patch literal 1329 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

*C7T$?5!b= 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

  • ))p{K&C$~e_HKxVEfjIyVgf1 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 F{{XrZ)dBzj literal 0 HcmV?d00001 diff --git a/web/src/access.ts b/web/src/access.ts new file mode 100644 index 0000000..e823e24 --- /dev/null +++ b/web/src/access.ts @@ -0,0 +1,9 @@ +/** + * @see https://umijs.org/zh-CN/plugins/plugin-access + * */ +export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) { + const { currentUser } = initialState ?? {}; + return { + canAdmin: currentUser && currentUser.access === 'admin', + }; +} diff --git a/web/src/app.tsx b/web/src/app.tsx new file mode 100644 index 0000000..10f2366 --- /dev/null +++ b/web/src/app.tsx @@ -0,0 +1,101 @@ +import Footer from '@/components/Footer'; +import RightContent from '@/components/RightContent'; +import type {Settings as LayoutSettings} from '@ant-design/pro-components'; +import {PageLoading, SettingDrawer} from '@ant-design/pro-components'; +import type {RunTimeLayoutConfig} from 'umi'; +import {history} from 'umi'; +import {ConfigProvider} from 'antd'; +import defaultSettings from '../config/defaultSettings'; +import {currentUser as queryCurrentUser} from './services/ant-design-pro/api'; + +const loginPath = '/user/login'; + +ConfigProvider.config({ + theme: { + primaryColor: '#76b39d', + } +}); + +/** 获取用户信息比较慢的时候会展示一个 loading */ +export const initialStateConfig = { + loading: , +}; + +/** + * @see https://umijs.org/zh-CN/plugins/plugin-initial-state + * */ +export async function getInitialState(): Promise<{ + settings?: Partial; + currentUser?: API.CurrentUser; + loading?: boolean; + fetchUserInfo?: () => Promise; +}> { + const fetchUserInfo = async () => { + try { + const msg = await queryCurrentUser(); + return msg.data; + } catch (error) { + history.push(loginPath); + } + return undefined; + }; + // 如果不是登录页面,执行 + if (history.location.pathname !== loginPath) { + const currentUser = await fetchUserInfo(); + return { + fetchUserInfo, + currentUser, + settings: defaultSettings, + }; + } + return { + fetchUserInfo, + settings: defaultSettings, + }; +} + +// ProLayout 支持的api https://procomponents.ant.design/components/layout +export const layout: RunTimeLayoutConfig = ({initialState, setInitialState}) => { + return { + rightContentRender: () => , + disableContentMargin: false, + waterMarkProps: false, + // content: initialState?.currentUser?.name, + // }, + footerRender: () =>