chenlei 9 ay önce
ebeveyn
işleme
08229e7dc9
45 değiştirilmiş dosya ile 2680 ekleme ve 49 silme
  1. 2 1
      package.json
  2. 427 4
      pnpm-lock.yaml
  3. 65 3
      src/App.scss
  4. 5 0
      src/assets/icons/icon_reset.svg
  5. 5 0
      src/assets/icons/icon_search.svg
  6. 21 0
      src/components/AddIndexModal/index.module.scss
  7. 106 0
      src/components/AddIndexModal/index.tsx
  8. 73 0
      src/components/AddIndexTemplateModal/index.tsx
  9. 129 0
      src/components/AddRoleModal/index.tsx
  10. 32 0
      src/components/FileTemplateModal/constants.ts
  11. 21 0
      src/components/FileTemplateModal/index.module.scss
  12. 180 0
      src/components/FileTemplateModal/index.tsx
  13. 11 0
      src/components/FileTemplateTable/index.module.scss
  14. 115 0
      src/components/FileTemplateTable/index.tsx
  15. 5 0
      src/components/FormPageFooter/index.scss
  16. 4 1
      src/components/FormPageFooter/index.tsx
  17. 4 2
      src/components/PageContainer/index.tsx
  18. 39 0
      src/components/Search/index.module.scss
  19. 41 0
      src/components/Search/index.tsx
  20. 6 0
      src/components/index.ts
  21. 10 0
      src/pages/Assessment/Index/CreateOrEdit/index.module.scss
  22. 14 33
      src/pages/Assessment/Index/CreateOrEdit/index.tsx
  23. 0 0
      src/pages/Assessment/Template/CreateOrEdit/index.module.scss
  24. 53 0
      src/pages/Assessment/Template/CreateOrEdit/index.tsx
  25. 10 0
      src/pages/Assessment/Template/constants.ts
  26. 32 0
      src/pages/Assessment/Template/index.module.scss
  27. 93 0
      src/pages/Assessment/Template/index.tsx
  28. 10 0
      src/pages/AssessmentDetail/components/OverallAssessment/index.module.scss
  29. 44 0
      src/pages/AssessmentDetail/components/OverallAssessment/index.tsx
  30. 52 0
      src/pages/AssessmentDetail/index.module.scss
  31. 65 0
      src/pages/AssessmentDetail/index.tsx
  32. 26 5
      src/pages/Layout/index.tsx
  33. 21 0
      src/pages/Management/Index/CreateOrEdit/index.module.scss
  34. 235 0
      src/pages/Management/Index/CreateOrEdit/index.tsx
  35. 177 0
      src/pages/Management/Index/SettingIndex/index.tsx
  36. 35 0
      src/pages/Management/Index/SettingRole/components/Pane/index.module.scss
  37. 53 0
      src/pages/Management/Index/SettingRole/components/Pane/index.tsx
  38. 94 0
      src/pages/Management/Index/SettingRole/index.tsx
  39. 7 0
      src/pages/Management/Index/index.module.scss
  40. 214 0
      src/pages/Management/Index/index.tsx
  41. 38 0
      src/pages/Management/constants.ts
  42. 19 0
      src/pages/Management/types.ts
  43. 64 0
      src/router/index.tsx
  44. 4 0
      src/router/types.ts
  45. 19 0
      src/utils/index.ts

+ 2 - 1
package.json

@@ -4,8 +4,9 @@
   "private": true,
   "dependencies": {
     "@ant-design/icons": "^5.1.4",
+    "@ant-design/pro-components": "^2.8.1",
     "@babel/core": "^7.16.0",
-    "@dage/pc-components": "^1.3.2",
+    "@dage/pc-components": "^1.3.3",
     "@dage/service": "^1.0.3",
     "@dage/utils": "^1.0.2",
     "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",

+ 427 - 4
pnpm-lock.yaml

@@ -8,12 +8,15 @@ dependencies:
   '@ant-design/icons':
     specifier: ^5.1.4
     version: 5.5.1(react-dom@18.3.1)(react@18.3.1)
+  '@ant-design/pro-components':
+    specifier: ^2.8.1
+    version: 2.8.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1)
   '@babel/core':
     specifier: ^7.16.0
     version: 7.26.0
   '@dage/pc-components':
-    specifier: ^1.3.2
-    version: 1.3.2(@ant-design/icons@5.5.1)(@wangeditor/core@1.1.19)(antd@5.21.6)(lodash@4.17.21)(react-dom@18.3.1)(react@18.3.1)
+    specifier: ^1.3.3
+    version: 1.3.3(@ant-design/icons@5.5.1)(@wangeditor/core@1.1.19)(antd@5.21.6)(lodash@4.17.21)(react-dom@18.3.1)(react@18.3.1)
   '@dage/service':
     specifier: ^1.0.3
     version: 1.0.4(lodash@4.17.21)
@@ -325,6 +328,264 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
+  /@ant-design/pro-card@2.9.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-PG2kKlCDOeyRN8VbCi7lRDRUZz3lOlrGat7qqFqMHShwQKQTYF6UOHlCzUpOTdlCSEI2zEgPqzOJgYgUeT10GQ==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+    dependencies:
+      '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/icons': 5.5.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-provider': 2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-utils': 2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      classnames: 2.5.1
+      omit.js: 2.0.2
+      rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1)
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+    transitivePeerDependencies:
+      - react-dom
+    dev: false
+
+  /@ant-design/pro-components@2.8.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-gV4tr9zAfUvIJ+Z8s6k8n56H7X2ZhZNd+BMXE4dKkB7pM1FkG7UQCu6kghMz6MEjbqAjA9YRONdpQoYxfEZ1Ew==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+    dependencies:
+      '@ant-design/pro-card': 2.9.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-descriptions': 2.6.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-field': 2.17.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-form': 2.31.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-layout': 7.21.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-list': 2.6.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-provider': 2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-skeleton': 2.2.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-table': 3.18.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-utils': 2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+    transitivePeerDependencies:
+      - rc-field-form
+    dev: false
+
+  /@ant-design/pro-descriptions@2.6.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-bv73XJnh1sR78m7qJHFh+ca3WVY1rRcsUy9hhNbXfmYYBgWujQ25Idpq6PIEjdU1gN4YRo8OUZz4qec0+c6hAA==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+    dependencies:
+      '@ant-design/pro-field': 2.17.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-form': 2.31.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-provider': 2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-skeleton': 2.2.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-utils': 2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      rc-resize-observer: 0.2.6(react-dom@18.3.1)(react@18.3.1)
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+    transitivePeerDependencies:
+      - rc-field-form
+      - react-dom
+    dev: false
+
+  /@ant-design/pro-field@2.17.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-FndQNIj30fiDVeHcEWn5F+ePTqvxIcr1BOzYwRT39C1Xv5IKL9N0YkBwdUax4anP9UFFTwZbhwiJKzUQf5mNxg==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+    dependencies:
+      '@ant-design/icons': 5.5.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-provider': 2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-utils': 2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      '@chenshuai2144/sketch-color': 1.0.9(react@18.3.1)
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      omit.js: 2.0.2
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      swr: 2.2.5(react@18.3.1)
+    transitivePeerDependencies:
+      - react-dom
+    dev: false
+
+  /@ant-design/pro-form@2.31.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-k5L48GsS773UJUrUYdLWeVM1Iw21SpXklpxeHa2i9GJUVoLyVglrlZkke0xdJieozqGWpfn4bbpxXmvIZIu5lQ==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      rc-field-form: '>=1.22.0'
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+    dependencies:
+      '@ant-design/icons': 5.5.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-field': 2.17.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-provider': 2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-utils': 2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      '@chenshuai2144/sketch-color': 1.0.9(react@18.3.1)
+      '@umijs/use-params': 1.0.9(react@18.3.1)
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      omit.js: 2.0.2
+      rc-field-form: 2.4.0(react-dom@18.3.1)(react@18.3.1)
+      rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1)
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+    dev: false
+
+  /@ant-design/pro-layout@7.21.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-9B+MBPTYZbhe5xdUxgfikoUZtB/77R3hAve8iI4fVNBoc4KAm/wpejasvKgWZn/8gRht8KOXfmfe2ngjz/BWOA==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+    dependencies:
+      '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/icons': 5.5.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-provider': 2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-utils': 2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      '@umijs/route-utils': 4.0.1
+      '@umijs/use-params': 1.0.9(react@18.3.1)
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      classnames: 2.5.1
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      omit.js: 2.0.2
+      path-to-regexp: 8.0.0
+      rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1)
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      swr: 2.2.5(react@18.3.1)
+      warning: 4.0.3
+    dev: false
+
+  /@ant-design/pro-list@2.6.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-72bhe1WsLLld4gGrv6i/GPtOKVxBPVZshtPJJM1Bi8up4df07/ySxNe3b+cZqiPTFstsQYJAPBNdzz89lf5wog==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+    dependencies:
+      '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/icons': 5.5.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-card': 2.9.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-field': 2.17.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-table': 3.18.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-utils': 2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1)
+      rc-util: 4.21.1
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+    transitivePeerDependencies:
+      - rc-field-form
+    dev: false
+
+  /@ant-design/pro-provider@2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-UiNhQ3Yl+h8liC24LLWpqbcdhC9ggiXvHeCYrqBHjOx2WNFWpWO5JP0327k3RQjShprt+jZQ/cdNHx6s7BToXA==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+    dependencies:
+      '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      '@ctrl/tinycolor': 3.6.1
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      dayjs: 1.11.13
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      swr: 2.2.5(react@18.3.1)
+    dev: false
+
+  /@ant-design/pro-skeleton@2.2.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-3M2jNOZQZWEDR8pheY00OkHREfb0rquvFZLCa6DypGmiksiuuYuR9Y4iA82ZF+mva2FmpHekdwbje/GpbxqBeg==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+    dependencies:
+      '@babel/runtime': 7.26.0
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+    dev: false
+
+  /@ant-design/pro-table@3.18.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-w7xRBdXl3PFAQ5WiWIebFAxSjAnw/dwwu3gHzu956zl9dLAC3XysBbUgyS7pSrB0mSZJqv5YEZK8/t2xnOqfFg==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      rc-field-form: '>=1.22.0'
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+    dependencies:
+      '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/icons': 5.5.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-card': 2.9.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-field': 2.17.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-form': 2.31.1(antd@5.21.6)(rc-field-form@2.4.0)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-provider': 2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-utils': 2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      '@dnd-kit/core': 6.1.0(react-dom@18.3.1)(react@18.3.1)
+      '@dnd-kit/modifiers': 6.0.1(@dnd-kit/core@6.1.0)(react@18.3.1)
+      '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.1.0)(react@18.3.1)
+      '@dnd-kit/utilities': 3.2.2(react@18.3.1)
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      omit.js: 2.0.2
+      rc-field-form: 2.4.0(react-dom@18.3.1)(react@18.3.1)
+      rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1)
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+    dev: false
+
+  /@ant-design/pro-utils@2.16.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-GaBEgP638fvddO0YqevuIT3O8pQIrqfHhlQ4UDnf1JkhVxuVxP3XdwDituQWnyRoxLPSjNBZBOBghBBMb8nWsg==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+    dependencies:
+      '@ant-design/icons': 5.5.1(react-dom@18.3.1)(react@18.3.1)
+      '@ant-design/pro-provider': 2.15.1(antd@5.21.6)(react-dom@18.3.1)(react@18.3.1)
+      '@babel/runtime': 7.26.0
+      antd: 5.21.6(react-dom@18.3.1)(react@18.3.1)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      safe-stable-stringify: 2.5.0
+      swr: 2.2.5(react@18.3.1)
+    dev: false
+
   /@ant-design/react-slick@1.1.2(react@18.3.1):
     resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==}
     peerDependencies:
@@ -1821,6 +2082,16 @@ packages:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: false
 
+  /@chenshuai2144/sketch-color@1.0.9(react@18.3.1):
+    resolution: {integrity: sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w==}
+    peerDependencies:
+      react: '>=16.12.0'
+    dependencies:
+      react: 18.3.1
+      reactcss: 1.2.3(react@18.3.1)
+      tinycolor2: 1.6.0
+    dev: false
+
   /@csstools/normalize.css@12.1.1:
     resolution: {integrity: sha512-YAYeJ+Xqh7fUou1d1j9XHl44BmsuThiTr4iNrgCQ3J27IbhXsxXDGZ1cXv8Qvs99d4rBbLiSKy3+WZiet32PcQ==}
     dev: false
@@ -1987,8 +2258,8 @@ packages:
     resolution: {integrity: sha512-VHNVJbY5gAMvqur7pOmxZ8W9l4LRnwK/OqMIuAt4VLXLkldUTyyfJmWRkPpCdHDWNbn7bATgAA/+ziV01rozDA==}
     dev: false
 
-  /@dage/pc-components@1.3.2(@ant-design/icons@5.5.1)(@wangeditor/core@1.1.19)(antd@5.21.6)(lodash@4.17.21)(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-WxkPxz7EVnuWNxyEva00upBe+RCygVf3ev4JqkwM8ZAVlHv4r36/pAldQ4euYbP48u6YbtCU8APFLXknEjJKuQ==}
+  /@dage/pc-components@1.3.3(@ant-design/icons@5.5.1)(@wangeditor/core@1.1.19)(antd@5.21.6)(lodash@4.17.21)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-J0vxKZiRM8iEZqfYWmYKIKMoR0PxxvPNPCf/t1MpS9nTKtfHyK8NMcjEht9DV+Y4gwUg1SNlLKAIZhTaX6aXNQ==}
     peerDependencies:
       '@ant-design/icons': 5.*
       antd: 5.*
@@ -2031,6 +2302,61 @@ packages:
       query-string: 8.2.0
     dev: false
 
+  /@dnd-kit/accessibility@3.1.0(react@18.3.1):
+    resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==}
+    peerDependencies:
+      react: '>=16.8.0'
+    dependencies:
+      react: 18.3.1
+      tslib: 2.8.1
+    dev: false
+
+  /@dnd-kit/core@6.1.0(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==}
+    peerDependencies:
+      react: '>=16.8.0'
+      react-dom: '>=16.8.0'
+    dependencies:
+      '@dnd-kit/accessibility': 3.1.0(react@18.3.1)
+      '@dnd-kit/utilities': 3.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      tslib: 2.8.1
+    dev: false
+
+  /@dnd-kit/modifiers@6.0.1(@dnd-kit/core@6.1.0)(react@18.3.1):
+    resolution: {integrity: sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==}
+    peerDependencies:
+      '@dnd-kit/core': ^6.0.6
+      react: '>=16.8.0'
+    dependencies:
+      '@dnd-kit/core': 6.1.0(react-dom@18.3.1)(react@18.3.1)
+      '@dnd-kit/utilities': 3.2.2(react@18.3.1)
+      react: 18.3.1
+      tslib: 2.8.1
+    dev: false
+
+  /@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.1.0)(react@18.3.1):
+    resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==}
+    peerDependencies:
+      '@dnd-kit/core': ^6.0.7
+      react: '>=16.8.0'
+    dependencies:
+      '@dnd-kit/core': 6.1.0(react-dom@18.3.1)(react@18.3.1)
+      '@dnd-kit/utilities': 3.2.2(react@18.3.1)
+      react: 18.3.1
+      tslib: 2.8.1
+    dev: false
+
+  /@dnd-kit/utilities@3.2.2(react@18.3.1):
+    resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
+    peerDependencies:
+      react: '>=16.8.0'
+    dependencies:
+      react: 18.3.1
+      tslib: 2.8.1
+    dev: false
+
   /@emotion/hash@0.8.0:
     resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
     dev: false
@@ -3474,6 +3800,18 @@ packages:
       eslint-visitor-keys: 3.4.3
     dev: false
 
+  /@umijs/route-utils@4.0.1:
+    resolution: {integrity: sha512-+1ixf1BTOLuH+ORb4x8vYMPeIt38n9q0fJDwhv9nSxrV46mxbLF0nmELIo9CKQB2gHfuC4+hww6xejJ6VYnBHQ==}
+    dev: false
+
+  /@umijs/use-params@1.0.9(react@18.3.1):
+    resolution: {integrity: sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w==}
+    peerDependencies:
+      react: '*'
+    dependencies:
+      react: 18.3.1
+    dev: false
+
   /@ungap/structured-clone@1.2.0:
     resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
     dev: false
@@ -3868,6 +4206,12 @@ packages:
     hasBin: true
     dev: false
 
+  /add-dom-event-listener@1.1.0:
+    resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==}
+    dependencies:
+      object-assign: 4.1.1
+    dev: false
+
   /address@1.2.2:
     resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==}
     engines: {node: '>= 10.0.0'}
@@ -4706,6 +5050,10 @@ packages:
       source-map: 0.6.1
     dev: false
 
+  /client-only@0.0.1:
+    resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+    dev: false
+
   /cliui@7.0.4:
     resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
     dependencies:
@@ -8221,6 +8569,10 @@ packages:
       p-locate: 5.0.0
     dev: false
 
+  /lodash-es@4.17.21:
+    resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+    dev: false
+
   /lodash.camelcase@4.3.0:
     resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
     dev: false
@@ -8672,6 +9024,10 @@ packages:
     resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
     dev: false
 
+  /omit.js@2.0.2:
+    resolution: {integrity: sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg==}
+    dev: false
+
   /on-finished@2.4.1:
     resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
     engines: {node: '>= 0.8'}
@@ -8857,6 +9213,11 @@ packages:
     resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
     dev: false
 
+  /path-to-regexp@8.0.0:
+    resolution: {integrity: sha512-GAWaqWlTjYK/7SVpIUA6CTxmcg65SP30sbjdCvyYReosRkk7Z/LyHWwkK3Vu0FcIi0FNTADUs4eh1AsU5s10cg==}
+    engines: {node: '>=16'}
+    dev: false
+
   /path-type@4.0.0:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
@@ -10136,6 +10497,20 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
+  /rc-resize-observer@0.2.6(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA==}
+    peerDependencies:
+      react: '>=16.9.0'
+      react-dom: '>=16.9.0'
+    dependencies:
+      '@babel/runtime': 7.26.0
+      classnames: 2.5.1
+      rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      resize-observer-polyfill: 1.5.1
+    dev: false
+
   /rc-resize-observer@1.4.0(react-dom@18.3.1)(react@18.3.1):
     resolution: {integrity: sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q==}
     peerDependencies:
@@ -10330,6 +10705,16 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
+  /rc-util@4.21.1:
+    resolution: {integrity: sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==}
+    dependencies:
+      add-dom-event-listener: 1.1.0
+      prop-types: 15.8.1
+      react-is: 16.13.1
+      react-lifecycles-compat: 3.0.4
+      shallowequal: 1.1.0
+    dev: false
+
   /rc-util@5.43.0(react-dom@18.3.1)(react@18.3.1):
     resolution: {integrity: sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==}
     peerDependencies:
@@ -10437,6 +10822,10 @@ packages:
     resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
     dev: false
 
+  /react-lifecycles-compat@3.0.4:
+    resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
+    dev: false
+
   /react-redux@8.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)(redux@4.2.1):
     resolution: {integrity: sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==}
     peerDependencies:
@@ -10506,6 +10895,15 @@ packages:
       loose-envify: 1.4.0
     dev: false
 
+  /reactcss@1.2.3(react@18.3.1):
+    resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==}
+    peerDependencies:
+      react: '*'
+    dependencies:
+      lodash: 4.17.21
+      react: 18.3.1
+    dev: false
+
   /read-cache@1.0.0:
     resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
     dependencies:
@@ -10821,6 +11219,11 @@ packages:
       is-regex: 1.1.4
     dev: false
 
+  /safe-stable-stringify@2.5.0:
+    resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+    engines: {node: '>=10'}
+    dev: false
+
   /safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
     dev: false
@@ -11557,6 +11960,16 @@ packages:
       stable: 0.1.8
     dev: false
 
+  /swr@2.2.5(react@18.3.1):
+    resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==}
+    peerDependencies:
+      react: ^16.11.0 || ^17.0.0 || ^18.0.0
+    dependencies:
+      client-only: 0.0.1
+      react: 18.3.1
+      use-sync-external-store: 1.2.2(react@18.3.1)
+    dev: false
+
   /symbol-tree@3.2.4:
     resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
     dev: false
@@ -11703,6 +12116,10 @@ packages:
     resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
     dev: false
 
+  /tinycolor2@1.6.0:
+    resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
+    dev: false
+
   /tmpl@1.0.5:
     resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
     dev: false
@@ -12046,6 +12463,12 @@ packages:
       makeerror: 1.0.12
     dev: false
 
+  /warning@4.0.3:
+    resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
+    dependencies:
+      loose-envify: 1.4.0
+    dev: false
+
   /watchpack@2.4.2:
     resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
     engines: {node: '>=10.13.0'}

+ 65 - 3
src/App.scss

@@ -77,6 +77,10 @@ body {
   max-width: 650px;
 }
 
+.mw1125 {
+  max-width: 1125px;
+}
+
 * {
   box-sizing: border-box;
 }
@@ -105,18 +109,76 @@ body {
   margin-top: 20px;
 }
 
-.cus-table {
+.custom-pro-table {
   .ant-table-content {
     .ant-table-thead {
       .ant-table-cell {
-        background: rgba(46, 175, 255, 0.15);
+        background: #e0f3ff;
       }
     }
     .ant-table-cell {
-      padding: 6px 16px;
+      padding: 6px 16px !important;
     }
+  }
+}
+
+.cus-table {
+  &.gray {
     .ant-table-tbody {
       background: rgba(217, 217, 217, 0.1);
     }
   }
+  .ant-table-content {
+    .ant-table-thead {
+      .ant-table-cell {
+        background: #e0f3ff;
+      }
+    }
+    .ant-table-cell {
+      padding: 6px 16px;
+    }
+  }
+  .ant-table-container table > thead > tr:first-child > *:first-child {
+    border-start-start-radius: 0;
+  }
+  .ant-table-container table > thead > tr:first-child > *:last-child {
+    border-start-end-radius: 0;
+  }
+}
+
+.inline-form {
+  gap: 15px;
+
+  .ant-form-item {
+    margin-inline-end: 0;
+  }
+}
+
+.primary-button {
+  font-size: 12px;
+
+  & + .ant-btn {
+    margin-left: 15px;
+  }
+}
+
+.second-button {
+  font-size: 12px;
+  background: var(--second-color);
+
+  & + .ant-btn {
+    margin-left: 15px;
+  }
+}
+
+.ant-btn-lg.ant-btn {
+  font-size: 14px;
+}
+
+.ant-btn {
+  border-radius: 0;
+}
+
+.ant-pro-card .ant-pro-card-body {
+  padding-inline: 0;
 }

Dosya farkı çok büyük olduğundan ihmal edildi
+ 5 - 0
src/assets/icons/icon_reset.svg


Dosya farkı çok büyük olduğundan ihmal edildi
+ 5 - 0
src/assets/icons/icon_search.svg


+ 21 - 0
src/components/AddIndexModal/index.module.scss

@@ -0,0 +1,21 @@
+.modal {
+  :global {
+    .ant-form-item-label > label {
+      white-space: wrap;
+      height: auto !important;
+      min-height: 32px;
+    }
+    .ant-radio-button-wrapper {
+      font-size: 14px;
+    }
+    .ant-modal-footer {
+      text-align: center;
+
+      .ant-btn {
+        border-radius: 0;
+        width: 90px;
+        height: 40px;
+      }
+    }
+  }
+}

+ 106 - 0
src/components/AddIndexModal/index.tsx

@@ -0,0 +1,106 @@
+import { FC, Key, useEffect, useState } from "react";
+import { Form, Modal, ModalProps, Tree } from "antd";
+import { getSelectedNodes } from "@/utils";
+import style from "./index.module.scss";
+
+export interface AddIndexModalProps extends Omit<ModalProps, "onOk"> {
+  checkedKeys: Key[];
+  onCancel?: () => void;
+  onOk?: (keys: Key[], items: any) => void;
+}
+
+const treeData: any[] = [
+  {
+    name: "0-0",
+    id: "0-0",
+    children: [
+      {
+        name: "0-0-0",
+        id: "0-0-0",
+        children: [
+          { id: "0-0-0-0", level: 1, name: "指标名称1" },
+          { id: "0-0-0-1", level: 1, name: "指标名称2" },
+          { id: "0-0-0-2", level: 1, name: "指标名称3" },
+        ],
+      },
+    ],
+  },
+];
+
+export const AddIndexModal: FC<AddIndexModalProps> = ({
+  open,
+  checkedKeys,
+  onOk,
+  onCancel,
+  ...rest
+}) => {
+  const [form] = Form.useForm<any>();
+  const [_checkedKeys, setCheckedKeys] = useState<Key[]>([]);
+
+  const handleCancel = () => {
+    form.resetFields();
+    onCancel?.();
+  };
+
+  const handleConfirm = () => {
+    form.submit();
+  };
+
+  const handleSubmit = async (values: any) => {
+    onOk?.(values.checkedKeys, getSelectedNodes(values.checkedKeys, treeData));
+    handleCancel();
+  };
+
+  useEffect(() => {
+    if (open) {
+      setCheckedKeys(checkedKeys);
+    }
+  }, [open, checkedKeys]);
+
+  return (
+    <Modal
+      className={style.modal}
+      title="新增指标"
+      okText="提交"
+      cancelText="取消"
+      open={open}
+      width={640}
+      okButtonProps={{
+        disabled: false,
+      }}
+      onOk={handleConfirm}
+      onCancel={handleCancel}
+      {...rest}
+    >
+      <Form
+        labelCol={{ span: 4, offset: 2 }}
+        form={form}
+        onFinish={handleSubmit}
+      >
+        <Form.Item
+          label="采用指标"
+          name="checkedKeys"
+          required
+          rules={[{ required: true, message: "请选择指标" }]}
+        >
+          <Tree
+            checkable
+            defaultExpandAll
+            fieldNames={{
+              key: "id",
+              title: "name",
+            }}
+            checkedKeys={_checkedKeys}
+            treeData={treeData}
+            onCheck={(checkedKeys) => {
+              setCheckedKeys(checkedKeys as Key[]);
+              form.setFieldsValue({
+                checkedKeys,
+              });
+            }}
+          />
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+};

+ 73 - 0
src/components/AddIndexTemplateModal/index.tsx

@@ -0,0 +1,73 @@
+import { FC, useRef } from "react";
+import { Modal, ModalProps, Table } from "antd";
+import style from "../AddIndexModal/index.module.scss";
+
+export interface AddIndexTemplateModalProps extends Omit<ModalProps, "onOk"> {
+  onCancel?: () => void;
+  onOk?: (rows: any) => void;
+}
+
+const data = [
+  {
+    id: 1,
+    name: "name",
+    description: "description",
+  },
+];
+
+export const AddIndexTemplateModal: FC<AddIndexTemplateModalProps> = ({
+  open,
+  onOk,
+  onCancel,
+  ...rest
+}) => {
+  const selectedRows = useRef<null | typeof data>(null);
+
+  const handleCancel = () => {
+    onCancel?.();
+  };
+
+  const handleConfirm = () => {
+    onOk?.(selectedRows);
+    onCancel?.();
+  };
+
+  return (
+    <Modal
+      className={style.modal}
+      title="从模板中添加"
+      okText="提交"
+      cancelText="取消"
+      open={open}
+      width={640}
+      okButtonProps={{
+        disabled: false,
+      }}
+      onOk={handleConfirm}
+      onCancel={handleCancel}
+      {...rest}
+    >
+      <Table
+        className="cus-table"
+        rowSelection={{
+          type: "radio",
+          onChange: (selectedRowKeys, _selectedRows) => {
+            selectedRows.current = _selectedRows;
+          },
+        }}
+        dataSource={data}
+        rowKey="id"
+        columns={[
+          {
+            title: "模板名称",
+            dataIndex: "name",
+          },
+          {
+            title: "说明",
+            dataIndex: "description",
+          },
+        ]}
+      />
+    </Modal>
+  );
+};

+ 129 - 0
src/components/AddRoleModal/index.tsx

@@ -0,0 +1,129 @@
+import { FC } from "react";
+import { Form, Input, Modal, ModalProps, Radio, Select } from "antd";
+import style from "../AddIndexModal/index.module.scss";
+
+export interface AddRoleModalProps extends Omit<ModalProps, "onOk"> {
+  onCancel?: () => void;
+  onOk?: () => void;
+}
+
+export const MUSEUM_LEVEL_LIST = [
+  {
+    label: "一级",
+    value: 1,
+  },
+  {
+    label: "二级",
+    value: 2,
+  },
+  {
+    label: "三级",
+    value: 3,
+  },
+];
+
+const { TextArea } = Input;
+
+export const AddRoleModal: FC<AddRoleModalProps> = ({
+  open,
+  onOk,
+  onCancel,
+  ...rest
+}) => {
+  const [form] = Form.useForm<any>();
+
+  const handleCancel = () => {
+    form.resetFields();
+    onCancel?.();
+  };
+
+  const handleConfirm = () => {
+    form.submit();
+  };
+
+  const handleSubmit = async (values: any) => {
+    onOk?.();
+    handleCancel();
+  };
+
+  return (
+    <Modal
+      className={style.modal}
+      title="新增角色"
+      okText="提交"
+      cancelText="取消"
+      open={open}
+      width={640}
+      okButtonProps={{
+        disabled: false,
+      }}
+      onOk={handleConfirm}
+      onCancel={handleCancel}
+      {...rest}
+    >
+      <Form
+        labelCol={{ span: 4, offset: 2 }}
+        form={form}
+        onFinish={handleSubmit}
+      >
+        <Form.Item
+          label="部门名称"
+          name="name"
+          required
+          rules={[{ required: true, message: "请输入部门名称" }]}
+        >
+          <Input placeholder="请输入内容,最多20字" maxLength={20} />
+        </Form.Item>
+        <Form.Item
+          label="博物馆级别"
+          name="level"
+          required
+          rules={[{ required: true, message: "请选择博物馆级别" }]}
+        >
+          <Radio.Group
+            options={MUSEUM_LEVEL_LIST}
+            optionType="button"
+            buttonStyle="solid"
+          />
+        </Form.Item>
+        <Form.Item label="组别说明" name="description">
+          <TextArea
+            rows={4}
+            placeholder="请输入内容,最多200字"
+            maxLength={200}
+          />
+        </Form.Item>
+        <Form.Item
+          label="部门主管"
+          required
+          rules={[{ required: true, message: "请选择部门主管" }]}
+        >
+          <Select mode="multiple" placeholder="请选择" />
+        </Form.Item>
+        <Form.Item label="部门成员">
+          <Select mode="multiple" placeholder="请选择" />
+        </Form.Item>
+        <Form.Item label="自评权限" required>
+          <Form.Item noStyle name="rating" initialValue={0}>
+            <Radio.Group
+              options={[
+                {
+                  label: "是",
+                  value: 1,
+                },
+                {
+                  label: "否",
+                  value: 0,
+                },
+              ]}
+            />
+          </Form.Item>
+
+          <span style={{ color: "rgba(36, 36, 36, 0.4)" }}>
+            当选择是时,允许考核部门自评分数
+          </span>
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+};

+ 32 - 0
src/components/FileTemplateModal/constants.ts

@@ -0,0 +1,32 @@
+export const FILE_TYPE_ENUM = [
+  ".jpg",
+  ".gif",
+  ".png",
+  ".ico",
+  ".bmp",
+  ".jpeg",
+  ".zip",
+  ".zp",
+  ".rar",
+  ".mp3",
+  ".mp4",
+  ".avi",
+  ".mov",
+  ".flv",
+  ".3gp",
+  ".rmvb",
+  ".4dage",
+  ".wav",
+  ".ma",
+  ".obj",
+  ".pdf",
+  ".audio",
+  ".pt",
+  ".ptx",
+  ".xls",
+  ".xlsx",
+  ".doc",
+  ".docx",
+  ".txt",
+  ".xmind",
+];

+ 21 - 0
src/components/FileTemplateModal/index.module.scss

@@ -0,0 +1,21 @@
+.modal {
+  :global {
+    .ant-form-item-label > label {
+      white-space: wrap;
+      height: auto !important;
+      min-height: 32px;
+    }
+    .ant-radio-button-wrapper {
+      font-size: 14px;
+    }
+    .ant-modal-footer {
+      text-align: center;
+
+      .ant-btn {
+        border-radius: 0;
+        width: 90px;
+        height: 40px;
+      }
+    }
+  }
+}

+ 180 - 0
src/components/FileTemplateModal/index.tsx

@@ -0,0 +1,180 @@
+import {
+  Checkbox,
+  CheckboxProps,
+  Form,
+  Input,
+  Modal,
+  ModalProps,
+  Radio,
+} from "antd";
+import { FC, useEffect, useMemo, useState } from "react";
+import style from "./index.module.scss";
+import {
+  DageFileResponseType,
+  DageUpload,
+  DageUploadConsumer,
+  DageUploadProvider,
+  DageUploadType,
+} from "@dage/pc-components";
+import { uniqueId } from "lodash";
+import { FILE_TYPE_ENUM } from "./constants";
+
+export interface FileTemplateForm {
+  id: string;
+  name: string;
+  required: number;
+  file?: DageFileResponseType[];
+  types?: string[];
+}
+
+export interface FileTemplateModalProps extends ModalProps {
+  item?: FileTemplateForm | null;
+  onCancel?: () => void;
+  onOk?: (val: any, isEdit?: boolean) => void;
+}
+
+export const FileTemplateModal: FC<FileTemplateModalProps> = ({
+  item,
+  open,
+  onOk,
+  onCancel,
+  ...rest
+}) => {
+  const [form] = Form.useForm<FileTemplateForm>();
+  const [fields, setFields] = useState<any[]>([
+    {
+      name: "required",
+      value: 0,
+    },
+  ]);
+  const typesVal = Form.useWatch("types", form);
+
+  const checkAll = useMemo(
+    () => FILE_TYPE_ENUM.length === typesVal?.length,
+    [typesVal]
+  );
+  const indeterminate = useMemo(
+    () =>
+      (typesVal?.length || 0) > 0 &&
+      (typesVal?.length || 0) < FILE_TYPE_ENUM.length,
+    [typesVal]
+  );
+
+  const handleCancel = () => {
+    form.resetFields();
+    onCancel?.();
+  };
+
+  const handleConfirm = () => {
+    form.submit();
+  };
+
+  const handleSubmit = async (values: FileTemplateForm) => {
+    onOk?.(
+      {
+        // @ts-ignore
+        id: item?.id || uniqueId("template"),
+        ...values,
+      },
+      Boolean(item)
+    );
+    handleCancel();
+  };
+
+  const onCheckAllChange: CheckboxProps["onChange"] = (e) => {
+    form.setFieldValue("types", e.target.checked ? [...FILE_TYPE_ENUM] : []);
+  };
+
+  useEffect(() => {
+    if (!item) {
+      form.resetFields();
+      return;
+    }
+
+    form.setFieldsValue(item);
+  }, [item, form]);
+
+  return (
+    <DageUploadProvider>
+      <DageUploadConsumer>
+        {(consumer) => (
+          <Modal
+            title="上传资料"
+            okText="提交"
+            cancelText="取消"
+            open={open}
+            width={640}
+            okButtonProps={{
+              disabled: consumer?.uploading,
+            }}
+            onOk={handleConfirm}
+            onCancel={handleCancel}
+            className={style.modal}
+            {...rest}
+          >
+            <Form
+              fields={fields}
+              labelCol={{ span: 4, offset: 1 }}
+              form={form}
+              onFieldsChange={(_, newFields) => {
+                setFields(newFields);
+              }}
+              onFinish={handleSubmit}
+            >
+              <Form.Item
+                label="资料名称"
+                rules={[{ required: true }]}
+                name="name"
+              >
+                <Input
+                  placeholder="请输入内容,最多20字"
+                  showCount
+                  autoComplete="off"
+                  maxLength={20}
+                />
+              </Form.Item>
+              <Form.Item label="填报指标时必须上传" name="required">
+                <Radio.Group
+                  size="large"
+                  options={[
+                    {
+                      label: "是",
+                      value: 1,
+                    },
+                    {
+                      label: "否",
+                      value: 0,
+                    },
+                  ]}
+                  optionType="button"
+                  buttonStyle="solid"
+                />
+              </Form.Item>
+              <Form.Item label="资料模板" name="file">
+                <DageUpload
+                  action="/api/cms/goods/upload"
+                  dType={DageUploadType.DOC}
+                  maxCount={1}
+                  tips="最多1个附件"
+                />
+              </Form.Item>
+              <Form.Item label="资料格式">
+                <Checkbox
+                  checked={checkAll}
+                  indeterminate={indeterminate}
+                  onChange={onCheckAllChange}
+                >
+                  全部
+                </Checkbox>
+
+                <Form.Item name="types" noStyle>
+                  <Checkbox.Group options={FILE_TYPE_ENUM} />
+                </Form.Item>
+              </Form.Item>
+            </Form>
+          </Modal>
+        )}
+      </DageUploadConsumer>
+    </DageUploadProvider>
+  );
+};

+ 11 - 0
src/components/FileTemplateTable/index.module.scss

@@ -0,0 +1,11 @@
+.top {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  margin-bottom: 10px;
+
+  p {
+    font-size: 12px;
+    color: rgba(36, 36, 36, 0.4);
+  }
+}

+ 115 - 0
src/components/FileTemplateTable/index.tsx

@@ -0,0 +1,115 @@
+import { FC, useState } from "react";
+import { PlusOutlined } from "@ant-design/icons";
+import { Button, Table } from "antd";
+import { DageTableActions } from "@dage/pc-components";
+import { FileTemplateForm, FileTemplateModal } from "../FileTemplateModal";
+import style from "./index.module.scss";
+
+export interface FileTemplateTableProps {
+  value?: FileTemplateForm[];
+  tips?: string;
+  onChange?: (val: FileTemplateForm[]) => void;
+}
+
+export const FileTemplateTable: FC<FileTemplateTableProps> = ({
+  tips,
+  value,
+  onChange,
+}) => {
+  const [checkedItem, setCheckedItem] = useState<FileTemplateForm | null>(null);
+  const [templateVisible, setTemplateVisible] = useState(false);
+
+  const handleTemplate = (val: FileTemplateForm, isEdit?: boolean) => {
+    const temp = value ? [...value] : [];
+
+    if (isEdit) {
+      const idx = temp.findIndex((i) => i.id === val.id);
+      temp.splice(idx, 1, val);
+    } else {
+      temp.push(val);
+    }
+
+    onChange?.(temp);
+  };
+
+  const handleDelete = (index: number) => {
+    const temp = value ? [...value] : [];
+    temp.splice(index, 1);
+    onChange?.(temp);
+  };
+
+  const handleCloseModal = () => {
+    setTemplateVisible(false);
+    setCheckedItem(null);
+  };
+
+  return (
+    <div>
+      <div className={style.top}>
+        <Button
+          className="second-button"
+          icon={<PlusOutlined />}
+          size="large"
+          type="primary"
+          onClick={() => setTemplateVisible(true)}
+        >
+          新增
+        </Button>
+
+        {tips && <p>{tips}</p>}
+      </div>
+
+      <Table
+        className="cus-table mw650"
+        dataSource={value}
+        rowKey="id"
+        columns={[
+          {
+            title: "资料名称",
+            dataIndex: "name",
+            key: "name",
+            align: "center",
+          },
+          {
+            title: "必须上传",
+            key: "age",
+            align: "center",
+            render: (item: FileTemplateForm) => {
+              return item.required ? "是" : "否";
+            },
+          },
+          {
+            title: "模板",
+            key: "address",
+            align: "center",
+            render: (item: FileTemplateForm) => {
+              return item.file ? item.file[0].name : "/";
+            },
+          },
+          {
+            title: "操作",
+            align: "center",
+            render: (item: FileTemplateForm, record, index) => {
+              return (
+                <DageTableActions
+                  onEdit={() => {
+                    setCheckedItem(item);
+                    setTemplateVisible(true);
+                  }}
+                  onDelete={handleDelete.bind(undefined, index)}
+                />
+              );
+            },
+          },
+        ]}
+      />
+
+      <FileTemplateModal
+        item={checkedItem}
+        open={templateVisible}
+        onOk={handleTemplate}
+        onCancel={handleCloseModal}
+      />
+    </div>
+  );
+};

+ 5 - 0
src/components/FormPageFooter/index.scss

@@ -18,5 +18,10 @@
     > *:not(:first-child) {
       margin-left: 20px;
     }
+    .ant-btn {
+      width: 90px;
+      font-size: 14px;
+      border-radius: 0;
+    }
   }
 }

+ 4 - 1
src/components/FormPageFooter/index.tsx

@@ -36,6 +36,7 @@ export const FormPageFooter: FC<FormPageFooterProps> = memo(
         <div className="form-page-footer-container">
           {showSubmit && (
             <Button
+              size="large"
               disabled={disabled}
               loading={loading}
               type="primary"
@@ -44,7 +45,9 @@ export const FormPageFooter: FC<FormPageFooterProps> = memo(
               提交
             </Button>
           )}
-          <Button onClick={handleCancel}>取消</Button>
+          <Button size="large" onClick={handleCancel}>
+            取消
+          </Button>
         </div>
       </div>
     );

+ 4 - 2
src/components/PageContainer/index.tsx

@@ -1,19 +1,21 @@
-import { FC, ReactNode } from "react";
+import { CSSProperties, FC, ReactNode } from "react";
 import "./index.scss";
 
 export interface PageContainerProps {
   title: string;
+  style?: CSSProperties;
   headerSlot?: ReactNode;
   children?: ReactNode;
 }
 
 export const PageContainer: FC<PageContainerProps> = ({
   title,
+  style,
   headerSlot,
   children,
 }) => {
   return (
-    <div className="page-container">
+    <div className="page-container" style={style}>
       <div className="page-container-header">
         <h3>{title}</h3>
 

+ 39 - 0
src/components/Search/index.module.scss

@@ -0,0 +1,39 @@
+.search {
+  display: flex;
+  align-items: center;
+  padding-left: 10px;
+  width: 100%;
+  height: 40px;
+  overflow: hidden;
+  border-radius: 2px;
+  border: 1px solid #d9d9d9;
+  box-sizing: border-box;
+}
+
+.toolbar {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+
+  > * {
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 40px;
+    height: 38px;
+    font-size: 20px;
+    cursor: pointer;
+
+    &:not(:last-child)::after {
+      content: "";
+      position: absolute;
+      top: 50%;
+      right: 0;
+      width: 1px;
+      height: 20px;
+      background: rgba(0, 0, 0, 0.1);
+      transform: translateY(-50%);
+    }
+  }
+}

+ 41 - 0
src/components/Search/index.tsx

@@ -0,0 +1,41 @@
+import { Input, InputProps } from "antd";
+import { FC } from "react";
+import Icon from "@ant-design/icons";
+import { ReactComponent as searchIcon } from "@/assets/icons/icon_search.svg";
+import { ReactComponent as resetIcon } from "@/assets/icons/icon_reset.svg";
+import styl from "./index.module.scss";
+
+export interface SearchProps extends Omit<InputProps, "size" | "borderless"> {
+  onSearch?: () => void;
+  onReset?: () => void;
+}
+
+export const Search: FC<SearchProps> = ({
+  style,
+  onSearch,
+  onReset,
+  ...rest
+}) => {
+  return (
+    <div className={styl.search} style={style}>
+      <Input {...rest} size="small" variant="borderless" allowClear />
+
+      <div className={styl.toolbar}>
+        {Boolean(onSearch) && (
+          <Icon
+            component={searchIcon}
+            style={{ color: "#4284FF" }}
+            onClick={() => onSearch?.()}
+          />
+        )}
+        {Boolean(onReset) && (
+          <Icon
+            component={resetIcon}
+            style={{ color: "#494949" }}
+            onClick={() => onReset?.()}
+          />
+        )}
+      </div>
+    </div>
+  );
+};

+ 6 - 0
src/components/index.ts

@@ -1,2 +1,8 @@
 export * from "./FormPageFooter";
 export * from "./PageContainer";
+export * from "./FileTemplateModal";
+export * from "./FileTemplateTable";
+export * from "./Search";
+export * from "./AddIndexModal";
+export * from "./AddIndexTemplateModal";
+export * from "./AddRoleModal";

+ 10 - 0
src/pages/Assessment/Index/CreateOrEdit/index.module.scss

@@ -20,3 +20,13 @@
   color: #ff5858;
   line-height: 32px;
 }
+
+.button {
+  width: 90px;
+  border-radius: 0;
+  font-size: 14px;
+
+  & + .button {
+    margin-left: 10px;
+  }
+}

+ 14 - 33
src/pages/Assessment/Index/CreateOrEdit/index.tsx

@@ -1,8 +1,8 @@
 import { FC, useState } from "react";
 import classNames from "classnames";
-import { Col, Form, Input, InputNumber, Radio, Row, Select, Table } from "antd";
-import { DageTableActions } from "@dage/pc-components";
-import { PageContainer } from "@/components";
+import { Col, Form, Input, InputNumber, Radio, Row, Select } from "antd";
+import { useNavigate } from "react-router-dom";
+import { FileTemplateTable, FormPageFooter, PageContainer } from "@/components";
 import style from "./index.module.scss";
 
 const { TextArea } = Input;
@@ -18,6 +18,7 @@ const CONFIRM_OPTIONS = [
 ];
 
 const CreateOrEditIndex: FC = () => {
+  const navigate = useNavigate();
   const [form] = Form.useForm();
   const [fields, setFields] = useState<any[]>([
     {
@@ -30,6 +31,12 @@ const CreateOrEditIndex: FC = () => {
     },
   ]);
 
+  const handleSubmit = async () => {
+    if (!(await form.validateFields())) return;
+
+    console.log(form.getFieldsValue());
+  };
+
   return (
     <PageContainer title="新增指标">
       <Form
@@ -39,6 +46,7 @@ const CreateOrEditIndex: FC = () => {
         onFieldsChange={(_, newFields) => {
           setFields(newFields);
         }}
+        onFinish={handleSubmit}
       >
         <Form.Item label="父级指标">
           <Select className="mw650" placeholder="请选择" options={[]} />
@@ -169,36 +177,7 @@ const CreateOrEditIndex: FC = () => {
         )}
 
         <Form.Item label="需上传资料">
-          <Table
-            className="cus-table mw650"
-            columns={[
-              {
-                title: "资料名称",
-                dataIndex: "name",
-                key: "name",
-                align: "center",
-              },
-              {
-                title: "必须上传",
-                dataIndex: "age",
-                key: "age",
-                align: "center",
-              },
-              {
-                title: "模板",
-                dataIndex: "address",
-                key: "address",
-                align: "center",
-              },
-              {
-                title: "操作",
-                align: "center",
-                render: (item: any) => {
-                  return <DageTableActions />;
-                },
-              },
-            ]}
-          />
+          <FileTemplateTable />
         </Form.Item>
 
         <Form.Item required label="排序值">
@@ -210,6 +189,8 @@ const CreateOrEditIndex: FC = () => {
             className="mw650 w100"
           />
         </Form.Item>
+
+        <FormPageFooter onSubmit={handleSubmit} onCancel={() => navigate(-1)} />
       </Form>
     </PageContainer>
   );

+ 0 - 0
src/pages/Assessment/Template/CreateOrEdit/index.module.scss


+ 53 - 0
src/pages/Assessment/Template/CreateOrEdit/index.tsx

@@ -0,0 +1,53 @@
+import { FC } from "react";
+import { Form, Input, Tree } from "antd";
+import { useNavigate } from "react-router-dom";
+import { FormPageFooter, PageContainer } from "@/components";
+
+const { TextArea } = Input;
+
+const CreateOrEditTemplate: FC = () => {
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+
+  const handleSubmit = async () => {
+    if (!(await form.validateFields())) return;
+
+    console.log(form.getFieldsValue());
+  };
+
+  return (
+    <PageContainer title="新增模板">
+      <Form labelCol={{ span: 4 }} form={form} onFinish={handleSubmit}>
+        <Form.Item
+          label="模板名称"
+          required
+          name="name"
+          rules={[{ required: true, message: "请输入模板名称" }]}
+        >
+          <Input
+            className="mw650"
+            placeholder="请输入内容,最多20字"
+            showCount
+            maxLength={20}
+          />
+        </Form.Item>
+        <Form.Item label="模板说明">
+          <TextArea
+            className="mw650"
+            showCount
+            maxLength={500}
+            placeholder="请输入内容,最多500字"
+            style={{ height: 120, resize: "none" }}
+          />
+        </Form.Item>
+        <Form.Item label="采用指标">
+          <Tree checkable />
+        </Form.Item>
+
+        <FormPageFooter onSubmit={handleSubmit} onCancel={() => navigate(-1)} />
+      </Form>
+    </PageContainer>
+  );
+};
+
+export default CreateOrEditTemplate;

+ 10 - 0
src/pages/Assessment/Template/constants.ts

@@ -0,0 +1,10 @@
+export enum TEMPLATE_TABS {
+  /**
+   * 定级评估模板
+   */
+  GRADING = 1,
+  /**
+   * 运行评估模板
+   */
+  RUNNING = 2,
+}

+ 32 - 0
src/pages/Assessment/Template/index.module.scss

@@ -0,0 +1,32 @@
+.pageTools {
+  flex: 1;
+  display: flex;
+  justify-content: space-between;
+
+  :global {
+    .ant-btn {
+      font-size: 12px;
+      border-radius: 5px;
+
+      & + .ant-btn {
+        margin-left: 10px;
+      }
+    }
+  }
+}
+
+.secondButton {
+  background: var(--second-color);
+}
+
+.pageToolsRight {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+
+  :global {
+    .ant-input-affix-wrapper-lg {
+      font-size: 12px;
+    }
+  }
+}

+ 93 - 0
src/pages/Assessment/Template/index.tsx

@@ -0,0 +1,93 @@
+import { useMemo, useState } from "react";
+import { Button, Input, Table } from "antd";
+import { PlusOutlined } from "@ant-design/icons";
+import { useNavigate } from "react-router-dom";
+import { PageContainer } from "@/components";
+import style from "./index.module.scss";
+import { TEMPLATE_TABS } from "./constants";
+
+const { Search } = Input;
+
+const TemplatePage = () => {
+  const navigate = useNavigate();
+  const [tab, setTab] = useState(TEMPLATE_TABS.GRADING);
+  const isGrading = useMemo(() => tab === TEMPLATE_TABS.GRADING, [tab]);
+
+  const handleTab = (type: TEMPLATE_TABS) => {
+    setTab(type);
+  };
+
+  return (
+    <PageContainer
+      title="考核模板"
+      headerSlot={
+        <div className={style.pageTools}>
+          <div>
+            <Button
+              type="primary"
+              ghost={!isGrading}
+              size="large"
+              onClick={handleTab.bind(undefined, TEMPLATE_TABS.GRADING)}
+            >
+              定级评估模板 11
+            </Button>
+            <Button
+              type="primary"
+              ghost={isGrading}
+              size="large"
+              onClick={handleTab.bind(undefined, TEMPLATE_TABS.RUNNING)}
+            >
+              运行评估模板 0
+            </Button>
+          </div>
+
+          <div className={style.pageToolsRight}>
+            {isGrading ? (
+              <>
+                <label>搜索</label>
+                <Search
+                  size="large"
+                  placeholder="请输入模板名称"
+                  allowClear
+                  style={{ width: 170 }}
+                />
+              </>
+            ) : (
+              ""
+            )}
+
+            <Button
+              type="primary"
+              icon={<PlusOutlined />}
+              size="large"
+              className={style.secondButton}
+              onClick={() => navigate("/assessment/template/create")}
+            >
+              新增定级评估模板
+            </Button>
+          </div>
+        </div>
+      }
+    >
+      <Table
+        className="cus-table large"
+        columns={[
+          {
+            title: "名称",
+            dataIndex: "name",
+            key: "name",
+            align: "center",
+          },
+          {
+            title: "说明",
+            dataIndex: "description",
+            key: "description",
+            align: "center",
+          },
+        ]}
+      />
+    </PageContainer>
+  );
+};
+
+export default TemplatePage;

+ 10 - 0
src/pages/AssessmentDetail/components/OverallAssessment/index.module.scss

@@ -0,0 +1,10 @@
+.form {
+  margin-top: 20px;
+
+  :global {
+    .ant-form-item-no-colon {
+      font-size: 16px !important;
+      font-weight: bold;
+    }
+  }
+}

+ 44 - 0
src/pages/AssessmentDetail/components/OverallAssessment/index.tsx

@@ -0,0 +1,44 @@
+import { Form } from "antd";
+import { FC } from "react";
+import classNames from "classnames";
+import style from "./index.module.scss";
+
+export const OverallAssessment: FC = () => {
+  return (
+    <Form
+      labelCol={{ offset: 1, span: 3 }}
+      wrapperCol={{
+        offset: 1,
+      }}
+      colon={false}
+      className={classNames(style.form, "mw1125")}
+    >
+      <Form.Item label="考核说明">
+        <p>
+          一、绩效考核的优势
+          1.鼓励员工增强工作积极性。通过绩效考核,员工可以明确工作目标,把工作分解成具体任务,并制定完成时间和标准。这样可以有针对性地制定工作计划,提高工作效率和工作积极性。
+          2.强化员工责任感和纪律性。通过绩效考核,可以为员工的工作成果负责,提高员工的责任感,使员工更有纪律性,遵循公司的规章制度和业务流程。
+          3.提升企业整体绩效。通过绩效考核,可以及时发现和解决员工工作不足,保证公司的工作效率和整体绩效。
+          二、绩效考核的劣势
+          1.评价标准不合理。有些公司在绩效考核过程中,没有明确的评价标准,导致员工之间评价不公,对于那些表现突出但不符合标准的员工,也可能被低估。
+          2.评价方式不合理。
+          评价方式主要包括逐级评价、360度评价和目标管理等,但这些评价方式大多数针对高级管理人员,对于基层员工基本没有评价。这导致员工对绩效考核过程不满,也不利于员工的提升和发展。
+          3.员工积极性下降。有些公司用相对公正的评价方式,但是考核过程不透明,甚至没有足够的沟通和反馈,导致员工积极性下降,对于公司的发展也不利。
+        </p>
+      </Form.Item>
+
+      <Form.Item label="评定意见">
+        <p>
+          1、
+          工作中具有独立思考、不断创新的能力。对工作有高度的事业心和责任感,积极主动工作,认真履行自己的职责。
+          2、
+          该同志工作态度端正,对工作有高度的事业心和责任感,政治立场坚定,方向明确,忠诚并献身于党和人民的教育事业,治学严谨,热爱学生,以身作则,为人师表。
+          3、
+          该同志遵纪守法,具有良好的社会公德和家庭美德,有高尚的职业道德,关爱学生,面向全体学生,注重学生的全面发展,认真履行教师职务职责,有强烈的服务意识。
+          4、
+          该同志政治立场坚定,教育思想端正,专业知识及基本功扎实过硬,具有很强的表达能力,善于做思想政治工作。严格遵守学校的作息时间和工作纪律,全身心地投入工作。
+        </p>
+      </Form.Item>
+    </Form>
+  );
+};

+ 52 - 0
src/pages/AssessmentDetail/index.module.scss

@@ -0,0 +1,52 @@
+.topContent {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding-bottom: 25px;
+
+  span {
+    font-weight: bold;
+  }
+}
+
+.topContentLeft,
+.topContentRight {
+  display: flex;
+  align-items: center;
+  gap: 50px;
+}
+
+.topContentItem {
+  label {
+    margin-right: 10px;
+  }
+}
+
+.pane {
+  margin-top: 20px;
+  padding: 5px 25px 25px;
+  background: white;
+  border-radius: 5px;
+  box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.05);
+}
+
+.tabs {
+  :global {
+    .ant-tabs-nav::before {
+      display: none;
+    }
+    .ant-tabs-tab-active {
+      .ant-tabs-tab-btn {
+        color: #242424 !important;
+      }
+    }
+    .ant-tabs-tab-btn {
+      font-size: 20px;
+      font-weight: bold;
+      color: rgba(36, 36, 36, 0.5);
+    }
+    .ant-tabs-ink-bar {
+      height: 6px !important;
+    }
+  }
+}

+ 65 - 0
src/pages/AssessmentDetail/index.tsx

@@ -0,0 +1,65 @@
+import { FC } from "react";
+import { Tabs } from "antd";
+import { PageContainer } from "@/components";
+import { OverallAssessment } from "./components/OverallAssessment";
+import style from "./index.module.scss";
+
+const AssessmentDetailPage: FC = () => {
+  return (
+    <>
+      <PageContainer
+        title="考核标题"
+        style={{
+          background: "white",
+          borderRadius: 5,
+          padding: "0 25px",
+          boxShadow: "0px 0px 10px 0px rgba(0,0,0,0.05)",
+        }}
+      >
+        <div className={style.topContent}>
+          <div className={style.topContentLeft}>
+            <div className={style.topContentItem}>
+              <label>总分值</label>
+              <span>432</span>
+            </div>
+          </div>
+
+          <div className={style.topContentRight}>
+            <div className={style.topContentItem}>
+              <label>考核周期</label>
+              <span>2020.1.1~2021.1.1</span>
+            </div>
+            <div className={style.topContentItem}>
+              <label>钱桂英</label>
+              <span style={{ fontWeight: "normal" }}>2024-09-22 21:19:20</span>
+            </div>
+          </div>
+        </div>
+      </PageContainer>
+
+      <div className={style.pane}>
+        <Tabs
+          size="large"
+          tabBarGutter={60}
+          className={style.tabs}
+          indicator={{
+            size: 75,
+          }}
+          items={[
+            {
+              key: "1",
+              label: "总体考核",
+              children: <OverallAssessment />,
+            },
+            {
+              key: "2",
+              label: "指标考核",
+            },
+          ]}
+        />
+      </div>
+    </>
+  );
+};
+
+export default AssessmentDetailPage;

+ 26 - 5
src/pages/Layout/index.tsx

@@ -1,7 +1,13 @@
-import React, { useMemo, useEffect, Suspense } from "react";
+import React, { useMemo, useEffect, Suspense, useState } from "react";
 import { App, Layout } from "antd";
 import { useSelector } from "react-redux";
-import { Route, Routes, useNavigate, Navigate } from "react-router-dom";
+import {
+  Route,
+  Routes,
+  useNavigate,
+  Navigate,
+  useLocation,
+} from "react-router-dom";
 import { Content } from "antd/es/layout/layout";
 import { hasToken, getTokenInfo, DageLoading } from "@dage/pc-components";
 import store from "@/store";
@@ -10,20 +16,26 @@ import { RootState } from "@/store";
 import LogoImage from "@/assets/images/logo.png";
 import { DEFAULT_ADMIN_MENU, DEFAULT_MENU, DageRouteItem } from "@/router";
 import "./index.scss";
+import { findRouteByPath } from "./utils";
 
 const NotFound = React.lazy(() => import("@/components/NotFound"));
 
 export default function CustomLayout() {
   const navigate = useNavigate();
+  const location = useLocation();
   const baseStore = useSelector<RootState, RootState["base"]>(
     (state) => state.base
   );
+  const [curMeta, setCurMeta] = useState<null | DageRouteItem["meta"]>(null);
   const menuList = useMemo<DageRouteItem[]>(() => {
     return baseStore.userInfo?.user.isAdmin
       ? [...DEFAULT_MENU, ...DEFAULT_ADMIN_MENU]
       : [...DEFAULT_MENU];
   }, [baseStore.userInfo]);
 
+  // 自定义 Content 样式
+  const isCustomStyle = curMeta && curMeta.custom;
+
   useEffect(() => {
     if (!hasToken()) {
       navigate("/login", {
@@ -34,6 +46,11 @@ export default function CustomLayout() {
     }
   }, [navigate]);
 
+  useEffect(() => {
+    const route = findRouteByPath(menuList, location.pathname);
+    setCurMeta(route?.meta);
+  }, [menuList, location]);
+
   return (
     <App>
       <Layout hasSider className="layout">
@@ -70,9 +87,13 @@ export default function CustomLayout() {
               margin: "15px",
               overflow: "auto",
               position: "relative",
-              background: "#ffffff",
-              padding: "0 25px 25px",
-              borderRadius: 4,
+              ...(isCustomStyle
+                ? {}
+                : {
+                    background: "#ffffff",
+                    padding: "0 25px 25px",
+                    borderRadius: 4,
+                  }),
             }}
           >
             <Suspense fallback={<DageLoading />}>

+ 21 - 0
src/pages/Management/Index/CreateOrEdit/index.module.scss

@@ -0,0 +1,21 @@
+.button {
+  width: 90px;
+  border-radius: 0;
+  font-size: 14px;
+
+  & + .button {
+    margin-left: 10px;
+  }
+}
+
+.buttonGroup {
+  display: flex;
+  align-items: center;
+}
+
+.tips {
+  margin-left: 10px;
+  font-size: 12px;
+  line-height: 32px;
+  color: rgba(36, 36, 36, 0.4);
+}

+ 235 - 0
src/pages/Management/Index/CreateOrEdit/index.tsx

@@ -0,0 +1,235 @@
+import { FC, useRef } from "react";
+import { DatePicker, Form, Input, InputNumber } from "antd";
+import {
+  EditableFormInstance,
+  EditableProTable,
+} from "@ant-design/pro-components";
+import { useNavigate } from "react-router-dom";
+import { uniqueId } from "lodash";
+import { FileTemplateTable, FormPageFooter, PageContainer } from "@/components";
+import { DEFAULT_BONUS_ITEM } from "../../constants";
+import { TableBonusType } from "../../types";
+import style from "./index.module.scss";
+
+const { TextArea } = Input;
+const { RangePicker } = DatePicker;
+
+const CreateOrEditManagementIndex: FC = () => {
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+  /** 加分项列表 */
+  const bonusRef = useRef<EditableFormInstance<TableBonusType>>();
+  // 减分项列表
+  const deductionRef = useRef<EditableFormInstance<TableBonusType>>();
+
+  const handleSubmit = async () => {
+    if (
+      !(await form.validateFields()) ||
+      !(await bonusRef.current?.validateFields()) ||
+      !(await deductionRef.current?.validateFields())
+    )
+      return;
+
+    console.log(form.getFieldsValue());
+  };
+
+  return (
+    <PageContainer title="新增考核">
+      <Form labelCol={{ span: 4 }} form={form} onFinish={handleSubmit}>
+        <Form.Item label="考核名称" required>
+          <Input
+            className="mw650"
+            placeholder="请输入内容,最多20字"
+            showCount
+            maxLength={20}
+          />
+        </Form.Item>
+        <Form.Item label="考核周期" required>
+          <RangePicker />
+        </Form.Item>
+        <Form.Item label="说明">
+          <TextArea
+            className="mw650"
+            showCount
+            maxLength={500}
+            placeholder="请输入内容,最多500字"
+            style={{ height: 120, resize: "none" }}
+          />
+        </Form.Item>
+
+        <Form.Item label="需上传资料" name="file">
+          <FileTemplateTable tips="注:该资料用于此次整体考核,与具体指标无关" />
+        </Form.Item>
+
+        <Form.Item label="附加项">
+          <div className={style.buttonGroup}>
+            <p className={style.tips}>
+              注:经加减分后,总得分不大于指标指标总分值,不小于0分
+            </p>
+          </div>
+
+          <Form.Item noStyle name="bonus">
+            <EditableProTable<TableBonusType>
+              className="custom-pro-table mw650"
+              editableFormRef={bonusRef}
+              rowKey="id"
+              style={{
+                margin: "20px 0",
+              }}
+              recordCreatorProps={{
+                newRecordType: "dataSource",
+                creatorButtonText: "新增加分项",
+                style: {
+                  display: "inline-flex",
+                },
+                record: () => ({
+                  id: uniqueId("bonus"),
+                  ...DEFAULT_BONUS_ITEM,
+                }),
+              }}
+              columns={[
+                {
+                  title: "加分项名称",
+                  dataIndex: "name",
+                  align: "center",
+                  formItemProps: () => {
+                    return {
+                      rules: [{ required: true, message: "此项为必填项" }],
+                    };
+                  },
+                  renderFormItem: () => (
+                    <Input
+                      maxLength={50}
+                      variant="borderless"
+                      allowClear
+                      size="small"
+                      placeholder="请输入内容,最多50字"
+                    />
+                  ),
+                },
+                {
+                  title: "可加分值",
+                  dataIndex: "num",
+                  align: "center",
+                  width: "150px",
+                  formItemProps: () => {
+                    return {
+                      rules: [{ required: true, message: "此项为必填项" }],
+                    };
+                  },
+                  renderFormItem: () => (
+                    <InputNumber
+                      variant="borderless"
+                      size="small"
+                      precision={0}
+                      placeholder="请填入正整数"
+                      className="w100"
+                    />
+                  ),
+                },
+                {
+                  title: "操作",
+                  align: "center",
+                  valueType: "option",
+                  render: () => {
+                    return null;
+                  },
+                },
+              ]}
+              editable={{
+                type: "multiple",
+                actionRender: (row, config, defaultDoms) => {
+                  return [defaultDoms.delete];
+                },
+                onValuesChange: (record, recordList) => {
+                  form.setFieldValue("bonus", recordList);
+                },
+              }}
+            />
+          </Form.Item>
+
+          <Form.Item noStyle name="deduction">
+            <EditableProTable<TableBonusType>
+              className="custom-pro-table mw650"
+              editableFormRef={deductionRef}
+              rowKey="id"
+              recordCreatorProps={{
+                newRecordType: "dataSource",
+                creatorButtonText: "新增减分项",
+                style: {
+                  display: "inline-flex",
+                },
+                record: () => ({
+                  id: uniqueId("deduction"),
+                  ...DEFAULT_BONUS_ITEM,
+                }),
+              }}
+              columns={[
+                {
+                  title: "减分项名称",
+                  dataIndex: "name",
+                  align: "center",
+                  formItemProps: () => {
+                    return {
+                      rules: [{ required: true, message: "此项为必填项" }],
+                    };
+                  },
+                  renderFormItem: () => (
+                    <Input
+                      variant="borderless"
+                      size="small"
+                      maxLength={50}
+                      allowClear
+                      placeholder="请输入内容,最多50字"
+                    />
+                  ),
+                },
+                {
+                  title: "可扣分值",
+                  dataIndex: "num",
+                  align: "center",
+                  width: "150px",
+                  formItemProps: () => {
+                    return {
+                      rules: [{ required: true, message: "此项为必填项" }],
+                    };
+                  },
+                  renderFormItem: () => (
+                    <InputNumber
+                      variant="borderless"
+                      size="small"
+                      precision={0}
+                      placeholder="请填入正整数"
+                      className="w100"
+                    />
+                  ),
+                },
+                {
+                  title: "操作",
+                  align: "center",
+                  valueType: "option",
+                  render: () => {
+                    return null;
+                  },
+                },
+              ]}
+              editable={{
+                type: "multiple",
+                actionRender: (row, config, defaultDoms) => {
+                  return [defaultDoms.delete];
+                },
+                onValuesChange: (record, recordList) => {
+                  form.setFieldValue("deduction", recordList);
+                },
+              }}
+            />
+          </Form.Item>
+        </Form.Item>
+
+        <FormPageFooter onSubmit={handleSubmit} onCancel={() => navigate(-1)} />
+      </Form>
+    </PageContainer>
+  );
+};
+
+export default CreateOrEditManagementIndex;

+ 177 - 0
src/pages/Management/Index/SettingIndex/index.tsx

@@ -0,0 +1,177 @@
+import { FC, Key, useRef, useState } from "react";
+import { Button, Form, InputNumber, Radio, Space, Tag, Tooltip } from "antd";
+import { useNavigate } from "react-router-dom";
+import {
+  ActionType,
+  EditableFormInstance,
+  EditableProTable,
+} from "@ant-design/pro-components";
+import { PlusOutlined } from "@ant-design/icons";
+import {
+  AddIndexModal,
+  AddIndexTemplateModal,
+  FormPageFooter,
+  PageContainer,
+  Search,
+} from "@/components";
+import { TableIndexType } from "../../types";
+
+const SettingIndexPage: FC = () => {
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+  const actionRef = useRef<ActionType>();
+  const tableRef = useRef<EditableFormInstance<TableIndexType>>();
+  const [keyword, setKeyword] = useState("");
+  const [indexModalVisible, setIndexModalVisible] = useState(false);
+  const [indexTemplateModalVisible, setIndexTemplateModalVisible] =
+    useState(false);
+  const [editableKeys, setEditableKeys] = useState<Key[]>([]);
+
+  const handleSubmit = async () => {
+    if (!(await form.validateFields())) return;
+
+    console.log(form.getFieldsValue());
+  };
+
+  const handleAddIndexItem = (keys: Key[], list: TableIndexType[]) => {
+    form.setFieldValue("list", [...list]);
+    setEditableKeys([...keys]);
+  };
+
+  return (
+    <PageContainer title="设置指标">
+      <Form labelCol={{ span: 4, offset: 3 }} form={form} size="large">
+        <Form.Item label="指标总分值">
+          <Button type="primary">计算</Button>
+
+          <span style={{ paddingLeft: 15 }}>534分</span>
+        </Form.Item>
+        <Form.Item required label="考核指标">
+          <Form.Item noStyle name="list">
+            <EditableProTable<TableIndexType>
+              className="custom-pro-table mw650"
+              actionRef={actionRef}
+              editableFormRef={tableRef}
+              rowKey="id"
+              style={{ marginTop: -15 }}
+              recordCreatorProps={false}
+              columns={[
+                {
+                  title: "指标级别",
+                  dataIndex: "level",
+                  align: "center",
+                  editable: false,
+                },
+                {
+                  title: "指标名称",
+                  dataIndex: "name",
+                  align: "center",
+                  editable: false,
+                },
+                {
+                  title: "填报方式",
+                  dataIndex: "type",
+                  align: "center",
+                  width: "230px",
+                  renderFormItem: () => (
+                    <Radio.Group>
+                      <Radio value="point">手动填报</Radio>
+                      <Radio value="api">
+                        API{" "}
+                        <Tooltip title="以 type,start_date,end_date请求API数据。当API获取数据失败时,将自动转为手动填报">
+                          <Tag bordered={false} color="warning">
+                            注
+                          </Tag>
+                        </Tooltip>
+                      </Radio>
+                    </Radio.Group>
+                  ),
+                },
+                {
+                  title: "分值",
+                  dataIndex: "num",
+                  align: "center",
+                  width: "130px",
+                  formItemProps: () => {
+                    return {
+                      rules: [{ required: true, message: "此项为必填项" }],
+                    };
+                  },
+                  renderFormItem: () => (
+                    <InputNumber
+                      variant="borderless"
+                      size="small"
+                      precision={0}
+                      placeholder="请填入正整数"
+                      className="w100"
+                    />
+                  ),
+                },
+                {
+                  title: "操作",
+                  align: "center",
+                  valueType: "option",
+                  render: () => {
+                    return null;
+                  },
+                },
+              ]}
+              headerTitle={
+                <Space>
+                  <Button
+                    type="primary"
+                    icon={<PlusOutlined />}
+                    style={{ background: "var(--second-color)" }}
+                    onClick={() => setIndexModalVisible(true)}
+                  >
+                    新建指标
+                  </Button>
+                  <Button
+                    type="primary"
+                    onClick={() => setIndexTemplateModalVisible(true)}
+                  >
+                    从模板中添加
+                  </Button>
+                </Space>
+              }
+              toolBarRender={() => [
+                <Search
+                  placeholder="请输入要搜索的指标名称"
+                  style={{ maxWidth: 335 }}
+                  onChange={(v) => setKeyword(v.target.value)}
+                  onSearch={() => {}}
+                  onReset={() => {}}
+                />,
+              ]}
+              editable={{
+                type: "multiple",
+                editableKeys,
+                actionRender: (row, config, defaultDoms) => {
+                  return [defaultDoms.delete];
+                },
+                onValuesChange: (record, recordList) => {
+                  form.setFieldValue("deduction", recordList);
+                },
+              }}
+            />
+          </Form.Item>
+        </Form.Item>
+        <FormPageFooter onSubmit={handleSubmit} onCancel={() => navigate(-1)} />
+      </Form>
+
+      <AddIndexModal
+        checkedKeys={editableKeys}
+        open={indexModalVisible}
+        onOk={handleAddIndexItem}
+        onCancel={() => setIndexModalVisible(false)}
+      />
+      <AddIndexTemplateModal
+        open={indexTemplateModalVisible}
+        onOk={() => {}}
+        onCancel={() => setIndexTemplateModalVisible(false)}
+      />
+    </PageContainer>
+  );
+};
+
+export default SettingIndexPage;

+ 35 - 0
src/pages/Management/Index/SettingRole/components/Pane/index.module.scss

@@ -0,0 +1,35 @@
+.pane {
+  &.required {
+    .paneHeader p::before {
+      display: inline-block;
+      content: "*";
+      font-size: 32px;
+      color: #ff5858;
+    }
+  }
+}
+
+.paneHeader {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  p {
+    display: flex;
+    align-items: center;
+    font-size: 20px;
+    font-weight: bold;
+    height: 40px;
+
+    span {
+      padding-left: 10px;
+      font-size: 14px;
+      font-weight: lighter;
+      color: rgba(36, 36, 36, 0.3);
+    }
+  }
+}
+
+.paneMain {
+  margin: 15px -25px 0;
+}

+ 53 - 0
src/pages/Management/Index/SettingRole/components/Pane/index.tsx

@@ -0,0 +1,53 @@
+import { CSSProperties, FC, PropsWithChildren } from "react";
+import { Button } from "antd";
+import classNames from "classnames";
+import { PlusOutlined } from "@ant-design/icons";
+import _style from "./index.module.scss";
+
+export interface PaneProps {
+  style?: CSSProperties;
+  title: string;
+  tips?: string;
+  required?: boolean;
+  onAdd?: () => void;
+}
+
+export const Pane: FC<PropsWithChildren<PaneProps>> = ({
+  title,
+  tips,
+  style,
+  required,
+  children,
+  onAdd,
+}) => {
+  return (
+    <div
+      style={style}
+      className={classNames(
+        {
+          [_style.required]: required,
+        },
+        _style.pane
+      )}
+    >
+      <div className={_style.paneHeader}>
+        <p>
+          {title}
+          {tips && <span>{tips}</span>}
+        </p>
+
+        <Button
+          type="primary"
+          icon={<PlusOutlined />}
+          className="second-button"
+          size="large"
+          onClick={onAdd}
+        >
+          新增
+        </Button>
+      </div>
+
+      <div className={_style.paneMain}>{children}</div>
+    </div>
+  );
+};

+ 94 - 0
src/pages/Management/Index/SettingRole/index.tsx

@@ -0,0 +1,94 @@
+import { AddRoleModal, PageContainer } from "@/components";
+import { FC, useState } from "react";
+import { Pane } from "./components/Pane";
+import { Table } from "antd";
+import { DageTableActions } from "@dage/pc-components";
+
+const SettingRole: FC = () => {
+  const [addRoleVisible, setAddRoleVisible] = useState(false);
+
+  return (
+    <PageContainer title="设置角色">
+      <Pane
+        title="自评组"
+        required
+        tips="负责填报指标或上传资料的部门或组织"
+        onAdd={() => setAddRoleVisible(true)}
+      >
+        <Table
+          className="cus-table gray"
+          columns={[
+            {
+              title: "自评组名称",
+              align: "center",
+              dataIndex: "name",
+            },
+            {
+              title: "博物馆级别",
+              align: "center",
+              dataIndex: "level",
+            },
+            {
+              title: "组别说明",
+              align: "center",
+              dataIndex: "description",
+            },
+            {
+              title: "自评组成员",
+              align: "center",
+              dataIndex: "group",
+            },
+            {
+              title: "操作",
+              align: "center",
+              render: (item: any, record, index) => {
+                return <DageTableActions />;
+              },
+            },
+          ]}
+        />
+      </Pane>
+
+      <Pane
+        title="评定组"
+        tips="负责评定责任部门填报结果的人员"
+        style={{ marginTop: 40 }}
+      >
+        <Table
+          className="cus-table gray"
+          columns={[
+            {
+              title: "评定组名称",
+              align: "center",
+              dataIndex: "name",
+            },
+            {
+              title: "组别说明",
+              align: "center",
+              dataIndex: "description",
+            },
+            {
+              title: "评定组成员",
+              align: "center",
+              dataIndex: "group",
+            },
+            {
+              title: "操作",
+              align: "center",
+              render: (item: any, record, index) => {
+                return <DageTableActions />;
+              },
+            },
+          ]}
+        />
+      </Pane>
+
+      <AddRoleModal
+        open={addRoleVisible}
+        onCancel={() => setAddRoleVisible(false)}
+      />
+    </PageContainer>
+  );
+};
+
+export default SettingRole;

+ 7 - 0
src/pages/Management/Index/index.module.scss

@@ -0,0 +1,7 @@
+.table {
+  margin: 20px -25px;
+}
+
+.button {
+  padding: 0 7px;
+}

+ 214 - 0
src/pages/Management/Index/index.tsx

@@ -0,0 +1,214 @@
+import classNames from "classnames";
+import { useState } from "react";
+import { Button, Form, Input, Select, Table } from "antd";
+import { PlusOutlined } from "@ant-design/icons";
+import { useNavigate } from "react-router-dom";
+import { PageContainer } from "@/components";
+import { DageTableActions } from "@dage/pc-components";
+import style from "./index.module.scss";
+import { PUBLISH_STATUS_MAP, PUBLISH_TYPE } from "../constants";
+
+const ManagementIndexPage = () => {
+  const navigate = useNavigate();
+  const [list] = useState([
+    {
+      id: 1,
+      name: "模板名称",
+      a: "定级评估",
+      description: "模板说明",
+      b: "",
+      c: PUBLISH_TYPE.PENDINIG,
+      d: "",
+      e: "",
+      f: "2024-09-24 16:50:30",
+      g: "钱韵澄",
+    },
+  ]);
+
+  return (
+    <PageContainer
+      title="考核管理"
+      headerSlot={
+        <div>
+          <Button
+            type="primary"
+            icon={<PlusOutlined />}
+            size="large"
+            className="second-button"
+            onClick={() => navigate("/management/index/create")}
+          >
+            新增定级评估考核
+          </Button>
+
+          <Button
+            type="primary"
+            icon={<PlusOutlined />}
+            size="large"
+            className="second-button"
+            onClick={() => navigate("/management/index/create")}
+          >
+            新增运行评估考核
+          </Button>
+        </div>
+      }
+    >
+      <div className={style.filter}>
+        <Form layout="inline" className="inline-form">
+          <Form.Item label="考核类别">
+            <div className="w160">
+              <Select />
+            </div>
+          </Form.Item>
+          <Form.Item label="发布状态">
+            <div className="w160">
+              <Select />
+            </div>
+          </Form.Item>
+          <Form.Item label="自评进度">
+            <div className="w160">
+              <Select />
+            </div>
+          </Form.Item>
+          <Form.Item label="评价进度">
+            <div className="w160">
+              <Select />
+            </div>
+          </Form.Item>
+          <Form.Item label="搜索">
+            <Input className="w160" placeholder="请输入考核名称" />
+          </Form.Item>
+          <Form.Item>
+            <Button type="primary">查询</Button>
+          </Form.Item>
+        </Form>
+      </div>
+
+      <div className={style.table}>
+        <Table
+          className={classNames("cus-table large")}
+          dataSource={list}
+          rowKey="id"
+          scroll={{ x: "max-content" }}
+          columns={[
+            {
+              title: "名称",
+              dataIndex: "name",
+              key: "name",
+              align: "center",
+              minWidth: 100,
+            },
+            {
+              title: "类别",
+              dataIndex: "a",
+              key: "a",
+              align: "center",
+              minWidth: 100,
+            },
+            {
+              title: "说明",
+              dataIndex: "description",
+              key: "description",
+              align: "center",
+              minWidth: 100,
+            },
+            {
+              title: "考核周期",
+              dataIndex: "b",
+              key: "b",
+              align: "center",
+              minWidth: 100,
+            },
+            {
+              title: "发布状态",
+              key: "c",
+              align: "center",
+              minWidth: 100,
+              render: (item: (typeof list)[0]) => {
+                return (
+                  <p style={{ color: PUBLISH_STATUS_MAP[item.c].color }}>
+                    {PUBLISH_STATUS_MAP[item.c].label}
+                  </p>
+                );
+              },
+            },
+            {
+              title: "自评状态",
+              key: "d",
+              align: "center",
+              minWidth: 100,
+              render: (item: (typeof list)[0]) => {
+                return item.d || "/";
+              },
+            },
+            {
+              title: "评定状态",
+              key: "e",
+              align: "center",
+              minWidth: 100,
+              render: (item: (typeof list)[0]) => {
+                return item.e || "/";
+              },
+            },
+            {
+              title: "编辑时间",
+              dataIndex: "f",
+              key: "f",
+              align: "center",
+              minWidth: 160,
+            },
+            {
+              title: "编辑人",
+              dataIndex: "g",
+              key: "g",
+              align: "center",
+              minWidth: 100,
+            },
+            {
+              title: "操作",
+              key: "h",
+              align: "center",
+              fixed: "right",
+              render: (item: (typeof list)[0]) => {
+                return (
+                  <DageTableActions
+                    renderBefore={
+                      <>
+                        <Button
+                          type="text"
+                          className={style.button}
+                          onClick={() => navigate("/management/index/detail")}
+                        >
+                          查看
+                        </Button>
+                        <Button
+                          type="text"
+                          className={style.button}
+                          onClick={() =>
+                            navigate("/management/index/setting-index")
+                          }
+                        >
+                          设置指标
+                        </Button>
+                        <Button
+                          type="text"
+                          className={style.button}
+                          onClick={() =>
+                            navigate("/management/index/setting-role")
+                          }
+                        >
+                          设置角色
+                        </Button>
+                      </>
+                    }
+                  />
+                );
+              },
+            },
+          ]}
+        />
+      </div>
+    </PageContainer>
+  );
+};
+
+export default ManagementIndexPage;

+ 38 - 0
src/pages/Management/constants.ts

@@ -0,0 +1,38 @@
+export enum PUBLISH_TYPE {
+  PENDINIG = 0,
+  PUBLISHED = 1,
+  ENDED = 2,
+}
+
+export const PUBLISH_STATUS_MAP = {
+  [PUBLISH_TYPE.PENDINIG]: {
+    label: "待发布",
+    color: "#FF5858",
+  },
+  [PUBLISH_TYPE.PUBLISHED]: {
+    label: "已发布",
+    color: "#242424",
+  },
+  [PUBLISH_TYPE.ENDED]: {
+    label: "已结束",
+    color: "#6BC6FF",
+  },
+};
+
+/**
+ * 初始化指标
+ */
+export const DEFAULT_INDEX_ITEM = {
+  level: "",
+  name: "",
+  type: "",
+  num: null,
+};
+
+/**
+ * 初始化加分项
+ */
+export const DEFAULT_BONUS_ITEM = {
+  name: "",
+  num: null,
+};

+ 19 - 0
src/pages/Management/types.ts

@@ -0,0 +1,19 @@
+/**
+ * 加分项类型
+ */
+export type TableBonusType = {
+  id: string;
+  name: string;
+  num: number | null;
+};
+
+/**
+ * 指标类型
+ */
+export type TableIndexType = {
+  id: string;
+  name: string;
+  level: string;
+  type: string;
+  num: number | null;
+};

+ 64 - 0
src/router/index.tsx

@@ -3,6 +3,7 @@ import Icon from "@ant-design/icons";
 import { DageRouteItem } from "./types";
 import { ReactComponent as SettingIcon } from "@/assets/icons/systems.svg";
 import { ReactComponent as AssessmentIcon } from "@/assets/icons/icon_check.svg";
+import { ReactComponent as managementIcon } from "@/assets/icons/icon_management.svg";
 
 export const DEFAULT_MENU: DageRouteItem[] = [
   {
@@ -26,6 +27,69 @@ export const DEFAULT_MENU: DageRouteItem[] = [
           },
         ],
       },
+      {
+        path: "/assessment/template",
+        title: "考核模板",
+        Component: React.lazy(() => import("../pages/Assessment/Template")),
+        children: [
+          {
+            hide: true,
+            path: "/assessment/template/create",
+            title: "新增模板",
+            Component: React.lazy(
+              () => import("../pages/Assessment/Template/CreateOrEdit")
+            ),
+          },
+        ],
+      },
+    ],
+  },
+  {
+    path: "/management",
+    title: "考核管理",
+    icon: <Icon component={managementIcon} />,
+    redirect: "/management/index",
+    children: [
+      {
+        path: "/management/index",
+        title: "考核管理",
+        Component: React.lazy(() => import("../pages/Management/Index")),
+        children: [
+          {
+            hide: true,
+            path: "/management/index/create",
+            title: "新增考核",
+            Component: React.lazy(
+              () => import("../pages/Management/Index/CreateOrEdit")
+            ),
+          },
+          {
+            hide: true,
+            path: "/management/index/setting-index",
+            title: "设置指标",
+            Component: React.lazy(
+              () => import("../pages/Management/Index/SettingIndex")
+            ),
+          },
+          {
+            hide: true,
+            path: "/management/index/setting-role",
+            title: "设置角色",
+            Component: React.lazy(
+              () => import("../pages/Management/Index/SettingRole")
+            ),
+          },
+          {
+            hide: true,
+            meta: {
+              custom: true,
+            },
+            path: "/management/index/detail",
+            title: "考核详情",
+            Component: React.lazy(() => import("../pages/AssessmentDetail")),
+          },
+        ],
+      },
     ],
   },
 ];

+ 4 - 0
src/router/types.ts

@@ -12,5 +12,9 @@ export interface DageRouteItem {
    */
   hide?: boolean;
   icon?: ReactNode;
+  meta?: {
+    /** 是否自定义Content样式 */
+    custom?: boolean;
+  };
   children?: DageRouteItem[];
 }

+ 19 - 0
src/utils/index.ts

@@ -1,5 +1,6 @@
 import { removeTokenInfo } from "@dage/pc-components";
 import { logoutApi } from "@/api";
+import { Key } from "react";
 
 export const logout = async () => {
   await logoutApi();
@@ -7,3 +8,21 @@ export const logout = async () => {
   removeTokenInfo();
   globalThis.location.href = "#/login";
 };
+
+export const getSelectedNodes = (selectedKeys: Key[], treeData: any[]) => {
+  let selectedNodes: any[] = [];
+
+  const findNodes = (keys: Key[], data: any[]) => {
+    data.forEach((node) => {
+      if (keys.includes(node.id) && !node.children) {
+        selectedNodes.push(node);
+      }
+      if (node.children) {
+        findNodes(keys, node.children);
+      }
+    });
+  };
+
+  findNodes(selectedKeys, treeData);
+  return selectedNodes;
+};