beetle 11 mēneši atpakaļ
vecāks
revīzija
94e16a5f97
100 mainītis faili ar 4299 papildinājumiem un 0 dzēšanām
  1. 25 0
      .gitignore
  2. 16 0
      YBEE.EQM.Admin/.editorconfig
  3. 8 0
      YBEE.EQM.Admin/.eslintignore
  4. 7 0
      YBEE.EQM.Admin/.eslintrc.js
  5. 41 0
      YBEE.EQM.Admin/.gitignore
  6. 7 0
      YBEE.EQM.Admin/.husky/commit-msg
  7. 4 0
      YBEE.EQM.Admin/.husky/pre-commit
  8. 22 0
      YBEE.EQM.Admin/.prettierignore
  9. 23 0
      YBEE.EQM.Admin/.prettierrc.js
  10. 57 0
      YBEE.EQM.Admin/README.md
  11. 21 0
      YBEE.EQM.Admin/config/app.config.dev.js
  12. 21 0
      YBEE.EQM.Admin/config/app.config.js
  13. 160 0
      YBEE.EQM.Admin/config/config.ts
  14. 24 0
      YBEE.EQM.Admin/config/defaultSettings.ts
  15. 44 0
      YBEE.EQM.Admin/config/proxy.ts
  16. 303 0
      YBEE.EQM.Admin/config/routes.ts
  17. 23 0
      YBEE.EQM.Admin/jest.config.ts
  18. 11 0
      YBEE.EQM.Admin/jsconfig.json
  19. 109 0
      YBEE.EQM.Admin/package.json
  20. 1 0
      YBEE.EQM.Admin/public/CNAME
  21. 63 0
      YBEE.EQM.Admin/public/css/iconfont.css
  22. 0 0
      YBEE.EQM.Admin/public/css/iconfont.js
  23. 93 0
      YBEE.EQM.Admin/public/css/iconfont.json
  24. BIN
      YBEE.EQM.Admin/public/css/iconfont.ttf
  25. BIN
      YBEE.EQM.Admin/public/css/iconfont.woff
  26. BIN
      YBEE.EQM.Admin/public/css/iconfont.woff2
  27. BIN
      YBEE.EQM.Admin/public/doc-templates/学生信息上报-填报模板.xlsx
  28. BIN
      YBEE.EQM.Admin/public/doc-templates/教师任教科目(初中)上报-填报模板.xlsx
  29. BIN
      YBEE.EQM.Admin/public/doc-templates/教师任教科目(小学)上报-填报模板.xlsx
  30. BIN
      YBEE.EQM.Admin/public/doc-templates/教师任教科目(高中)上报-填报模板.xlsx
  31. BIN
      YBEE.EQM.Admin/public/doc-templates/教师信息上报-填报模板.xlsx
  32. BIN
      YBEE.EQM.Admin/public/doc-templates/特殊学生信息(初中)上报-填报模板.xlsx
  33. BIN
      YBEE.EQM.Admin/public/doc-templates/特殊学生信息(小学)上报-填报模板.xlsx
  34. BIN
      YBEE.EQM.Admin/public/favicon.ico
  35. BIN
      YBEE.EQM.Admin/public/handbook/学校操作手册.pdf
  36. BIN
      YBEE.EQM.Admin/public/handbook/批量上传文件报Wrong Local header signature错误的解决方案.pdf
  37. 1 0
      YBEE.EQM.Admin/public/images/avatar-default.svg
  38. 34 0
      YBEE.EQM.Admin/public/images/avatar-default2.svg
  39. BIN
      YBEE.EQM.Admin/public/images/login-avatar-320.png
  40. BIN
      YBEE.EQM.Admin/public/images/login-avatar.png
  41. BIN
      YBEE.EQM.Admin/public/images/logo.png
  42. 0 0
      YBEE.EQM.Admin/public/images/logo.svg
  43. BIN
      YBEE.EQM.Admin/public/images/logo@2x.png
  44. BIN
      YBEE.EQM.Admin/public/images/page-bg-dark.jpg
  45. BIN
      YBEE.EQM.Admin/public/images/page-bg.jpg
  46. 202 0
      YBEE.EQM.Admin/public/scripts/loading.js
  47. 3 0
      YBEE.EQM.Admin/scripts/gapi/config.js
  48. 643 0
      YBEE.EQM.Admin/scripts/gapi/index.js
  49. 19 0
      YBEE.EQM.Admin/src/access.ts
  50. 194 0
      YBEE.EQM.Admin/src/app.tsx
  51. 36 0
      YBEE.EQM.Admin/src/common/BNumber.ts
  52. 34 0
      YBEE.EQM.Admin/src/common/cache/AccessToken.ts
  53. 34 0
      YBEE.EQM.Admin/src/common/cache/RefreshAccessToken.ts
  54. 33 0
      YBEE.EQM.Admin/src/common/cache/ThemeMode.ts
  55. 3 0
      YBEE.EQM.Admin/src/common/cache/index.ts
  56. 31 0
      YBEE.EQM.Admin/src/common/constant.ts
  57. 224 0
      YBEE.EQM.Admin/src/common/converter.ts
  58. 88 0
      YBEE.EQM.Admin/src/common/dicts.ts
  59. 188 0
      YBEE.EQM.Admin/src/common/helper.ts
  60. 24 0
      YBEE.EQM.Admin/src/common/index.ts
  61. 15 0
      YBEE.EQM.Admin/src/common/net/download.ts
  62. 280 0
      YBEE.EQM.Admin/src/common/permissions.ts
  63. 55 0
      YBEE.EQM.Admin/src/common/typing.d.ts
  64. 141 0
      YBEE.EQM.Admin/src/common/validator.ts
  65. 4 0
      YBEE.EQM.Admin/src/common/valueEnum.ts
  66. 95 0
      YBEE.EQM.Admin/src/components/AuditModal/index.tsx
  67. 19 0
      YBEE.EQM.Admin/src/components/CardStepTitle/index.tsx
  68. 99 0
      YBEE.EQM.Admin/src/components/ChangePassword/index.tsx
  69. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/ai.svg
  70. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/audio.svg
  71. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/download.svg
  72. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/excel.svg
  73. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/folder-group.svg
  74. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/folder-locked.svg
  75. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/folder-open.svg
  76. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/folder-personal.svg
  77. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/folder-share.svg
  78. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/folder.svg
  79. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/gif.svg
  80. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/html.svg
  81. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/jpg.svg
  82. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/pdf.svg
  83. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/png.svg
  84. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/ppt.svg
  85. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/psd.svg
  86. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/txt.svg
  87. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/upload.svg
  88. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/video.svg
  89. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/white.svg
  90. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/word.svg
  91. 1 0
      YBEE.EQM.Admin/src/components/FileIcon/icons/zip.svg
  92. 89 0
      YBEE.EQM.Admin/src/components/FileIcon/index.tsx
  93. 125 0
      YBEE.EQM.Admin/src/components/FileLink/index.tsx
  94. 176 0
      YBEE.EQM.Admin/src/components/FileUpload/index.tsx
  95. 40 0
      YBEE.EQM.Admin/src/components/Footer/index.tsx
  96. 29 0
      YBEE.EQM.Admin/src/components/HeaderDropdown/index.tsx
  97. 24 0
      YBEE.EQM.Admin/src/components/IconFont/index.tsx
  98. 106 0
      YBEE.EQM.Admin/src/components/JsonEditor/index.tsx
  99. 39 0
      YBEE.EQM.Admin/src/components/MenuFooter/index.tsx
  100. 66 0
      YBEE.EQM.Admin/src/components/MovableModal/index.tsx

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+# Build and Release Folders
+bin-debug/
+bin-release/
+[Oo]bj/
+[Bb]in/
+
+logs/
+.vs/
+Migrations/
+wwwroot/
+
+temp/
+
+# Other files and folders
+.settings/
+
+# Executables
+*.swf
+*.air
+*.ipa
+*.apk
+
+# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
+# should NOT be excluded as they contain compiler settings and other important
+# information for Eclipse / Flash Builder.

+ 16 - 0
YBEE.EQM.Admin/.editorconfig

@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab

+ 8 - 0
YBEE.EQM.Admin/.eslintignore

@@ -0,0 +1,8 @@
+/lambda/
+/scripts
+/config
+.history
+public
+dist
+.umi
+mock

+ 7 - 0
YBEE.EQM.Admin/.eslintrc.js

@@ -0,0 +1,7 @@
+module.exports = {
+    extends: [require.resolve('@umijs/lint/dist/config/eslint')],
+    globals: {
+        page: true,
+        REACT_APP_ENV: true,
+    },
+};

+ 41 - 0
YBEE.EQM.Admin/.gitignore

@@ -0,0 +1,41 @@
+# 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
+*bak
+.vscode
+
+
+# visual studio code
+.history
+*.log
+functions/*
+.temp/**
+
+# umi
+.umi
+.umi-production
+.umi-test
+
+# screenshot
+screenshot
+.firebase
+.eslintcache
+
+build

+ 7 - 0
YBEE.EQM.Admin/.husky/commit-msg

@@ -0,0 +1,7 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+# Export Git hook params
+export GIT_PARAMS=$*
+
+npx --no-install fabric verify-commit

+ 4 - 0
YBEE.EQM.Admin/.husky/pre-commit

@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx --no-install lint-staged

+ 22 - 0
YBEE.EQM.Admin/.prettierignore

@@ -0,0 +1,22 @@
+**/*.svg
+.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

+ 23 - 0
YBEE.EQM.Admin/.prettierrc.js

@@ -0,0 +1,23 @@
+module.exports = {
+  singleQuote: true,
+  trailingComma: 'all',
+  printWidth: 100,
+  proseWrap: 'never',
+  endOfLine: 'lf',
+  tabWidth: 4,
+  useTabs: false,
+  overrides: [
+    {
+      files: '.prettierrc',
+      options: {
+        parser: 'json',
+      },
+    },
+    {
+      files: 'document.ejs',
+      options: {
+        parser: 'html',
+      },
+    },
+  ],
+};

+ 57 - 0
YBEE.EQM.Admin/README.md

@@ -0,0 +1,57 @@
+# Ant Design Pro
+
+This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.
+
+## Environment Prepare
+
+Install `node_modules`:
+
+```bash
+npm install
+```
+
+or
+
+```bash
+yarn
+```
+
+## Provided Scripts
+
+Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test.
+
+Scripts provided in `package.json`. It's safe to modify or add additional script:
+
+### Start project
+
+```bash
+npm start
+```
+
+### Build project
+
+```bash
+npm run build
+```
+
+### Check code style
+
+```bash
+npm run lint
+```
+
+You can also use script to auto fix some lint error:
+
+```bash
+npm run lint:fix
+```
+
+### Test code
+
+```bash
+npm test
+```
+
+## More
+
+You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).

+ 21 - 0
YBEE.EQM.Admin/config/app.config.dev.js

@@ -0,0 +1,21 @@
+window.AppConfig = {
+    dev: true,
+    apiRoot: 'http://localhost:8601',
+    fileViewRoot: 'http://localhost:8601/api/file/view',
+    title: '区域教育评估与质量监测',
+    systemName: '区域教育评估与质量监测',
+    company: '',
+    companyFullName: '',
+    edition: 'debug',
+    publicKey: `
+    -----BEGIN PUBLIC KEY-----
+    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApkEAip3hPiQ4AehG1Pr9
+    2NwavhX2RVXbZArpMH3FpNEax4bZ8dtlxXTeAJeK7TcKLVF3YzK+iISBBgXZi+Kj
+    iSNFZL29MVen/gnH7ZRXBiBY7LQNsQGoPmdwHbeLA0sEFFKGAuaq22xh9JdfjS/t
+    oKsTaDMqiIYO4L0sQlMCYbUhL22awi7WIjlkmTePj0+PVpXHhccR85mI89f8lfux
+    FK1Yuoq7QqLBT+nySEWCMRpVcktY2mvhkd0e/1JZbCmSgTogvRZRizkm84tn82qn
+    GcCe7s3rG3QK5CpH8MO9Uw/nLePQwfsAZBLLiivCN2/vR6ccrQtbtTSSI1FyCsrZ
+    DQIDAQAB
+    -----END PUBLIC KEY-----
+    `,
+};

+ 21 - 0
YBEE.EQM.Admin/config/app.config.js

@@ -0,0 +1,21 @@
+window.AppConfig = {
+    dev: false,
+    apiRoot: '',
+    fileViewRoot: '/api/file/view',
+    title: '区域教育评估与质量监测',
+    systemName: '区域教育评估与质量监测',
+    company: '',
+    companyFullName: '',
+    edition: 'release',
+    publicKey: `
+    -----BEGIN PUBLIC KEY-----
+    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApkEAip3hPiQ4AehG1Pr9
+    2NwavhX2RVXbZArpMH3FpNEax4bZ8dtlxXTeAJeK7TcKLVF3YzK+iISBBgXZi+Kj
+    iSNFZL29MVen/gnH7ZRXBiBY7LQNsQGoPmdwHbeLA0sEFFKGAuaq22xh9JdfjS/t
+    oKsTaDMqiIYO4L0sQlMCYbUhL22awi7WIjlkmTePj0+PVpXHhccR85mI89f8lfux
+    FK1Yuoq7QqLBT+nySEWCMRpVcktY2mvhkd0e/1JZbCmSgTogvRZRizkm84tn82qn
+    GcCe7s3rG3QK5CpH8MO9Uw/nLePQwfsAZBLLiivCN2/vR6ccrQtbtTSSI1FyCsrZ
+    DQIDAQAB
+    -----END PUBLIC KEY-----
+    `,
+};

+ 160 - 0
YBEE.EQM.Admin/config/config.ts

@@ -0,0 +1,160 @@
+// https://umijs.org/config/
+import { defineConfig } from '@umijs/max';
+import minimist from 'minimist';
+import defaultSettings from './defaultSettings';
+import proxy from './proxy';
+import routes from './routes';
+
+const { REACT_APP_ENV = 'dev' } = process.env;
+
+const { NODE_ENV } = process.env;
+const argvs = minimist(process.argv);
+const NE = argvs.env ?? NODE_ENV;
+
+const outputPath = '../YBEE.EQM.Web.Entry/wwwroot';
+
+export default defineConfig({
+    /**
+     * @name 开启 hash 模式
+     * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。
+     * @doc https://umijs.org/docs/api/config#hash
+     */
+    hash: true,
+
+    /**
+     * @name 兼容性设置
+     * @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖
+     * @doc https://umijs.org/docs/api/config#targets
+     */
+    // targets: {
+    //   ie: 11,
+    // },
+    /**
+     * @name 路由的配置,不在路由中引入的文件不会编译
+     * @description 只支持 path,component,routes,redirect,wrappers,title 的配置
+     * @doc https://umijs.org/docs/guides/routes
+     */
+    // umi routes: https://umijs.org/docs/routing
+    routes,
+    /**
+     * @name 主题的配置
+     * @description 虽然叫主题,但是其实只是 less 的变量设置
+     * @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn
+     * @doc umi 的theme 配置 https://umijs.org/docs/api/config#theme
+     */
+    theme: {
+        // 如果不想要 configProvide 动态设置主题需要把这个设置为 default
+        // 只有设置为 variable, 才能使用 configProvide 动态设置主色调
+        'root-entry-name': 'variable',
+    },
+    /**
+     * @name moment 的国际化配置
+     * @description 如果对国际化没有要求,打开之后能减少js的包大小
+     * @doc https://umijs.org/docs/api/config#ignoremomentlocale
+     */
+    ignoreMomentLocale: true,
+    /**
+     * @name 代理配置
+     * @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了
+     * @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。
+     * @doc 代理介绍 https://umijs.org/docs/guides/proxy
+     * @doc 代理配置 https://umijs.org/docs/api/config#proxy
+     */
+    proxy: proxy[REACT_APP_ENV as keyof typeof proxy],
+    /**
+     * @name 快速热更新配置
+     * @description 一个不错的热更新组件,更新时可以保留 state
+     */
+    fastRefresh: true,
+    //============== 以下都是max的插件配置 ===============
+    /**
+     * @name 数据流插件
+     * @@doc https://umijs.org/docs/max/data-flow
+     */
+    model: {},
+    /**
+     * 一个全局的初始数据流,可以用它在插件之间共享数据
+     * @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。
+     * @doc https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81
+     */
+    initialState: {},
+    /**
+     * @name layout 插件
+     * @doc https://umijs.org/docs/max/layout-menu
+     */
+    layout: {
+        locale: true,
+        ...defaultSettings,
+    },
+    /**
+     * @name moment2dayjs 插件
+     * @description 将项目中的 moment 替换为 dayjs
+     * @doc https://umijs.org/docs/max/moment2dayjs
+     */
+    moment2dayjs: {
+        preset: 'antd',
+        plugins: ['duration'],
+    },
+    /**
+     * @name 国际化插件
+     * @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 插件
+     * @description 内置了 babel import 插件
+     * @doc https://umijs.org/docs/max/antd#antd
+     */
+    antd: {},
+    /**
+     * @name 网络请求配置
+     * @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
+     * @doc https://umijs.org/docs/max/request
+     */
+    request: {},
+    /**
+     * @name 权限插件
+     * @description 基于 initialState 的权限插件,必须先打开 initialState
+     * @doc https://umijs.org/docs/max/access
+     */
+    access: {},
+    metas: [
+        { "http-equiv": 'Pragma', content: 'no-cache' },
+        { "http-equiv": 'Cache-Control', content: 'no-cache' },
+        { "http-equiv": 'Expires', content: '0' },
+    ],
+    /**
+     * @name <head> 中额外的 script
+     * @description 配置 <head> 中额外的 script
+     */
+    headScripts: [
+        // 解决首次加载时白屏的问题
+        { src: '/scripts/loading.js' },
+        { src: '/scripts/app.config.js' },
+    ],
+    links: [{ href: '/css/iconfont.css', rel: 'stylesheet' }],
+    manifest: {
+        basePath: '/',
+    },
+    outputPath,
+    copy: [
+        {
+            from: `./config/app.config${NE === 'production' ? '' : '.dev'}.js`,
+            to: `${outputPath}/scripts/app.config.js`,
+        },
+    ],
+
+    //================ pro 插件配置 =================
+    presets: ['umi-presets-pro'],
+
+    mfsu: {
+        strategy: 'normal',
+    },
+    requestRecord: {},
+});

+ 24 - 0
YBEE.EQM.Admin/config/defaultSettings.ts

@@ -0,0 +1,24 @@
+import { ProLayoutProps } from '@ant-design/pro-components';
+
+/**
+ * @name
+ */
+const Settings: ProLayoutProps & {
+    pwa?: boolean;
+    logo?: string;
+} = {
+    navTheme: 'light',
+    // 拂晓蓝
+    colorPrimary: '#2f54eb',
+    layout: 'mix',
+    contentWidth: 'Fluid',
+    fixedHeader: false,
+    fixSiderbar: true,
+    colorWeak: false,
+    siderWidth: 208,
+    pwa: true,
+    logo: '/images/logo.svg',
+    iconfontUrl: '/css/iconfont.js',
+};
+
+export default Settings;

+ 44 - 0
YBEE.EQM.Admin/config/proxy.ts

@@ -0,0 +1,44 @@
+/**
+ * @name 代理的配置
+ * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
+ * -------------------------------
+ * 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
+ *
+ * @doc https://umijs.org/docs/guides/proxy
+ */
+export default {
+    // 如果需要自定义本地开发服务器  请取消注释按需调整
+    dev: {
+        // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
+        '/api/': {
+            // 要代理的地址
+            target: 'http://localhost:46570',
+            // 配置了这个可以从 http 代理到 https
+            // 依赖 origin 的功能可能需要这个,比如 cookie
+            changeOrigin: true,
+        },
+    },
+
+    /**
+     * @name 详细的代理配置
+     * @doc https://github.com/chimurai/http-proxy-middleware
+     */
+    test: {
+        // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
+        '/api/': {
+            target: 'https://proapi.azurewebsites.net',
+            changeOrigin: true,
+            pathRewrite: { '^': '' },
+        },
+    },
+    pre: {
+        '/api/': {
+            target: 'your pre url',
+            changeOrigin: true,
+            pathRewrite: { '^': '' },
+        },
+    },
+};

+ 303 - 0
YBEE.EQM.Admin/config/routes.ts

@@ -0,0 +1,303 @@
+/**
+ * @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 [
+    /** 登录认证 */
+    {
+        path: '/login',
+        layout: false,
+        component: './auth/Login',
+    },
+
+
+    /** 工作台 */
+    {
+        path: '/wb',
+        component: './Workbench',
+    },
+    /** 根目录跳工作台 */
+    {
+        path: '/',
+        redirect: '/wb',
+    },
+
+
+    // -------------------------------------------------------------------
+    // 监管端
+    // -------------------------------------------------------------------
+    /** 基础资料 */
+    {
+        path: '/base-s',
+        routes: [
+            // {
+            //     path: '/base-s',
+            //     redirect: '/base-s/class',
+            // },
+
+            /** 学校班级 */
+            {
+                path: '/base-s/class',
+                component: './base-org/SchoolClass',
+            },
+        ],
+    },
+    /** 监测管理 */
+    {
+        path: '/exam-c',
+        routes: [
+            // {
+            //     path: '/exam-c',
+            //     redirect: '/exam-c/plan',
+            // },
+
+            // ------------------------------------------------
+            /** 监测计划管理 */
+            {
+                path: '/exam-c/plan',
+                component: './exam-center/ExamPlan',
+            },
+            /** 监测计划详细 */
+            {
+                path: '/exam-c/plan/detail/:id',
+                component: './exam-center/ExamPlanDetail',
+            },
+            /** 监测结果管理 */
+            {
+                path: '/exam-c/plan/result/:examPlanId/:publishId',
+                component: './exam-center/ExamResultDetail',
+            },
+
+            // ------------------------------------------------
+            /** 监测特殊学生审核计划列表 */
+            {
+                path: '/exam-c/sp-stu-audit',
+                component: './exam-center/student/special-student-audit',
+            },
+            /** 监测特殊学生审核学校列表 */
+            {
+                path: '/exam-c/sp-stu-audit/list/:examPlanId',
+                component: './exam-center/student/special-student-audit/ExamSpecialStudentAuditList',
+            },
+            /** 监测特殊学生审核学校主页 */
+            {
+                path: '/exam-c/sp-stu-audit/org/:sysOrgId/:examPlanId',
+                component: './exam-center/student/special-student-audit/ExamSpecialStudentAuditOrg',
+            },
+        ],
+    },
+
+
+    // -------------------------------------------------------------------
+    // 学校端
+    // -------------------------------------------------------------------
+    /** 评估监测 */
+    {
+        path: '/exam-s',
+        routes: [
+            // {
+            //     path: '/exam-s',
+            //     redirect: '/exam-s/plan',
+            // },
+
+            // ------------------------------------------------
+            /** 监测计划管理 */
+            {
+                path: '/exam-s/plan',
+                component: './exam-org/OrgExamPlan',
+            },
+            /** 监测计划详细 */
+            {
+                path: '/exam-s/plan/detail/:id',
+                component: './exam-org/OrgExamPlanDetail',
+            },
+
+            // ------------------------------------------------
+            /** 监测学生信息上报列表 */
+            {
+                path: '/exam-s/stu',
+                component: './exam-org/student',
+            },
+            /** 监测学生信息上报处理 */
+            {
+                path: '/exam-s/stu/report/:examPlanId',
+                component: './exam-org/student/OrgExamStudentReport',
+            },
+            /** 监测学生信息批量导入 */
+            {
+                path: '/exam-s/stu/import/:examPlanId',
+                component: './exam-org/student/OrgExamStudentImport',
+            },
+
+            // ------------------------------------------------
+            /** 监测特殊学生上报列表 */
+            {
+                path: '/exam-s/sp-stu',
+                component: './exam-org/special-student',
+            },
+            /** 监测特殊学生上报处理 */
+            {
+                path: '/exam-s/sp-stu/report/:examPlanId',
+                component: './exam-org/special-student/OrgExamSpecialStudentReport',
+            },
+            /** 监测特殊学生批量导入 */
+            {
+                path: '/exam-s/sp-stu/import/:examPlanId',
+                component: './exam-org/special-student/OrgExamSpecialStudentImport',
+            },
+
+            // ------------------------------------------------
+            /** 监测教师信息上报列表 */
+            {
+                path: '/exam-s/teacher',
+                component: './exam-org/teacher',
+            },
+            /** 监测教师信息上报处理 */
+            {
+                path: '/exam-s/teacher/report/:examPlanId',
+                component: './exam-org/teacher/OrgExamTeacherReport',
+            },
+            /** 监测教师信息批量导入 */
+            {
+                path: '/exam-s/teacher/import/:examPlanId',
+                component: './exam-org/teacher/OrgExamTeacherImport',
+            },
+
+            // ------------------------------------------------
+            /** 监测教师任教科目上报列表 */
+            {
+                path: '/exam-s/t-course',
+                component: './exam-org/teacher-course',
+            },
+            /** 监测教师任教科目上报处理 */
+            {
+                path: '/exam-s/t-course/report/:examPlanId',
+                component: './exam-org/teacher-course/OrgExamTeacherCourseReport',
+            },
+            /** 监测教师任教科目批量导入 */
+            {
+                path: '/exam-s/t-course/import/:examPlanId',
+                component: './exam-org/teacher-course/OrgExamTeacherCourseImport',
+            },
+
+            // ------------------------------------------------
+            /** 家长问卷进度列表 */
+            {
+                path: '/exam-s/questionnaire/patriarch',
+                component: './exam-org/questionnaire/patriarch',
+            },
+            /** 家长问卷进度详情 */
+            {
+                path: '/exam-s/questionnaire/patriarch/progress/:examPlanId',
+                component: './exam-org/questionnaire/patriarch/OrgExamPatriarchProgress',
+            },
+        ],
+    },
+
+
+    // -------------------------------------------------------------------
+    // 管理端
+    // -------------------------------------------------------------------
+    /** 基础数据 */
+    {
+        path: '/bd',
+        routes: [
+            // {
+            //     path: '/bd',
+            //     redirect: '/bd/grade',
+            // },
+
+            /** 年级 */
+            {
+                path: '/bd/grade',
+                component: './bd/Grade',
+            },
+            /** 学科 */
+            {
+                path: '/bd/course',
+                component: './bd/Course',
+            },
+            /** 高中选科组合 */
+            {
+                path: '/bd/course-comb',
+                component: './bd/CourseComb',
+            },
+            /** 学期 */
+            {
+                path: '/bd/semester',
+                component: './bd/Semester',
+            },
+            /** 基础字典 */
+            {
+                path: '/bd/dict',
+                component: './bd/Dict',
+            },
+        ],
+    },
+    /** 系统管理 */
+    {
+        path: '/sys',
+        routes: [
+            // {
+            //     path: '/sys',
+            //     redirect: '/sys/org',
+            // },
+
+            /** 组织机构 */
+            {
+                path: '/sys/org',
+                component: './system/Org',
+            },
+            /** 用户管理 */
+            {
+                path: '/sys/user',
+                component: './system/User',
+            },
+            /** 角色权限 */
+            {
+                path: '/sys/role',
+                component: './system/Role',
+            },
+            /** 功能管理 */
+            {
+                path: '/sys/menu',
+                component: './system/Menu',
+            },
+            /** 日志管理 */
+            {
+                path: '/sys/log',
+                routes: [
+                    /** 访问日志 */
+                    {
+                        path: '/sys/log/vis',
+                        component: './system/log/LogVis',
+                    },
+                    /** 操作日志 */
+                    {
+                        path: '/sys/log/op',
+                        component: './system/log/LogOp',
+                    },
+                    /** 异常日志 */
+                    {
+                        path: '/sys/log/ex',
+                        component: './system/log/LogEx',
+                    },
+                ]
+            },
+        ],
+    },
+
+    {
+        path: '*',
+        layout: false,
+        component: './404',
+    },
+];

+ 23 - 0
YBEE.EQM.Admin/jest.config.ts

@@ -0,0 +1,23 @@
+import { configUmiAlias, createConfig } from '@umijs/max/test';
+
+export default async () => {
+  const config = await configUmiAlias({
+    ...createConfig({
+      target: 'browser',
+    }),
+  });
+
+  console.log();
+  return {
+    ...config,
+    testEnvironmentOptions: {
+      ...(config?.testEnvironmentOptions || {}),
+      url: 'http://localhost:8000',
+    },
+    setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'],
+    globals: {
+      ...config.globals,
+      localStorage: null,
+    },
+  };
+};

+ 11 - 0
YBEE.EQM.Admin/jsconfig.json

@@ -0,0 +1,11 @@
+{
+    "compilerOptions": {
+        "jsx": "react-jsx",
+        "emitDecoratorMetadata": true,
+        "experimentalDecorators": true,
+        "baseUrl": ".",
+        "paths": {
+            "@/*": ["./src/*"]
+        }
+    }
+}

+ 109 - 0
YBEE.EQM.Admin/package.json

@@ -0,0 +1,109 @@
+{
+    "name": "ybee.eqm",
+    "version": "1.0.0",
+    "private": true,
+    "description": "",
+    "scripts": {
+        "analyze": "cross-env ANALYZE=1 max build",
+        "build": "max build",
+        "deploy": "npm run build && npm run gh-pages",
+        "dev": "npm run start:dev",
+        "gapi": "node ./scripts/gapi/index.js",
+        "gh-pages": "gh-pages -d dist",
+        "i18n-remove": "pro i18n-remove --locale=zh-CN --write",
+        "postinstall": "max setup",
+        "jest": "jest",
+        "lint": "npm run lint:js && 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 ",
+        "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
+        "lint:prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto",
+        "openapi": "max openapi",
+        "prepare": "husky install",
+        "prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"",
+        "preview": "npm run build && max preview --port 8000",
+        "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login",
+        "serve": "umi-serve",
+        "start": "cross-env UMI_ENV=dev max dev",
+        "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev",
+        "start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev",
+        "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
+        "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
+        "test": "jest",
+        "test:coverage": "npm run jest -- --coverage",
+        "test:update": "npm run jest -- -u",
+        "tsc": "tsc --noEmit"
+    },
+    "lint-staged": {
+        "**/*.{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": "^5.1.4",
+        "@ant-design/pro-components": "^2.6.35",
+        "@ant-design/use-emotion-css": "1.0.4",
+        "@reduxjs/toolkit": "^1.9.5",
+        "@umijs/route-utils": "^4.0.1",
+        "ahooks": "^3.7.8",
+        "antd": "^5.11.2",
+        "bignumber.js": "^9.1.1",
+        "classnames": "^2.3.2",
+        "content-disposition": "^0.5.4",
+        "echarts": "^5.4.3",
+        "jsencrypt": "^3.3.2",
+        "lodash": "^4.17.21",
+        "moment": "^2.29.4",
+        "omit.js": "^2.0.2",
+        "query-string": "^8.1.0",
+        "rc-menu": "^9.8.2",
+        "rc-util": "^5.27.2",
+        "react": "^18.2.0",
+        "react-dev-inspector": "^2.0.0",
+        "react-dom": "^18.2.0",
+        "react-draggable": "^4.4.5",
+        "react-helmet-async": "^1.3.0",
+        "react-json-view": "^1.21.3",
+        "vanilla-jsoneditor": "^0.18.11"
+    },
+    "devDependencies": {
+        "@ant-design/pro-cli": "^3.1.0",
+        "@testing-library/react": "^14.0.0",
+        "@types/babel__core": "^7.20.4",
+        "@types/babel__generator": "^7.6.7",
+        "@types/classnames": "^2.3.1",
+        "@types/express": "^4.17.17",
+        "@types/history": "^5.0.0",
+        "@types/jest": "^29.5.3",
+        "@types/lodash": "^4.14.191",
+        "@types/react": "^18.2.15",
+        "@types/react-dom": "^18.2.7",
+        "@types/react-helmet": "^6.1.6",
+        "@umijs/fabric": "^4.0.1",
+        "@umijs/lint": "^4.0.52",
+        "@umijs/max": "^4.0.52",
+        "cross-env": "^7.0.3",
+        "eslint": "^8.45.0",
+        "express": "^4.18.2",
+        "gh-pages": "^5.0.0",
+        "husky": "^8.0.3",
+        "jest": "^29.6.1",
+        "jest-environment-jsdom": "^29.6.1",
+        "lint-staged": "^13.2.3",
+        "mockjs": "^1.1.0",
+        "prettier": "^3.0.0",
+        "ts-node": "^10.9.1",
+        "typescript": "^5.1.6",
+        "umi-presets-pro": "^2.0.2"
+    },
+    "engines": {
+        "node": ">=12.0.0"
+    }
+}

+ 1 - 0
YBEE.EQM.Admin/public/CNAME

@@ -0,0 +1 @@
+preview.pro.ant.design

+ 63 - 0
YBEE.EQM.Admin/public/css/iconfont.css

@@ -0,0 +1,63 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 4156318 */
+  src: url('iconfont.woff2?t=1688710512610') format('woff2'),
+       url('iconfont.woff?t=1688710512610') format('woff'),
+       url('iconfont.ttf?t=1688710512610') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-finance:before {
+  content: "\e6be";
+}
+
+.icon-lightning:before {
+  content: "\e723";
+}
+
+.icon-apps:before {
+  content: "\e726";
+}
+
+.icon-box:before {
+  content: "\ea00";
+}
+
+.icon-chart:before {
+  content: "\e837";
+}
+
+.icon-form:before {
+  content: "\e65d";
+}
+
+.icon-order:before {
+  content: "\e621";
+}
+
+.icon-home:before {
+  content: "\e70a";
+}
+
+.icon-system:before {
+  content: "\e727";
+}
+
+.icon-theme-dark:before {
+  content: "\e806";
+}
+
+.icon-theme-auto:before {
+  content: "\e807";
+}
+
+.icon-theme-light:before {
+  content: "\e808";
+}
+

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
YBEE.EQM.Admin/public/css/iconfont.js


+ 93 - 0
YBEE.EQM.Admin/public/css/iconfont.json

@@ -0,0 +1,93 @@
+{
+    "id": "4156318",
+    "name": "MC3.AdNetwork.Admin",
+    "font_family": "iconfont",
+    "css_prefix_text": "icon-",
+    "description": "",
+    "glyphs": [
+        {
+            "icon_id": "13769837",
+            "name": "资金中心",
+            "font_class": "finance",
+            "unicode": "e6be",
+            "unicode_decimal": 59070
+        },
+        {
+            "icon_id": "20212237",
+            "name": "预警管理",
+            "font_class": "lightning",
+            "unicode": "e723",
+            "unicode_decimal": 59171
+        },
+        {
+            "icon_id": "20212240",
+            "name": "增值服务",
+            "font_class": "apps",
+            "unicode": "e726",
+            "unicode_decimal": 59174
+        },
+        {
+            "icon_id": "24342060",
+            "name": "package 4-fill",
+            "font_class": "inventory2",
+            "unicode": "ea00",
+            "unicode_decimal": 59904
+        },
+        {
+            "icon_id": "36341255",
+            "name": "Chart-copy",
+            "font_class": "chart",
+            "unicode": "e837",
+            "unicode_decimal": 59447
+        },
+        {
+            "icon_id": "1410313",
+            "name": "drive-form",
+            "font_class": "form",
+            "unicode": "e65d",
+            "unicode_decimal": 58973
+        },
+        {
+            "icon_id": "6664190",
+            "name": "order",
+            "font_class": "order",
+            "unicode": "e621",
+            "unicode_decimal": 58913
+        },
+        {
+            "icon_id": "17592151",
+            "name": "店铺管理",
+            "font_class": "home",
+            "unicode": "e70a",
+            "unicode_decimal": 59146
+        },
+        {
+            "icon_id": "20212241",
+            "name": "后台管理",
+            "font_class": "system",
+            "unicode": "e727",
+            "unicode_decimal": 59175
+        },
+        {
+            "icon_id": "35199602",
+            "name": "theme-dark",
+            "font_class": "theme-dark",
+            "unicode": "e806",
+            "unicode_decimal": 59398
+        },
+        {
+            "icon_id": "35199610",
+            "name": "theme-auto",
+            "font_class": "theme-auto",
+            "unicode": "e807",
+            "unicode_decimal": 59399
+        },
+        {
+            "icon_id": "35199611",
+            "name": "theme-light",
+            "font_class": "theme-light",
+            "unicode": "e808",
+            "unicode_decimal": 59400
+        }
+    ]
+}

BIN
YBEE.EQM.Admin/public/css/iconfont.ttf


BIN
YBEE.EQM.Admin/public/css/iconfont.woff


BIN
YBEE.EQM.Admin/public/css/iconfont.woff2


BIN
YBEE.EQM.Admin/public/doc-templates/学生信息上报-填报模板.xlsx


BIN
YBEE.EQM.Admin/public/doc-templates/教师任教科目(初中)上报-填报模板.xlsx


BIN
YBEE.EQM.Admin/public/doc-templates/教师任教科目(小学)上报-填报模板.xlsx


BIN
YBEE.EQM.Admin/public/doc-templates/教师任教科目(高中)上报-填报模板.xlsx


BIN
YBEE.EQM.Admin/public/doc-templates/教师信息上报-填报模板.xlsx


BIN
YBEE.EQM.Admin/public/doc-templates/特殊学生信息(初中)上报-填报模板.xlsx


BIN
YBEE.EQM.Admin/public/doc-templates/特殊学生信息(小学)上报-填报模板.xlsx


BIN
YBEE.EQM.Admin/public/favicon.ico


BIN
YBEE.EQM.Admin/public/handbook/学校操作手册.pdf


BIN
YBEE.EQM.Admin/public/handbook/批量上传文件报Wrong Local header signature错误的解决方案.pdf


+ 1 - 0
YBEE.EQM.Admin/public/images/avatar-default.svg

@@ -0,0 +1 @@
+<?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="1700342093190" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6076" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M0 512c0 282.4 229.6 512 512 512s512-229.6 512-512S794.4 0 512 0 0 229.6 0 512z" fill="#C0E0FF" p-id="6077"></path><path d="M709.6 824H315.2c-36 0-65.6-29.6-65.6-65.6 0-117.6 95.2-213.6 213.6-213.6h98.4c117.6 0 213.6 95.2 213.6 213.6 0 36-28.8 65.6-65.6 65.6zM512 528c-90.4 0-164-73.6-164-164S421.6 200 512 200s164 73.6 164 164C676.8 455.2 602.4 528 512 528z" fill="#64B9FF" p-id="6078"></path><path d="M709.6 824H315.2c-36 0-65.6-29.6-65.6-65.6 0-117.6 95.2-213.6 213.6-213.6h98.4c117.6 0 213.6 95.2 213.6 213.6 0 36-28.8 65.6-65.6 65.6zM512.8 528c-90.4 0-164-73.6-164-164C348 273.6 421.6 200 512 200s164 73.6 164 164C676.8 455.2 602.4 528 512.8 528z" fill="#64B9FF" p-id="6079"></path></svg>

+ 34 - 0
YBEE.EQM.Admin/public/images/avatar-default2.svg

@@ -0,0 +1,34 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 980.76 1011.49">
+    <defs>
+        <style>.cls-1{fill:#e3f0d5;}.cls-2{fill:#fff;}.cls-3{fill:#e5afa1;}.cls-4{fill:#f4d5c1;}.cls-5{fill:#d2a597;}.cls-6{fill:#374968;}.cls-7{fill:#28395e;}.cls-8{fill:#1d2d49;}.cls-9{fill:#b8c4cc;}.cls-10{fill:#668faf;}.cls-11{fill:#1a191e;}.cls-12{fill:none;}.cls-13{fill:#46667c;}</style>
+    </defs>
+    <title>未知</title>
+    <g id="图层_2" data-name="图层 2">
+        <g id="图层_1-2" data-name="图层 1">
+            <circle class="cls-1" cx="490.38" cy="521.11" r="477.85"/>
+            <path class="cls-2" d="M490.38,1011.49A490.51,490.51,0,0,1,299.5,69.27,490.5,490.5,0,0,1,681.26,972.94,487.26,487.26,0,0,1,490.38,1011.49Zm0-955.71A465.45,465.45,0,0,0,309.26,949.87,465.45,465.45,0,0,0,671.5,92.34,462.42,462.42,0,0,0,490.38,55.78Z"/>
+            <polygon class="cls-3" points="435.24 631.96 437.19 673.32 498.49 717.64 540.34 703.85 544.23 620.14 435.24 631.96"/>
+            <path class="cls-3" d="M728.5,389.68s58.11-4.92,46.29,43.33c0,0-20.68,80.76-64,99.48,0,0-9.85,3-23.63,1S728.5,389.68,728.5,389.68Z"/>
+            <path class="cls-3" d="M234.45,372s-58.1-4.92-46.28,43.34c0,0,20.68,80.76,64,99.47,0,0,9.85,3,23.64,1S234.45,372,234.45,372Z"/>
+            <path class="cls-4" d="M230.19,341.75S250.87,581.07,368.07,631.3c0,0,58.11,28.56,115.23,24.62,0,0,86.67-3.94,141.82-39.39,0,0,90.34-54.41,120.09-252.46,0,0-43.88-103.67-115.17-109,0,0-6.71,42.35-118,45.31l-43.52,15.75-21.33-15.75L401.25,286.6s-69.69-59.42-71.66-23c0,0-34.4,7.22-14.7-1.64C314.89,262,256.78,306.29,230.19,341.75Z"/>
+            <path class="cls-5" d="M742.43,402.8s-18.18-61.23-44.77-71.08c0,0-27,22.42-43.78,46.06,0,0-9.85-35.46-47.28-25.61,0,0,2,26.59-1,30.53s-113.26-40.38-113.26-40.38l-2,21.67S356.84,404,312.52,314.41c0,0-45.43,38.61-93.69,119.37,0,0-28.79-138.13-19.93-187.38l-23.63-8.86S192,167,253,114.82c0,0,11.82-13.79-7.87-28.56,0,0-12.81-20.69,133.94-34.47,0,0,100.45-64,229.47-41.37l2,25.61s58.11,2.95,68,14.77l-6.69,20.68s44.19,32.77,55,35.73L690.32,103s76.94,74.61,84.82,177l-14.26,6.36S749.33,374.24,742.43,402.8Z"/>
+            <path class="cls-6" d="M762.06,211.11S677.13,320,527.43,319c0,0-172.36,2.95-236.37-142.81,0,0-41.7-39-55.49-89.26,0,0,40.67-29.47,136.25-25,0,0,74.85-62,239.32-45.3l-2.33,28.71L675.16,62s-3.94,18.71-4.93,24.62L706.6,89.2l-10.76,43.67S746.3,167.77,762.06,211.11Z"/>
+            <path class="cls-6" d="M762.81,201.81s-80.76,104.4-230.46,103.41c0,0-172.35,3-236.37-142.8,0,0-40.58-29-54.37-79.21,0,0,47.32-35.14,135.13-35,0,0,74.85-62,239.32-45.3l-2.33,28.71,66.35,16.59s-3.94,18.71-4.92,24.62l51.68,34.42-21.61,11.87S747.05,158.48,762.81,201.81Z"/>
+            <path class="cls-7" d="M740.51,406.31s-20.12-67.53-46.71-77.38c0,0-27,22.42-43.78,46.06,0,0-9.85-35.45-47.28-25.6,0,0,2,26.59-1,30.53S488.5,339.54,488.5,339.54l-2,21.66S353,401.25,308.66,311.63c0,0-45.43,38.61-93.69,119.37,0,0-26.85-131.84-18-181.09l-23.64-8.86s14.78-76.82,75.84-129c0,0,11.82-13.79-7.88-28.56,0,0-12.81-20.68,133.94-34.47,0,0,100.46-64,229.47-41.36l2,25.6s58.11,3,68,14.78L668,68.7s43.56,29.38,54.4,32.33l-35.89-.82s82.73,82.73,90.61,185.16L759,289.9S747.4,377.75,740.51,406.31Z"/>
+            <path class="cls-8" d="M763,208.35S673.27,317.21,523.57,316.23c0,0-172.35,2.95-236.37-142.81,0,0-41.7-39-55.49-89.26,0,0,40.67-29.47,136.25-25,0,0,74.85-62,239.32-45.3L605,42.59,671.3,59.17s-3.94,18.72-4.93,24.63l27.3,16.13L692,130.09S747.2,165,763,208.35Z"/>
+            <path class="cls-6" d="M759,199S678.19,303.42,528.49,302.44c0,0-172.35,2.95-236.37-142.81,0,0-40.58-29-54.37-79.21,0,0,47.32-35.13,135.13-35,0,0,76.79-55.76,241.26-39L609.87,28.8l79.44,15s-16.86,13.37-19.13,18.74L722.35,101l-21,15.27S743.19,155.69,759,199Z"/>
+            <path class="cls-9" d="M702,931.78,648,723.21S645,704,593.09,684.43c14,9.27,27.69,25.85,33.62,55.46L649,939.49l1.59,19.85q3.07-1.11,6.17-2.26C672.1,951.35,689.56,940.07,702,931.78Z"/>
+            <path class="cls-9" d="M331.47,957.43c-2.42-9.26,26.76-207.71,32.27-224.81,2.39-7.43,3.83-16.83,9.05-26.21,5.75-12.65,18.1-27,44.36-35.06-8.43,1.62-21,5-39.56,11.61,0,0-52.79,17.38-56.85,59.3L261.21,925a461.57,461.57,0,0,0,70.62,33.16A2.49,2.49,0,0,1,331.47,957.43Z"/>
+            <path class="cls-10" d="M626.71,738.25c-14.45-72.22-83.09-74.92-83.09-74.92L498.29,704.7l-61.13-37.8c-75.84,11.27-69.07,65-69.14,64.78l.43,2.86S386.4,817,446.8,874.82c44.54,42.6,135,77.12,179.4,92.32q12.42-3.74,24.52-8.15L649,937.85Z"/>
+            <circle class="cls-11" cx="492.37" cy="722.75" r="9.52"/>
+            <circle class="cls-11" cx="492.37" cy="790.97" r="9.52"/>
+            <path class="cls-12" d="M433.78,670.79,491.33,702l1.3-.76-55.47-34.3a135.78,135.78,0,0,0-20,4.45C431.12,668.67,433.78,670.79,433.78,670.79Z"/>
+            <polygon class="cls-9" points="437.16 666.9 493.86 701.96 543.62 663.33 573.06 694.8 528.74 739.75 533.56 764.27 492.37 808.4 447.19 764.27 454.89 740.48 414.23 700.25 437.16 666.9"/>
+            <polygon class="cls-2" points="456.8 678.39 493.86 701.96 526.01 677 492.37 808.4 456.8 678.39"/>
+            <path class="cls-13" d="M331.47,957.43a2.49,2.49,0,0,0,.36.76,465,465,0,0,0,294.37,8.95c-44.43-15.2-134.86-49.72-179.4-92.32C386.4,817,368.45,734.54,368.45,734.54l-.43-2.86a52.21,52.21,0,0,1,4.77-25.27c-5.22,9.38-6.66,18.78-9.05,26.21C358.23,749.72,329.05,948.17,331.47,957.43Z"/>
+            <circle class="cls-11" cx="490.38" cy="843.33" r="9.52"/>
+            <circle class="cls-11" cx="490.38" cy="887.15" r="9.52"/>
+            <circle class="cls-11" cx="490.38" cy="930.97" r="9.52"/>
+        </g>
+    </g>
+</svg>

BIN
YBEE.EQM.Admin/public/images/login-avatar-320.png


BIN
YBEE.EQM.Admin/public/images/login-avatar.png


BIN
YBEE.EQM.Admin/public/images/logo.png


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
YBEE.EQM.Admin/public/images/logo.svg


BIN
YBEE.EQM.Admin/public/images/logo@2x.png


BIN
YBEE.EQM.Admin/public/images/page-bg-dark.jpg


BIN
YBEE.EQM.Admin/public/images/page-bg.jpg


+ 202 - 0
YBEE.EQM.Admin/public/scripts/loading.js

@@ -0,0 +1,202 @@
+/**
+ * loading 占位
+ * 解决首次加载时白屏的问题
+ */
+ (function () {
+  const _root = document.querySelector('#root');
+  if (_root && _root.innerHTML === '') {
+    _root.innerHTML = `
+      <style>
+        html,
+        body,
+        #root {
+          height: 100%;
+          margin: 0;
+          padding: 0;
+        }
+        #root {
+          background-repeat: no-repeat;
+          background-size: 100% auto;
+        }
+
+        .loading-title {
+          font-size: 1.1rem;
+        }
+
+        .loading-sub-title {
+          margin-top: 20px;
+          font-size: 1rem;
+          color: #888;
+        }
+
+        .page-loading-warp {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          padding: 26px;
+        }
+        .ant-spin {
+          position: absolute;
+          display: none;
+          -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+          margin: 0;
+          padding: 0;
+          color: rgba(0, 0, 0, 0.65);
+          color: #1890ff;
+          font-size: 14px;
+          font-variant: tabular-nums;
+          line-height: 1.5;
+          text-align: center;
+          list-style: none;
+          opacity: 0;
+          -webkit-transition: -webkit-transform 0.3s
+            cubic-bezier(0.78, 0.14, 0.15, 0.86);
+          transition: -webkit-transform 0.3s
+            cubic-bezier(0.78, 0.14, 0.15, 0.86);
+          transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+          transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
+            -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+          -webkit-font-feature-settings: "tnum";
+          font-feature-settings: "tnum";
+        }
+
+        .ant-spin-spinning {
+          position: static;
+          display: inline-block;
+          opacity: 1;
+        }
+
+        .ant-spin-dot {
+          position: relative;
+          display: inline-block;
+          width: 20px;
+          height: 20px;
+          font-size: 20px;
+        }
+
+        .ant-spin-dot-item {
+          position: absolute;
+          display: block;
+          width: 9px;
+          height: 9px;
+          background-color: #1890ff;
+          border-radius: 100%;
+          -webkit-transform: scale(0.75);
+          -ms-transform: scale(0.75);
+          transform: scale(0.75);
+          -webkit-transform-origin: 50% 50%;
+          -ms-transform-origin: 50% 50%;
+          transform-origin: 50% 50%;
+          opacity: 0.3;
+          -webkit-animation: antspinmove 1s infinite linear alternate;
+          animation: antSpinMove 1s infinite linear alternate;
+        }
+
+        .ant-spin-dot-item:nth-child(1) {
+          top: 0;
+          left: 0;
+        }
+
+        .ant-spin-dot-item:nth-child(2) {
+          top: 0;
+          right: 0;
+          -webkit-animation-delay: 0.4s;
+          animation-delay: 0.4s;
+        }
+
+        .ant-spin-dot-item:nth-child(3) {
+          right: 0;
+          bottom: 0;
+          -webkit-animation-delay: 0.8s;
+          animation-delay: 0.8s;
+        }
+
+        .ant-spin-dot-item:nth-child(4) {
+          bottom: 0;
+          left: 0;
+          -webkit-animation-delay: 1.2s;
+          animation-delay: 1.2s;
+        }
+
+        .ant-spin-dot-spin {
+          -webkit-transform: rotate(45deg);
+          -ms-transform: rotate(45deg);
+          transform: rotate(45deg);
+          -webkit-animation: antrotate 1.2s infinite linear;
+          animation: antRotate 1.2s infinite linear;
+        }
+
+        .ant-spin-lg .ant-spin-dot {
+          width: 32px;
+          height: 32px;
+          font-size: 32px;
+        }
+
+        .ant-spin-lg .ant-spin-dot i {
+          width: 14px;
+          height: 14px;
+        }
+
+        @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+          .ant-spin-blur {
+            background: #fff;
+            opacity: 0.5;
+          }
+        }
+
+        @-webkit-keyframes antSpinMove {
+          to {
+            opacity: 1;
+          }
+        }
+
+        @keyframes antSpinMove {
+          to {
+            opacity: 1;
+          }
+        }
+
+        @-webkit-keyframes antRotate {
+          to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+          }
+        }
+
+        @keyframes antRotate {
+          to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+          }
+        }
+      </style>
+
+      <div style="
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        height: 100%;
+        min-height: 362px;
+      ">
+        <div class="page-loading-warp">
+          <div class="ant-spin ant-spin-lg ant-spin-spinning">
+            <span class="ant-spin-dot ant-spin-dot-spin">
+              <i class="ant-spin-dot-item"></i>
+              <i class="ant-spin-dot-item"></i>
+              <i class="ant-spin-dot-item"></i>
+              <i class="ant-spin-dot-item"></i>
+            </span>
+          </div>
+        </div>
+        <div class="loading-title">
+          正在加载资源
+        </div>
+        <div class="loading-sub-title">
+          初次加载资源可能需要较多时间 请耐心等待
+        </div>
+      </div>
+    `;
+  }
+})();

+ 3 - 0
YBEE.EQM.Admin/scripts/gapi/config.js

@@ -0,0 +1,3 @@
+module.exports = {
+    swaggerJson: "http://localhost:8601/swagger/Default/swagger.json",
+};

+ 643 - 0
YBEE.EQM.Admin/scripts/gapi/index.js

@@ -0,0 +1,643 @@
+/**
+ * 根据 Swagger json 自动生成接口服务文件
+ */
+
+const config = require('./config');
+
+// 输出文件及目录定义
+const ROOT_PATH = './src/services';
+const ENUM_FILE = `${ROOT_PATH}/enums.ts`;
+const TYPING_D = `${ROOT_PATH}/typing.d.ts`;
+const API_PATH = `${ROOT_PATH}/apis`;
+
+/** 数值类型映射 */
+const IntegerMapping = {
+    int16: 'number',
+    int32: 'number',
+    int64: 'string',
+};
+
+/** 返回结果类型映射 */
+const ResultMapping = {
+    Int16: 'number',
+    Int32: 'number',
+    Int64: 'string',
+    Boolean: 'boolean',
+    Object: 'any',
+    String: 'string',
+    DateTime: 'string',
+};
+
+/** 统一注释 */
+const UnifyComment = `// @ts-ignore\n/* eslint-disable */\n\n// 该文件自动生成,请勿手动修改!`;
+
+// ----------------------------------------
+
+// 执行外部命令
+const execSync = require('child_process').execSync;
+// 文件操作
+const fs = require('fs');
+
+// ----------------------------------------
+
+/**
+ * 下划线中横线分隔字符串转换为驼峰格式
+ * @param {string} value 待转换字符串
+ * @param {boolean} [capitalize] 首字母大写
+ * @returns
+ */
+const kebabToCamelCase = (value, capitalize = false) => {
+    let n = value.replace(/[_-][a-zA-z]/g, (str) => str.substr(-1).toUpperCase()).trim() || '';
+    if (capitalize && n.length != 0) {
+        n = n.slice(0, 1).toUpperCase() + n.slice(1);
+    }
+    return n;
+};
+
+/**
+ * 根据路径生成操作名称
+ * @param {string} path 接口路径
+ * @returns 取路径最后一级的名称
+ */
+const getActionName = (path) => {
+    const pp = path.split('/');
+    if (pp.length == 0) {
+        return '';
+    }
+    const n = pp[pp.length - 1];
+    let an = kebabToCamelCase(n);
+    if (['import', 'delete'].includes(an.toLocaleLowerCase())) {
+        an = `${an}Action`;
+    }
+    return an;
+};
+
+/**
+ * 生成控制器名称
+ * @param {string} name Swagger的tag名称
+ * @returns
+ */
+const getControllerName = (name) => {
+    const n = kebabToCamelCase(name, true);
+    return `${n}Controller`;
+};
+
+/**
+ * 清除文件
+ * @param {*} dir 需要清空的目录
+ */
+const clearFiles = (dir) => {
+    const oldFiles = fs.readdirSync(dir);
+    oldFiles.forEach((f) => {
+        const filePath = `${dir}/${f}`;
+        const s = fs.statSync(filePath);
+        if (s.isDirectory()) {
+            return;
+        }
+        fs.unlinkSync(filePath);
+    });
+};
+
+/** 获取引用类型 */
+const getRefEntity = ($ref) => {
+    const rs = $ref.split('/');
+    if (rs.length == 0) {
+        return 'any';
+    }
+    return rs[rs.length - 1];
+};
+
+/** 获取简单类型 */
+const getSimpleType = ({ type, format }) => {
+    if (!type) {
+        return 'any';
+    }
+    const ltype = type.toLowerCase();
+    if (['string', 'boolean', 'number'].includes(type)) {
+        return ltype;
+    }
+    if (ltype == 'integer') {
+        return IntegerMapping[format] ?? 'number';
+    }
+    return 'any';
+};
+
+/** 获取响应结果类型 */
+const getResultType = (type) => {
+    const t = ResultMapping[type];
+    if (t) {
+        return t;
+    }
+    return `API.${type}`;
+};
+
+/** 获取实体类型 */
+const getEntityType = (name, schema) => {
+    const props = schema.properties;
+
+    let entity = '';
+    const desc = schema.description;
+    if (desc) {
+        entity = `/** ${desc} */\n`;
+    }
+    entity = `${entity}type ${name} = {\n`;
+
+    const requiredList = schema.required ?? [];
+
+    for (const pname in props) {
+        const prop = props[pname];
+        let member = '';
+        const memberDesc = prop.description;
+        if (memberDesc) {
+            member = `/** ${memberDesc} */\n`;
+        }
+
+        let memberType = '';
+        if (prop['$ref']) {
+            memberType = `${getRefEntity(prop['$ref'])}`;
+        } else if (prop['items']) {
+            if (prop['items']['$ref']) {
+                memberType = `${getRefEntity(prop['items']['$ref'])}[]`;
+            } else {
+                memberType = `${getSimpleType(prop['items'])}[]`;
+            }
+        } else {
+            memberType = `${getSimpleType(prop)}`;
+        }
+
+        const required = requiredList.includes(pname) || (prop.nullable !== undefined && !prop.nullable);
+
+        member = `${member}${pname}${required ? '' : '?'}: ${memberType};\n`;
+        entity = `${entity}${member}`;
+    }
+    entity = `${entity}}\n`;
+    return entity;
+};
+
+/** 获取请求类型 */
+const getRequestBodyType = (reqBody) => {
+    if (reqBody['content']['application/json']) {
+        const schema = reqBody['content']['application/json']['schema'];
+        let tps = '';
+        if (schema['$ref']) {
+            tps = getRefEntity(schema['$ref']);
+        } else if (schema['items']) {
+            if (schema['items']['$ref']) {
+                tps = getRefEntity(schema['items']['$ref']);
+            } else {
+                tps = getSimpleType(schema['items']);
+            }
+        } else {
+            tps = getSimpleType(schema);
+        }
+        if (schema['type'] == 'array') {
+            return `${tps}[]`;
+        }
+        return tps;
+    }
+    if (reqBody['content']['multipart/form-data']) {
+        return 'FormData';
+    }
+    return 'any';
+};
+
+/** 获取统一返回类型 */
+const getXnRestfulResultType = (propRef) => {
+    const rtype = getRefEntity(propRef).replaceAll('XnRestfulResult_', '');
+    const ps = rtype.split('_');
+    if (ps.length == 1) {
+        return getResultType(ps);
+    }
+    if (ps[0] == 'List') {
+        return `${getResultType(ps[1])}[]`;
+    }
+    if (ps[0] == 'PageResult') {
+        return `API.PageResponse<${getResultType(ps[1])}>`;
+    }
+    // return getResultType(ps[1]);
+    // return getResultType(ps[0]);
+    return getResultType(rtype);
+};
+
+/** 获取响应类型 */
+const getResponseType = (res) => {
+    const schema = res['200']?.['content']?.['application/json']?.['schema'];
+    if (!schema) {
+        return 'any';
+    }
+    if (schema['$ref']) {
+        return getXnRestfulResultType(schema['$ref']);
+    } else if (schema['items']) {
+        if (schema['items']['$ref']) {
+            return getXnRestfulResultType(schema['items']['$ref']);
+        } else {
+            return getSimpleType(schema['items']);
+        }
+    } else {
+        return getSimpleType(schema);
+    }
+};
+
+/** 获取查询参数类型 */
+const getParamsType = (parameters) => {
+    let pb = '';
+    for (const p of parameters) {
+        const pdesc = p.description ?? p.name;
+        pb = `${pb}/** ${pdesc} */\n${p.name}${p.required ? '' : '?'}:`;
+
+        const prop = p.schema;
+        let ptype = '';
+        if (prop['$ref']) {
+            ptype = `${getRefEntity(prop['$ref'])}`;
+        } else if (prop['items']) {
+            if (prop['items']['$ref']) {
+                ptype = `${getRefEntity(prop['items']['$ref'])}[]`;
+            } else {
+                ptype = `${getSimpleType(prop['items'])}[]`;
+            }
+        } else {
+            ptype = `${getSimpleType(prop)}`;
+        }
+
+        pb = `${pb}${ptype};\n`;
+    }
+    return `{\n${pb}}`;
+};
+
+/** 判断是否简单类型 */
+const isSimpleType = (type) => {
+    return ['string', 'number', 'boolean', 'object', 'any', 'formdata'].includes(type.toLowerCase());
+};
+
+/** 获取 tag 描述字典 */
+const getTagDescDict = (tags) => {
+    let tagDict = {};
+    for (const tag of tags) {
+        tagDict[tag.name] = tag.description ?? tag.name;
+    }
+    return tagDict;
+};
+
+// ----------------------------------------
+
+/** 生成枚举 */
+const generateEnums = (schemas) => {
+    const enums = [];
+    const importEnums = [];
+    for (let ek in schemas) {
+        if (schemas[ek].enum) {
+            const desc = schemas[ek].description;
+            if (!desc) {
+                continue;
+            }
+            const s1 = desc.replaceAll('<br />', '').split('&nbsp;');
+            if (s1 && s1.length > 1) {
+                const enumComment = s1?.[0];
+                const items = [];
+                for (let j = 1; j < s1.length; j++) {
+                    const s2 = s1[j].split(' ');
+                    if (s2.length > 3) {
+                        const itemComent = s2[0];
+                        const itemCode = s2[1];
+                        const itemValue = s2[3];
+                        items.push(`\n    /** ${itemComent} */\n    ${itemCode} = ${itemValue},`);
+                    }
+                }
+                enums.push(`\n/** ${enumComment} */\nexport enum ${ek} {${items.join('')}\n}`);
+                importEnums.push(ek);
+            }
+        }
+    }
+
+    const fs = require('fs');
+    const es = enums.join('\n');
+    const ies = importEnums.map((t) => `    ${t}`).join(',\n');
+    fs.writeFileSync(ENUM_FILE, `${UnifyComment}\n\n${es}\n\nexport default {\n${ies},\n};\n`);
+    execSync(`prettier --write ${ENUM_FILE}`);
+    return importEnums;
+};
+
+/** 生成类型定义文件 */
+const generateEntity = (schemas, enums) => {
+    // 生成类型
+    let entities = [];
+    for (const name in schemas) {
+        const schema = schemas[name];
+        if (
+            name.includes('XnRestfulResult_') ||
+            name.includes('PageResult_') ||
+            schema.enum ||
+            !schema.properties
+        ) {
+            continue;
+        }
+
+        // const entity = getEntityType(name.split('_')[0], schema);
+        // const ns = name.split('_');
+        // const entity = getEntityType(ns[ns.length - 1], schema);
+        const entity = getEntityType(name, schema);
+        entities.push(entity);
+    }
+
+    const fs = require('fs');
+    const ies = enums.map((t) => `    ${t}`).join(',\n');
+    const es = entities.join('\n');
+    fs.writeFileSync(
+        TYPING_D,
+        `${UnifyComment}
+
+        import {
+            ${ies},
+        } from './enums';
+
+        declare global {
+            declare namespace API {
+                /** 分页查询请求参数基类型 */
+                type PageQueryType = {
+                    /** 当前页值 */
+                    pageIndex: number;
+                    /** 每页大小 */
+                    pageSize: number;
+
+                    /** 搜索值 */
+                    searchValue?: string;
+                    /** 搜索开始时间 */
+                    searchBeginTime?: string;
+                    /** 搜索结束时间 */
+                    searchEndTime?: string;
+                    /** 排序字段 */
+                    sortField?: string;
+                    /** 排序方法,默认升序,否则降序(配合antd前端,约定参数为 Ascend,Dscend) */
+                    sortOrder?: string;
+                    /** 降序排序(不要问我为什么是descend不是desc,前端约定参数就是这样) */
+                    descStr?: string;
+                    /** 复杂查询条件 */
+                    searchParameters?: Condition[];
+                };
+
+                /** 后端服务请求返回参数 */
+                type ResponseType<T = any> = {
+                    /** 执行成功 */
+                    success?: boolean;
+                    /** 状态码 */
+                    code?: number;
+                    /** 错误码 */
+                    errorCode?: number;
+                    /** 错误信息 */ 
+                    errorMessage?: any;
+                    /** 消息显示类型 */
+                    showType?: number;
+                    /** 数据 */
+                    data?: T;
+                    /** 附加数据 */
+                    extras?: any;
+                    /** 时间戳 */
+                    timestamp?: number;
+                };
+
+                /** 分页数据对象 */
+                type PageResponse<T = any> = {
+                    /** 当前页值  */
+                    pageIndex: number;
+                    /** 每页大小 */
+                    pageSize: number;
+                    /** 数据总数 */
+                    totalCount: number;
+                    /** 总页数 */
+                    totalPage?: number;
+                    /** 数据行  */
+                    items?: T[];
+                };
+
+            ${es}}
+        }`,
+    );
+    execSync(`prettier --write ${TYPING_D}`);
+};
+
+/** 生成控制器文件 */
+const generateController = (paths, tagDict, enums) => {
+    // 构造控制器
+    let controllers = {};
+    for (const apiPath in paths) {
+        const actionName = getActionName(apiPath);
+        const api = paths[apiPath];
+        for (const verb in api) {
+            // 只处理 post 和 get,其它忽略
+            if (!['post', 'get'].includes(verb)) {
+                continue;
+            }
+            const action = api[verb];
+            const actionDesc = action.summary ?? actionName;
+            // tag 作为控制器名称,如果没有 tag 则跳过
+            const tags = action.tags ?? [];
+            if (tags.length < 1) {
+                continue;
+            }
+            for (const tag of tags) {
+                const controllerName = getControllerName(tag);
+                if (!controllers.hasOwnProperty(controllerName)) {
+                    controllers[controllerName] = {
+                        description: tagDict[tag],
+                        actions: [],
+                        actionNames: [],
+                        enums: [],
+                    };
+                }
+                let paramsType = '';
+                let dataType = '';
+                let responseType = '';
+                if (action.parameters) {
+                    paramsType = getParamsType(action.parameters);
+                }
+                if (action.requestBody) {
+                    dataType = getRequestBodyType(action.requestBody);
+                }
+                if (action.responses) {
+                    responseType = getResponseType(action.responses);
+                }
+                const es = enums.filter(t => paramsType.indexOf(t) !== -1 || dataType.indexOf(t) !== -1 || responseType.indexOf(t) !== -1);
+                if (es.length > 0) {
+                    controllers[controllerName].enums.push(es[0]);
+                }
+
+                const actionFullDesc = `/** ${actionDesc} ${verb.toUpperCase()} ${apiPath} */\n`;
+                let actionBody = actionFullDesc;
+                // export api
+                if (actionName.toLowerCase().includes('export') || actionName.toLowerCase().includes('download')) {
+                    actionBody = `${actionFullDesc}export async function ${actionName}(`;
+                    if (verb == 'get') {
+                        if (paramsType != '') {
+                            actionBody = `${actionBody}params:${paramsType},`;
+                        }
+                    } else {
+                        if (paramsType != '') {
+                            actionBody = `${actionBody}params:${paramsType},`;
+                        }
+                        if (dataType != '') {
+                            const prefix = isSimpleType(dataType) ? '' : 'API.';
+                            actionBody = `${actionBody}data:${prefix}${dataType},`;
+                        }
+                    }
+                    actionBody = `${actionBody}options?:{[key:string]:any}){\n`;
+                    actionBody = `${actionBody}const url='${apiPath}';\n`;
+                    actionBody = `${actionBody}const config={method:'${verb.toUpperCase()}',`;
+                    if (verb == 'get' && paramsType != '') {
+                        actionBody = `${actionBody}params,`;
+                    }
+                    if (verb == 'post') {
+                        let pbv = '';
+                        if (paramsType != '') {
+                            pbv = 'params,';
+                        }
+                        if (dataType != '') {
+                            pbv = `${pbv}data,`;
+                        }
+                        actionBody = `${actionBody}${pbv}`;
+                    }
+                    actionBody = `${actionBody}...(options||{}), responseType: 'blob', getResponse: true} as RequestOptions;\n`;
+                    actionBody = `${actionBody}const res = await request(url, config);\n`;
+                    actionBody = `${actionBody}const hcd = res.request.getResponseHeader('Content-Disposition');\n`;
+                    actionBody = `${actionBody}let fileName = '';\n`;
+                    actionBody = `${actionBody}const cd = contentDisposition.parse(hcd)\n`;
+                    actionBody = `${actionBody}if (cd?.parameters?.filename) { fileName = cd?.parameters?.filename; }\n`;
+                    actionBody = `${actionBody}return { fileName, data:res.data };\n}\n`;
+                }
+                // other api
+                else {
+                    actionBody = `${actionFullDesc}export async function ${actionName}(`;
+                    if (verb == 'get') {
+                        if (paramsType != '') {
+                            actionBody = `${actionBody}params:${paramsType},`;
+                        }
+                    } else {
+                        if (paramsType != '') {
+                            actionBody = `${actionBody}params:${paramsType},`;
+                        }
+                        if (dataType != '') {
+                            const prefix = isSimpleType(dataType) ? '' : 'API.';
+                            actionBody = `${actionBody}data:${prefix}${dataType},`;
+                        }
+                    }
+                    actionBody = `${actionBody}options?:{[key:string]:any}){\n`;
+                    actionBody = `${actionBody}const url='${apiPath}';\n`;
+                    actionBody = `${actionBody}const config={method:'${verb.toUpperCase()}',`;
+                    if (verb == 'get' && paramsType != '') {
+                        actionBody = `${actionBody}params,`;
+                    }
+                    if (verb == 'post') {
+                        let pbv = '';
+                        if (paramsType != '') {
+                            pbv = 'params,';
+                        }
+                        if (dataType != '') {
+                            pbv = `${pbv}data,`;
+                        }
+                        actionBody = `${actionBody}${pbv}`;
+                    }
+                    actionBody = `${actionBody}...(options||{})};\n`;
+                    actionBody = `${actionBody}const res = await request<API.ResponseType<${responseType}>>(url, config);`;
+                    actionBody = `${actionBody}return res?.data;\n}\n`;
+                }
+
+                controllers[controllerName].actions.push(actionBody);
+                controllers[controllerName].actionNames.push(`${actionFullDesc}${actionName},\n`);
+            }
+        }
+    }
+
+    // 生成控制器文件
+    let csnames = [];
+    for (let ck in controllers) {
+        const c = controllers[ck];
+        const es = [...new Set(c.enums)].map((t) => `${t}, `);
+        let importEnums = '';
+        if (es.length > 0) {
+            importEnums = `\nimport { ${es.join('')} } from '../enums';`;
+        }
+        csnames.push({ name: ck, description: c.description });
+        const controllerFile = `${API_PATH}/${ck}.ts`;
+
+        const isBolb = c.actionNames.findIndex(t => t.includes('export') || t.includes('download')) !== -1;
+
+        fs.writeFileSync(
+            controllerFile,
+            `${UnifyComment}
+
+            // --------------------------------------------------------------------------
+            // ${c.description}
+            // --------------------------------------------------------------------------
+
+            import { request${isBolb ? ', RequestOptions' : ''} } from '@umijs/max';${importEnums}
+            ${isBolb ? 'import contentDisposition from \'content-disposition\';' : ''}
+
+            ${c.actions.join('\n')}
+
+            
+            export default {
+                ${c.actionNames.join('')}
+            };
+            `,
+        );
+        execSync(`prettier --write ${controllerFile}`);
+    }
+
+    // 生成index.ts
+    const ecs = csnames.map((t) => `import * as ${t.name} from './${t.name}';\n`);
+    const edefaults = csnames.map((t) => `/** ${t.description} */\n${t.name},\n`);
+    const indexFile = `${API_PATH}/index.ts`;
+    fs.writeFileSync(
+        indexFile,
+        `${UnifyComment}
+
+        ${ecs.join('')}
+
+        export default {
+            ${edefaults.join('')}
+        };
+        `,
+    );
+    execSync(`prettier --write ${indexFile}`);
+};
+
+// ----------------------------------------
+
+/** 生成接口服务 */
+const generate = (error, response, body) => {
+    // OpenApi 描述文件
+    const swagger = JSON.parse(body);
+    // 实体模型
+    const schemas = swagger['components']['schemas'];
+    // API路径
+    const apiPaths = swagger['paths'];
+
+    // 开始处理
+    console.info('生成后台接口文件');
+    console.info(`-----------------\n${new Date().toLocaleString()}\n-----------------\n清理文件`);
+    clearFiles(ROOT_PATH);
+    if (!fs.existsSync(API_PATH)) {
+        fs.mkdirSync(API_PATH);
+    } else {
+        clearFiles(API_PATH);
+    }
+    console.info('清理完成!');
+
+    console.log('-----------------\n开始生成');
+
+    // 生成枚举文件
+    console.log('> 生成枚举文件');
+    const enums = generateEnums(schemas);
+
+    // 生成实体类型文件
+    console.log('> 生成实体类型文件');
+    generateEntity(schemas, enums);
+
+    // 生成控制器文件
+    console.log('> 生成控制器文件');
+    generateController(apiPaths, getTagDescDict(swagger['tags'] ?? []), enums);
+
+    console.info('生成完成!');
+};
+
+const request = require('request');
+request.get(config.swaggerJson, generate);

+ 19 - 0
YBEE.EQM.Admin/src/access.ts

@@ -0,0 +1,19 @@
+/**
+ * @see https://umijs.org/zh-CN/plugins/plugin-access
+ * */
+
+type InitialStateType = { currentUser?: API.LoginOutput };
+
+export default function access(initialState: InitialStateType | undefined) {
+    const { currentUser } = initialState ?? {};
+    const { permissions } = currentUser ?? {};
+
+    return {
+        hasPermission: (code: string) => {
+            if (!permissions || permissions?.length === 0) {
+                return false;
+            }
+            return permissions.includes(code.toLowerCase());
+        },
+    };
+}

+ 194 - 0
YBEE.EQM.Admin/src/app.tsx

@@ -0,0 +1,194 @@
+import SysAuthController from '@/services/apis/SysAuthController';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import type { Settings as LayoutSettings, MenuDataItem } from '@ant-design/pro-components';
+import type { RunTimeLayoutConfig } from '@umijs/max';
+import { history } from '@umijs/max';
+import { theme } from 'antd';
+import React from 'react';
+import defaultSettings from '../config/defaultSettings';
+import { Footer, IconFont, MenuFooter, ThemeSwitch } from './components';
+import AvatarDropdown from './components/RightContent/AvatarDropdown';
+import RootLayout from './layouts/RootLayout';
+import requestConfig, { LOGIN_PATH } from './requestConfig';
+import { MenuType } from './services/enums';
+
+/**
+ * 是否忽略权限验证页面
+ */
+const isIgnoreAuthPage = () => {
+    const pn = window.location.pathname;
+    return [LOGIN_PATH, '/login-auth'].includes(pn);
+};
+
+export function rootContainer(container: JSX.Element, args: any) {
+    return React.createElement(RootLayout, args, container);
+}
+
+/**
+ * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
+ * */
+export async function getInitialState(): Promise<{
+    settings?: Partial<LayoutSettings>;
+    currentUser?: API.LoginOutput;
+    loading?: boolean;
+    authCodes?: string[];
+    menus?: API.AntMenuOutput[];
+    dark?: boolean;
+    fetchUserInfo?: () => Promise<API.LoginOutput | undefined>;
+}> {
+    const fetchUserInfo = async () => {
+        const userInfo = await SysAuthController.getLoginUser();
+        if (userInfo) {
+            return userInfo;
+        } else {
+            history.push(LOGIN_PATH);
+            return undefined;
+        }
+    };
+    // 如果不是登录页面,执行
+    if (!isIgnoreAuthPage()) {
+        const currentUser = await fetchUserInfo();
+        return {
+            fetchUserInfo,
+            currentUser,
+            settings: defaultSettings as Partial<LayoutSettings>,
+        };
+    }
+    return {
+        fetchUserInfo,
+        settings: defaultSettings as Partial<LayoutSettings>,
+    };
+}
+
+// ProLayout 支持的api https://procomponents.ant.design/components/layout
+export const layout: RunTimeLayoutConfig = ({ initialState }) => {
+    const { currentUser } = initialState ?? {};
+    // const tm = ThemeMode.get();
+    const { token } = theme.useToken();
+    // const isDark = tm?.themeMode === 'dark' || (tm?.themeMode === 'auto' && isDarkTheme.matches);
+
+    return {
+        actionsRender: () => [
+            <QuestionCircleOutlined key="help" onClick={() => {
+                window.open('/handbook/学校操作手册.pdf');
+            }} />,
+            <ThemeSwitch key="theme" />,
+            <div key="org" style={{ lineHeight: 1, fontSize: token.fontSize }}>
+                {currentUser?.sysOrg?.fullName}
+                (机构代码:{currentUser?.sysOrg?.code})
+            </div>
+        ],
+        avatarProps: {
+            src: currentUser?.avatar || '/images/avatar-default.svg',
+            title: currentUser?.name ? `${currentUser?.name}(${currentUser.account})` : '未登录',
+            render: (_, avatarChildren) => {
+                return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
+            },
+        },
+        headerTitleRender: (logo, title) => {
+            const newTitle = React.cloneElement(title as React.ReactElement, {
+                style: {
+                    fontWeight: 'normal',
+                    lineHeight: '30px',
+                    fontSize: '20px',
+                }
+            });
+            return (
+                <>
+                    {logo}
+                    {newTitle}
+                </>
+            );
+        },
+        footerRender: () => <Footer />,
+        onPageChange: () => {
+            const { location } = history;
+            // 如果没有登录,重定向到 login
+            if (!initialState?.currentUser && location.pathname !== LOGIN_PATH) {
+                history.push(LOGIN_PATH);
+            }
+        },
+        menuHeaderRender: undefined,
+        collapsedButtonRender: false,
+        menuFooterRender: (menuProps) => {
+            const collapsed = !menuProps?.collapsed;
+            return (<MenuFooter collapsed={collapsed} onClick={() => menuProps?.onCollapse?.(collapsed)} />);
+        },
+        menu: {
+            locale: false,
+            request: async () => {
+                if (isIgnoreAuthPage()) {
+                    return [];
+                }
+
+                const buildMenus = (pid?: number): MenuDataItem[] => {
+                    const ms: MenuDataItem[] = [];
+                    currentUser?.menus
+                        ?.filter((item) => item.pid === pid)
+                        .forEach((item) => {
+                            let icon: React.ReactNode | null = null;
+                            if (item.icon) {
+                                icon = <IconFont icon={item.icon} style={{ fontSize: 14, marginRight: 4 }} />;
+                                // icon = <IconFont icon={item.icon} />;
+                            }
+
+                            const children = buildMenus(item.id);
+                            const mitem: MenuDataItem = {
+                                children,
+                                hideInMenu: item.menuType === MenuType.BUTTON,
+                                name: item.title ?? '',
+                                path: item.path ?? '',
+                                icon,
+                            };
+                            ms.push(mitem);
+                        });
+                    return ms;
+                };
+
+                const menus = buildMenus(0);
+                return menus;
+            },
+        },
+        token: {
+            // 参见ts声明,demo 见文档,通过token 修改样式
+            //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F
+            bgLayout: token.colorBgLayout,
+            // bgLayout: isDark ? 'rgb(57, 63, 74)' : 'rgb(245, 248, 255)',
+            header: {
+                // colorBgHeader: token.colorPrimaryActive,
+                // colorBgHeader: '#001529',
+                // colorHeaderTitle: token.colorWhite,
+                // colorTextRightActionsItem: token.colorWhite,
+            },
+            pageContainer: {
+                paddingInlinePageContainerContent: 24,
+                paddingBlockPageContainerContent: 24,
+            },
+            sider: {
+                // colorBgMenuItemHover: token.colorPrimaryBg,
+                colorBgMenuItemSelected: token.colorPrimaryBgHover,
+                colorTextMenuSelected: token.colorPrimaryActive,
+                colorTextMenu: token.colorText,
+                colorTextMenuActive: token.colorTextHeading,
+                // colorMenuItemDivider: '#f00'
+                // colorTextMenuItemHover: token.colorWhite,
+            },
+        },
+        // 自定义 403 页面
+        // unAccessible: <div>unAccessible</div>,
+        // 增加一个 loading 的状态
+        childrenRender: (children) => {
+            // if (initialState?.loading) return <PageLoading />;
+            return <>{children}</>;
+        },
+        ...initialState?.settings,
+        title: AppConfig.systemName,
+    };
+};
+
+/**
+ * @name request 配置,可以配置错误处理
+ * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
+ * @doc https://umijs.org/docs/max/request#配置
+ */
+export const request = requestConfig;

+ 36 - 0
YBEE.EQM.Admin/src/common/BNumber.ts

@@ -0,0 +1,36 @@
+import BigNumber from "bignumber.js";
+
+/** 高精度数值计算 */
+export default {
+    /**
+     * 加法运算
+     * @param {*} a 
+     * @param {*} b 
+     * @returns 
+     */
+    plus: (a: number, b: number) => new BigNumber(a).plus(new BigNumber(b)).toNumber(),
+
+    /**
+     * 减法运算
+     * @param {*} a 
+     * @param {*} b 
+     * @returns 
+     */
+    minus: (a: number, b: number) => new BigNumber(a).minus(new BigNumber(b)).toNumber(),
+
+    /**
+     * 除法运算
+     * @param {*} a 
+     * @param {*} b 
+     * @returns 
+     */
+    div: (a: number, b: number) => new BigNumber(a).dividedBy(new BigNumber(b)).toNumber(),
+
+    /**
+     * 乘法运算
+     * @param {*} a 
+     * @param {*} b 
+     * @returns 
+     */
+    mul: (a: number, b: number) => new BigNumber(a).multipliedBy(new BigNumber(b)).toNumber(),
+};

+ 34 - 0
YBEE.EQM.Admin/src/common/cache/AccessToken.ts

@@ -0,0 +1,34 @@
+import { decryptJWT } from "../helper";
+
+/** token */
+class AccessToken {
+    /** 存储键 */
+    static _storageKey = 'access-token';
+
+    /**
+     * 保存
+     * @param {String} token
+     */
+    public static set(token: string) {
+        const stoken = { token, value: decryptJWT(token) };
+        sessionStorage.setItem(this._storageKey, JSON.stringify(stoken));
+    }
+    /**
+     * 获取
+     */
+    public static get() {
+        const res = sessionStorage.getItem(this._storageKey);
+        if (res) {
+            return JSON.parse(res);
+        }
+        return undefined;
+    }
+    /**
+     * 移出
+     */
+    public static clear() {
+        return sessionStorage.removeItem(this._storageKey);
+    }
+}
+
+export default AccessToken;

+ 34 - 0
YBEE.EQM.Admin/src/common/cache/RefreshAccessToken.ts

@@ -0,0 +1,34 @@
+import { decryptJWT } from "../helper";
+
+/** 刷新token*/
+class RefreshAccessToken {
+    /** 存储键 */
+    static _storageKey = 'x-access-token';
+
+    /**
+     * 保存
+     * @param {String} token
+     */
+    public static set(token: string) {
+        const stoken = { token, value: decryptJWT(token) };
+        sessionStorage.setItem(this._storageKey, JSON.stringify(stoken));
+    }
+    /**
+     * 获取
+     */
+    public static get() {
+        const res = sessionStorage.getItem(this._storageKey);
+        if (res) {
+            return JSON.parse(res);
+        }
+        return undefined;
+    }
+    /**
+     * 移出
+     */
+    public static clear() {
+        return sessionStorage.removeItem(this._storageKey);
+    }
+}
+
+export default RefreshAccessToken;

+ 33 - 0
YBEE.EQM.Admin/src/common/cache/ThemeMode.ts

@@ -0,0 +1,33 @@
+import { RootLayoutState } from "@/layouts/RootLayout/rootLayoutSlice";
+
+/** 主题 */
+class ThemeMode {
+    /** 存储键 */
+    static _storageKey = 'ybee_eqm_theme_mode';
+
+    /**
+     * 保存
+     * @param {RootLayoutState} themeMode
+     */
+    public static set(themeMode: RootLayoutState) {
+        localStorage.setItem(this._storageKey, JSON.stringify(themeMode));
+    }
+    /**
+     * 获取
+     */
+    public static get() {
+        const res = localStorage.getItem(this._storageKey);
+        if (res) {
+            return JSON.parse(res) as RootLayoutState;
+        }
+        return undefined;
+    }
+    /**
+     * 移出
+     */
+    public static clear() {
+        return localStorage.removeItem(this._storageKey);
+    }
+}
+
+export default ThemeMode;

+ 3 - 0
YBEE.EQM.Admin/src/common/cache/index.ts

@@ -0,0 +1,3 @@
+export { default as AccessToken } from './AccessToken';
+export { default as RefreshAccessToken } from './RefreshAccessToken';
+export { default as ThemeMode } from './ThemeMode';

+ 31 - 0
YBEE.EQM.Admin/src/common/constant.ts

@@ -0,0 +1,31 @@
+export { PERMISSIONS } from "./permissions";
+
+export const THEME_MODE_KEY = 'ytwms_theme_mode';
+
+/** 权限授权码 */
+export const AuthCode = {
+    VIEW: 'view',
+    ADD: 'add',
+    EDIT: 'edit',
+    DEL: 'del',
+    PUBLISH: 'publish',
+    SUBMIT: 'submit',
+    AUDIT: 'audit',
+    REAUDIT: 'reaudit',
+    BUILD: 'build',
+    IMPORT: 'import',
+    EXPORT: 'export',
+    DOWNLOAD: 'download',
+    PRINT: 'print',
+    CANCEL: 'cancel',
+    CLOSE: 'close',
+    REOPEN: 'reopen',
+    SYNC: 'sync',
+};
+
+/** 默认分页参数 */
+export const DefaultPagination = {
+    PAGE_INDEX: 1,
+    PAGE_SIZE: 20,
+};
+

+ 224 - 0
YBEE.EQM.Admin/src/common/converter.ts

@@ -0,0 +1,224 @@
+import type { TreeDataNode } from 'antd';
+import lodash from 'lodash';
+
+/**
+ * 生成树结构
+ * @param list 带parentId属性的列表
+ * @param parentId 父ID
+ * @param mapping 自定义节点数据映射函数
+ * @returns 树节点数据
+ */
+export function buildTreeData(
+    list: DICTS.ParentTreeNode[],
+    parentId: number | string = 0,
+    mapping?: (item: any) => DICTS.ParentTreeNode,
+): DICTS.ParentTreeNode[] {
+    const nodes: DICTS.ParentTreeNode[] = [];
+    list.filter((item) => item.pid === parentId).forEach((item) => {
+        let node = { ...item };
+        if (mapping) {
+            node = mapping(node);
+        }
+        if (list.filter((t) => t.pid === item.id).length > 0) {
+            node.children = buildTreeData(list, item.id, mapping);
+        } else {
+            node.isLeaf = true;
+        }
+
+        nodes.push(node);
+    });
+    return nodes;
+}
+
+/**
+ * 生成树节点
+ * @param list
+ * @param pid
+ * @param mapping
+ * @returns
+ */
+export function buildTreeNodes<TData = any>(
+    list: DICTS.ParentTreeNode[],
+    pid: number | string = 0,
+    mapping?: (item: any) => TreeDataNode,
+): (TreeDataNode & { data: TData })[] {
+    const nodes: (TreeDataNode & { data: TData })[] = [];
+    list.filter((item) => item.pid === pid).forEach((item) => {
+        let node = {
+            key: item.id,
+            title: item.name,
+            isLeaf: false,
+            data: item
+        } as TreeDataNode;
+        if (mapping) {
+            node = mapping({ ...item, ...node });
+        }
+        if (list.filter((t) => t.pid === item.id).length > 0) {
+            node.children = buildTreeNodes(list, item.id, mapping);
+        } else {
+            node.isLeaf = true;
+        }
+
+        nodes.push(node as TreeDataNode & { data: TData });
+    });
+    return nodes;
+
+    // checkable?: boolean;
+    // children?: DataNode[];
+    // disabled?: boolean;
+    // disableCheckbox?: boolean;
+    // icon?: IconType;
+    // isLeaf?: boolean;
+    // key: string | number;
+    // title?: React.ReactNode;
+    // selectable?: boolean;
+    // switcherIcon?: IconType;
+    // /** Set style of TreeNode. This is not recommend if you don't have any force requirement */
+    // className?: string;
+    // style?: React.CSSProperties;
+}
+
+/**
+ * 生成树形表数据
+ * @param list
+ * @param parentId
+ * @param mapping
+ * @returns
+ */
+export function buildTableTreeData(
+    list: any[],
+    idKey: string = 'id',
+    parentKey: string = 'pid',
+    parentId: number | string = 0,
+    mapping?: (item: any) => any,
+): any[] {
+    const nodes: any[] = [];
+    list.filter((item) => item[parentKey] === parentId).forEach((item) => {
+        let node = { ...item };
+        if (mapping) {
+            node = mapping(node);
+        }
+        if (list.filter((t) => t[parentKey] === item[idKey]).length > 0) {
+            node.children = buildTableTreeData(list, idKey, parentKey, item[idKey], mapping);
+        }
+
+        nodes.push(node);
+    });
+    return nodes;
+}
+
+/**
+ * 生成树形表数据(自动设置顶级节点)
+ * @param list 
+ * @param idKey 
+ * @param parentKey 
+ * @param mapping 
+ * @returns 
+ */
+export function buildTableTreeData2(
+    list: any[],
+    idKey: string = 'id',
+    parentKey: string = 'pid',
+    mapping?: (item: any) => any,
+): any[] {
+    const slist = list.map((t, i) => ({ ...t, _treeOrginSort: i }));
+    const parentId = lodash.min(slist.map(t => t.pid));
+    const plist = buildTableTreeData(slist, idKey, parentKey, parentId, mapping);
+    let hids: any[] = [];
+    const getIds = (pls: any[]) => {
+        pls.forEach((t: any) => {
+            hids.push(t.id);
+            if (t.children) {
+                getIds(t.children);
+            }
+        });
+    }
+    getIds(plist);
+    const tlist = slist.filter(t => !hids.includes(t.id)).map(t => ({ ...t }));
+    return [...plist, ...tlist].sort((a, b) => a._treeOrginSort - b._treeOrginSort);
+}
+
+export function getParentPath(
+    list: any[],
+    idKey: string = 'id',
+    parentKey: string = 'parentId',
+    id: number | string = 0,
+    mapping?: (item: any) => any,
+) {
+    let nodes: any[] = [];
+    const c = list.find((item) => item[idKey] === id);
+    if (c) {
+        let node = { ...c };
+        if (mapping) {
+            node = mapping(node);
+        }
+        nodes.splice(0, 1, node);
+        const p = list.find((item) => item[idKey] === c[parentKey]);
+        if (p) {
+            const ps = getParentPath(list, idKey, parentKey, c[parentKey], mapping);
+            nodes = [...ps, ...nodes];
+        }
+    }
+    return nodes;
+}
+
+/**
+ * 转换为选项
+ * @param list 列表
+ * @param mapping 自定义映射函数
+ * @returns 选项列表
+ */
+export function toSelectOptions(
+    list?: any[],
+    mapping?: (item: any) => DICTS.SelectOptionItem,
+): DICTS.SelectOptionItem[] {
+    if (!list) {
+        return [];
+    }
+    const items: DICTS.SelectOptionItem[] = [];
+    if (mapping) {
+        for (const item of list) {
+            items.push(mapping(item));
+        }
+    } else {
+        for (const item of list) {
+            items.push({
+                key: item.id,
+                value: item.id,
+                label: item.name,
+            });
+        }
+    }
+    return items;
+}
+
+/**
+ * 列表转valueEnum
+ * @param list 
+ * @returns 
+ */
+export function toValueEnum(list: any[]) {
+    if (!list || list.length === 0) {
+        return {};
+    }
+    let items: Record<string | number, { text: string, status?: string }> = {};
+    list.forEach(t => {
+        items[t.id as number] = { text: t.name };
+    });
+    return items;
+}
+
+/**
+ * 转换为以ID为键的字典
+ * @param list 列表
+ * @returns
+ */
+export function toIdKeyDict(list: any[]) {
+    const dict: { [key: string]: any;[key: number]: any } = {};
+    let id = 1;
+    for (const item of list) {
+        const opt = { id: id++, ...item };
+        dict[opt.id] = opt;
+    }
+    return dict;
+}

+ 88 - 0
YBEE.EQM.Admin/src/common/dicts.ts

@@ -0,0 +1,88 @@
+import { ProSchemaValueEnumMap, ProSchemaValueEnumObj } from '@ant-design/pro-components';
+
+/** 字典基类 */
+export class Dict {
+    /** 转换为数组 */
+    toList(): DICTS.DictItem[] {
+        let items: DICTS.DictItem[] = [];
+        for (const key in this) {
+            if (this.hasOwnProperty(key)) {
+                items.push(this[key] as unknown as DICTS.DictItem);
+            }
+        }
+        items = items.sort((a, b) => (a.sequence || 0) - (b.sequence || 0));
+        return items;
+    }
+    /** 转换为选项 */
+    toSelectOptions(): DICTS.SelectOptionItem[] {
+        const items: DICTS.SelectOptionItem[] = [];
+        for (const key in this) {
+            if (this.hasOwnProperty(key)) {
+                const item = this[key] as unknown as DICTS.DictItem;
+                items.push({
+                    key: key,
+                    value: item.id,
+                    label: item.name,
+                });
+            }
+        }
+        return items;
+    }
+    /** 转换为以ID为键的字典 */
+    toIdKeyDict() {
+        const dict: { [key: number]: DICTS.DictItem } = {};
+        let id = 1;
+        for (const key in this) {
+            if (this.hasOwnProperty(key)) {
+                const item = { id: id++, ...this[key] } as unknown as DICTS.DictItem;
+                dict[item.id] = item;
+            }
+        }
+        return dict;
+    }
+    /** 转换为以code为键的字典 */
+    toCodeKeyDict() {
+        const dict: { [key: string]: DICTS.DictItem } = {};
+        for (const key in this) {
+            if (this.hasOwnProperty(key)) {
+                const item = { ...this[key] } as DICTS.DictItem;
+                dict[item.code] = item;
+            }
+        }
+        return dict;
+    }
+    /** 转换为ProTable中的valueEnum */
+    toValueEnum(
+        codeKey: boolean = false,
+        excludes?: number[],
+    ): ProSchemaValueEnumObj | ProSchemaValueEnumMap {
+        const ve: ProSchemaValueEnumObj | ProSchemaValueEnumMap = {};
+        for (const key in this) {
+            if (this.hasOwnProperty(key)) {
+                const n = this[key] as unknown as DICTS.DictItem;
+                if (excludes?.includes(n.id)) {
+                    continue;
+                }
+
+                const item = {
+                    text: n?.name || '',
+                    status: n?.status || '',
+                };
+                if (codeKey) {
+                    ve[n.code] = item;
+                } else {
+                    ve[n.id] = item;
+                }
+            }
+        }
+        return ve;
+    }
+}
+
+/** -----------------------------------------==类型==---------------------------------------- */
+
+
+/** -----------------------------------------==状态==---------------------------------------- */
+
+
+

+ 188 - 0
YBEE.EQM.Admin/src/common/helper.ts

@@ -0,0 +1,188 @@
+import JSEncrypt from "jsencrypt";
+
+/** 数组项交换位置 */
+export const swapArrayItem = (arr: any[], index1: number, index2: number) => {
+    arr[index1] = arr.splice(index2, 1, arr[index1])[0];
+    return arr;
+};
+
+/** 从对象中摘取成员 */
+export const pluck = <T, K extends keyof T>(t: T, ...k: K[]) => {
+    return k.reduce((a, b) => ((a[b] = t[b]), a), {} as Pick<T, K>);
+};
+
+/**
+ * 为字符串中间加脱敏字符
+ * @param text 原始字符串
+ * @param start 开始位置
+ * @param end 结束位置
+ * @param symbol 脱敏字符
+ * @returns 脱敏后的字符串
+ */
+export function textMask(text: string, start: number, end: number, symbol: string = '*') {
+    return text.split('').fill(symbol, start, end).join('');
+}
+
+/**
+ * 为字符串中间加脱敏字符2
+ * @param text 原始字符串
+ * @param keepHead 保留前导字符数量
+ * @param keepTail 保留尾部字符数量
+ * @param mark 脱敏标志配置
+ * @param mark.symbol 脱敏符号,默认*
+ * @param mark.minLen 符号最小长度
+ * @param mark.maxLen 符号最大长度
+ */
+export function textMask2(
+    /** 原始字符串 */
+    text: string,
+    /** 保留前导字符数量 */
+    keepHead: number,
+    /** 保留尾部字符数量 */
+    keepTail: number,
+    /** 脱敏标志配置 */
+    mark?: {
+        /** 脱敏符号,默认* */
+        symbol?: string;
+        /** 符号最小长度 */
+        minLen?: number;
+        /** 符号最大长度 */
+        maxLen?: number;
+    },
+) {
+    const { symbol, minLen, maxLen } = mark ?? { symbol: '*' };
+    const atext = text?.trim() ?? '';
+    const sb = symbol ?? '*';
+    const ms = minLen ? sb.repeat(minLen) : '';
+
+    if (atext.length === 0) {
+        return ms;
+    }
+    const alen = atext.length;
+    const keepLen = keepHead + keepTail;
+    if (keepLen < alen) {
+        const h = atext.substring(0, keepHead);
+        const t = atext.substring(alen - keepTail);
+        const markCount = alen - keepLen;
+        const sc = Math.min(minLen ?? markCount, maxLen ?? markCount);
+        return `${h}${sb.repeat(sc)}${t}`;
+    }
+    if (keepHead >= alen) {
+        return `${atext}${ms}`;
+    } else {
+        const h = atext.substring(0, keepHead);
+        const t = atext.substring(keepHead);
+        return `${h}${ms}${t}`;
+    }
+}
+
+/** 扁平化数组 */
+export function flatten<T>(array?: T[][]) {
+    let list: T[] = [];
+    array?.forEach((t1) => {
+        list = [...list, ...t1];
+    });
+    return list;
+}
+
+/**
+ * 加密字符串
+ * @param str 原始字符串
+ * @returns 加密后的字符串
+ */
+export function encrptRsa(str: string) {
+    const publicKey = AppConfig.publicKey;
+    const encryptStr = new JSEncrypt();
+    encryptStr.setPublicKey(publicKey); // 设置 加密公钥
+    return encryptStr.encrypt(str); // 进行加密
+}
+
+/**
+ * 生成数字字母随机串
+ * @param length 长度
+ * @returns
+ */
+export const buildRandomCode = (length: number) => {
+    const model = '0123456789abcdefghjkmnpqrstuvwxyz';
+    let pwd = '';
+    for (let m = 0; m < length; m++) {
+        const mi = Math.floor(Math.random() * model.length);
+        pwd = `${pwd}${model[mi]}`;
+    }
+    return pwd;
+};
+
+/**
+ * 下划线中横线分隔字符串转换为驼峰格式
+ * @param {string} value 待转换字符串
+ * @param {boolean} [capitalize] 首字母大写
+ * @returns
+ */
+export const kebabToCamelCase = (value: string, capitalize: boolean = false) => {
+    let n = value.replace(/[_-][a-zA-z]/g, (str) => str.substr(-1).toUpperCase()).trim() || '';
+    if (capitalize && n.length !== 0) {
+        n = n.slice(0, 1).toUpperCase() + n.slice(1);
+    }
+    return n;
+};
+
+export const toUnderlineCase = (value: string, upperCase: boolean = false) => {
+    const n = value.replace(/[-]/g, () => '_') ?? '';
+    if (upperCase) {
+        return n.toUpperCase();
+    }
+    return n;
+};
+
+/**
+ * 解密 JWT token 的信息
+ * @param token jwt token 字符串
+ * @returns <any>object
+ */
+export function decryptJWT(token: string): any {
+    const tk = token.replace(/_/g, "/").replace(/-/g, "+");
+    const json = decodeURIComponent(escape(window.atob(tk.split(".")[1])));
+    const t = JSON.parse(json);
+    const dt = new Date();
+    t.expireTime = dt.setSeconds(dt.getSeconds() + (t.exp - t.iat) - 30);
+    return t;
+}
+
+/**
+ * 获取元素屏幕 Offset Top
+ * @param ele 元素
+ */
+export function calcElementTop(ele?: HTMLElement | null) {
+    if (!ele) {
+        return 0;
+    }
+    let t = 0;
+    if (ele.offsetTop > 0 && (ele.offsetHeight + ele.offsetTop) <= (ele.parentElement?.offsetHeight ?? 0)) {
+        t += ele.offsetTop;
+    }
+    if (ele.parentElement) {
+        t += calcElementTop(ele.parentElement);
+    }
+    return t;
+}
+
+export default {
+    /** 数组项交换位置 */
+    swapArrayItem,
+    /** 从对象中摘取成员 */
+    pluck,
+    /** 为字符串中间加脱敏字符 */
+    textMask,
+    /** 为字符串中间加脱敏字符 */
+    textMask2,
+    /** 扁平化数组 */
+    flatten,
+    /** 加密字符串 */
+    encrptRsa,
+    /** 生成数字字母随机串 */
+    buildRandomCode,
+    /** 解密 JWT token 的信息 */
+    decryptJWT,
+    /** 获取元素屏幕 Offset Top */
+    calcElementTop,
+};

+ 24 - 0
YBEE.EQM.Admin/src/common/index.ts

@@ -0,0 +1,24 @@
+import { default as BNumber } from './BNumber';
+import * as constant from './constant';
+import * as converter from './converter';
+import * as dicts from './dicts';
+import * as validator from './validator';
+import * as helper from './helper';
+import * as permissions from './permissions';
+
+export default {
+    /** 高精度数值计算 */
+    BNumber,
+    /** 公共字典 */
+    dicts,
+    /** 转换方法 */
+    converter,
+    /** 常量 */
+    constant,
+    /** 验证器 */
+    validator,
+    /** 帮助类 */
+    helper,
+    /** 权限标识 */
+    permissions,
+};

+ 15 - 0
YBEE.EQM.Admin/src/common/net/download.ts

@@ -0,0 +1,15 @@
+/**
+ * 下载文件到本地
+ * @param blob 文件流
+ * @param fileName 存储文件名称
+ */
+export const downloadFileByBlob = (blob: Blob, fileName?: string) => {
+    const blobUrl = window.URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.download = fileName ?? `下载文件_${new Date().getTime()}`;
+    link.style.display = 'none';
+    link.href = blobUrl;
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+};

+ 280 - 0
YBEE.EQM.Admin/src/common/permissions.ts

@@ -0,0 +1,280 @@
+/** 权限标识 */
+export const PERMISSIONS = {
+    /** 工作台 **/
+    WB: {
+    },
+    /** 采购管理 **/
+    PUR: {
+        /** 采购订单 **/
+        ORDER: {
+            /** 送检 **/
+            OP_T_CHECK: 'pur.order.t-check',
+        },
+    },
+    /** 销售管理 **/
+    SALES: {
+        /** 销售订单 **/
+        ORDER: {
+            /** 出库 **/
+            OP_OUT_STK: 'sales.order.out-stk',
+        },
+    },
+    /** 生产管理 **/
+    MF: {
+        /** 生产任务 **/
+        ORDER: {
+            /** 领料 **/
+            OP_PICK: 'mf.order.pick',
+            /** 打印 **/
+            OP_PRINT: 'mf.order.print',
+            /** 送检 **/
+            OP_T_CHECK: 'mf.order.t-check',
+        },
+        /** 生产领料 **/
+        PMR: {
+            /** 打印 **/
+            OP_PRINT: 'mf.pmr.print',
+        },
+    },
+    /** 委外管理 **/
+    SCONT: {
+        /** 委外订单 **/
+        ORDER: {
+            /** 出库 **/
+            OP_OUT_STK: 'scont.order.out-stk',
+            /** 入库 **/
+            OP_IN_STK: 'scont.order.in-stk',
+        },
+    },
+    /** 质量管理 **/
+    QA: {
+        /** 采购质检 **/
+        PUR: {
+            /** 质检 **/
+            OP_CHECK: 'qa.pur.check',
+            /** 审核 **/
+            OP_AUDIT: 'qa.pur.audit',
+            /** 打印 **/
+            OP_PRINT: 'qa.pur.print',
+        },
+        /** 生产质检 **/
+        MF: {
+            /** 质检 **/
+            OP_CHECK: 'qa.mf.check',
+            /** 审核 **/
+            OP_AUDIT: 'qa.mf.audit',
+            /** 打印 **/
+            OP_PRINT: 'qa.mf.print',
+        },
+        /** 委外质检 **/
+        SCONT: {
+            /** 质检 **/
+            OP_CHECK: 'qa.scont.check',
+            /** 审核 **/
+            OP_AUDIT: 'qa.scont.audit',
+            /** 打印 **/
+            OP_PRINT: 'qa.scont.print',
+        },
+    },
+    /** 入库管理 **/
+    STK_IN: {
+        /** 采购入库 **/
+        PUR: {
+        },
+        /** 生产入库 **/
+        MF: {
+        },
+        /** 委外入库 **/
+        SCONT: {
+        },
+        /** 其他入库 **/
+        OTHER: {
+            /** 添加 **/
+            OP_ADD: 'stk-in.other.add',
+            /** 修改 **/
+            OP_EDIT: 'stk-in.other.edit',
+            /** 删除 **/
+            OP_DEL: 'stk-in.other.del',
+            /** 提交 **/
+            OP_SUBMIT: 'stk-in.other.submit',
+            /** 审核 **/
+            OP_AUDIT: 'stk-in.other.audit',
+            /** 打印 **/
+            OP_PRINT: 'stk-in.other.print',
+        },
+    },
+    /** 出库管理 **/
+    STK_OUT: {
+        /** 销售出库 **/
+        SALES: {
+        },
+        /** 委外出库 **/
+        SCONT: {
+        },
+        /** 其他出库 **/
+        OTHER: {
+        },
+    },
+    /** 库存管理 **/
+    INV: {
+        /** 实时库存 **/
+        REAL: {
+        },
+        /** 调拨管理 **/
+        ALLOC: {
+            /** 添加 **/
+            OP_ADD: 'inv.alloc.add',
+            /** 修改 **/
+            OP_EDIT: 'inv.alloc.edit',
+            /** 删除 **/
+            OP_DEL: 'inv.alloc.del',
+            /** 提交 **/
+            OP_SUBMIT: 'inv.alloc.submit',
+            /** 审核 **/
+            OP_AUDIT: 'inv.alloc.audit',
+            /** 打印 **/
+            OP_PRINT: 'inv.alloc.print',
+        },
+        /** MTO调整 **/
+        MTO: {
+            /** 添加 **/
+            OP_ADD: 'inv.mto.add',
+            /** 修改 **/
+            OP_EDIT: 'inv.mto.edit',
+            /** 删除 **/
+            OP_DEL: 'inv.mto.del',
+            /** 提交 **/
+            OP_SUBMIT: 'inv.mto.submit',
+            /** 审核 **/
+            OP_AUDIT: 'inv.mto.audit',
+        },
+        /** 盘亏管理 **/
+        LOSS: {
+            /** 添加 **/
+            OP_ADD: 'inv.loss.add',
+            /** 修改 **/
+            OP_EDIT: 'inv.loss.edit',
+            /** 删除 **/
+            OP_DEL: 'inv.loss.del',
+            /** 提交 **/
+            OP_SUBMIT: 'inv.loss.submit',
+            /** 审核 **/
+            OP_AUDIT: 'inv.loss.audit',
+            /** 打印 **/
+            OP_PRINT: 'inv.loss.print',
+        },
+        /** 盘盈管理 **/
+        PROFIT: {
+            /** 添加 **/
+            OP_ADD: 'inv.profit.add',
+            /** 修改 **/
+            OP_EDIT: 'inv.profit.edit',
+            /** 删除 **/
+            OP_DEL: 'inv.profit.del',
+            /** 提交 **/
+            OP_SUBMIT: 'inv.profit.submit',
+            /** 审核 **/
+            OP_AUDIT: 'inv.profit.audit',
+            /** 打印 **/
+            OP_PRINT: 'inv.profit.print',
+        },
+    },
+    /** 基础数据 **/
+    BASE: {
+        /** 物料 **/
+        MAT: {
+        },
+        /** 计量单位 **/
+        UNIT: {
+        },
+        /** 计量单位组 **/
+        UG: {
+        },
+        /** 客户 **/
+        CUST: {
+        },
+        /** 供应商 **/
+        SUP: {
+        },
+        /** 仓库 **/
+        WH: {
+        },
+        /** 仓位 **/
+        WH_P: {
+        },
+        /** 仓位组 **/
+        WH_PG: {
+        },
+        /** 工作类型 **/
+        WT: {
+        },
+        /** K3部门 **/
+        KDEPT: {
+        },
+    },
+    /** 系统管理 **/
+    SYS: {
+        /** 部门人员 **/
+        DEPT: {
+            /** 添加 **/
+            OP_ADD: 'sys.dept.add',
+            /** 修改 **/
+            OP_EDIT: 'sys.dept.edit',
+            /** 删除 **/
+            OP_DEL: 'sys.dept.del',
+        },
+        /** 角色权限 **/
+        ROLE: {
+            /** 添加 **/
+            OP_ADD: 'sys.role.add',
+            /** 修改 **/
+            OP_EDIT: 'sys.role.edit',
+            /** 删除 **/
+            OP_DEL: 'sys.role.del',
+            /** 授权 **/
+            OP_GRANT: 'sys.role.grant',
+        },
+        /** 字典管理 **/
+        DICT: {
+            /** 添加 **/
+            OP_ADD: 'sys.dict.add',
+            /** 修改 **/
+            OP_EDIT: 'sys.dict.edit',
+            /** 删除 **/
+            OP_DEL: 'sys.dict.del',
+        },
+        /** 功能管理 **/
+        MENU: {
+            /** 添加 **/
+            OP_ADD: 'sys.menu.add',
+            /** 修改 **/
+            OP_EDIT: 'sys.menu.edit',
+            /** 删除 **/
+            OP_DEL: 'sys.menu.del',
+        },
+        /** 参数设置 **/
+        CONFIG: {
+        },
+        /** 日志管理 **/
+        LOG: {
+            /** 异常日志 **/
+            EX: {
+                /** 清除日志 **/
+                OP_CLEAR: 'sys.log.ex.clear',
+            },
+            /** 访问日志 **/
+            VIS: {
+                /** 清除日志 **/
+                OP_CLEAR: 'sys.log.vis.clear',
+            },
+            /** 操作日志 **/
+            OP: {
+                /** 清除日志 **/
+                OP_CLEAR: 'sys.log.op.clear',
+            },
+        },
+        /** 金蝶同步 **/
+        KSYNC: {
+        },
+    },
+};

+ 55 - 0
YBEE.EQM.Admin/src/common/typing.d.ts

@@ -0,0 +1,55 @@
+// @ts-ignore
+/* eslint-disable */
+
+namespace DICTS {
+    /** 字典项 */
+    type DictItem = {
+        /** ID */
+        id: number;
+        /** 父ID */
+        parentId?: number;
+        /** 类型ID */
+        typeId?: number;
+        /** 名称 */
+        name: string;
+        /** 编码 */
+        code: string;
+        /** 顺序 */
+        sequence?: number;
+        /** 备注 */
+        remark?: string;
+        /** 是否已启用 */
+        enabled?: boolean;
+        /** valueEnum中的状态 */
+        status?: 'Success' | 'Error' | 'Processing' | 'Warning' | 'Default' | string;
+        /** 颜色值 */
+        color?: string;
+
+        [key: string]: any;
+    };
+
+    type DictKeyItem = {
+        [key: string | number]: DictItem;
+    };
+
+    /** Select组件选项内容项 */
+    type SelectOptionItem = {
+        key: number | string;
+        value: number | string;
+        label: string;
+    };
+
+    /** 树节点数据 */
+    type ParentTreeNode = {
+        id: number | string;
+        key?: number | string;
+        parentId?: number | string;
+        children?: ParentTreeNode[];
+        [key: string]: any;
+    };
+
+    type ValueEnumItem = {
+        text?: string;
+        status?: string;
+    };
+}

+ 141 - 0
YBEE.EQM.Admin/src/common/validator.ts

@@ -0,0 +1,141 @@
+import { Gender } from "@/services/enums";
+
+/** 身份证信息 */
+export type IdCardInfo = {
+    /** 身份证号码 */
+    idNumber: string;
+    /** 性别 */
+    gender?: Gender;
+    /** 出生日期 */
+    birthDate?: Date;
+    /** 出生日期字符串 */
+    birth?: string;
+    /** 验证是否成功 */
+    success: boolean;
+    /** 错误消息 */
+    errorMessage: string;
+};
+
+// /**
+//  * 验证身份证号码
+//  * @param id 身份证号码
+//  * @returns
+//  */
+// export function validateIdCard(id: string) {
+//     // 15位和18位身份证号码的正则表达式
+//     const regIdCard =
+//         /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/;
+
+//     const ret: IdCardInfo = {
+//         idNumber: id,
+//         success: false,
+//         errorMessage: '',
+//     };
+
+//     if (regIdCard.test(id)) {
+//         if (id.length === 18) {
+//             const idCardWi = new Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2); // 将前17位加权因子保存在数组里
+//             const idCardY = new Array(1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2); // 这是除以11后,可能产生的11位余数、验证码,也保存成数组
+//             let idCardWiSum = 0; // 用来保存前17位各自乖以加权因子后的总和
+//             for (let i = 0; i < 17; i++) {
+//                 idCardWiSum += parseInt(id.substring(i, i + 1)) * idCardWi[i];
+//             }
+//             const idCardMod = idCardWiSum % 11; // 计算出校验码所在数组的位置
+//             const idCardLast = id.substring(17); // 得到最后一位身份证号码
+//             if (idCardMod === 2 && (idCardLast === 'X' || idCardLast === 'x')) {
+//                 //如果等于2,则说明校验码是10,身份证号码最后一位应该是X
+//                 ret.success = true;
+//             } else if (idCardLast === `${idCardY[idCardMod]}`) {
+//                 // 用计算出的验证码与最后一位身份证号码匹配,如果一致,说明通过,否则是无效的身份证号码
+//                 ret.success = true;
+//             }
+//             else {
+//                 ret.errorMessage = '';
+//             }
+//         }
+//         else {
+//             ret.errorMessage = '';
+//         }
+//     }
+//     if (ret.success) {
+//         ret.gender = parseInt(id[16]) % 2 === 1 ? Gender.MALE : Gender.FEMALE;
+//         ret.birthDate = new Date(
+//             parseInt(id.substring(6, 10)),
+//             parseInt(id.substring(10, 12)) - 1,
+//             parseInt(id.substring(12, 14)),
+//         );
+//         ret.birth = `${id.substring(6, 10)}-${id.substring(10, 12)}-${id.substring(12, 14)}`;
+//     }
+
+//     return ret;
+// }
+
+/**
+ * 验证身份证号码
+ * @param idNumber 身份证号码
+ * @returns 
+ */
+export function validateIdCard(idNumber: string): IdCardInfo {
+    const id = idNumber.trim();
+    const regIdCard = /(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
+
+    let ret: IdCardInfo = {
+        idNumber: id,
+        success: false,
+        errorMessage: '',
+    };
+
+    // 格式验证
+    if (!regIdCard.test(id)) {
+        ret.errorMessage = '格式(应为18位数字,或17位数字+X)';
+        return ret;
+    }
+
+    // 出生日期验证
+    ret.birth = `${id.substring(6, 10)}-${id.substring(10, 12)}-${id.substring(12, 14)}`;
+    const year = parseInt(id.substring(6, 10));
+    const month = parseInt(id.substring(10, 12)) - 1;
+    const day = parseInt(id.substring(12, 14));
+    const birthDate = new Date(year, month, day);
+    if (birthDate.getFullYear() !== year || birthDate.getMonth() !== month || birthDate.getDate() !== day) {
+        ret.errorMessage = "第7至14位出生日期";
+        return ret;
+    }
+    ret.birthDate = birthDate;
+
+    // 校验码验证
+    const idCardWi = new Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2); // 将前17位加权因子保存在数组里
+    const idCardY = new Array(1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2); // 这是除以11后,可能产生的11位余数、验证码,也保存成数组
+    let idCardWiSum = 0; // 用来保存前17位各自乖以加权因子后的总和
+    for (let i = 0; i < 17; i++) {
+        idCardWiSum += parseInt(id.substring(i, i + 1)) * idCardWi[i];
+    }
+    const idCardMod = idCardWiSum % 11; // 计算出校验码所在数组的位置
+    const idCardLast = id.substring(17); // 得到最后一位身份证号码
+    if (!(
+        idCardMod === 2 && (idCardLast === 'X' || idCardLast === 'x') //如果等于2,则说明校验码是10,身份证号码最后一位应该是X
+        || idCardLast === `${idCardY[idCardMod]}` // 用计算出的验证码与最后一位身份证号码匹配,如果一致,说明通过,否则是无效的身份证号码
+    )) {
+        ret.errorMessage = '第18位(校验位)';
+        return ret;
+    }
+
+    // 性别
+    ret.gender = parseInt(id[16]) % 2 === 1 ? Gender.MALE : Gender.FEMALE;
+    ret.success = true;
+    return ret;
+}
+
+/**
+ * 验证手机号码格式
+ * @param value 手机号码
+ * @param ignoreBlank 是否忽略空
+ * @returns
+ */
+export function validateMobilePhone(value: string, ignoreBlank: boolean = true): boolean {
+    const reg_tel = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
+    if (ignoreBlank && (value === null || value === undefined || value === '')) {
+        return true;
+    }
+    return reg_tel.test(value);
+}

+ 4 - 0
YBEE.EQM.Admin/src/common/valueEnum.ts

@@ -0,0 +1,4 @@
+export const TrueOrFalseValueEnum = {
+    true: { text: '是' },
+    false: { text: '否' },
+};

+ 95 - 0
YBEE.EQM.Admin/src/components/AuditModal/index.tsx

@@ -0,0 +1,95 @@
+import { MovableModalForm } from '@/components';
+import { FormInstance, ProFormRadio, ProFormTextArea } from '@ant-design/pro-components';
+import { App } from 'antd';
+import { useRef, useState } from 'react';
+
+/** 审核数据类型 */
+export type AuditFormValueType = {
+    isApproved: boolean;
+    remark?: string;
+};
+
+/** 审核对话框属性 */
+export type AuditModalProps = {
+    title?: string;
+    alert?: React.ReactNode;
+    width?: number;
+    onAudit: (values: AuditFormValueType) => Promise<boolean>;
+    onClose: () => void;
+};
+
+/** 通用审核对话框 */
+const AuditModal: React.FC<AuditModalProps> = ({ title, alert, width, onAudit, onClose }) => {
+    const [open, setOpen] = useState(true);
+    const handleClose = () => { setOpen(false); setTimeout(() => onClose?.(), 300); };
+
+    const formRef = useRef<FormInstance>();
+    const [isApproved, setIsApproved] = useState();
+
+    const { message } = App.useApp();
+
+    return (
+        <MovableModalForm<AuditFormValueType>
+            title={title ?? '审核'}
+            width={width ?? 560}
+            open={open}
+            formRef={formRef}
+            modalProps={{
+                centered: true,
+                maskClosable: false,
+                onCancel: () => {
+                    formRef?.current?.resetFields();
+                    handleClose();
+                },
+            }}
+            onFinish={async (values: any) => {
+                try {
+                    const res = await onAudit(values);
+                    if (res) {
+                        handleClose();
+                        return true;
+                    }
+                    return false;
+                } catch {
+                    message.error('操作失败!');
+                    return false;
+                }
+            }}
+        >
+            {alert}
+            <ProFormRadio.Group
+                label="审核类型"
+                name="isApproved"
+                options={[
+                    { label: '通过', value: true },
+                    { label: '驳回', value: false },
+                ]}
+                required
+                rules={[{ required: true, message: '${label}必须选择' }]}
+                fieldProps={{ onChange: (e) => { setIsApproved(e.target.value) } }}
+            />
+            <ProFormTextArea
+                label="审核意见"
+                tooltip="审核类型为【驳回】时,必须输入审核意见"
+                name="remark"
+                dependencies={['isApproved']}
+                fieldProps={{ rows: 6 }}
+                required={isApproved === false}
+                rules={[
+                    ({ getFieldValue }) => ({
+                        validator: (_, value) => {
+                            const iad = getFieldValue('isApproved');
+                            if (iad === false && !value) {
+                                return Promise.reject('驳回必须输入审核意见!');
+                            }
+                            return Promise.resolve();
+                        },
+                    }),
+                ]}
+            />
+        </MovableModalForm>
+    );
+};
+
+export default AuditModal;
+

+ 19 - 0
YBEE.EQM.Admin/src/components/CardStepTitle/index.tsx

@@ -0,0 +1,19 @@
+import { Typography, theme } from "antd";
+
+const CardStepTitle: React.FC<{ handlerColor?: string; children?: React.ReactNode }> = ({ handlerColor, children }) => {
+    const { token } = theme.useToken();
+    return (
+        <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
+            <div style={{
+                width: 4,
+                height: token.fontSizeHeading5,
+                backgroundColor: handlerColor ?? token.colorPrimary,
+                marginRight: token.marginXS,
+                fontSize: token.sizeMD
+            }} />
+            <Typography.Title level={5} style={{ marginBottom: 0 }}>{children}</Typography.Title>
+        </div>
+    );
+}
+
+export default CardStepTitle;

+ 99 - 0
YBEE.EQM.Admin/src/components/ChangePassword/index.tsx

@@ -0,0 +1,99 @@
+import { encrptRsa } from '@/common/helper';
+import { MovableModalForm } from '@/components';
+import SysUserController from '@/services/apis/SysUserController';
+import type { ProFormInstance } from '@ant-design/pro-components';
+import { App, Form, Input } from 'antd';
+import { useRef, useState } from 'react';
+
+interface ChangePasswordProps {
+    onClose: () => void;
+}
+const ChangePassword: React.FC<ChangePasswordProps> = ({ onClose }) => {
+    const [visible, setVisible] = useState<boolean>(true);
+    const handleClose = () => {
+        setVisible(false);
+        setTimeout(onClose, 0);
+    };
+    const formRef = useRef<ProFormInstance>();
+
+    const { message } = App.useApp();
+
+    return (
+        <MovableModalForm<{
+            oldPassword: string;
+            newPassword: string;
+            newPasswordConfirm: string;
+        }>
+            title="修改密码"
+            width="400px"
+            open={visible}
+            formRef={formRef}
+            modalProps={{
+                centered: true,
+                maskClosable: false,
+                onCancel: handleClose,
+            }}
+            onFinish={async (values) => {
+                const hide = message.loading('提交中...', 0);
+                const { oldPassword, newPassword } = values;
+                const params = {
+                    oldPassword: encrptRsa(oldPassword) || '',
+                    newPassword: encrptRsa(newPassword) || '',
+                };
+                try {
+                    await SysUserController.changePassword(params);
+                    message.success('修改成功');
+                    handleClose();
+                }
+                catch {
+                    message.error('修改失败');
+                }
+                finally {
+                    hide();
+                }
+            }}
+        >
+            <Form.Item label="原密码" name="oldPassword" rules={[{ required: true }]}>
+                <Input type="password" />
+            </Form.Item>
+            <Form.Item
+                label="新密码(6至32位)"
+                name="newPassword"
+                rules={[
+                    { required: true, min: 6, max: 32 },
+                    ({ getFieldValue }) => ({
+                        validator: (_, value) => {
+                            const np2 = getFieldValue('newPasswordConfirm');
+                            if (!np2 || np2 === '' || np2 === value) {
+                                return Promise.resolve();
+                            }
+                            return Promise.reject('两次输入新密码不一致!');
+                        },
+                    }),
+                ]}
+            >
+                <Input type="password" />
+            </Form.Item>
+            <Form.Item
+                label="确认新密码"
+                name="newPasswordConfirm"
+                rules={[
+                    { required: true, min: 6, max: 32 },
+                    ({ getFieldValue }) => ({
+                        validator: (_, value) => {
+                            const np = getFieldValue('newPassword');
+                            if (np === value) {
+                                return Promise.resolve();
+                            }
+                            return Promise.reject('两次输入新密码不一致!');
+                        },
+                    }),
+                ]}
+            >
+                <Input type="password" />
+            </Form.Item>
+        </MovableModalForm>
+    );
+};
+
+export default ChangePassword;

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/ai.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M168 32c-12 0-24.8 4.8-33.6 14.4C124.8 55.2 120 68 120 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L648 32H168z" fill="#FFAC33" /><path d="M920 304H696c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#FFDEAD" /><path d="M788.8 714.4c-18.4 0-34.4 12.8-38.4 29.6-54.4 9.6-166.4 17.6-255.2-53.6 1.6-4 2.4-8.8 2.4-12.8 0-22.4-17.6-40-40-40-4 0-7.2 0.8-11.2 1.6-68.8-93.6-71.2-211.2-67.2-268.8 17.6-4.8 29.6-20 29.6-38.4 0-22.4-17.6-40-40-40s-40 17.6-40 40c0 13.6 7.2 26.4 18.4 33.6-4.8 54.4-4 160.8 49.6 256.8L287.2 512c1.6-3.2 2.4-6.4 2.4-10.4 0-13.6-10.4-24-24-24s-24 10.4-24 24 10.4 24 24 24c4 0 7.2-0.8 10.4-2.4l143.2 143.2c-0.8 3.2-1.6 7.2-1.6 11.2 0 22.4 17.6 40 40 40 4 0 7.2-0.8 11.2-1.6l143.2 143.2c-1.6 3.2-2.4 6.4-2.4 10.4 0 13.6 10.4 24 24 24s24-10.4 24-24-10.4-24-24-24c-4 0-7.2 0.8-10.4 2.4L522.4 746.4c53.6 28 109.6 36.8 156.8 36.8 30.4 0 56.8-3.2 76.8-7.2 7.2 11.2 19.2 18.4 33.6 18.4 22.4 0 40-17.6 40-40s-18.4-40-40.8-40z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/audio.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#FF5562" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#FFBBC0" /><path d="M669.6 491.2c0-1.6 0.8-4 0-6.4V369.6c0-4.8-2.4-8.8-5.6-12-4-3.2-8-4-12.8-3.2l-250.4 70.4c-7.2 1.6-12 7.2-12 14.4v275.2c-9.6-4-20.8-6.4-32.8-6.4-8.8 0-17.6 0.8-26.4 3.2-40.8 11.2-66.4 43.2-58.4 72.8 6.4 23.2 30.4 37.6 60.8 37.6 8.8 0 17.6-1.6 26.4-3.2 36.8-10.4 60.8-36.8 60-63.2 0.8-1.6 0.8-3.2 0.8-5.6V570.4l220-61.6v136c-9.6-4-20.8-6.4-32.8-6.4-8.8 0-17.6 0.8-26.4 3.2-40.8 11.2-66.4 43.2-58.4 72.8 6.4 23.2 30.4 37.6 60.8 37.6 8.8 0 17.6-0.8 26.4-3.2 36-9.6 60-36 60-62.4 0.8-1.6 0.8-3.2 0.8-5.6V491.2z m-250.4 48v-53.6L639.2 424v53.6l-220 61.6z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/download.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#5ACC9B" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#BDEBD7" /><path d="M500.8 684.8c3.2 2.4 6.4 4.8 11.2 4.8 4 0 8-1.6 11.2-4.8l142.4-136c2.4-2.4 3.2-5.6 1.6-8.8-1.6-3.2-4-4.8-7.2-4.8H576v-136c0-4-1.6-8-4.8-11.2-3.2-3.2-7.2-4.8-11.2-4.8H464c-4 0-8 1.6-11.2 4.8-3.2 3.2-4.8 7.2-4.8 11.2v136H364c-3.2 0-6.4 1.6-7.2 4.8-1.6 3.2 0 6.4 1.6 8.8l142.4 136zM712 751.2H312c-8.8 0-16 7.2-16 16s7.2 16 16 16h400c8.8 0 16-7.2 16-16s-7.2-16-16-16z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/excel.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#5ACC9B" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#BDEBD7" /><path d="M475.2 537.6l-108.8-152h75.2l71.2 108.8 74.4-108.8h72.8l-111.2 152 116.8 161.6h-76L511.2 584l-78.4 116h-74.4l116.8-162.4z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/folder-group.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M977.6 246.4c-9.6-9.6-21.6-14.4-33.6-14.4H472L366.4 126.4c-4-4-9.6-8-15.2-10.4-6.4-2.4-12-4-18.4-4H80c-12 0-24.8 4.8-33.6 14.4S32 148 32 160v280h960V280c0-12-4.8-24.8-14.4-33.6z" fill="#FFD766" /><path d="M944 920H80c-26.4 0-48-21.6-48-48V360h960v512c0 26.4-21.6 48-48 48z" fill="#FFAC33" /><path d="M782.4 759.2c0 8-6.4 13.6-13.6 13.6H501.6c-8 0-13.6-6.4-13.6-13.6 0 0 0-50.4 36.8-68.8 23.2-11.2 14.4-2.4 43.2-14.4s35.2-16 35.2-16v-27.2s-10.4-8-14.4-33.6c-6.4 1.6-8.8-8-9.6-14.4 0-6.4-4-24.8 4-23.2-1.6-12.8-3.2-24-2.4-29.6 2.4-20.8 22.4-43.2 53.6-44.8 36.8 1.6 51.2 23.2 53.6 44.8 0.8 5.6-0.8 17.6-2.4 29.6 8-1.6 4.8 16.8 4 23.2 0 6.4-2.4 16-9.6 14.4-3.2 25.6-14.4 33.6-14.4 33.6v27.2s6.4 4 35.2 16 19.2 3.2 43.2 14.4c38.4 18.4 38.4 68.8 38.4 68.8z" fill="#FFDCB3" /><path d="M640 808.8c0 10.4-8 18.4-18.4 18.4H258.4c-10.4 0-18.4-8-18.4-18.4 0 0 0-68 49.6-92.8 32-16 19.2-3.2 58.4-19.2 38.4-16 48-21.6 48-21.6v-36.8s-14.4-11.2-19.2-45.6c-8.8 2.4-12-10.4-12.8-19.2-0.8-8-5.6-33.6 5.6-31.2-2.4-16.8-4-32.8-3.2-40.8 2.4-28.8 30.4-58.4 72.8-60.8 49.6 2.4 69.6 32 72.8 60.8 0.8 8-0.8 23.2-3.2 40.8 11.2-2.4 6.4 23.2 5.6 31.2-0.8 8.8-3.2 21.6-12.8 19.2-4.8 35.2-19.2 45.6-19.2 45.6v36.8s8.8 5.6 48 21.6c38.4 16 26.4 4 58.4 20 51.2 23.2 51.2 92 51.2 92z" fill="#FFDCB3" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/folder-locked.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M985.6 246.4c-9.6-9.6-21.6-14.4-33.6-14.4H480L374.4 126.4c-4-4-9.6-8-15.2-10.4-6.4-2.4-12-4-18.4-4H88c-12 0-24.8 4.8-33.6 14.4S40 148 40 160v280h960V280c0-12-4.8-24.8-14.4-33.6z" fill="#FFD766" /><path d="M952 920H88c-26.4 0-48-21.6-48-48V360h960v512c0 26.4-21.6 48-48 48z" fill="#FFAC33" /><path d="M664 584h8v-8c0-79.2-64.8-144-144-144h-16c-79.2 0-144 64.8-144 144v8h8c-26.4 0-48 21.6-48 48v160c0 26.4 21.6 48 48 48h288c26.4 0 48-21.6 48-48V632c0-26.4-21.6-48-48-48zM520 784c-22.4 0-40-17.6-40-40 0-16 9.6-30.4 24-36.8V656c0-8.8 7.2-16 16-16s16 7.2 16 16v51.2c14.4 6.4 24 20 24 36.8 0 22.4-17.6 40-40 40zM416 584v-8c0-52.8 43.2-96 96-96h16c52.8 0 96 43.2 96 96v8H416z" fill="#FFDCB3" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/folder-open.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M841.6 238.4c-9.6-9.6-21.6-14.4-33.6-14.4H432L326.4 118.4c-4-4-9.6-8-15.2-10.4-6.4-2.4-12-4-18.4-4H80c-12 0-24.8 4.8-33.6 14.4S32 140 32 152v712c0 12 4.8 24.8 14.4 33.6S68 912 80 912h728c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V272c0-12-4.8-24.8-14.4-33.6z" fill="#FFD766" /><path d="M858.4 877.6c-3.2 9.6-8.8 18.4-17.6 24.8-8.8 6.4-18.4 9.6-28.8 9.6H88.8c-14.4 0-28.8-6.4-38.4-19.2s-12-28.8-7.2-42.4l139.2-464c3.2-9.6 8.8-18.4 17.6-24.8 8.8-6.4 18.4-9.6 28.8-9.6h724c14.4 0 28.8 6.4 38.4 19.2 9.6 12.8 12 28.8 7.2 42.4l-140 464z" fill="#FFAC33" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/folder-personal.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M985.6 246.4c-9.6-9.6-21.6-14.4-33.6-14.4H480L374.4 126.4c-4-4-9.6-8-15.2-10.4-6.4-2.4-12-4-18.4-4H88c-12 0-24.8 4.8-33.6 14.4S40 148 40 160v280h960V280c0-12-4.8-24.8-14.4-33.6z" fill="#FFD766" /><path d="M952 920H88c-26.4 0-48-21.6-48-48V360h960v512c0 26.4-21.6 48-48 48z" fill="#FFAC33" /><path d="M728 808.8c0 10.4-8 18.4-18.4 18.4H346.4c-10.4 0-18.4-8-18.4-18.4 0 0 0-68 49.6-92.8 32-16 19.2-3.2 58.4-19.2 38.4-16 48-21.6 48-21.6v-36.8s-14.4-11.2-19.2-45.6c-8.8 2.4-12-10.4-12.8-19.2-0.8-8-5.6-33.6 5.6-31.2-2.4-16.8-4-32.8-3.2-40.8 2.4-28.8 30.4-58.4 72.8-60.8 49.6 2.4 69.6 32 72.8 60.8 0.8 8-0.8 23.2-3.2 40.8 11.2-2.4 6.4 23.2 5.6 31.2-0.8 8.8-3.2 21.6-12.8 19.2-4.8 35.2-19.2 45.6-19.2 45.6v36.8s8.8 5.6 48 21.6c38.4 16 26.4 4 58.4 20 51.2 23.2 51.2 92 51.2 92z" fill="#FFDCB3" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/folder-share.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M985.6 246.4c-9.6-9.6-21.6-14.4-33.6-14.4H480L374.4 126.4c-4-4-9.6-8-15.2-10.4-6.4-2.4-12-4-18.4-4H88c-12 0-24.8 4.8-33.6 14.4S40 148 40 160v280h960V280c0-12-4.8-24.8-14.4-33.6z" fill="#FFD766" /><path d="M952 920H88c-26.4 0-48-21.6-48-48V360h960v512c0 26.4-21.6 48-48 48z" fill="#FFAC33" /><path d="M664 736c-19.2 0-36.8 8.8-48 22.4L406.4 653.6c0.8-4 1.6-8.8 1.6-13.6s-0.8-8.8-1.6-13.6L616 521.6c12 13.6 28.8 22.4 48 22.4 35.2 0 64-28.8 64-64s-28.8-64-64-64-64 28.8-64 64c0 4.8 0.8 8.8 1.6 13.6L392 598.4c-12-13.6-28.8-22.4-48-22.4-35.2 0-64 28.8-64 64s28.8 64 64 64c19.2 0 36.8-8.8 48-22.4l209.6 104.8c-0.8 4-1.6 8.8-1.6 13.6 0 35.2 28.8 64 64 64s64-28.8 64-64-28.8-64-64-64z" fill="#FFDCB3" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/folder.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M977.6 238.4c-9.6-9.6-21.6-14.4-33.6-14.4H472L366.4 118.4c-4-4-9.6-8-15.2-10.4-6.4-2.4-12-4-18.4-4H80c-12 0-24.8 4.8-33.6 14.4S32 140 32 152v280h960V272c0-12-4.8-24.8-14.4-33.6z" fill="#FFD766" /><path d="M944 912H80c-26.4 0-48-21.6-48-48V352h960v512c0 26.4-21.6 48-48 48z" fill="#FFAC33" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/gif.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#5ACC9B" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#BDEBD7" /><path d="M640.8 644m-136 0a136 136 0 1 0 272 0 136 136 0 1 0-272 0Z" fill="#9CE0C3" /><path d="M512.8 596m-136 0a136 136 0 1 0 272 0 136 136 0 1 0-272 0Z" fill="#CDF0E1" /><path d="M384.8 540m-136 0a136 136 0 1 0 272 0 136 136 0 1 0-272 0Z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/html.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#FF8976" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#FFD0C8" /><path d="M421.6 564L304 610.4l117.6 46.4v36.8l-162.4-64.8V592l162.4-64.8v36.8zM538.4 457.6h36.8l-89.6 240h-36.8l89.6-240zM602.4 657.6L720 611.2l-117.6-46.4V528l162.4 64v37.6l-162.4 64.8v-36.8z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/jpg.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#FF5562" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#FFBBC0" /><path d="M758.4 705.6L658.4 550.4c-3.2-4.8-8-7.2-13.6-7.2s-10.4 3.2-13.6 7.2l-53.6 83.2-120-194.4c-3.2-4.8-8-7.2-13.6-7.2s-10.4 3.2-13.6 7.2L265.6 705.6c-3.2 4.8-3.2 11.2 0 16 3.2 5.6 8 8 13.6 8h465.6c5.6 0 11.2-3.2 14.4-8 2.4-5.6 2.4-12-0.8-16z" fill="#FFFFFF" /><path d="M662.4 412m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/pdf.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#FF5562" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#FFBBC0" /><path d="M696 843.2c-50.4 0-95.2-86.4-119.2-142.4-40-16.8-84-32-126.4-42.4-37.6 24.8-100.8 61.6-149.6 61.6-30.4 0-52-15.2-60-41.6-6.4-21.6-0.8-36.8 5.6-44.8 12.8-17.6 39.2-26.4 79.2-26.4 32 0 72.8 5.6 118.4 16.8 29.6-20.8 59.2-44.8 85.6-70.4-12-56-24.8-146.4 8-188 16-20 40.8-26.4 70.4-17.6 32.8 9.6 44.8 29.6 48.8 44.8 13.6 54.4-48.8 128-91.2 171.2 9.6 37.6 21.6 76.8 36.8 112.8C663.2 704 735.2 744 743.2 788c3.2 15.2-1.6 29.6-13.6 41.6-10.4 8.8-21.6 13.6-33.6 13.6z m-74.4-121.6c30.4 61.6 59.2 90.4 74.4 90.4 2.4 0 5.6-0.8 10.4-4.8 5.6-5.6 5.6-9.6 4.8-12.8-3.2-16-28.8-42.4-89.6-72.8z m-296-82.4c-39.2 0-50.4 9.6-53.6 13.6-0.8 1.6-4 5.6-0.8 16.8 2.4 9.6 8.8 19.2 29.6 19.2 25.6 0 62.4-14.4 105.6-40-31.2-6.4-58.4-9.6-80.8-9.6z m158.4-4.8c25.6 7.2 52 16 76.8 25.6-8.8-23.2-16-47.2-22.4-70.4-17.6 15.2-36 30.4-54.4 44.8zM583.2 376c-8.8 0-15.2 3.2-20.8 9.6-16.8 20.8-18.4 73.6-5.6 140.8 48.8-52 75.2-100 68.8-125.6-0.8-4-4-15.2-26.4-21.6-6.4-2.4-11.2-3.2-16-3.2z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/png.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M168 32c-12 0-24.8 4.8-33.6 14.4C124.8 55.2 120 68 120 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L648 32H168z" fill="#6CCBFF" /><path d="M920 304H696c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#C4EAFF" /><path d="M320 400h80v80H320zM480 400h80v80H480zM640 400h80v80H640zM400 480h80v80H400zM560 480h80v80H560zM400 640h80v80H400zM560 640h80v80H560zM320 560h80v80H320zM480 560h80v80H480zM640 560h80v80H640zM320 720h80v80H320zM480 720h80v80H480zM640 720h80v80H640z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/ppt.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#FF8976" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#FFD0C8" /><path d="M385.6 385.6h176c70.4 0 92.8 47.2 92.8 97.6 0 48-28 96.8-92 96.8H445.6v120h-60V385.6z m60 145.6h96.8c34.4 0 52.8-10.4 52.8-47.2 0-38.4-24.8-48-48-48H445.6v95.2z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/psd.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M168 32c-12 0-24.8 4.8-33.6 14.4C124.8 55.2 120 68 120 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L648 32H168z" fill="#8095FF" /><path d="M920 304H696c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#CCD5FF" /><path d="M504 550.4c4.8-2.4 10.4-4 16-4s11.2 1.6 16 4l184.8 107.2c4.8 2.4 8 8 8 13.6s-3.2 11.2-8 13.6L534.4 792.8c-4.8 2.4-10.4 4-16 4s-11.2-1.6-16-4L317.6 685.6c-4.8-2.4-8-8-8-13.6s3.2-11.2 8-13.6L504 550.4z" fill="#C0CAFF" /><path d="M504 390.4c4.8-2.4 10.4-4 16-4s11.2 1.6 16 4l184.8 107.2c4.8 2.4 8 8 8 13.6s-3.2 11.2-8 13.6L534.4 632.8c-4.8 2.4-10.4 4-16 4s-11.2-1.6-16-4L317.6 525.6c-4.8-2.4-8-8-8-13.6s3.2-11.2 8-13.6L504 390.4z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/txt.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#E5E5E5" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#CCCCCC" /><path d="M264 376h208c13.6 0 24-10.4 24-24s-10.4-24-24-24H264c-13.6 0-24 10.4-24 24s10.4 24 24 24zM264 536h496c13.6 0 24-10.4 24-24s-10.4-24-24-24H264c-13.6 0-24 10.4-24 24s10.4 24 24 24zM760 648H264c-13.6 0-24 10.4-24 24s10.4 24 24 24h496c13.6 0 24-10.4 24-24s-10.4-24-24-24z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/upload.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M168 32c-12 0-24.8 4.8-33.6 14.4C124.8 55.2 120 68 120 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L648 32H168z" fill="#6CCBFF" /><path d="M920 304H696c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#C4EAFF" /><path d="M372 543.2H456v136c0 4 1.6 8 4.8 11.2 3.2 3.2 7.2 4.8 11.2 4.8h96c4 0 8-1.6 11.2-4.8 3.2-3.2 4.8-7.2 4.8-11.2v-136h84c3.2 0 6.4-1.6 7.2-4.8 1.6-3.2 0-6.4-1.6-8.8l-142.4-136c-3.2-2.4-6.4-4.8-11.2-4.8-4.8 0-8 1.6-11.2 4.8l-142.4 136c-2.4 2.4-3.2 5.6-1.6 8.8 1.6 3.2 4 4.8 7.2 4.8zM720 751.2H320c-8.8 0-16 7.2-16 16s7.2 16 16 16h400c8.8 0 16-7.2 16-16s-7.2-16-16-16z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/video.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M80 34.4h864v960H80z" fill="#8095FF" /><path d="M176 112m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M176 272m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M176 432m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M176 592m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M176 752m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M176 912m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M864 112m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M864 272m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M864 432m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M864 592m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M864 752m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M864 912m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" /><path d="M648 508L436 362.4c-4.8-3.2-11.2-4-16.8-0.8-5.6 3.2-8.8 8.8-8.8 14.4v290.4c0 5.6 3.2 11.2 8.8 14.4 5.6 3.2 12 2.4 16.8-0.8L648 533.6c4.8-2.4 7.2-8 7.2-12.8 0-4.8-3.2-9.6-7.2-12.8z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/white.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#E5E5E5" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#CCCCCC" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/word.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6 9.6 9.6 21.6 14.4 33.6 14.4h704c12 0 24.8-4.8 33.6-14.4 9.6-9.6 14.4-21.6 14.4-33.6V304L640 32H160z" fill="#6CCBFF" /><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#C4EAFF" /><path d="M280 385.6h64.8l64.8 244h0.8l71.2-244H544l72 244 65.6-244H744L648 700h-64.8L512 458.4h-0.8l-72 240.8h-64.8L280 385.6z" fill="#FFFFFF" /></svg>

+ 1 - 0
YBEE.EQM.Admin/src/components/FileIcon/icons/zip.svg

@@ -0,0 +1 @@
+<?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 class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M944 944H80c-26.4 0-48-18.4-48-40.8V656h960v247.2c0 22.4-21.6 40.8-48 40.8z" fill="#5ACC9B" /><path d="M80 80h864c26.4 0 48 18.4 48 40.8V368H32V120.8c0-22.4 21.6-40.8 48-40.8z" fill="#6CCBFF" /><path d="M32 368h960v288H32z" fill="#FFD766" /><path d="M352 80h320v864H352z" fill="#FF5562" /><path d="M444 128h64v48h-64zM508 80h64v48h-64zM508 176h64v48h-64zM444 224h64v48h-64zM508 272h64v48h-64zM444 320h64v48h-64zM508 368h64v48h-64zM444 416h64v48h-64zM508 464h64v48h-64zM444 512h64v48h-64zM508 560h64v48h-64zM444 608h64v48h-64zM508 656h64v48h-64zM444 704h64v48h-64zM508 752h64v48h-64zM444 800h64v48h-64zM444 896h64v48h-64zM508 848h64v48h-64z" fill="#FFFFFF" /></svg>

+ 89 - 0
YBEE.EQM.Admin/src/components/FileIcon/index.tsx

@@ -0,0 +1,89 @@
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import { CSSProperties } from 'react';
+
+import ai from './icons/ai.svg';
+import audio from './icons/audio.svg';
+import download from './icons/download.svg';
+import excel from './icons/excel.svg';
+import folderGroup from './icons/folder-group.svg';
+import folderLocked from './icons/folder-locked.svg';
+import folderOpen from './icons/folder-open.svg';
+import folderPersonal from './icons/folder-personal.svg';
+import folderShare from './icons/folder-share.svg';
+import folder from './icons/folder.svg';
+import gif from './icons/gif.svg';
+import html from './icons/html.svg';
+import jpg from './icons/jpg.svg';
+import pdf from './icons/pdf.svg';
+import png from './icons/png.svg';
+import ppt from './icons/ppt.svg';
+import psd from './icons/psd.svg';
+import txt from './icons/txt.svg';
+import upload from './icons/upload.svg';
+import video from './icons/video.svg';
+import white from './icons/white.svg';
+import word from './icons/word.svg';
+import zip from './icons/zip.svg';
+
+const FileTypeMapper: { [key: string]: any } = {
+    'png': png,
+    'bmp': png,
+    'jpg': jpg,
+    'gif': gif,
+    'psd': psd,
+    'ai': ai,
+    'svg': ai,
+    'pdf': pdf,
+    'txt': txt,
+    'ppt': ppt,
+    'pptx': ppt,
+    'mp4': video,
+    'flv': video,
+    'avi': video,
+    'mp3': audio,
+    'wav': audio,
+    'doc': word,
+    'docx': word,
+    'xls': excel,
+    'xlsx': excel,
+    'htm': html,
+    'html': html,
+    'zip': zip,
+    'rar': zip,
+    'upload': upload,
+    'download': download,
+    'folder': folder,
+    'folderGroup': folderGroup,
+    'folderOpen': folderOpen,
+    'folderLocked': folderLocked,
+    'folderPersonal': folderPersonal,
+    'folderShar': folderShare,
+    'unknown': white,
+};
+
+/** 文件图标 */
+const FileIcon: React.FC<{
+    size?: number;
+    style?: CSSProperties;
+    className?: string;
+    /** 类型 */
+    type?: string;
+}> = ({ size, style, className, type }) => {
+    const svgClassName = useEmotionCss(({ token }) => {
+        return {
+            width: size ?? token.fontSizeLG,
+            lineHeight: 0,
+        };
+    });
+
+    let fileType: string = type?.replaceAll('.', '')?.toLowerCase() ?? 'unknown';
+    if (!FileTypeMapper.hasOwnProperty(fileType)) {
+        fileType = 'unknown';
+    }
+
+    return (
+        <img src={FileTypeMapper[fileType]} className={`${svgClassName} ${className ?? ''}`} style={style} />
+    );
+}
+
+export default FileIcon;

+ 125 - 0
YBEE.EQM.Admin/src/components/FileLink/index.tsx

@@ -0,0 +1,125 @@
+import { DeleteOutlined } from "@ant-design/icons";
+import { useEmotionCss } from "@ant-design/use-emotion-css";
+import { Button, Tooltip } from "antd";
+import { useState } from "react";
+import FileIcon from "../FileIcon";
+
+const FileLink: React.FC<{
+    fileExtName: string;
+    fileName: string;
+    fileSize?: number | string;
+    url?: string;
+    thumbUrl?: string;
+    card?: boolean;
+    fileId?: string;
+    thumbFileId?: string;
+    onDownload?: () => Promise<void>;
+    onDelete?: () => Promise<void>;
+}> = ({ fileExtName, fileName, fileSize, url, thumbUrl, card, fileId, thumbFileId, onDownload, onDelete }) => {
+    const fileUrl = fileId ? `${AppConfig.fileViewRoot}?id=${fileId}` : url;
+    const fileThumbUrl = (thumbFileId && thumbFileId !== '0') ? `${AppConfig.fileViewRoot}?id=${thumbFileId}` : thumbUrl;
+
+    const [downloading, setDownloading] = useState(false);
+
+    const className = useEmotionCss(({ token }) => {
+        return {
+            width: '100%',
+            display: 'flex',
+            flexDirection: 'row',
+            alignItems: 'center',
+            paddingInline: token.paddingXXS,
+            borderRadius: token.borderRadiusSM,
+            '&.card': {
+                paddingBlock: token.paddingXXS,
+                borderWidth: 1,
+                borderColor: token.colorBorder,
+                borderStyle: 'solid',
+            },
+            '& .ant-btn-link': {
+                maxWidth: '100%',
+                overflow: 'hidden',
+            },
+            '& .ant-btn-link > span:hover': {
+                textDecoration: 'underline',
+            },
+            '& .size': {
+                flex: 1,
+                color: token.colorTextSecondary,
+                textAlign: 'right',
+                whiteSpace: 'nowrap',
+            },
+            '&:hover': {
+                backgroundColor: token.colorBgTextHover,
+                '& .delete': {
+                    opacity: 1,
+                },
+            },
+            '& .delete': {
+                opacity: 0.2,
+                marginLeft: token.marginXS,
+                color: token.colorTextSecondary,
+                '&:hover': {
+                    color: token.colorErrorActive,
+                },
+            },
+            '& .thumb': {
+                // marginLeft: token.marginXXS,
+                flexShrink: 0,
+                width: 48,
+                height: 40,
+                borderRadius: token.borderRadiusXS,
+                objectFit: 'cover',
+            },
+        };
+    });
+
+    const fsize = parseFloat(`${fileSize ?? 0}`);
+    let fsizeTxt = `${(fsize / 1024 / 1024).toFixed(2)} MB`;
+    if (fsize < 1024 * 1024) {
+        fsizeTxt = `${(fsize / 1024).toFixed(0)} KB`;
+    }
+
+    const handleClick = async () => {
+        if (onDownload) {
+            setDownloading(true);
+            try {
+                await onDownload();
+            }
+            catch { }
+            finally {
+                setDownloading(false);
+            }
+        } else if (fileUrl) {
+            window.open(fileUrl);
+        }
+    }
+
+    return (
+        <div className={`${className} ${card ? 'card' : ''}`}>
+            {!fileThumbUrl && <FileIcon type={fileExtName} />}
+            {fileThumbUrl &&
+                <img className="thumb" src={fileThumbUrl} onClick={handleClick} />
+            }
+            <Button type="link" size="small" disabled={downloading} onClick={handleClick} style={{ maxWidth: '100%', overflow: 'hidden' }}>
+                <Tooltip title={fileName}>
+                    <div style={{ maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{fileName}</div>
+                </Tooltip>
+            </Button>
+            <span className="size">{(fileSize && fileSize !== '0') ? fsizeTxt : null}</span>
+            {onDelete &&
+                <DeleteOutlined className="delete" onClick={async () => {
+                    setDownloading(true);
+                    try {
+                        await onDelete?.();
+                    }
+                    catch { }
+                    finally {
+                        setDownloading(false);
+                    }
+                }} />
+            }
+        </div>
+    );
+}
+
+export default FileLink;

+ 176 - 0
YBEE.EQM.Admin/src/components/FileUpload/index.tsx

@@ -0,0 +1,176 @@
+import { PlusOutlined } from "@ant-design/icons";
+import { useEmotionCss } from "@ant-design/use-emotion-css";
+import { App, Button, Progress, Tooltip, theme } from "antd";
+import { useRef, useState } from "react";
+import TextEllipsisMiddle from "../TextEllipsisMiddle";
+
+/** 列表式文件上传 */
+const FileUpload: React.FC<{
+    /** 添加文件按钮文本 */
+    addText?: string;
+    /** 上传错误重选文件按钮文本 */
+    reselectText?: string;
+    /** 上传错误重试按钮文本 */
+    retryText?: string;
+    /** 选择文件接受限制 */
+    accept?: string;
+    /** 提示文本 */
+    tipText?: string;
+    /** 限制大小MB */
+    limitSize?: number;
+    /** 上传处理回调 */
+    onUpload: (file: File, onUploadProgress: (progress: number) => void) => Promise<{ success: boolean; errorType?: 'fileTypeError'; errorMessage?: string; }>;
+}> = ({ addText, reselectText, retryText, accept, tipText, limitSize, onUpload }) => {
+    const [uploading, setUploading] = useState(false);
+    const [file, setFile] = useState<File>();
+    const [uploadStatus, setUploadStatus] = useState<'active' | 'exception' | 'success' | 'normal'>('normal');
+    const [progress, setProgress] = useState(0);
+    const [fileErrorMsg, setFileErrorMsg] = useState<string>();
+
+    const inputRef = useRef<HTMLInputElement>(null);
+    const { token } = theme.useToken();
+    const { message } = App.useApp();
+
+    const className = useEmotionCss(({ token }) => {
+        return {
+            width: '100%',
+            display: 'flex',
+            flexDirection: 'row',
+            alignItems: 'center',
+            paddingInline: token.paddingXXS,
+            borderRadius: token.borderRadiusSM,
+            '&.card': {
+                paddingBlock: token.paddingXXS,
+                borderWidth: 1,
+                borderColor: token.colorBorder,
+                borderStyle: 'dashed',
+            },
+            '& input[type="file"]': {
+                display: 'none',
+            },
+            '&:hover': {
+                backgroundColor: token.colorBgTextHover,
+                '& .delete': {
+                    opacity: 1,
+                },
+            },
+            '&.error': {
+                borderColor: token.colorError,
+            },
+            '& .delete': {
+                opacity: 0.2,
+                marginLeft: token.marginSM,
+                color: token.colorTextSecondary,
+                '&:hover': {
+                    color: token.colorErrorActive,
+                },
+            },
+            '& .loading': {
+                // marginLeft: token.marginXXS,
+                width: 48,
+                height: 40,
+                borderRadius: token.borderRadiusXS,
+                objectFit: 'cover',
+            },
+            '& .add': {
+                flex: 1,
+                display: 'flex',
+                flexDirection: 'column',
+                alignItems: 'center',
+                justifyContent: 'center',
+                '& .tip': {
+                    marginInline: token.marginXS,
+                    color: token.colorWarningText,
+                    fontSize: token.fontSize,
+                },
+            },
+            '& .uploading': {
+                // paddingLeft: token.paddingXS,
+                flex: 1,
+                display: 'flex',
+                flexDirection: 'column',
+                alignItems: 'stretch',
+                overflow: 'hidden',
+                '& .buttons': {
+                    display: 'flex',
+                    flexDirection: 'row',
+                    alignItems: 'center',
+                    justifyContent: 'space-between',
+                },
+            },
+        };
+    });
+
+    const handleChoiceFile = () => { inputRef.current?.click(); }
+    const handleUpload = async (f: File) => {
+        if (limitSize && f.size / 1024 / 1024 > limitSize) {
+            const femsg = `文件大小超出限制,最大${limitSize}MB`;
+            message.error(femsg);
+            setFileErrorMsg(femsg);
+            setUploadStatus('exception');
+            return;
+        }
+        try {
+            setUploading(true);
+            setUploadStatus('active');
+            const res = await onUpload?.(f, (p) => setProgress(p));
+            if (res.success) {
+                setUploadStatus('success');
+            }
+            else {
+                setUploadStatus('exception');
+                if (res.errorType === 'fileTypeError') {
+                    setFileErrorMsg(res.errorMessage);
+                }
+            }
+        }
+        catch {
+            setUploadStatus('exception');
+        }
+        finally {
+            setUploading(false);
+        }
+    }
+
+    return (
+        <Tooltip title={uploadStatus === 'exception' ? fileErrorMsg ?? '上传错误' : ''} color={token.colorError}>
+            <div className={`${className} card ${uploadStatus === 'exception' ? 'error' : ''}`}>
+                <input
+                    type="file"
+                    ref={inputRef}
+                    accept={accept}
+                    onChange={async (e) => {
+                        const f = e.target.files?.[0];
+                        if (!f) {
+                            return;
+                        }
+                        setFile(f);
+                        await handleUpload(f);
+                    }}
+                />
+                {!file &&
+                    <div className="add">
+                        <Button type="link" icon={<PlusOutlined />} disabled={uploading} onClick={handleChoiceFile}>
+                            {addText ?? '上传文件'}
+                        </Button>
+                        {(tipText || limitSize) && <span className="tip">{tipText}{limitSize && `(最大${limitSize}MB)`}</span>}
+                    </div>
+                }
+                {file &&
+                    <div className="uploading">
+                        <TextEllipsisMiddle suffixCount={3}>{file?.name ?? '未选择文件'}</TextEllipsisMiddle>
+                        <Progress style={{ width: uploadStatus === 'active' ? 'calc(100% - 12px)' : '100%' }} percent={progress} size="small" status={uploadStatus} />
+                        {uploadStatus === 'exception' &&
+                            <div className="buttons">
+                                <Button type="link" size="small" onClick={handleChoiceFile}>{reselectText ?? '重新选择文件'}</Button>
+                                <Button type="link" size="small" onClick={() => handleUpload(file)}>{retryText ?? '重试'}</Button>
+                            </div>
+                        }
+                    </div>
+                }
+            </div>
+        </Tooltip>
+    );
+}
+
+export default FileUpload;

+ 40 - 0
YBEE.EQM.Admin/src/components/Footer/index.tsx

@@ -0,0 +1,40 @@
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import React from 'react';
+import packageInfo from '../../../package.json';
+
+const Footer: React.FC = () => {
+    const className = useEmotionCss(({ token }) => {
+        return {
+            padding: token.paddingSM,
+            display: 'flex',
+            flexDirection: 'column',
+            alignItems: 'center',
+            justifyContent: 'center',
+            fontSize: token.fontSize,
+            '> p': {
+                fontSize: token.fontSize,
+                verticalAlign: 'middle',
+                color: token.colorTextTertiary,
+                padding: 0,
+                margin: 0,
+
+                'span': {
+                    padding: '0 8px',
+                    color: token.colorTextDisabled
+                }
+            },
+        };
+    });
+
+    return (
+        <div className={className}>
+            {/* <p>{AppConfig.companyFullName}</p> */}
+            {/* <p>© 2023-{new Date().getFullYear()} <span>|</span> v{packageInfo?.version ?? ''}</p> */}
+            <p>重庆国家应用数学中心大数据与最优化研究所</p>
+            <p>© 2023-{new Date().getFullYear()} <span>|</span> v{packageInfo?.version ?? ''}</p>
+        </div>
+    );
+    // return null;
+};
+
+export default Footer;

+ 29 - 0
YBEE.EQM.Admin/src/components/HeaderDropdown/index.tsx

@@ -0,0 +1,29 @@
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import { Dropdown } from 'antd';
+import type { DropDownProps } from 'antd/es/dropdown';
+import classNames from 'classnames';
+import React from 'react';
+
+export type HeaderDropdownProps = {
+    overlayClassName?: string;
+    placement?:
+        | 'bottomLeft'
+        | 'bottomRight'
+        | 'topLeft'
+        | 'topCenter'
+        | 'topRight'
+        | 'bottomCenter';
+} & Omit<DropDownProps, 'overlay'>;
+
+const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => {
+    const className = useEmotionCss(({ token }) => {
+        return {
+            [`@media screen and (max-width: ${token.screenXS})`]: {
+                width: '100%',
+            },
+        };
+    });
+    return <Dropdown overlayClassName={classNames(className, cls)} {...restProps} />;
+};
+
+export default HeaderDropdown;

+ 24 - 0
YBEE.EQM.Admin/src/components/IconFont/index.tsx

@@ -0,0 +1,24 @@
+import type { HTMLAttributes } from 'react';
+import React from 'react';
+
+type IconProps = HTMLAttributes<HTMLSpanElement> & {
+    icon?: string;
+};
+
+const IconFont: React.FC<IconProps> = (props) => {
+    const { icon, className, style, ...restProps } = props;
+    if (!icon) {
+        return null;
+    }
+    return (
+        <span
+            className={`iconfont icon-${icon} ${className ?? ''}`}
+            style={{
+                ...style,
+            }}
+            {...restProps}
+        />
+    );
+};
+
+export default IconFont;

+ 106 - 0
YBEE.EQM.Admin/src/components/JsonEditor/index.tsx

@@ -0,0 +1,106 @@
+import { ThemeMode } from '@/common/cache';
+import { MovableModal } from '@/components';
+import { isDarkTheme } from '@/layouts/RootLayout';
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import { Alert, message } from 'antd';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { Content, JSONEditor, Mode } from 'vanilla-jsoneditor';
+import 'vanilla-jsoneditor/themes/jse-theme-dark.css';
+
+export type JsonEditorModalProps = {
+    title: string;
+    data: string;
+    tooltip?: string;
+    onFinish: (json: string) => Promise<boolean>;
+    onClose?: () => void;
+}
+
+const JsonEditorModal: React.FC<JsonEditorModalProps> = ({ title, data, tooltip, onFinish, onClose }) => {
+    const [open, setOpen] = useState(true);
+    const handleClose = useCallback(() => { setOpen(false); setTimeout(() => onClose?.(), 300); }, []);
+
+    const editError = useRef(false);
+    const configRef = useRef(JSON.parse(data));
+    const editorRef = useRef(null);
+
+    const [messageApi, contextHolder] = message.useMessage();
+
+    useEffect(() => {
+        if (!editorRef.current) {
+            return;
+        }
+        let content: Content = {
+            text: undefined,
+            json: configRef.current,
+        }
+        new JSONEditor({
+            target: editorRef.current,
+            props: {
+                content,
+                mode: Mode.text,
+                mainMenuBar: false,
+                indentation: 4,
+                onChange: (updatedContent, previousContent, { contentErrors }) => {
+                    editError.current = !!contentErrors;
+                    configRef.current = updatedContent;
+                }
+            }
+        });
+    }, []);
+
+    const wrapperClassName = useEmotionCss(({ token }) => {
+        const radius = token.borderRadius;
+        return {
+            width: '100%',
+            height: 600,
+            '.jse-text-mode': {
+                borderTopLeftRadius: radius,
+                borderTopRightRadius: radius,
+                '.jse-contents': {
+                    borderTopLeftRadius: radius,
+                    borderTopRightRadius: radius,
+                },
+                '.jse-status-bar': {
+                    borderBottomLeftRadius: radius,
+                    borderBottomRightRadius: radius,
+                },
+            }
+        };
+    });
+
+    const handleOk = useCallback(async () => {
+        if (editError.current) {
+            messageApi.error("语法错误");
+            return;
+        }
+        const success = await onFinish(configRef.current.text);
+        if (!success) {
+            return;
+        }
+        handleClose();
+    }, []);
+
+    const tm = ThemeMode.get();
+    const isDark = tm?.themeMode === 'dark' || (tm?.themeMode === 'auto' && isDarkTheme.matches);
+
+    return (
+        <MovableModal
+            title={title}
+            width={960}
+            open={open}
+            centered={true}
+            maskClosable={false}
+            onCancel={handleClose}
+            onOk={handleOk}
+        >
+            {contextHolder}
+            {tooltip && <Alert type="warning" showIcon={true} message={tooltip} style={{ marginBottom: 8 }} closable />}
+            <div
+                ref={editorRef}
+                className={`${isDark ? 'jse-theme-dark' : ''} ${wrapperClassName}`}
+            />
+        </MovableModal >
+    );
+}
+
+export default JsonEditorModal;

+ 39 - 0
YBEE.EQM.Admin/src/components/MenuFooter/index.tsx

@@ -0,0 +1,39 @@
+import { MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons";
+import { useEmotionCss } from "@ant-design/use-emotion-css";
+import { Tooltip } from "antd";
+
+const MenuFooter: React.FC<{ collapsed: boolean; onClick: () => void; }> = ({ collapsed, onClick }) => {
+    const className = useEmotionCss(({ token }) => {
+        return {
+            paddingInline: token.paddingXS,
+            paddingBlock: token.paddingXS,
+            marginTop: token.marginXS,
+            marginBottom: -token.marginXS,
+            display: 'flex',
+            flexDirection: 'row',
+            alignItems: 'center',
+            justifyContent: 'center',
+            borderRadius: token.borderRadius,
+            cursor: 'pointer',
+            ':hover': {
+                backgroundColor: token.colorBgContainer,
+            },
+            '.menu-footer-text': {
+                paddingLeft: token.paddingXS,
+            },
+        };
+    });
+    return (
+        <Tooltip
+            placement="right"
+            title={collapsed ? null : '展开菜单'}
+        >
+            <div onClick={onClick} className={className}>
+                {collapsed ? <MenuFoldOutlined /> : <MenuUnfoldOutlined />}
+                {collapsed && <span className="menu-footer-text">收起菜单</span>}
+            </div>
+        </Tooltip>
+    );
+}
+
+export default MenuFooter;

+ 66 - 0
YBEE.EQM.Admin/src/components/MovableModal/index.tsx

@@ -0,0 +1,66 @@
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import type { ModalProps } from 'antd';
+import { Modal } from 'antd';
+import { useRef, useState } from 'react';
+import Draggable from 'react-draggable';
+
+/** 可移动模态窗口 */
+const MovableModal: React.FC<ModalProps> = (props) => {
+    const { title, className, ...restProps } = props;
+    const [disabled, setDisabled] = useState<boolean>(true);
+    const draggleRef = useRef<any>();
+    const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
+    const onStart = (event: any, uiData: any) => {
+        const { clientWidth, clientHeight } = window?.document?.documentElement;
+        const targetRect = draggleRef?.current?.getBoundingClientRect();
+        setBounds({
+            left: -targetRect?.left + uiData?.x,
+            right: clientWidth - (targetRect?.right - uiData?.x),
+            top: -targetRect?.top + uiData?.y,
+            bottom: clientHeight - (targetRect?.bottom - uiData?.y),
+        });
+    };
+
+    const titleClassName = useEmotionCss(({ token }) => {
+        return {
+            padding: `${token.padding}px ${token.paddingLG}px`,
+            marginTop: -token.marginMD,
+            marginLeft: -token.paddingLG,
+            marginRight: -token.marginLG,
+            ':hover': {
+                backgroundColor: 'rgba(0, 0, 0, 0.01)',
+                cursor: 'move',
+            },
+        };
+    });
+
+    return (
+        <Modal
+            className={className ?? ''}
+            title={
+                <div
+                    className={titleClassName}
+                    onMouseOver={() => disabled && setDisabled(false)}
+                    onMouseOut={() => setDisabled(true)}
+                >
+                    {title}
+                </div>
+            }
+            centered
+            {...restProps}
+            modalRender={(modal) => (
+                <Draggable
+                    disabled={disabled}
+                    bounds={bounds}
+                    onStart={(event, uiData) => onStart(event, uiData)}
+                >
+                    <div ref={draggleRef}>{modal}</div>
+                </Draggable>
+            )}
+        >
+            {props.children}
+        </Modal>
+    );
+};
+
+export default MovableModal;

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels