张涛 1 vuosi sitten
commit
bc4f89229d
100 muutettua tiedostoa jossa 9014 lisäystä ja 0 poistoa
  1. 25
    0
      .gitignore
  2. 17
    0
      index.html
  3. 8
    0
      jsconfig.json
  4. 36
    0
      package.json
  5. 3547
    0
      pnpm-lock.yaml
  6. 30
    0
      public/config.js
  7. BIN
      public/logo-1.png
  8. BIN
      public/logo.png
  9. BIN
      public/mandatoryleave.xlsx
  10. 110
    0
      public/particles/particles.json
  11. 9
    0
      public/particles/particles.min.js
  12. BIN
      public/rotation.xlsx
  13. 1
    0
      public/vite.svg
  14. BIN
      public/woman.png
  15. BIN
      src/assets/chinaBank.png
  16. 1
    0
      src/assets/react.svg
  17. 93
    0
      src/components/Curd/index.jsx
  18. 107
    0
      src/components/EditForm/index.jsx
  19. 15
    0
      src/components/EditableTag/Tag.jsx
  20. 21
    0
      src/components/EditableTag/index.jsx
  21. 27
    0
      src/components/EditableTag/style.less
  22. 18
    0
      src/components/EmojiSelect/Emoji.jsx
  23. 52
    0
      src/components/EmojiSelect/Part.jsx
  24. 58
    0
      src/components/EmojiSelect/data.js
  25. 47
    0
      src/components/EmojiSelect/index.jsx
  26. 20
    0
      src/components/EmojiSelect/style.module.less
  27. 14
    0
      src/components/Error/index.jsx
  28. 11
    0
      src/components/Loading/index.jsx
  29. 4
    0
      src/components/Loading/style.module.less
  30. 24
    0
      src/components/Massage/index.jsx
  31. 24
    0
      src/components/Massage/index.module.less
  32. 4
    0
      src/components/Money/float.js
  33. 30
    0
      src/components/Money/index.jsx
  34. 12
    0
      src/components/Page/Curd.jsx
  35. 49
    0
      src/components/Page/Edit.jsx
  36. 46
    0
      src/components/Page/List.jsx
  37. 68
    0
      src/components/Page/index.jsx
  38. 67
    0
      src/components/Staff/StaffSearch.jsx
  39. 62
    0
      src/components/Upload/ModalForms.jsx
  40. 10
    0
      src/components/Upload/Upload.jsx
  41. 72
    0
      src/components/Upload/UploadImage.jsx
  42. 110
    0
      src/components/Upload/UploadImageList.jsx
  43. 94
    0
      src/components/Upload/UploadVideo.jsx
  44. 11
    0
      src/components/Upload/index.jsx
  45. 44
    0
      src/components/Upload/request.js
  46. 7
    0
      src/components/Upload/style.less
  47. 86
    0
      src/components/Wangeditor/index.jsx
  48. 36
    0
      src/components/XTable/components/Actions.jsx
  49. 126
    0
      src/components/XTable/index.jsx
  50. 32
    0
      src/components/auth/Auth.jsx
  51. 21
    0
      src/components/auth/AuthDanger.jsx
  52. 13
    0
      src/components/auth/Predicate.jsx
  53. 38
    0
      src/components/auth/RequireLogin.jsx
  54. 16
    0
      src/components/auth/index.jsx
  55. 81
    0
      src/components/chart/index.jsx
  56. 76
    0
      src/index.less
  57. 27
    0
      src/layouts/AuthLayout/components/Container.jsx
  58. 19
    0
      src/layouts/AuthLayout/components/Footer.jsx
  59. 47
    0
      src/layouts/AuthLayout/components/Header/Exit.jsx
  60. 24
    0
      src/layouts/AuthLayout/components/Header/Title.jsx
  61. 115
    0
      src/layouts/AuthLayout/components/Header/User.jsx
  62. 28
    0
      src/layouts/AuthLayout/components/Header/index.jsx
  63. 20
    0
      src/layouts/AuthLayout/components/HtmlTitle.jsx
  64. 17
    0
      src/layouts/AuthLayout/components/Logo.jsx
  65. 63
    0
      src/layouts/AuthLayout/components/Menus.jsx
  66. 23
    0
      src/layouts/AuthLayout/components/PageTransition/index.jsx
  67. 21
    0
      src/layouts/AuthLayout/components/PageTransition/style.less
  68. 20
    0
      src/layouts/AuthLayout/components/SiderBar.jsx
  69. 44
    0
      src/layouts/AuthLayout/index.jsx
  70. 88
    0
      src/layouts/AuthLayout/style.less
  71. 25
    0
      src/layouts/PageContainer.jsx
  72. 13
    0
      src/main.jsx
  73. 29
    0
      src/pages/404/index.jsx
  74. 119
    0
      src/pages/home/Detail.jsx
  75. 34
    0
      src/pages/home/History.jsx
  76. 144
    0
      src/pages/home/index.jsx
  77. 11
    0
      src/pages/home/style.less
  78. 143
    0
      src/pages/mandatoryleave/Edit.jsx
  79. 222
    0
      src/pages/mandatoryleave/index.jsx
  80. 58
    0
      src/pages/rotationManage/compatibilityQuery/components/SearchResult.jsx
  81. 144
    0
      src/pages/rotationManage/compatibilityQuery/index.jsx
  82. 227
    0
      src/pages/rotationManage/rotationList/Edit.jsx
  83. 261
    0
      src/pages/rotationManage/rotationList/index.jsx
  84. 79
    0
      src/pages/system/Incompatible/Edit.jsx
  85. 80
    0
      src/pages/system/Incompatible/index.jsx
  86. 56
    0
      src/pages/system/log/index.jsx
  87. 101
    0
      src/pages/system/org/components/Form.jsx
  88. 163
    0
      src/pages/system/org/index.jsx
  89. 23
    0
      src/pages/system/org/styles.module.less
  90. 130
    0
      src/pages/system/position/edit/index.jsx
  91. 112
    0
      src/pages/system/position/index.jsx
  92. 23
    0
      src/pages/system/role/index.jsx
  93. 169
    0
      src/pages/system/role/list.jsx
  94. 156
    0
      src/pages/system/role/menus.jsx
  95. 19
    0
      src/pages/system/role/style.less
  96. 162
    0
      src/pages/system/user/Edit.jsx
  97. 36
    0
      src/pages/system/user/components/SelectRole.jsx
  98. 49
    0
      src/pages/system/user/components/UserDrawer.jsx
  99. 140
    0
      src/pages/system/user/index.jsx
  100. 0
    0
      src/pages/work/index.jsx

+ 25
- 0
.gitignore Näytä tiedosto

@@ -0,0 +1,25 @@
1
+# Logs
2
+logs
3
+*.log
4
+npm-debug.log*
5
+yarn-debug.log*
6
+yarn-error.log*
7
+pnpm-debug.log*
8
+lerna-debug.log*
9
+
10
+node_modules
11
+dist
12
+dist-ssr
13
+*.local
14
+.history
15
+
16
+# Editor directories and files
17
+.vscode/*
18
+!.vscode/extensions.json
19
+.idea
20
+.DS_Store
21
+*.suo
22
+*.ntvs*
23
+*.njsproj
24
+*.sln
25
+*.sw?

+ 17
- 0
index.html Näytä tiedosto

@@ -0,0 +1,17 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+
4
+<head>
5
+  <meta charset="UTF-8" />
6
+  <link rel="icon" type="image/svg+xml" href="https://yz-crm.oss-cn-nanjing.aliyuncs.com/images/favicon.ico" />
7
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+  <script src="./config.js"></script>
9
+  <title>法规部不相容岗位系统</title>
10
+</head>
11
+
12
+<body>
13
+  <div id="root"></div>
14
+  <script type="module" src="/src/main.jsx"></script>
15
+</body>
16
+
17
+</html>

+ 8
- 0
jsconfig.json Näytä tiedosto

@@ -0,0 +1,8 @@
1
+{
2
+  "compilerOptions": {
3
+    "baseUrl": ".",
4
+    "paths": {
5
+      "@/*": ["./src/*"]
6
+    } 
7
+  }
8
+}

+ 36
- 0
package.json Näytä tiedosto

@@ -0,0 +1,36 @@
1
+{
2
+  "name": "vite-project",
3
+  "private": true,
4
+  "version": "0.0.0",
5
+  "type": "module",
6
+  "scripts": {
7
+    "dev": "vite",
8
+    "build": "vite build",
9
+    "preview": "vite preview"
10
+  },
11
+  "dependencies": {
12
+    "@ant-design/icons": "^5.2.5",
13
+    "@ant-design/pro-components": "^2.6.14",
14
+    "@wangeditor/editor": "^5.1.23",
15
+    "@wangeditor/editor-for-react": "^1.0.6",
16
+    "@zjxpcyc/react-tiny-store": "^4.3.1",
17
+    "antd": "5.8.5",
18
+    "axios": "^1.5.0",
19
+    "classnames": "^2.3.2",
20
+    "dayjs": "^1.11.10",
21
+    "echarts": "^5.4.3",
22
+    "md5": "^2.3.0",
23
+    "react": "18.2.0",
24
+    "react-dom": "18.2.0",
25
+    "react-helmet": "^6.1.0",
26
+    "react-router-dom": "^6.15.0",
27
+    "react-transition-group": "^4.4.5"
28
+  },
29
+  "devDependencies": {
30
+    "@types/react": "^18.2.21",
31
+    "@types/react-dom": "^18.2.7",
32
+    "@vitejs/plugin-react": "^4.0.4",
33
+    "less": "^4.2.0",
34
+    "vite": "^4.4.9"
35
+  }
36
+}

+ 3547
- 0
pnpm-lock.yaml
File diff suppressed because it is too large
Näytä tiedosto


+ 30
- 0
public/config.js Näytä tiedosto

@@ -0,0 +1,30 @@
1
+var AUTH_SERVER = "http://192.168.89.13:9001/";
2
+
3
+var AUTH_RUL = "http://192.168.89.13:3000";
4
+
5
+// var AUTH_LOGIN_PAGE = `${AUTH_RUL}/#/login`;
6
+
7
+var APPID = "sms_platform";
8
+
9
+var SERVER_BASE = "/api";
10
+
11
+var uploadAPI = "http://192.168.89.147:7200/api/upload/admin/" + APPID;
12
+
13
+function getRealPath(url) {
14
+  const prefix = "http://192.168.89.13:8080";
15
+  return `${prefix}/${url}`;
16
+}
17
+function getLoginPage() {
18
+  return `${AUTH_RUL}/#/login?redirect=${encodeURIComponent(
19
+    window.location.href.replace(/ticket=[^?&/]*/, "")
20
+  )}&mode=ticket&appid=${APPID}`;
21
+}
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+ const mandatoryleave="./mandatoryleave.xlsx"
30
+ const rotation="./rotation.xlsx"

BIN
public/logo-1.png Näytä tiedosto


BIN
public/logo.png Näytä tiedosto


BIN
public/mandatoryleave.xlsx Näytä tiedosto


+ 110
- 0
public/particles/particles.json Näytä tiedosto

@@ -0,0 +1,110 @@
1
+{
2
+  "particles": {
3
+    "number": {
4
+      "value": 80,
5
+      "density": {
6
+        "enable": true,
7
+        "value_area": 800
8
+      }
9
+    },
10
+    "color": {
11
+      "value": "#1890ff"
12
+    },
13
+    "shape": {
14
+      "type": "circle",
15
+      "stroke": {
16
+        "width": 0,
17
+        "color": "#000000"
18
+      },
19
+      "polygon": {
20
+        "nb_sides": 5
21
+      },
22
+      "image": {
23
+        "src": "img/github.svg",
24
+        "width": 100,
25
+        "height": 100
26
+      }
27
+    },
28
+    "opacity": {
29
+      "value": 0.5,
30
+      "random": false,
31
+      "anim": {
32
+        "enable": false,
33
+        "speed": 1,
34
+        "opacity_min": 0.1,
35
+        "sync": false
36
+      }
37
+    },
38
+    "size": {
39
+      "value": 3,
40
+      "random": true,
41
+      "anim": {
42
+        "enable": false,
43
+        "speed": 40,
44
+        "size_min": 0.1,
45
+        "sync": false
46
+      }
47
+    },
48
+    "line_linked": {
49
+      "enable": true,
50
+      "distance": 150,
51
+      "color": "#1890ff",
52
+      "opacity": 0.4,
53
+      "width": 1
54
+    },
55
+    "move": {
56
+      "enable": true,
57
+      "speed": 2,
58
+      "direction": "none",
59
+      "random": false,
60
+      "straight": false,
61
+      "out_mode": "out",
62
+      "bounce": false,
63
+      "attract": {
64
+        "enable": false,
65
+        "rotateX": 600,
66
+        "rotateY": 1200
67
+      }
68
+    }
69
+  },
70
+  "interactivity": {
71
+    "detect_on": "canvas",
72
+    "events": {
73
+      "onhover": {
74
+        "enable": false,
75
+        "mode": "repulse"
76
+      },
77
+      "onclick": {
78
+        "enable": false,
79
+        "mode": "push"
80
+      },
81
+      "resize": true
82
+    },
83
+    "modes": {
84
+      "grab": {
85
+        "distance": 400,
86
+        "line_linked": {
87
+          "opacity": 1
88
+        }
89
+      },
90
+      "bubble": {
91
+        "distance": 400,
92
+        "size": 40,
93
+        "duration": 2,
94
+        "opacity": 8,
95
+        "speed": 3
96
+      },
97
+      "repulse": {
98
+        "distance": 200,
99
+        "duration": 0.4
100
+      },
101
+      "push": {
102
+        "particles_nb": 4
103
+      },
104
+      "remove": {
105
+        "particles_nb": 2
106
+      }
107
+    }
108
+  },
109
+  "retina_detect": true
110
+}

+ 9
- 0
public/particles/particles.min.js
File diff suppressed because it is too large
Näytä tiedosto


BIN
public/rotation.xlsx Näytä tiedosto


+ 1
- 0
public/vite.svg Näytä tiedosto

@@ -0,0 +1 @@
1
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

BIN
public/woman.png Näytä tiedosto


BIN
src/assets/chinaBank.png Näytä tiedosto


+ 1
- 0
src/assets/react.svg Näytä tiedosto

@@ -0,0 +1 @@
1
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

+ 93
- 0
src/components/Curd/index.jsx Näytä tiedosto

@@ -0,0 +1,93 @@
1
+import React from "react";
2
+import { DrawerForm } from "@ant-design/pro-components";
3
+import { Form } from "antd";
4
+import XTable from "../XTable";
5
+
6
+export default function Curd(props) {
7
+  const {
8
+    rowKey,
9
+    columns,
10
+    request,
11
+    formProps,
12
+    renderFormItems,
13
+    transform,
14
+    convertValue,
15
+    ...leftProps
16
+  } = props;
17
+
18
+  const [form] = Form.useForm();
19
+  const listRef = React.useRef();
20
+  const rowRef = React.useRef();
21
+  const [open, setOpen] = React.useState(false);
22
+
23
+  const onAdd = () => {
24
+    rowRef.current = undefined;
25
+    form.resetFields();
26
+    setOpen(true);
27
+  };
28
+
29
+  const onEdit = (row) => {
30
+    rowRef.current = row;
31
+
32
+    const formData = convertValue ? convertValue(row) : row;
33
+    form.resetFields();
34
+    form.setFieldsValue(formData);
35
+
36
+    setOpen(true);
37
+  };
38
+
39
+  const onDelete = (row) => {
40
+    rowRef.current = row;
41
+
42
+    if (request.remove) {
43
+      request
44
+        .remove(row[rowKey])
45
+        .then(() => {
46
+          listRef.current.reload();
47
+        })
48
+        .catch(() => {
49
+          //
50
+        });
51
+    }
52
+  };
53
+
54
+  const onFinish = async (values) => {
55
+    const id = rowRef.current?.[rowKey];
56
+    const data = transform ? transform(id, values) : values;
57
+    if (rowRef.current && request.update) {
58
+      // await request.update(id, data);
59
+    } else if (request.save) {
60
+      // await request.save(data);
61
+    }
62
+    listRef.current.reload();
63
+
64
+    return true;
65
+  };
66
+
67
+  return (
68
+    <div>
69
+      <XTable
70
+        ref={listRef}
71
+        rowKey={rowKey}
72
+        columns={columns}
73
+        request={request.list}
74
+        onAdd={onAdd}
75
+        onDelete={onDelete}
76
+        onEdit={onEdit}
77
+        {...leftProps}
78
+      />
79
+
80
+      <DrawerForm
81
+        submitTimeout={3000}
82
+        open={open}
83
+        form={form}
84
+        drawerProps={{ destroyOnClose: true }}
85
+        onOpenChange={setOpen}
86
+        onFinish={onFinish}
87
+        {...formProps}
88
+      >
89
+        {renderFormItems && renderFormItems(rowRef.current, listRef, setOpen)}
90
+      </DrawerForm>
91
+    </div>
92
+  );
93
+}

+ 107
- 0
src/components/EditForm/index.jsx Näytä tiedosto

@@ -0,0 +1,107 @@
1
+import React from 'react'
2
+import { Button, Card, Row, Col, Form } from 'antd'
3
+import { ProForm } from '@ant-design/pro-components'
4
+import { useSearchParams, useNavigate } from 'react-router-dom'
5
+
6
+const noop = (x) => x
7
+
8
+export default React.forwardRef(function EditForm(props, ref) {
9
+  const {
10
+    rowId,
11
+    rowKey,
12
+    request,
13
+    transform, // 提交时转换数据
14
+    convertValue, // 初始化表单时数值转换
15
+    maxWidth = '800px',
16
+    renderItems,
17
+    formRef,
18
+    initialValues, // 初始化 form 只生效一次
19
+    initialData, // 初始化 form 每次都生效
20
+    onFinish,
21
+    ...leftProps
22
+  } = props
23
+
24
+  const navigate = useNavigate()
25
+  const [form] = Form.useForm()
26
+
27
+  if (formRef) {
28
+    formRef.current = form
29
+  }
30
+
31
+  // 表单提交
32
+  const onSubmit = async (values) => {
33
+    // 可能需要转换下格式
34
+    if (rowId) {
35
+      values[rowKey] = rowId
36
+    }
37
+    const data = transform ? transform(rowId, values) : values
38
+
39
+
40
+    
41
+    if (rowId && request?.update) {
42
+      const res = await request.update(rowId, data)
43
+      if (onFinish) {
44
+        onFinish(res)
45
+      }
46
+    } else if (request?.save) {
47
+      const res = await request.save(data)
48
+      if (onFinish) {
49
+        onFinish(res)
50
+      }
51
+    }
52
+
53
+    return true
54
+  }
55
+
56
+  // 查询详情
57
+  React.useEffect(() => {
58
+    form.resetFields()
59
+
60
+    if (rowId && request?.get) {
61
+      request.get(rowId).then((res) => {
62
+        // 此处可能需要转换下数据格式
63
+        const formData = convertValue ? convertValue(res) : res
64
+        form.setFieldsValue(formData)
65
+      })
66
+    }
67
+  }, [request?.get, rowId])
68
+
69
+  React.useEffect(() => {
70
+    form.resetFields()
71
+    const formData = convertValue ? convertValue(initialData) : initialData
72
+    form.setFieldsValue(formData)
73
+  }, [initialData])
74
+
75
+  React.useImperativeHandle(ref, () => ({
76
+    form: form,
77
+  }))
78
+
79
+  return (
80
+    <ProForm
81
+      form={form}
82
+      onFinish={onSubmit}
83
+      style={{ maxWidth }}
84
+      layout="horizontal"
85
+      labelCol={{ flex: '0 0 120px' }}
86
+      initialValues={initialValues}
87
+      submitter={{
88
+        render: (_, doms) => [
89
+          <Row gutter={48}>
90
+            <Col flex="200px"></Col>
91
+            <Col>
92
+              <Button type="primary" htmlType="submit">
93
+                提交
94
+              </Button>
95
+            </Col>
96
+            <Col>
97
+              <Button onClick={() => navigate(-1)}>返回</Button>
98
+            </Col>
99
+          </Row>,
100
+        ],
101
+      }}
102
+      {...leftProps}
103
+    >
104
+      {renderItems()}
105
+    </ProForm>
106
+  )
107
+})

+ 15
- 0
src/components/EditableTag/Tag.jsx Näytä tiedosto

@@ -0,0 +1,15 @@
1
+import React from 'react';
2
+import { Button } from 'antd';
3
+import { CloseOutlined } from '@ant-design/icons';
4
+import './style.less'
5
+
6
+export default (props) => {
7
+  const { size, type, onDelete, children } = props;
8
+
9
+  return (
10
+    <div className='tag-btn-group'>
11
+      <Button size={size} type={type}>{children}</Button>
12
+      <Button size={size} type="primary" icon={<CloseOutlined />} onClick={onDelete} />
13
+    </div>
14
+  )
15
+}

+ 21
- 0
src/components/EditableTag/index.jsx Näytä tiedosto

@@ -0,0 +1,21 @@
1
+import React from 'react';
2
+import { Button } from 'antd';
3
+import Tag from './Tag';
4
+import './style.less'
5
+
6
+export default (props) => {
7
+  const { size, type, list = [], onDelete, keyFuc, labelFunc } = props;
8
+
9
+  return (
10
+    <div className='editable-tag-box'>
11
+      {
12
+        list.map((item, index) => {
13
+          const key = keyFuc(item, index);
14
+          const label = labelFunc(item, index);
15
+
16
+          return <Tag key={key} size={size} type={type} onDelete={() => onDelete(item)}>{label}</Tag>
17
+        })
18
+      }
19
+    </div>
20
+  )
21
+}

+ 27
- 0
src/components/EditableTag/style.less Näytä tiedosto

@@ -0,0 +1,27 @@
1
+.tag-btn-group {
2
+  .ant-btn {
3
+    vertical-align: middle;
4
+  }
5
+
6
+  .ant-btn:first-child {
7
+    border-right: none;
8
+  }
9
+
10
+  .ant-btn:last-child {
11
+    // border-left: none;
12
+    border-top-left-radius: 0;
13
+    border-bottom-left-radius: 0;
14
+    margin-left: -1px;
15
+  }
16
+}
17
+
18
+.editable-tag-box {
19
+  display: flex;
20
+  flex-wrap: wrap;
21
+
22
+  .tag-btn-group {
23
+    flex: none;
24
+    box-sizing: border-box;
25
+    padding: 1em;
26
+  }
27
+}

+ 18
- 0
src/components/EmojiSelect/Emoji.jsx Näytä tiedosto

@@ -0,0 +1,18 @@
1
+import React from 'react';
2
+import { Collapse } from 'antd';
3
+import EmojiPart from './Part';
4
+import { data } from './data';
5
+
6
+export default function Emoji(props) {
7
+  const { value, onChange } = props;
8
+
9
+  const items = React.useMemo(() => Object.keys(data).map(key => ({
10
+    key,
11
+    label: data[key].label,
12
+    children: <EmojiPart data={data[key]} value={value} onChange={onChange} />
13
+  })), [value]);
14
+
15
+  return (
16
+    <Collapse items={items} defaultActiveKey={items[0].key} />
17
+  );
18
+}

+ 52
- 0
src/components/EmojiSelect/Part.jsx Näytä tiedosto

@@ -0,0 +1,52 @@
1
+import React from 'react';
2
+import classNames from 'classnames';
3
+import { Button } from 'antd';
4
+import styles from './style.module.less';
5
+
6
+const initNum = 20;
7
+
8
+export default function EmojiPart(props) {
9
+  const { data, value, onChange } = props;
10
+  const { label, path, num } = data;
11
+  const [current, setCurrent] = React.useState(initNum);
12
+
13
+  const list = new Array(current).fill();
14
+
15
+  const onMore = () => {
16
+    const next = current + 20;
17
+    if (next >= num) {
18
+      setCurrent(num);
19
+    } else {
20
+      setCurrent(next);
21
+    }
22
+  };
23
+
24
+  return (
25
+    <div className={styles['emoji-part']}>
26
+      <div className={styles.body}>
27
+      {
28
+        list.map((_, inx) => {
29
+          const src = `${path}${inx + 1}.png`;
30
+
31
+          return (
32
+            <img
33
+              key={inx}
34
+              alt=""
35
+              src={getRealPath(src)}
36
+              className={classNames({ [styles.active]: value === src })}
37
+              onClick={() => onChange(src)}
38
+            />
39
+          )
40
+        })
41
+      }
42
+      {
43
+        current < num && (
44
+          <Button type='link' onClick={onMore}>
45
+            加载更多
46
+          </Button>
47
+        )
48
+      }
49
+      </div>
50
+    </div>
51
+  );
52
+}

+ 58
- 0
src/components/EmojiSelect/data.js Näytä tiedosto

@@ -0,0 +1,58 @@
1
+
2
+export const data = {
3
+  activity : {
4
+    label: '活动',
5
+    path: 'emoji-emotions/activity/64/',
6
+    num: 185,
7
+  },
8
+  animals : {
9
+    label: '动物',
10
+    path: 'emoji-emotions/animals/64/',
11
+    num: 209,
12
+  },
13
+  buildings : {
14
+    label: '建筑',
15
+    path: 'emoji-emotions/buildings/64/',
16
+    num: 100,
17
+  },
18
+  food : {
19
+    label: '饮食',
20
+    path: 'emoji-emotions/food-and-drinks/64/',
21
+    num: 408,
22
+  },
23
+  gestures : {
24
+    label: '手势',
25
+    path: 'emoji-emotions/gestures/64/',
26
+    num: 96,
27
+  },
28
+  nature : {
29
+    label: '自然',
30
+    path: 'emoji-emotions/nature/64/',
31
+    num: 168,
32
+  },
33
+  objects : {
34
+    label: '工具',
35
+    path: 'emoji-emotions/objects/64/',
36
+    num: 192,
37
+  },
38
+  people : {
39
+    label: '人类',
40
+    path: 'emoji-emotions/people/64/',
41
+    num: 173,
42
+  },
43
+  smileys : {
44
+    label: '笑脸',
45
+    path: 'emoji-emotions/smileys/64/',
46
+    num: 432,
47
+  },
48
+  symbols : {
49
+    label: '符号',
50
+    path: 'emoji-emotions/symbols/64/',
51
+    num: 336,
52
+  },
53
+  travel: {
54
+    label: '旅行',
55
+    path: 'emoji-emotions/travel-and-places/64/',
56
+    num: 212,
57
+  },
58
+}

+ 47
- 0
src/components/EmojiSelect/index.jsx Näytä tiedosto

@@ -0,0 +1,47 @@
1
+import React from 'react';
2
+import { Button, Drawer, Image } from 'antd';
3
+import Emoji from './Emoji';
4
+
5
+export default function EmojiSelect(props) {
6
+
7
+  const { value, onChange } = props;
8
+  const [open, setOpen] = React.useState(false);
9
+  const [current, setCurrent] = React.useState(value);
10
+
11
+  const handleChange = src => {
12
+    setCurrent(src);
13
+  }
14
+
15
+  const onSubmit = () => {
16
+    setOpen(false);
17
+    onChange(current);
18
+  }
19
+
20
+  React.useEffect(() => {
21
+    setCurrent(value);
22
+  }, [value])
23
+
24
+  return (
25
+    <div>
26
+      {
27
+        value 
28
+          ? <Image src={getRealPath(value)} style={{ width: '48px', height: '48px' }} preview={false} onClick={() => setOpen(true)} />
29
+          : <Button type="primary" onClick={() => setOpen(true)}>选择图标</Button>
30
+      }
31
+      <Drawer
32
+        title="图标选择"
33
+        width='60vw'
34
+        open={open}
35
+        placement="right"
36
+        extra={(
37
+          <Button type="primary" onClick={onSubmit} disabled={!current || (value && value == current)}>
38
+            确定
39
+          </Button>
40
+        )}
41
+        onClose={() => setOpen(false)}
42
+      >
43
+        <Emoji onChange={handleChange} value={current} />
44
+      </Drawer>
45
+    </div>
46
+  );
47
+}

+ 20
- 0
src/components/EmojiSelect/style.module.less Näytä tiedosto

@@ -0,0 +1,20 @@
1
+
2
+.emoji-part {
3
+  .body {
4
+    display: flex;
5
+    flex-wrap: wrap;
6
+    align-items: center;
7
+    gap: 24px;
8
+    
9
+    img {
10
+      width: 48px;
11
+      height: 48px;
12
+      cursor: pointer;
13
+      border: 2px solid transparent;
14
+
15
+      &.active {
16
+        border: 2px solid red;
17
+      }
18
+    }
19
+  }
20
+}

+ 14
- 0
src/components/Error/index.jsx Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import React from 'react';
2
+import { Result } from 'antd';
3
+
4
+export default function Error(props) {
5
+  const { code = '404', ...leftProps } = props;
6
+
7
+  return (
8
+    <Result
9
+      {...leftProps}
10
+      status={code}
11
+      title={code}
12
+    />
13
+  );
14
+}

+ 11
- 0
src/components/Loading/index.jsx Näytä tiedosto

@@ -0,0 +1,11 @@
1
+import React from 'react';
2
+import { Spin } from 'antd';
3
+import styles from './style.module.less';
4
+
5
+export default function Loading(props) {
6
+  const { loading = true, ...leftProps } = props;
7
+
8
+  return (
9
+    <Spin {...leftProps} wrapperClassName={styles.loading} spinning={loading} />
10
+  );
11
+}

+ 4
- 0
src/components/Loading/style.module.less Näytä tiedosto

@@ -0,0 +1,4 @@
1
+
2
+.loading {
3
+  height: 100%;
4
+}

+ 24
- 0
src/components/Massage/index.jsx Näytä tiedosto

@@ -0,0 +1,24 @@
1
+import { ModalForm } from "@ant-design/pro-components";
2
+import { Button, Modal } from "antd";
3
+import React from "react";
4
+import  styles from "./index.module.less";
5
+export default (props) => {
6
+  const { title = "提示", open, setOpen, errList } = props;
7
+
8
+  const onChang = () => {
9
+    setOpen(false);
10
+  };
11
+  return (
12
+    <Modal
13
+      title={title}
14
+      open={open}
15
+      onCancel={onChang}
16
+      footer={false}
17
+      wrapClassName={styles.modalContainer}
18
+    >
19
+      {(errList || []).map((x, index) => (
20
+          <p key={index}>{x}</p>
21
+      ))}
22
+    </Modal>
23
+  );
24
+};

+ 24
- 0
src/components/Massage/index.module.less Näytä tiedosto

@@ -0,0 +1,24 @@
1
+// {
2
+//     .ant-modal {
3
+
4
+
5
+
6
+//         .ant-modal-body {
7
+//             overflow-y: scroll;
8
+//             height: 90%;
9
+//         }
10
+//     }
11
+// }
12
+
13
+.modalContainer {
14
+    :global(.ant-modal-content) {
15
+        height: 700px;
16
+        overflow: hidden;
17
+
18
+    }
19
+
20
+    :global(.ant-modal-body) {
21
+        overflow-y: scroll;
22
+        height: 90%;
23
+    }
24
+}

+ 4
- 0
src/components/Money/float.js Näytä tiedosto

@@ -0,0 +1,4 @@
1
+const epsilonN = N => num => Math.round( num * N + Number.EPSILON ) / N;
2
+const epsilon2 = epsilonN(1e2);
3
+
4
+export default epsilon2;

+ 30
- 0
src/components/Money/index.jsx Näytä tiedosto

@@ -0,0 +1,30 @@
1
+import { InputNumber } from "antd"
2
+import { useEffect, useState } from "react"
3
+import epsilon2 from './float'
4
+
5
+export default (props) => {
6
+
7
+  const { value, onChange, ...leftProps } = props
8
+
9
+  const [money, setMoney] = useState(0)
10
+
11
+  useEffect(() => {
12
+    setMoney(epsilon2(value / 100))
13
+  }, [value])
14
+
15
+  const handleChange = (val) => {
16
+    onChange(epsilon2(val * 100))
17
+  }
18
+
19
+  return (
20
+    <InputNumber
21
+      min='0'
22
+      {...leftProps}
23
+      value={money}
24
+      onChange={handleChange}
25
+      precision={2}
26
+      formatter={value => `¥ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
27
+      parser={value => value.replace(/\¥\s?|(,*)/g, '')}
28
+    />
29
+  )
30
+}

+ 12
- 0
src/components/Page/Curd.jsx Näytä tiedosto

@@ -0,0 +1,12 @@
1
+import React from "react";
2
+import Page from './index';
3
+import Curd from "../Curd";
4
+
5
+export default (props) => {
6
+  
7
+  return (
8
+    <Page>
9
+      <Curd {...props} />
10
+    </Page>
11
+  );
12
+};

+ 49
- 0
src/components/Page/Edit.jsx Näytä tiedosto

@@ -0,0 +1,49 @@
1
+import React from 'react';
2
+import { Button, Card, Row, Col, Form } from 'antd';
3
+import { ProForm } from '@ant-design/pro-components';
4
+import EditForm from '@/components/EditForm';
5
+import { useSearchParams, useNavigate } from "react-router-dom";
6
+import useBool from '@/utils/hooks/useBool';
7
+import Page from './index';
8
+
9
+export default function EditPage(props) {
10
+
11
+  const {
12
+    title,
13
+    onFinish,
14
+    ...leftProps
15
+  } = props;
16
+
17
+  const navigate = useNavigate();
18
+
19
+  // 获取 id query 参数, 如果存在说明是编辑,而不是新增
20
+  const [params] = useSearchParams();
21
+  const id = params.get("id");
22
+  
23
+  const navBack = React.useCallback(() => {
24
+    const t = setTimeout(() => {
25
+      navigate(-1);
26
+      clearTimeout(t);
27
+    }, 600);
28
+  }, [navigate]);
29
+
30
+  const handleFinish = (res) => {
31
+    if (onFinish) {
32
+      onFinish(res);
33
+    } else {
34
+      navBack();
35
+    }
36
+  }
37
+
38
+  return (
39
+    <Page title={title}>
40
+      <Card title={title}>
41
+        <EditForm
42
+          rowId={id}
43
+          onFinish={handleFinish}
44
+          {...leftProps}
45
+        />
46
+      </Card>
47
+    </Page>
48
+  )
49
+}

+ 46
- 0
src/components/Page/List.jsx Näytä tiedosto

@@ -0,0 +1,46 @@
1
+import React from 'react'
2
+import { message } from 'antd'
3
+import Page from './index'
4
+import XTable from '../XTable'
5
+
6
+export default React.forwardRef((props, ref) => {
7
+  /**
8
+   * request 与 ProTable 定义不同,这个只是普通的接口即可
9
+   */
10
+  const {
11
+    attach,
12
+    ...leftProps
13
+  } = props
14
+  const [loading, setLoading] = React.useState(false);
15
+  const actionRef = React.useRef()
16
+  const hideRef = React.useRef()
17
+
18
+  React.useImperativeHandle(
19
+    ref,
20
+    () => {
21
+      const showLoading = (msg) => (hideRef.current = message.loading(msg || '操作中, 请稍候...'))
22
+      const hideLoading = () => hideRef.current && hideRef.current()
23
+      const reload = () => actionRef.current?.reload && actionRef.current.reload()
24
+
25
+      return {
26
+        showLoading,
27
+        hideLoading,
28
+        reload,
29
+        setLoading,
30
+        actionRef: actionRef,
31
+      }
32
+    },
33
+    []
34
+  )
35
+
36
+  return (
37
+    <Page>
38
+      {attach}
39
+      <XTable
40
+        ref={actionRef}
41
+        loading={loading}
42
+        {...leftProps}
43
+      />
44
+    </Page>
45
+  )
46
+})

+ 68
- 0
src/components/Page/index.jsx Näytä tiedosto

@@ -0,0 +1,68 @@
1
+import React from 'react';
2
+import { Typography } from 'antd';
3
+import { useNavigate, useSearchParams } from 'react-router-dom';
4
+import useRoute from '@/utils/hooks/useRoute';
5
+import { ArrowLeftOutlined } from '@ant-design/icons';
6
+
7
+
8
+const pageStyle = {
9
+  // margin: '24px 24px 0 24px',
10
+  // margin: '24px',
11
+  // marginTop: 0,
12
+  // minHeight: 'calc(100% - 48px)',
13
+  height: '100%',
14
+}
15
+
16
+const titleStyle = {
17
+  display: 'flex',
18
+  justifyContent: 'space-between',
19
+  alignItems: 'center',
20
+  paddingBottom: '12px',
21
+}
22
+
23
+const backStyle = {
24
+  color: '#333',
25
+  marginRight: '0.5em',
26
+}
27
+
28
+const Title = (props) => {
29
+  const { extra, back } = props;
30
+  const navigate = useNavigate();
31
+
32
+  const goBack = (e) => {
33
+    e.preventDefault();
34
+    e.stopPropagation();
35
+
36
+    navigate(-1);
37
+  }
38
+  return (
39
+    <div style={titleStyle}>
40
+      <Typography.Title level={3} style={{ fontWeight: 400, flex: 'none' }}>
41
+        {
42
+          back && (
43
+            <span onClick={goBack}><ArrowLeftOutlined style={backStyle} /></span>
44
+          )
45
+        }
46
+        {props.children}
47
+      </Typography.Title>
48
+      <div style={{ flex: 'none' }}>{extra}</div>
49
+    </div>
50
+  )
51
+}
52
+
53
+export default (props) => {
54
+  const { extra, back } = props;
55
+
56
+  const { meta = {} } = useRoute() || {};
57
+  const style = meta.noLayout ? { height: '100%' } : pageStyle;
58
+  const title = props.title || meta.title;
59
+  const [searchParams] = useSearchParams();
60
+  const backParam = searchParams.get('back')
61
+  const isBack = back || new Boolean(backParam === 'false' ? 0 : backParam).valueOf();
62
+  return (
63
+    <div style={style}>
64
+      {title && !meta.noLayout && <Title extra={extra} back={isBack}>{title}</Title>}
65
+      {props.children}
66
+    </div>
67
+  )
68
+}

+ 67
- 0
src/components/Staff/StaffSearch.jsx Näytä tiedosto

@@ -0,0 +1,67 @@
1
+import React from 'react';
2
+import { Card, List, Input } from 'antd';
3
+import { getSysUser } from '@/service/authCenter';
4
+
5
+const { Search } = Input;
6
+const actStyle = {
7
+  background: '#e6f4ff',
8
+  borderLeft: '3px solid #1890ff',
9
+};
10
+const itStyle = {
11
+  width: '100%',
12
+  padding: '0 1em',
13
+  display: 'flex',
14
+  cursor: 'pointer',
15
+};
16
+
17
+export default (props) => {
18
+  const { onChange } = props;
19
+
20
+  const [loading, setLoading] = React.useState(false);
21
+  const [list, setList] = React.useState([]);
22
+  const [curStaff, setCurStaff] = React.useState();
23
+
24
+  const queryData = React.useCallback((params = {}) => {
25
+    setLoading(true);
26
+    getSysUser({...params, dict: true}).then((res) => {
27
+      setList(res.records || []);
28
+      setLoading(false);
29
+    }).catch(() => {
30
+      setLoading(false);
31
+    });
32
+  }, []);
33
+
34
+  const onSearch = (val) => {
35
+    queryData({pageSize: 500, name: val});
36
+  }
37
+
38
+  const onItemClick = (it) => {
39
+    setCurStaff(it);
40
+    onChange(it.userId, it);
41
+  }
42
+
43
+  React.useEffect(() => {
44
+    queryData({pageSize: 20});
45
+  }, []);
46
+
47
+  return (
48
+    <Card title={<Search placeholder="请输入名称" onSearch={onSearch} enterButton />} loading={loading}>
49
+      <List
50
+        rowKey="userId"
51
+        dataSource={list}
52
+        renderItem={it => (
53
+          <List.Item
54
+            style={curStaff && curStaff.userId == it.userId ? actStyle : undefined }
55
+            onClick={() => onItemClick(it)}
56
+          >
57
+            <div style={itStyle}>
58
+              <div style={{ flex: 'none' }}>{`[${it.hxId}]`}</div>
59
+              <div style={{ flex: 1, marginLeft: '1em' }}>{it.name}</div>
60
+              <div style={{ flex: 1, marginLeft: '1em', textAlign: 'right' }}>{it.orgName}</div>
61
+            </div>                  
62
+          </List.Item>
63
+        )}
64
+      />
65
+    </Card>
66
+  )
67
+}

+ 62
- 0
src/components/Upload/ModalForms.jsx Näytä tiedosto

@@ -0,0 +1,62 @@
1
+import { InboxOutlined } from "@ant-design/icons";
2
+import { ModalForm } from "@ant-design/pro-components/es";
3
+import { Modal, Upload, message } from "antd";
4
+import React, { forwardRef, useRef, useState } from "react";
5
+
6
+export default (props) => {
7
+  const { visible, setVisible, onFinish, onSuccess,uploadFile } = props;
8
+  const [fileList, setFileList] = useState([]);
9
+
10
+  const onChange = (info) => {
11
+    const { status, response } = info.file;
12
+    const newFileList = [...info.fileList];
13
+    setFileList(newFileList);
14
+
15
+    if (status === "done") {
16
+      onFinish(true);
17
+      setVisible(false);
18
+      setFileList([]);
19
+      onSuccess(response);
20
+    } else if (status === "error") {
21
+      onFinish(true);
22
+      setVisible(false);
23
+      setFileList([]);
24
+    }
25
+
26
+
27
+  };
28
+  const beforeUpload = (file, fileList) => {
29
+    let extension = file.name.split(".")[1] === "xlsx";
30
+    if (!extension) {
31
+      message.warning("导入文件只能是xlsx格式!");
32
+      return false;
33
+    }
34
+  };
35
+
36
+  const onCancel = (e) => {
37
+    setFileList([]);
38
+    setVisible(false);
39
+  };
40
+
41
+  return (
42
+    <Modal
43
+      title="模板导入"
44
+      open={visible}
45
+      onOpenChange={setVisible}
46
+      footer={false}
47
+      onCancel={onCancel}
48
+    >
49
+      <Upload.Dragger
50
+        name="file"
51
+        beforeUpload={beforeUpload}
52
+        onChange={onChange}
53
+        customRequest={uploadFile}
54
+        fileList={fileList}
55
+      >
56
+        <p className="ant-upload-drag-icon">
57
+          <InboxOutlined />
58
+        </p>
59
+      </Upload.Dragger>
60
+    </Modal>
61
+  );
62
+};

+ 10
- 0
src/components/Upload/Upload.jsx Näytä tiedosto

@@ -0,0 +1,10 @@
1
+import { Upload } from 'antd';
2
+import { uploadFile } from './request'
3
+
4
+export default (props) => {
5
+  return (
6
+    <Upload {...props} customRequest={uploadFile} >
7
+      {props.children}
8
+    </Upload>
9
+  )
10
+}

+ 72
- 0
src/components/Upload/UploadImage.jsx Näytä tiedosto

@@ -0,0 +1,72 @@
1
+import React, { useState } from 'react'
2
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'
3
+import Upload from './Upload'
4
+
5
+function beforeUpload(file) {
6
+  const isImage =
7
+    file.type === 'image/jpeg' ||
8
+    file.type === 'image/png' ||
9
+    file.type === 'image/gif'
10
+  if (!isImage) {
11
+    message.error('请上传 JPG , PNG或GIF 图片!')
12
+  }
13
+  const isLt10M = file.size / 1024 / 1024 < 10
14
+  if (!isLt10M) {
15
+    message.error('图片大小必须小于 10MB!')
16
+  }
17
+
18
+  return isImage && isLt10M
19
+}
20
+
21
+const UploadButton = (props) => (
22
+  <div>
23
+    {props.loading ? <LoadingOutlined /> : <PlusOutlined />}
24
+    <div style={{ marginTop: 8 }}>上传</div>
25
+  </div>
26
+)
27
+
28
+export default (props) => {
29
+  const { value, onChange } = props
30
+
31
+  const [loading, setLoading] = useState(false)
32
+
33
+  const handleChange = (info) => {
34
+
35
+    if (info.file.status === 'uploading') {
36
+      setLoading(true)
37
+      return
38
+    }
39
+    if (info.file.status === 'error') {
40
+      setLoading(false)
41
+      return
42
+    }
43
+
44
+    if (info.file.status === 'done') {
45
+      setLoading(false)
46
+      const { url, fileType } = info.file.response
47
+      onChange(url)
48
+    }
49
+  }
50
+
51
+  const previewUrl = getRealPath ? getRealPath(value) : value
52
+
53
+  return (
54
+    <Upload
55
+      listType="picture-card"
56
+      className="image-uploader"
57
+      showUploadList={false}
58
+      beforeUpload={beforeUpload}
59
+      onChange={handleChange}
60
+    >
61
+      {value ? (
62
+        <img
63
+          src={previewUrl}
64
+          alt="avatar"
65
+          style={{ width: '100%', height: '100%' }}
66
+        />
67
+      ) : (
68
+        <UploadButton loading={loading} />
69
+      )}
70
+    </Upload>
71
+  )
72
+}

+ 110
- 0
src/components/Upload/UploadImageList.jsx Näytä tiedosto

@@ -0,0 +1,110 @@
1
+
2
+import React, { useEffect, useState } from 'react';
3
+import { Modal } from 'antd'
4
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
5
+import Upload from './Upload';
6
+
7
+function beforeUpload(file) {
8
+  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'|| file.type === 'image/gif';
9
+  if (!isJpgOrPng) {
10
+    message.error('请上传 JPG,PNG 或 GIF 图片!');
11
+  }
12
+  const isLt10M = file.size / 1024 / 1024 < 10;
13
+  if (!isLt10M) {
14
+    message.error('图片大小必须小于 10MB!');
15
+  }
16
+
17
+  return isJpgOrPng && isLt10M;
18
+}
19
+
20
+const UploadButton = (props) => (
21
+  <div>
22
+    {props.loading ? <LoadingOutlined /> : <PlusOutlined />}
23
+    <div style={{ marginTop: 8 }}>上传</div>
24
+  </div>
25
+);
26
+
27
+const defaultInput = x => ({ uid: x, url: x });
28
+const defaultOutput = x => x.url;
29
+
30
+export default (props) => {
31
+  const { value, onChange, input = defaultInput, output = defaultOutput } = props;
32
+
33
+  const [loading, setLoading] = useState(false);
34
+  const [previewVisible, setPreviewVisible] = useState(false);
35
+  const [previewImage, setPreviewImage] = useState();
36
+  const [fileList, setFileList] = useState([]);
37
+
38
+  const handleChange = info => {
39
+    if (info.file.status === 'uploading') {
40
+      setLoading(true);
41
+      return;
42
+    }
43
+    if (info.file.status === 'error') {
44
+      setLoading(false);
45
+      return;
46
+    }
47
+
48
+    if (info.file.status === 'removed') {
49
+      toggleChange(info.fileList)
50
+    }
51
+  };
52
+
53
+  const handleSuccess = ({url}) => {
54
+    setLoading(false)
55
+    const inx = fileList?.length ? fileList.length + 1 : 1
56
+    const list = fileList.concat({
57
+      url,
58
+      uid: `new-${inx}`,
59
+      status: 'done'
60
+    })
61
+    toggleChange(list)
62
+  }
63
+
64
+  const toggleChange = (list) => {
65
+    onChange(list.map(x => output(x)))
66
+  }
67
+
68
+  const handlePreview = (file) => {
69
+    setPreviewImage(file.url)
70
+    setPreviewVisible(true)
71
+  }
72
+
73
+  useEffect(() => {
74
+    const lst = (value || []).map((it) => {
75
+      const { uid, url } = input(it)
76
+      return {
77
+        uid,
78
+        url,
79
+        status: 'done',
80
+        raw: it,
81
+      }
82
+    })
83
+    setFileList(lst)
84
+  }, [value])
85
+
86
+  return (
87
+    <>
88
+      <Upload
89
+        listType="picture-card"
90
+        className="image-uploader"
91
+        fileList={fileList}
92
+        beforeUpload={beforeUpload}
93
+        onChange={handleChange}
94
+        onPreview={handlePreview}
95
+        onSuccess={handleSuccess}
96
+      >
97
+        <UploadButton loading={loading} />
98
+      </Upload>
99
+      <Modal
100
+        visible={previewVisible}
101
+        title="图片预览"
102
+        footer={null}
103
+        onCancel={() => setPreviewVisible(false)}
104
+      >
105
+        <img alt="example" style={{ width: '100%' }} src={previewImage} />
106
+      </Modal>
107
+    </>
108
+  );
109
+}
110
+

+ 94
- 0
src/components/Upload/UploadVideo.jsx Näytä tiedosto

@@ -0,0 +1,94 @@
1
+import React, { useState, useRef, useEffect } from 'react';
2
+import { Button } from 'antd'
3
+import Upload from './Upload';
4
+// import { uplodeNoteImage } from '@/services/note';
5
+import styles from './style.less';
6
+
7
+function beforeUpload(file) {
8
+  const isMp4 = file.type === 'video/mp4';
9
+  if (!isMp4) {
10
+    message.error('请上传 MP4 视频!');
11
+  }
12
+
13
+  return isMp4;
14
+}
15
+
16
+export default (props) => {
17
+  //onPoster 改变主图事件   isScreenshot判断父组件是否需要截屏按钮
18
+  const { value, onChange, onPoster, isScreenshot } = props;
19
+  const [loading, setLoading] = useState(false);//上传按钮loading框
20
+  const [capturing, setCapturing] = useState(false);//截屏按钮loading框
21
+  const [video, setVideo] = useState()//当前上传的视频
22
+  const [isVideoReady, setIsVideoReady] = useState(false)//判断视频是否加载成功
23
+  
24
+  const canvasRef = useRef()
25
+  const videoRef = useRef()
26
+
27
+  useEffect(() => {
28
+    setVideo(value)//父组件的视频获取成功时改变当前页面的视频
29
+  }, [value])
30
+
31
+  const handleChange = info => {
32
+    if (info.file.status === 'uploading') {
33
+      setLoading(true);
34
+      return;
35
+    }
36
+    if (info.file.status === 'error') {
37
+      setLoading(false);
38
+      return;
39
+    }
40
+    if (info.file.status === 'done') {
41
+      onChange(info.file.response);
42
+      setLoading(false);
43
+    }
44
+  };
45
+
46
+  //开始截图
47
+  const handelScreenshot = () => {
48
+    const ctx = canvasRef.current.getContext("2d");  
49
+    ctx.drawImage(videoRef.current, 0, 0);    
50
+    const image = canvasRef.current.toDataURL("image/png", 0.9)
51
+    setCapturing(true)
52
+    // uplodeNoteImage({ base64: image }).then((res) => {
53
+    //   onPoster(res)
54
+    //   setCapturing(false)
55
+    // })
56
+  }
57
+
58
+  //视频加载成功事件
59
+  const handleVideoReady = (e) => {
60
+    setIsVideoReady(true)
61
+    canvasRef.current.width = e.target.videoWidth;
62
+    canvasRef.current.height = e.target.videoHeight;
63
+  }
64
+
65
+  return (
66
+    <div className={styles['video-uploader']}>
67
+      <Upload
68
+        className="image-uploader"
69
+        showUploadList={false}
70
+        beforeUpload={beforeUpload}
71
+        onChange={handleChange}
72
+      >
73
+        <div style={{ maxWidth: '1px', maxHeight: '1px', overflow: 'hidden' }}>
74
+          <canvas ref={canvasRef}></canvas>
75
+        </div>
76
+        <Button loading={loading}>
77
+          点击上传视频 (MP4)
78
+        </Button>
79
+
80
+      </Upload>
81
+      {
82
+        isScreenshot && (
83
+          // disabled={!isVideoReady} 当视频准备成功时截屏按钮可以点击  否则不能点击
84
+          <Button loading={capturing} style={{ marginLeft: '1em' }} onClick={handelScreenshot} disabled={!isVideoReady}>截屏</Button>
85
+        )
86
+      }
87
+      {
88
+        !!video && (
89
+          <video ref={videoRef} width="320" height='500' crossOrigin='anonymous' controls src={video} onCanPlay={handleVideoReady}></video>
90
+        )
91
+      }
92
+    </div>
93
+  );
94
+}

+ 11
- 0
src/components/Upload/index.jsx Näytä tiedosto

@@ -0,0 +1,11 @@
1
+import Upload from './Upload';
2
+import UploadImage from './UploadImage';
3
+import UploadImageList from './UploadImageList';
4
+import UploadVideo from './UploadVideo';
5
+
6
+export {
7
+  Upload,
8
+  UploadImage,
9
+  UploadImageList,
10
+  UploadVideo,
11
+}

+ 44
- 0
src/components/Upload/request.js Näytä tiedosto

@@ -0,0 +1,44 @@
1
+import request from "@/utils/request";
2
+
3
+const upload = (file,url ) => {
4
+  const formData = new FormData();
5
+
6
+  formData.append("file", file);
7
+  return request(`/upload/${url}`, { method: "POST", data: formData });
8
+};
9
+
10
+/**
11
+ * 上传文件
12
+ * @returns
13
+ */
14
+export function uploadFile(params) {
15
+  const { file, onSuccess, onError } = params;
16
+  upload(file,"taRotation")
17
+    .then((res) => {
18
+      onSuccess(res);
19
+    })
20
+    .catch((e) => {
21
+      onError(e);
22
+    });
23
+  return {
24
+    abort: () => {},
25
+  };
26
+}
27
+
28
+/**
29
+ * 上传文件
30
+ * @returns
31
+ */
32
+export function uploadFileTaMandatoryLeave(params) {
33
+  const { file, onSuccess, onError } = params;
34
+  upload(file, "taMandatoryLeave")
35
+    .then((res) => {
36
+      onSuccess(res);
37
+    })
38
+    .catch((e) => {
39
+      onError(e);
40
+    });
41
+  return {
42
+    abort: () => {},
43
+  };
44
+}

+ 7
- 0
src/components/Upload/style.less Näytä tiedosto

@@ -0,0 +1,7 @@
1
+.video-uploader {
2
+  video {
3
+    margin-top: 2em;
4
+    width: 100%;
5
+    max-height: 360px;
6
+  }
7
+}

+ 86
- 0
src/components/Wangeditor/index.jsx Näytä tiedosto

@@ -0,0 +1,86 @@
1
+import React, { useState, useEffect } from "react";
2
+import "@wangeditor/editor/dist/css/style.css";
3
+import { Editor, Toolbar } from "@wangeditor/editor-for-react";
4
+
5
+// 工具栏配置参考
6
+// https://www.cnblogs.com/-roc/p/16400965.html
7
+
8
+const defaultStyle = {
9
+  border: "1px solid #ccc",
10
+  zIndex: 100,
11
+  marginTop: "15px"
12
+}
13
+
14
+function MyEditor(props) {
15
+
16
+  const style = React.useMemo(() => ({ ...defaultStyle, ...(props.style || {}) }), [props.style])
17
+
18
+  const {
19
+    value = "",
20
+    onChange = (e) => {
21
+      setHtml(e);
22
+    },
23
+    toolbarConfig = {
24
+      excludeKeys: ["group-image", "group-video"],
25
+    },
26
+    editorConfig = {
27
+      placeholder: "请输入内容...",
28
+    },
29
+    readonly = false,
30
+  } = props;
31
+  const [editor, setEditor] = useState(null); // 存储 editor 实例
32
+  const [html, setHtml] = useState("");
33
+ 
34
+  // 模拟 ajax 请求,异步设置 html
35
+  useEffect(() => {
36
+    setHtml(value || "");
37
+  }, [value]);
38
+
39
+  // 及时销毁 editor
40
+  useEffect(() => {
41
+    return () => {
42
+      if (editor == null) return;
43
+      editor.destroy();
44
+      setEditor(null);
45
+    };
46
+  }, [editor]);
47
+
48
+  function insertText() {
49
+    if (editor == null) return;
50
+    editor.insertText(" hello ");
51
+  }
52
+
53
+  function printHtml() {
54
+    if (editor == null) return;
55
+  }
56
+
57
+  const handleChange = (editor) => {
58
+    const raw = editor.getHtml();
59
+    if (raw !== '<p><br></p>') {
60
+      onChange(raw)
61
+    }
62
+  }
63
+
64
+  return !readonly ? (
65
+    <div className={props.className} style={style}>
66
+      <Toolbar
67
+        editor={editor}
68
+        defaultConfig={toolbarConfig}
69
+        mode="default"
70
+        style={{ borderBottom: "1px solid #ccc" }}
71
+      />
72
+      <Editor
73
+        defaultConfig={editorConfig}
74
+        value={html}
75
+        onCreated={setEditor}
76
+        onChange={handleChange}
77
+        mode="default"
78
+        style={{ height: "500px" }}
79
+      />
80
+    </div>
81
+  ) : (
82
+    <div dangerouslySetInnerHTML={{ __html: value }}></div>
83
+  );
84
+}
85
+
86
+export default MyEditor;

+ 36
- 0
src/components/XTable/components/Actions.jsx Näytä tiedosto

@@ -0,0 +1,36 @@
1
+import React from 'react';
2
+import { Dropdown, Space } from 'antd';
3
+import { HolderOutlined } from '@ant-design/icons';
4
+
5
+export default function renderActions(children = []) {
6
+
7
+  // children 小于,等于 2 个直接返回
8
+  if (children.length <= 2) {
9
+    return (
10
+      <Space size={8}>
11
+        {children}
12
+      </Space>
13
+    );
14
+  }
15
+
16
+  // 第一个拿出来
17
+  const first = children.shift();
18
+  const items = children.map((item, index) => {
19
+    return ({
20
+      key: index,
21
+      label: item,
22
+    });
23
+  });
24
+
25
+  return (
26
+    <Space size={8}>
27
+      {first}
28
+      <Dropdown menu={{ items }}>
29
+        <a onClick={(e) => e.preventDefault()}>
30
+          更多
31
+          <HolderOutlined />
32
+        </a>
33
+      </Dropdown>
34
+    </Space>
35
+  );
36
+}

+ 126
- 0
src/components/XTable/index.jsx Näytä tiedosto

@@ -0,0 +1,126 @@
1
+import React from 'react';
2
+import { ProTable } from '@ant-design/pro-components'
3
+import { Button, Popconfirm, message } from 'antd'
4
+import { UploadOutlined, PlusOutlined } from '@ant-design/icons'
5
+import { queryTable } from '@/utils/request'
6
+import renderActions from './components/Actions'
7
+
8
+function XTable(props, ref) {
9
+  /**
10
+   * request 与 ProTable 定义不同,这个只是普通的接口即可
11
+   */
12
+  const {
13
+    request,
14
+    rowKey,
15
+    columns,
16
+    onAdd,
17
+    onDelete,
18
+    onEdit,
19
+    onDetail,
20
+    onExport,
21
+    toolBarRender,
22
+    columnOptionRender,
23
+    option = true,
24
+    ...leftProps
25
+  } = props;
26
+  const actionRef = React.useRef();
27
+  const paramsRef = React.useRef({});
28
+  const api = React.useMemo(() => request ? queryTable(request, { paramsCallback: x => paramsRef.current = x }) : undefined, [request]);
29
+
30
+  // 统一实现操作列
31
+  const newColumns = React.useMemo(
32
+    () => [
33
+      ...columns,
34
+      option ? {
35
+        title: '操作',
36
+        key: 'option',
37
+        fixed: 'right',
38
+        search: false,
39
+        width: 120,
40
+        render: (_, record) => renderActions([
41
+            onDetail && (
42
+              <Button
43
+                style={{ padding: 0 }}
44
+                type="link"
45
+                key="detail"
46
+                onClick={() => onDetail(record)}
47
+              >
48
+                详情
49
+              </Button>
50
+            ),
51
+            onEdit && (
52
+              <Button
53
+                style={{ padding: 0 }}
54
+                type="link"
55
+                key="edit"
56
+                onClick={() => onEdit(record)}
57
+              >
58
+                编辑
59
+              </Button>
60
+            ),
61
+            onDelete && (
62
+              <Popconfirm
63
+                key="delete"
64
+                title="您是否确认删除 ?"
65
+                onConfirm={() => onDelete(record, () => actionRef.current?.reload())}
66
+                okText="确定"
67
+                cancelText="取消"
68
+              >
69
+                <Button style={{ padding: 0 }} type="link" danger>
70
+                  删除
71
+                </Button>
72
+              </Popconfirm>
73
+            ),
74
+            ...(columnOptionRender ? columnOptionRender(_, record) : []),
75
+          ].filter(Boolean)),
76
+      } : false,
77
+    ].filter(Boolean),
78
+    [columns, option, onEdit, onDelete]
79
+  );
80
+
81
+  React.useImperativeHandle(ref, () => actionRef.current);
82
+
83
+  return (
84
+    <ProTable
85
+      rowKey={rowKey}
86
+      columns={newColumns}
87
+      request={api}
88
+      cardBordered
89
+      actionRef={actionRef}
90
+      form={{
91
+        // syncToUrl: true,
92
+        // syncToInitialValues: false,
93
+      }}
94
+      scroll={{ x: 'max-content' }}
95
+      toolBarRender={() =>
96
+        [
97
+          onAdd && (
98
+            <Button
99
+              key="add"
100
+              type="primary"
101
+              icon={<PlusOutlined />}
102
+              onClick={onAdd}
103
+            >
104
+              新增
105
+            </Button>
106
+          ),
107
+          onExport && (
108
+            <Button
109
+              ghost
110
+              key="export"
111
+              type="primary"
112
+              onClick={() => onExport(paramsRef.current)}
113
+              icon={<UploadOutlined />}
114
+            >
115
+              导出
116
+            </Button>
117
+          ),
118
+          ...(toolBarRender ? toolBarRender() : []),
119
+        ].filter(Boolean)
120
+      }
121
+      {...leftProps}
122
+    />
123
+  );
124
+}
125
+
126
+export default React.forwardRef(XTable);

+ 32
- 0
src/components/auth/Auth.jsx Näytä tiedosto

@@ -0,0 +1,32 @@
1
+import React from 'react';
2
+import Error from '@/components/Error';
3
+import useUserModel from '@/store/user';
4
+import Predicate from './Predicate';
5
+
6
+export default function Auth(props) {
7
+  const { code, loading } = props;
8
+  const [user] = useUserModel();
9
+  const permissions = user?.permissions || [];
10
+  
11
+  const fallback = React.useCallback(({ state }) => {
12
+    if (state == 'loading') {
13
+        return loading;
14
+    }
15
+
16
+    return <Error />; 
17
+  }, [loading]);
18
+
19
+  const calc = React.useCallback((roles) => {
20
+    if (!roles || roles.length == 0) {
21
+        return 'loading';
22
+    }
23
+
24
+    return roles.indexOf(code) > -1;
25
+  }, [code]);
26
+
27
+  return (
28
+    <Predicate fallback={fallback} value={permissions} calc={calc}>
29
+      {props.children}
30
+    </Predicate>
31
+  )
32
+}

+ 21
- 0
src/components/auth/AuthDanger.jsx Näytä tiedosto

@@ -0,0 +1,21 @@
1
+import React from 'react';
2
+import { Popconfirm, Button } from 'antd';
3
+import Auth from './index';
4
+
5
+export default (props) => {
6
+  const { code, fallback, title = '请确认进行此操作?', onClick, popProps, ...leftProps } = props;
7
+
8
+  return (
9
+    <Auth code={code} fallback={fallback}>
10
+      <Popconfirm
11
+          okText="确定"
12
+          cancelText="取消"
13
+          {...popProps}
14
+          title={title}
15
+          onConfirm={onClick}
16
+        >
17
+          <Button style={{ padding: 0 }} danger type="link" { ...leftProps } />
18
+        </Popconfirm>      
19
+    </Auth>
20
+  )
21
+}

+ 13
- 0
src/components/auth/Predicate.jsx Näytä tiedosto

@@ -0,0 +1,13 @@
1
+import React from 'react';
2
+
3
+export default function Predicate(props) {
4
+    const { fallback, value, calc } = props;
5
+
6
+    const [next, setNext] = React.useState(calc(value));
7
+
8
+    React.useEffect(() => {
9
+        setNext(calc(value));
10
+    }, [value]);
11
+
12
+    return next === true ? props.children : React.cloneElement(fallback, { state: next });
13
+}

+ 38
- 0
src/components/auth/RequireLogin.jsx Näytä tiedosto

@@ -0,0 +1,38 @@
1
+import React from 'react';
2
+// import { Navigate } from 'react-router-dom';
3
+import useUserModel from '@/store/user';
4
+import Predicate from './Predicate';
5
+
6
+function Fallback (props) {
7
+  const { state, loading } = props;
8
+  const [_, { getCurrentUser }] = useUserModel();
9
+
10
+  React.useEffect(() => {
11
+    if (state == 'loading') {
12
+      getCurrentUser();
13
+    }
14
+  }, [state]);
15
+
16
+  // return <Navigate replace to="/login" />;
17
+  //   window.location.href = getLoginPage();
18
+  return state == 'loading' ? loading : null;
19
+}
20
+
21
+export default function RequireLogin (props) {
22
+  const { loading } = props;
23
+  const [user] = useUserModel();
24
+
25
+  const calc = React.useCallback((user) => {
26
+    if (!user?.user) {
27
+      return 'loading';
28
+    }
29
+
30
+    return user.isLogin || false;
31
+  }, []);
32
+
33
+  return (
34
+    <Predicate fallback={<Fallback loading={loading} />} value={user} calc={calc}>
35
+      {props.children}
36
+    </Predicate>
37
+  )
38
+}

+ 16
- 0
src/components/auth/index.jsx Näytä tiedosto

@@ -0,0 +1,16 @@
1
+import useUserModel from '@/store/user';
2
+
3
+import React from "react";
4
+
5
+export default (props) => {
6
+  const { code, fallback = null, children } = props;
7
+  const [user] = useUserModel();
8
+
9
+  const isOk = React.useMemo(() => {
10
+    if (!code) return true;
11
+    if (!user || user.length < 1) return false;
12
+
13
+    return user.findIndex((x) => x == code) > -1;
14
+  }, [code, user]);
15
+  return isOk ? children : fallback;
16
+};

+ 81
- 0
src/components/chart/index.jsx Näytä tiedosto

@@ -0,0 +1,81 @@
1
+import React, { useRef, useEffect } from 'react';
2
+
3
+// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
4
+import * as echarts from 'echarts/core';
5
+// 引入图表,图表后缀都为 Chart
6
+import {
7
+  BarChart,
8
+  PieChart,
9
+  LineChart,
10
+  GaugeChart,
11
+  RadarChart,
12
+  PictorialBarChart,
13
+} from 'echarts/charts';
14
+// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
15
+import {
16
+  TitleComponent,
17
+  TooltipComponent,
18
+  ToolboxComponent,
19
+  GridComponent,
20
+  DatasetComponent,
21
+  LegendComponent,
22
+  DataZoomComponent,
23
+  TransformComponent,
24
+  VisualMapComponent,
25
+} from 'echarts/components';
26
+// 标签自动布局,全局过渡动画等特性
27
+import { LabelLayout, UniversalTransition } from 'echarts/features';
28
+// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
29
+import { CanvasRenderer } from 'echarts/renderers';
30
+
31
+// 注册必须的组件
32
+echarts.use([
33
+  TitleComponent,
34
+  TooltipComponent,
35
+  ToolboxComponent,
36
+  GridComponent,
37
+  DatasetComponent,
38
+  LegendComponent,
39
+  DataZoomComponent,
40
+  TransformComponent,
41
+  VisualMapComponent,
42
+  BarChart,
43
+  PieChart,
44
+  LineChart,
45
+  GaugeChart,
46
+  RadarChart,
47
+  PictorialBarChart,
48
+  LabelLayout,
49
+  UniversalTransition,
50
+  CanvasRenderer
51
+]);
52
+
53
+export default (props) => {
54
+  const { className, style, option } = props;
55
+
56
+  const domRef = useRef();
57
+  const chartRef = useRef();
58
+
59
+  useEffect(() => {
60
+    if (!chartRef.current) {
61
+      chartRef.current = echarts.init(domRef.current);
62
+    }
63
+
64
+    const resize = () => {
65
+      const t = setTimeout(() => {
66
+        clearTimeout(t);
67
+        chartRef.current.resize();
68
+      }, 100);
69
+    };
70
+
71
+    chartRef.current.setOption(option);
72
+    resize();
73
+
74
+    window.addEventListener('resize', resize);    
75
+    return () => window.removeEventListener('resize', resize);
76
+  }, [option]);
77
+
78
+  return (
79
+    <div className={className} style={style} ref={domRef}></div>
80
+  )
81
+}

+ 76
- 0
src/index.less Näytä tiedosto

@@ -0,0 +1,76 @@
1
+html, body, #root {
2
+  width: 100%;
3
+  height: 100%;
4
+  margin: 0;
5
+}
6
+
7
+:root {
8
+  --theme-color: #fff;
9
+  --theme-front: #000;
10
+  --header-height: 48px;
11
+  --siderbar-width: 240px;
12
+}
13
+
14
+.main-layout {
15
+  height: 100vh;
16
+  display: flex;
17
+  flex-direction: column;
18
+}
19
+
20
+.fixd-header {
21
+  width: 100%;
22
+  position: fixed;
23
+  top: 0;
24
+  height: var(--header-height);
25
+  line-height: var(--header-height);
26
+  z-index: 100;
27
+}
28
+
29
+.ant-layout {
30
+  background-color: #f0f2f5;
31
+}
32
+
33
+.ant-layout-header {
34
+  line-height: var(--header-height);
35
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
36
+  // border-bottom: 1px solid gainsboro;
37
+  z-index: 10;
38
+}
39
+
40
+.layout-sidebar {
41
+  width: var(--siderbar-width);
42
+  background-color: var(--theme-color);
43
+  color: var(--theme-front);
44
+  box-shadow: 1px 0 4px 0 rgba(0, 21, 41, 0.08);
45
+
46
+  .ant-menu {
47
+    background: transparent;
48
+  }
49
+}
50
+
51
+
52
+// 兼容 360
53
+.ant-pro-sider-collapsed-button {
54
+  top: 18px;
55
+  right: -13px;
56
+}
57
+
58
+.ant-pro .ant-pro-layout .ant-pro-sider-logo {
59
+  padding: 16px;
60
+}
61
+
62
+.ant-pro .ant-pro-query-filter .ant-form-item {
63
+  margin: 0;
64
+}
65
+
66
+.ant-pro-page-container .ant-pro-page-container-warp-page-header ~ .ant-pro-grid-content .ant-pro-page-container-children-content {
67
+  margin: 8px 40px 40px 40px;
68
+}
69
+
70
+.ant-pro-table-list-toolbar-container {
71
+  padding: 16px 0;
72
+}
73
+
74
+.ant-pro-card .ant-pro-card-body {
75
+  padding: 16px 24px;
76
+}

+ 27
- 0
src/layouts/AuthLayout/components/Container.jsx Näytä tiedosto

@@ -0,0 +1,27 @@
1
+import React from 'react';
2
+import { Layout } from 'antd';
3
+import { Outlet } from "react-router-dom";
4
+import PageTransition from './PageTransition';
5
+import Footer from './Footer';
6
+
7
+const { Content } = Layout;
8
+const style = {
9
+  margin: '24px',
10
+  marginTop: 0,
11
+  flexGrow: 1,
12
+};
13
+
14
+export default (props) => {
15
+  const { noFooter } = props;
16
+
17
+  return (
18
+    <div className='layout-container'>
19
+      <Content style={style}>
20
+        <PageTransition>
21
+          <Outlet />
22
+        </PageTransition>
23
+      </Content>
24
+      {!noFooter && <Footer />}
25
+    </div>
26
+  )
27
+}

+ 19
- 0
src/layouts/AuthLayout/components/Footer.jsx Näytä tiedosto

@@ -0,0 +1,19 @@
1
+import React from 'react';
2
+import { Layout } from 'antd';
3
+import useSystemModel from '@/store/system';
4
+
5
+const { Footer } = Layout;
6
+const style = {
7
+  textAlign: 'center',
8
+  color: 'rgba(0,0,0, 0.3)',
9
+}
10
+
11
+const year = new Date().getFullYear();
12
+export default React.forwardRef((props, ref) => {
13
+  const [app] = useSystemModel(s => s.app);
14
+  const copyRight = `${app.company || ''} @ ${year}`;
15
+
16
+  return (
17
+    <Footer ref={ref} style={style}>{copyRight}</Footer>
18
+  )
19
+})

+ 47
- 0
src/layouts/AuthLayout/components/Header/Exit.jsx Näytä tiedosto

@@ -0,0 +1,47 @@
1
+import React from "react";
2
+// import { useNavigate } from "react-router-dom";
3
+import { LogoutOutlined } from "@ant-design/icons";
4
+import { Button, Modal } from "antd";
5
+import useUserModel from "@/store/user";
6
+import { logout } from "@/service/login";
7
+const { confirm } = Modal;
8
+
9
+export default (props) => {
10
+  // const navigate = useNavigate();
11
+  const [_, { setUser }] = useUserModel();
12
+
13
+  const onExit = () => {
14
+    confirm({
15
+      title: "确认退出系统?",
16
+      onOk: () => {
17
+        // 调用接口
18
+        // localStorage.removeItem("token");
19
+        // setUser();
20
+        // window.location.href =
21
+        //   authUrl +
22
+        //   `?redirect=${encodeURIComponent(window.location.href)}&mode=ticket`;
23
+        logout().then((res) => {
24
+          localStorage.removeItem("token");
25
+          setUser();
26
+          window.location.href = getLoginPage();
27
+        }).catch((err) => {
28
+          console.error(err);
29
+          localStorage.removeItem("token");
30
+          setUser();
31
+          window.location.href = getLoginPage();
32
+        });
33
+      },
34
+    });
35
+  };
36
+
37
+  return (
38
+    <Button
39
+      className="font"
40
+      type="text"
41
+      icon={<LogoutOutlined />}
42
+      onClick={onExit}
43
+    >
44
+      退出
45
+    </Button>
46
+  );
47
+};

+ 24
- 0
src/layouts/AuthLayout/components/Header/Title.jsx Näytä tiedosto

@@ -0,0 +1,24 @@
1
+
2
+import useRoute from '@/utils/hooks/useRoute';
3
+
4
+const titleStyle = {
5
+  margin: 0,
6
+}
7
+
8
+const spanStyle = {
9
+  display: 'inline-block',
10
+  width: '24px',
11
+  textAlign: 'center'
12
+}
13
+
14
+export default (props) => {
15
+  const route = useRoute();
16
+  const { title } = route && route.meta || {}
17
+
18
+  return (
19
+    <h5 style={titleStyle}>
20
+      { title && <span style={spanStyle}>&raquo;</span> }
21
+      { title && <span>{title}</span> }
22
+    </h5>
23
+  );
24
+}

+ 115
- 0
src/layouts/AuthLayout/components/Header/User.jsx Näytä tiedosto

@@ -0,0 +1,115 @@
1
+import React, { useState, forwardRef, useRef, useImperativeHandle } from 'react';
2
+import { Avatar, Button, Dropdown, Menu, Form, Input, Modal } from 'antd';
3
+import {
4
+  ModalForm,
5
+  ProFormText,
6
+  ProFormDependency,
7
+} from '@ant-design/pro-components';
8
+import md5 from 'md5';
9
+import { changePassword } from '@/service/login';
10
+import useUserModel from '@/store/user';
11
+
12
+const DefaultAvatar = ({ color = "#fff" }) => (
13
+  <svg
14
+    viewBox="0 0 1024 1024"
15
+    version="1.1"
16
+    xmlns="http://www.w3.org/2000/svg"
17
+    width="24"
18
+    height="24"
19
+  >
20
+    <path
21
+      d="M938.666667 883.2c0-8.533333 0-42.666667-21.333334-81.066667-12.8-25.6-34.133333-42.666667-59.733333-59.733333-29.866667-17.066667-68.266667-34.133333-115.2-42.666667-4.266667 0-34.133333-4.266667-59.733333-12.8-29.866667-8.533333-42.666667-12.8-46.933334-17.066666v-25.6c4.266667-55.466667 34.133333-85.333333 59.733334-110.933334 8.533333-8.533333 17.066667-17.066667 25.6-29.866666 29.866667-42.666667 34.133333-85.333333 34.133333-93.866667 0-8.533333 0-12.8-4.266667-21.333333s-8.533333-17.066667-12.8-21.333334c0-12.8 4.266667-25.6 4.266667-34.133333 0-21.333333 4.266667-55.466667-4.266667-89.6 0-4.266667 0-12.8-4.266666-21.333333-8.533333-25.6-17.066667-46.933333-34.133334-64-8.533333 0-68.266667-59.733333-230.4-72.533334H405.333333c-8.533333 0-17.066667 0-25.6 4.266667-29.866667 8.533333-34.133333 29.866667-38.4 38.4v25.6l-4.266666 4.266667c-4.266667 4.266667-46.933333 42.666667-51.2 93.866666-4.266667 34.133333-4.266667 85.333333 0 123.733334-4.266667 8.533333-12.8 17.066667-12.8 38.4 0 8.533333 4.266667 51.2 34.133333 89.6 8.533333 8.533333 12.8 17.066667 25.6 29.866666 25.6 29.866667 55.466667 59.733333 59.733333 110.933334v25.6c-8.533333 4.266667-21.333333 12.8-51.2 21.333333s-59.733333 12.8-64 12.8c-42.666667 8.533333-81.066667 21.333333-110.933333 38.4-25.6 17.066667-46.933333 38.4-59.733333 59.733333-21.333333 38.4-21.333333 72.533333-21.333334 81.066667v21.333333c0 17.066667 17.066667 34.133333 38.4 34.133334h776.533334c21.333333 0 38.4-17.066667 38.4-38.4v-17.066667z"
22
+      fill={color}
23
+    ></path>
24
+  </svg>
25
+);
26
+
27
+const ChangePassword = forwardRef((props, ref) => {
28
+  const [visible, setVisible] = useState(false);
29
+
30
+  const onFinish = async (values) => {
31
+    const data = {
32
+      originPassword: md5(values.password),
33
+      newPassword: md5(values.newPassword),
34
+    };
35
+
36
+    await changePassword(data);
37
+
38
+    return true;
39
+  };
40
+
41
+  useImperativeHandle(ref, () => {
42
+    return {
43
+      show: () => setVisible(true),
44
+    };
45
+  });
46
+
47
+  return (
48
+    <ModalForm
49
+      title="修改密码"
50
+      width="480px"
51
+      open={visible}
52
+      onOpenChange={setVisible}
53
+      onFinish={onFinish}
54
+    >
55
+      <ProFormText.Password
56
+        label="原始密码"
57
+        name="password"
58
+        rules={[{ required: true, message: "请输入原始密码!" }]}
59
+      />
60
+      <ProFormText.Password
61
+        label="新密码"
62
+        name="newPassword"
63
+        rules={[{ required: true, message: "请输入新密码!" }]}
64
+      />
65
+
66
+      <ProFormDependency name={["newPassword"]}>
67
+        {({ newPassword }) => (
68
+          <ProFormText.Password
69
+            label="确认新密码"
70
+            name="newPassword2"
71
+            rules={[
72
+              { required: true, message: "请输入新密码!" },
73
+              () => ({
74
+                validator(_, value) {
75
+                  if (!value || value == newPassword) {
76
+                    return Promise.resolve();
77
+                  }
78
+
79
+                  return Promise.reject("两次输入密码不一致");
80
+                },
81
+              }),
82
+            ]}
83
+          />
84
+        )}
85
+      </ProFormDependency>
86
+    </ModalForm>
87
+  );
88
+});
89
+
90
+const items = [
91
+  {
92
+    key: 'changePassword',
93
+    label: '修改密码',
94
+  }
95
+];
96
+
97
+export default (props) => {
98
+  const [user] = useUserModel(s => s.user);
99
+  const passRef = useRef();
100
+  const onClick = ({ key }) => {
101
+    if (key === 'changePassword') {
102
+      passRef.current.show();
103
+    }
104
+  };
105
+
106
+  return (
107
+    <Dropdown menu={{ items, onClick }}>
108
+      <div className="user-info">
109
+        <Avatar size={28} src={<DefaultAvatar />} />
110
+        <span className='font'>{user?.name}</span>
111
+        <ChangePassword ref={passRef} />
112
+      </div>
113
+    </Dropdown>
114
+  )
115
+}

+ 28
- 0
src/layouts/AuthLayout/components/Header/index.jsx Näytä tiedosto

@@ -0,0 +1,28 @@
1
+import React, { useMemo } from 'react';
2
+import { Layout, Space } from 'antd';
3
+import classNames from 'classnames';
4
+import Title from './Title';
5
+import User from './User';
6
+import Exit from './Exit';
7
+import Logo from '../Logo';
8
+
9
+const { Header } = Layout;
10
+
11
+export default (props) => {
12
+  const { theme } = props;
13
+
14
+  const className = useMemo(() => classNames({
15
+    'layout-header': true,
16
+    'light': theme === 'light',
17
+  }), [theme]);
18
+
19
+  return (
20
+    <Header className={className}>
21
+      <Logo />
22
+      <Space>
23
+        <User />
24
+        <Exit />
25
+      </Space>
26
+    </Header>
27
+  )
28
+}

+ 20
- 0
src/layouts/AuthLayout/components/HtmlTitle.jsx Näytä tiedosto

@@ -0,0 +1,20 @@
1
+
2
+import { Helmet } from "react-helmet";
3
+import useSystemModel from '@/store/system';
4
+import useRoute from '@/utils/hooks/useRoute';
5
+
6
+export default (props) => {
7
+  const [app] = useSystemModel(s => s.app);
8
+
9
+  const route = useRoute();
10
+  const { title } = route && route.meta || {}
11
+  
12
+  const titleTemplate = `%s - ${app.fullName}`;
13
+  const defaultTitle = app.fullName;
14
+
15
+  return (
16
+    <Helmet titleTemplate={titleTemplate} defaultTitle={defaultTitle}>
17
+      <title>{title}</title>
18
+    </Helmet>
19
+  );
20
+}

+ 17
- 0
src/layouts/AuthLayout/components/Logo.jsx Näytä tiedosto

@@ -0,0 +1,17 @@
1
+import React from "react";
2
+// import { Typography } from "antd";
3
+import { NavLink } from "react-router-dom";
4
+import useSystemModel from "@/store/system";
5
+
6
+export default (props) => {
7
+  const [app] = useSystemModel(s => s.app);
8
+
9
+  return (
10
+    <div className="logo">
11
+      <NavLink to="/">
12
+        <img src="./logo.png" alt="" />
13
+        <h3>{app.shorName}</h3>
14
+      </NavLink>
15
+    </div>
16
+  );
17
+};

+ 63
- 0
src/layouts/AuthLayout/components/Menus.jsx Näytä tiedosto

@@ -0,0 +1,63 @@
1
+import React from 'react';
2
+import { useNavigate, useLocation } from "react-router-dom";
3
+import { Menu } from 'antd';
4
+import useUserModel from '@/store/user';
5
+import { getMenuItems } from '@/routes/menus';
6
+import { getAuthedRoutes } from '@/routes/permissions';
7
+
8
+const menuStyle = { height: '100%' };
9
+
10
+export default (props) => {
11
+  const { theme } = props;
12
+  const navigate = useNavigate();
13
+  const location = useLocation();
14
+  const [user] = useUserModel(s => s.user);
15
+
16
+  const [openKeys, setOpenKeys] = React.useState([]);
17
+
18
+  // 用户改变, 菜单改变
19
+  const items = React.useMemo(() => {
20
+    return getMenuItems(getAuthedRoutes());
21
+  }, [user]);
22
+
23
+  const selectedKeys = [location.pathname];
24
+
25
+  // 只执行一次, 不需要监控 selectedKeys
26
+  React.useEffect(() => {
27
+    const keys = selectedKeys.reduce((acc, item) => {
28
+      const arr = item.split('/').reduce((acc2, it) => {
29
+        const last = acc2[acc2.length - 1];
30
+        const path = [last, it].join('/').replace(/\/\//g, '/');
31
+        
32
+        return acc2.concat(path);
33
+      }, []);
34
+
35
+      return acc.concat(arr);
36
+    }, []);
37
+
38
+    setOpenKeys(keys);
39
+  }, []);
40
+
41
+  const onClick = ({ key }) => {
42
+    if (key.startsWith('http')) {
43
+      const a = document.createElement('a');
44
+      a.target = '_blank';
45
+      a.href = key;
46
+      a.click();
47
+    } else {
48
+      navigate(key);
49
+    }
50
+  }
51
+  return (
52
+    <Menu
53
+      mode='inline'
54
+      style={menuStyle}
55
+      theme={theme}
56
+      items={items}
57
+      selectedKeys={selectedKeys}
58
+      openKeys={openKeys}
59
+      onClick={onClick}
60
+      onOpenChange={setOpenKeys}
61
+    />
62
+  )
63
+}

+ 23
- 0
src/layouts/AuthLayout/components/PageTransition/index.jsx Näytä tiedosto

@@ -0,0 +1,23 @@
1
+import { TransitionGroup, CSSTransition } from 'react-transition-group';
2
+import { useLocation } from "react-router-dom";
3
+import './style.less';
4
+
5
+export default (props) => {
6
+  // const { location } = props;
7
+  const location = useLocation();
8
+  const currentURL = location.pathname + location.search;
9
+
10
+  return (
11
+    <TransitionGroup component={null}>
12
+      <CSSTransition 
13
+        key={currentURL}
14
+        addEndListener={(node, done) => node.addEventListener("transitionend", done, false)}
15
+        classNames="page-fade"
16
+      >
17
+        <div style={{ height: '100%' }}>
18
+          {props.children}
19
+        </div>
20
+      </CSSTransition>
21
+    </TransitionGroup>
22
+  );
23
+}

+ 21
- 0
src/layouts/AuthLayout/components/PageTransition/style.less Näytä tiedosto

@@ -0,0 +1,21 @@
1
+
2
+.page-fade-enter {
3
+  opacity: 0;
4
+  transform: translateY(10%);
5
+}
6
+.page-fade-enter-active {
7
+  opacity: 1;
8
+  transform: translateY(0%);
9
+}
10
+.page-fade-exit {
11
+  opacity: 1;
12
+  transform: translateY(0%);
13
+}
14
+.page-fade-exit-active {
15
+  opacity: 0;
16
+  transform: translateY(10%);
17
+}
18
+.page-fade-enter-active,
19
+.page-fade-exit-active {
20
+  transition: opacity 220ms, transform 220ms;
21
+}

+ 20
- 0
src/layouts/AuthLayout/components/SiderBar.jsx Näytä tiedosto

@@ -0,0 +1,20 @@
1
+import React, { useMemo } from 'react';
2
+import { Layout, Spin } from 'antd';
3
+import { getPropertyValue } from '@/utils/css';
4
+import Menus from './Menus';
5
+
6
+const { Sider } = Layout;
7
+
8
+export default (props) => {
9
+  const { theme } = props;
10
+
11
+  const width = useMemo(() => {
12
+    return /\d+/.exec(getPropertyValue('--siderbar-width'))[0] - 0;
13
+  }, []);
14
+
15
+  return (
16
+    <Sider className='layout-sidebar' theme={theme} collapsible width={width}>
17
+      <Menus theme={theme} />
18
+    </Sider>
19
+  );
20
+}

+ 44
- 0
src/layouts/AuthLayout/index.jsx Näytä tiedosto

@@ -0,0 +1,44 @@
1
+import React, { useEffect } from 'react';
2
+import { Layout, Spin } from 'antd';
3
+import { useLocation, Outlet } from "react-router-dom";
4
+import useUserModel from '@/store/user';
5
+import useSystemModel from '@/store/system';
6
+import useRoute from '@/utils/hooks/useRoute';
7
+import Loading from '@/components/Loading';
8
+import RequireLogin from '@/components/auth/RequireLogin';
9
+import SiderBar from './components/SiderBar';
10
+import Header from './components/Header';
11
+import Container from './components/Container';
12
+import HtmlTitle from './components/HtmlTitle';
13
+
14
+import './style.less';
15
+
16
+export default (props) => {
17
+  const [theme] = useSystemModel(s => s.theme);
18
+  const [userModel] = useUserModel();
19
+  const { user } = userModel || {};
20
+
21
+  const { meta } = useRoute() || {};
22
+  const { noLayout = false, noSiderBar = false, noFooter = false } = meta || {};
23
+
24
+  return (
25
+    <Loading loading={!user} size="large">
26
+      <HtmlTitle />
27
+      <RequireLogin loading={null}>
28
+        {
29
+          noLayout
30
+            ? <Outlet />
31
+            : (
32
+              <Layout style={{ height: '100vh' }}>
33
+                <Header theme={theme} />
34
+                <Layout style={{ height: 'calc(100vh - var(--header-height))' }}>
35
+                  { !noSiderBar && <SiderBar theme={theme} /> }
36
+                  <Container noFooter={noFooter} />
37
+                </Layout>
38
+              </Layout>
39
+            )
40
+        }
41
+      </RequireLogin>
42
+    </Loading>
43
+  );
44
+}

+ 88
- 0
src/layouts/AuthLayout/style.less Näytä tiedosto

@@ -0,0 +1,88 @@
1
+.layout-header {
2
+  height: var(--header-height);
3
+  box-sizing: border-box;
4
+  padding: 0;
5
+  display: flex;
6
+  align-items: center;
7
+  justify-content: space-between;
8
+  color: #fff;
9
+
10
+  // &.light {
11
+  //   background-color: #fff;
12
+  //   color: #000;
13
+  // }
14
+
15
+  .header-content {
16
+    flex: 1;
17
+  }
18
+
19
+  .font {
20
+    color: inherit;
21
+    display: inline-block;
22
+  }
23
+
24
+  .user-info {
25
+    cursor: pointer;
26
+
27
+    .ant-avatar {
28
+      border: 1px solid rgba(255, 255, 255, 0.4);
29
+    }
30
+
31
+    span {
32
+      margin-left: 1em;
33
+    }
34
+  }
35
+
36
+  .sys-exit {
37
+    margin-right: 1em;
38
+  }
39
+}
40
+
41
+.logo {
42
+  height: var(--header-height);
43
+  display: flex;
44
+  align-items: center;
45
+  box-sizing: border-box;
46
+  padding-left: 1em;
47
+  color: inherit;
48
+
49
+  a {
50
+    line-height: 1em;
51
+    color: #fff;
52
+    outline: none;
53
+    display: flex;
54
+    align-items: center;
55
+  }
56
+
57
+  & > * {
58
+    color: inherit;
59
+    margin: 0;
60
+  }
61
+
62
+  h5 {
63
+    margin: 0;
64
+  }
65
+
66
+  img {
67
+    width: 28px;
68
+    vertical-align: middle;
69
+    margin-right: 1em;
70
+  }
71
+}
72
+
73
+.layout-container {
74
+  flex: 1;
75
+  display: flex;
76
+  flex-direction: column;
77
+  height: 100%;
78
+
79
+  overflow-y: auto;
80
+  scrollbar-width: none;
81
+  -ms-overflow-style: none;
82
+
83
+  &::--webkit-scrollbar {
84
+    display: none;
85
+  }
86
+
87
+  padding-top: 0; // 避免子元素的 margin 影响
88
+}

+ 25
- 0
src/layouts/PageContainer.jsx Näytä tiedosto

@@ -0,0 +1,25 @@
1
+import React from 'react';
2
+import { Typography } from 'antd';
3
+import { Outlet } from 'react-router-dom';
4
+import useRoute from '@/utils/hooks/useRoute';
5
+
6
+const containerStyle = {
7
+  // margin: '24px 24px 0 24px',
8
+  // margin: '24px',
9
+  // marginTop: '0px',
10
+  // minHeight: 'calc(100% - 48px)',
11
+  height: '100%',
12
+}
13
+const { Title } = Typography;
14
+
15
+export default (props) => {
16
+  const { meta = {} } = useRoute() || {};
17
+  const style = meta.noLayout ? { height: '100%' } : containerStyle;
18
+
19
+  return (
20
+    <div style={style}>
21
+      { meta.title && !meta.noLayout && <Title level={4} style={{ paddingBottom: '12px' }}>{ meta.title }</Title> }
22
+      <Outlet />
23
+    </div>
24
+  )
25
+}

+ 13
- 0
src/main.jsx Näytä tiedosto

@@ -0,0 +1,13 @@
1
+import React from 'react';
2
+import ReactDOM from 'react-dom/client';
3
+import { ConfigProvider } from 'antd';
4
+import zhCN from 'antd/locale/zh_CN';
5
+import 'dayjs/locale/zh-cn';
6
+import Router from './routes/Router';
7
+import './index.less';
8
+
9
+ReactDOM.createRoot(document.getElementById('root')).render(
10
+  <ConfigProvider locale={zhCN}>
11
+    <Router />
12
+  </ConfigProvider>
13
+);

+ 29
- 0
src/pages/404/index.jsx Näytä tiedosto

@@ -0,0 +1,29 @@
1
+import React from 'react';
2
+import { Button, Result } from 'antd';
3
+import { Helmet } from "react-helmet";
4
+import { NavLink } from "react-router-dom";
5
+import useSystemModel from '@/store/system';
6
+
7
+const style = {
8
+  display: 'grid',
9
+  placeItems: 'center',
10
+  height: '100%'
11
+}
12
+
13
+export default (props) => {
14
+  const [app] = useSystemModel(s => s.app);
15
+
16
+  return (
17
+    <div style={style}>
18
+      <Helmet>
19
+        <title>{app.fullName}</title>
20
+      </Helmet>
21
+      <Result
22
+        status="404"
23
+        title="404"
24
+        subTitle="页面不存在"
25
+        extra={<NavLink to="/" replace><Button type="primary">返回首页</Button></NavLink>}
26
+      />
27
+    </div>
28
+  )
29
+}

+ 119
- 0
src/pages/home/Detail.jsx Näytä tiedosto

@@ -0,0 +1,119 @@
1
+import React, { useEffect, useRef, useState } from "react";
2
+import {
3
+  Button,
4
+  Select,
5
+  Row,
6
+  Col,
7
+  Form,
8
+  Descriptions,
9
+  Input,
10
+  Card,
11
+  Space,
12
+} from "antd";
13
+import {
14
+  ProForm,
15
+  ProFormText,
16
+  ProFormTextArea,
17
+} from "@ant-design/pro-components";
18
+import { useSearchParams } from "react-router-dom";
19
+import { useNavigate } from "react-router-dom";
20
+import { getTaMessageById, putTaMessage } from "@/service/taMessage";
21
+import { postTaRotation } from "@/service/taRotation";
22
+import Page from "@/components/Page";
23
+
24
+export default (props) => {
25
+  const [searchParams] = useSearchParams();
26
+  const id = searchParams.get("id");
27
+  const [form] = Form.useForm();
28
+  const navigate = useNavigate();
29
+
30
+  useEffect(() => {
31
+    form.resetFields();
32
+    getTaMessageById(id).then((res) => {
33
+      form.setFieldsValue(res);
34
+    });
35
+  }, []);
36
+
37
+  const onFinish = (values) => {
38
+    const sourceName = form.getFieldValue("sourceName");
39
+    const data = {
40
+      ...values,
41
+      sourceName: sourceName,
42
+    };
43
+    putTaMessage(id, data).then((res) => {
44
+      navigate(-1);
45
+    });
46
+  };
47
+
48
+  const onRatation = () => {
49
+    const sourceName = form.getFieldValue("sourceName");
50
+    const sourceId = form.getFieldValue("sourceId");
51
+    if (sourceName == "taRotation") {
52
+      navigate(
53
+        `/rotationMange/rotationList/edit?id=${sourceId}&disabled={true}`
54
+      );
55
+    } else {
56
+      navigate(
57
+        `/mandatoryLeaveMange/mandatoryleave/edit?id=${sourceId}&disabled={true}`
58
+      );
59
+    }
60
+  };
61
+
62
+  return (
63
+    <Page>
64
+      <Card>
65
+        <ProForm
66
+          style={{ marginTop: "24px" }}
67
+          form={form}
68
+          onFinish={onFinish}
69
+          layout="horizontal"
70
+          labelCol={{ span: 2 }}
71
+          wrapperCol={{ span: 8 }}
72
+          submitter={false}
73
+        >
74
+          <ProFormTextArea label="消息内容" name="contentSent" readonly />
75
+
76
+          <ProForm.Item noStyle shouldUpdate>
77
+            {({ getFieldValue }) => {
78
+              const waringType = getFieldValue("waringType");
79
+              const sourceName = getFieldValue("sourceName");
80
+              return (
81
+                <>
82
+                  <Form.Item label="查看按钮">
83
+                    <Button type="link" onClick={onRatation}>
84
+                      {sourceName == "taRotation"
85
+                        ? "点击查看轮岗详情"
86
+                        : "点击查看强制休假详情"}
87
+                    </Button>
88
+                  </Form.Item>
89
+                  {sourceName && sourceName == "taMandatoryLeave" ? null : (
90
+                    <>
91
+                      <ProFormTextArea
92
+                        label="核查反馈"
93
+                        name="remark"
94
+                        disabled={waringType == "prompt"}
95
+                      />
96
+                      <Form.Item>
97
+                        <Row>
98
+                          <Col offset={15}>
99
+                            <Button
100
+                              type="primary"
101
+                              htmlType="submit"
102
+                              disabled={waringType == "prompt"}
103
+                            >
104
+                              提交
105
+                            </Button>
106
+                          </Col>
107
+                        </Row>
108
+                      </Form.Item>
109
+                    </>
110
+                  )}
111
+                </>
112
+              );
113
+            }}
114
+          </ProForm.Item>
115
+        </ProForm>
116
+      </Card>
117
+    </Page>
118
+  );
119
+};

+ 34
- 0
src/pages/home/History.jsx Näytä tiedosto

@@ -0,0 +1,34 @@
1
+import React, { useEffect, useRef, useState } from "react";
2
+import { Button, Divider, Upload, Row, Col, Form, Input } from "antd";
3
+import { useNavigate } from "react-router-dom";
4
+import { getTaMessage } from "@/service/taMessage";
5
+import List from "@/components/Page/List";
6
+
7
+export default (props) => {
8
+  const ref = useRef();
9
+  const navigate = useNavigate();
10
+
11
+  const onDetail = (it) => {
12
+    navigate(`/home/detail?id=${it.messageId}`);
13
+  }
14
+
15
+  const columns = [
16
+    {
17
+      title: "内容",
18
+      dataIndex: "contentSent"
19
+    }
20
+  ]
21
+
22
+  return (
23
+    <List
24
+      ref={ref}
25
+      rowKey="rotationId"
26
+      columns={columns}
27
+      pagination={{ pageSize: 20 }}
28
+      request={getTaMessage}
29
+      columnOptionRender={(_, it) => [
30
+        <Button style={{ paddingLeft: 0 }} type="link" onClick={() => onDetail(it)}>查看详情</Button>
31
+      ]}
32
+    />
33
+  )
34
+}

+ 144
- 0
src/pages/home/index.jsx Näytä tiedosto

@@ -0,0 +1,144 @@
1
+import React, { useEffect, useRef, useState } from "react";
2
+import { ProTable } from "@ant-design/pro-components";
3
+import { Button, Select, Row, Col, Form, Input, Card } from "antd";
4
+import { useNavigate } from "react-router-dom";
5
+import { getTaMessage } from "@/service/taMessage";
6
+import Massage from "@/components/Massage";
7
+import ModalForms from "@/components/Upload/ModalForms";
8
+import { uploadFile } from "@/components/Upload/request";
9
+import { queryTable } from "@/utils/request";
10
+import "./style.less";
11
+
12
+export default (props) => {
13
+  const actionRef = useRef();
14
+  const actionRef2 = useRef();
15
+  const [open, setOpen] = useState(false);
16
+  const [visible, setVisible] = useState(false);
17
+  const [errList, setErrList] = useState([]);
18
+
19
+  const navigate = useNavigate();
20
+  const queryMessage = queryTable(getTaMessage);
21
+  const onHistory = () => {
22
+    navigate(`/home/history`);
23
+  };
24
+
25
+  const onSuccess = (x) => {
26
+    if (x) {
27
+      const res = x.split("读取文件失败:");
28
+      if ((res[0] = "读取文件失败")) {
29
+        setOpen(true);
30
+        const errlist = res[1].split("\n").filter((x) => x);
31
+        setErrList(errlist);
32
+      }
33
+    }
34
+  };
35
+
36
+  const onDetail = (it) => {
37
+    navigate(`/home/detail?id=${it.messageId}`);
38
+  };
39
+  const onFinish = () => { };
40
+  const columns = [
41
+    {
42
+      title: "内容",
43
+      dataIndex: "contentSent",
44
+    },
45
+    {
46
+      title: "操作",
47
+      key: "option",
48
+      render: (_, record) => [
49
+        <Button
50
+          key="1"
51
+          style={{ paddingLeft: 0 }}
52
+          type="link"
53
+          onClick={() => onDetail(record)}
54
+        >
55
+          查看详情
56
+        </Button>,
57
+      ],
58
+    },
59
+  ];
60
+  return (
61
+    <div className="grid-container">
62
+      <div className="left-side">
63
+        <Card
64
+          size="small"
65
+          bordered={false}
66
+          style={{ height: "48%", marginBottom: "24px" }}
67
+        >
68
+          <ProTable
69
+            rowKey="messageId"
70
+            params={{ sourceName: "taRotation" }}
71
+            request={queryMessage}
72
+            columns={columns}
73
+            headerTitle="轮岗信息"
74
+            options={false}
75
+            search={false}
76
+            actionRef={actionRef}
77
+            scroll={{
78
+              y: 190,
79
+            }}
80
+            toolBarRender={() => [
81
+              <Button key="1" type="link" onClick={onHistory}>
82
+                查看历史
83
+              </Button>,
84
+            ]}
85
+          />
86
+        </Card>
87
+        <Card size="small" bordered={false} style={{ height: "48%" }}>
88
+          <ProTable
89
+            rowKey="messageId"
90
+            request={queryMessage}
91
+            columns={columns}
92
+            params={{ sourceName: "taMandatoryLeave" }}
93
+            headerTitle="强制休假信息"
94
+            options={false}
95
+            scroll={{
96
+              y: 190,
97
+            }}
98
+            search={false}
99
+            actionRef={actionRef2}
100
+            toolBarRender={() => [
101
+              <Button key="1" type="link" onClick={onHistory}>
102
+                查看历史
103
+              </Button>,
104
+            ]}
105
+          />
106
+        </Card>
107
+      </div>
108
+      <div className="right-side">
109
+        <Card title={<div style={{ fontWeight: 400, padding: "20px 0 0 12px" }}>快捷入口</div>} style={{ height: "99%" }}>
110
+          <Card.Grid style={{ width: "50%", color: '#1677ff', cursor: 'pointer', textAlign: 'center' }} onClick={() => setVisible(true)}>轮岗导入</Card.Grid>
111
+          <Card.Grid style={{ width: "50%", color: '#1677ff', cursor: 'pointer', textAlign: 'center' }} onClick={() => setVisible(true)}>强制休假导入</Card.Grid>
112
+          <Card.Grid style={{ width: "50%", textAlign: 'center', cursor: 'pointer' }}>
113
+            <Button href={rotation} type="link" style={{ padding: 0 }}>下载轮岗模板</Button>
114
+          </Card.Grid>
115
+          <Card.Grid style={{ width: "50%", textAlign: 'center', cursor: 'pointer' }}>
116
+            <Button href={mandatoryleave} type="link" style={{ padding: 0 }}>下载强制休假模板</Button>
117
+          </Card.Grid>
118
+          <div style={{ width: "100%" }}>
119
+            <img
120
+              style={{ margin: "30% auto", display: "block" }}
121
+              src="/src/assets/chinaBank.png"
122
+              height="50%"
123
+              width="50%"
124
+              alt=""
125
+            />
126
+          </div>
127
+        </Card>
128
+        <ModalForms
129
+          visible={visible}
130
+          setVisible={setVisible}
131
+          onFinish={onFinish}
132
+          onSuccess={onSuccess}
133
+          uploadFile={uploadFile}
134
+        />
135
+        <Massage
136
+          title="读取错误提示"
137
+          open={open}
138
+          setOpen={setOpen}
139
+          errList={errList}
140
+        />
141
+      </div>
142
+    </div>
143
+  );
144
+};

+ 11
- 0
src/pages/home/style.less Näytä tiedosto

@@ -0,0 +1,11 @@
1
+.grid-container {
2
+  margin-top: 24px;
3
+  display: grid;
4
+  height: 100%;
5
+  grid-template-columns: 70% 30%; /* 左边70%,右边30% */
6
+  grid-gap: 24px; /* 可选,设置网格项之间的间隙 */
7
+  .left-side {
8
+  }
9
+  .right-side {
10
+  }
11
+}

+ 143
- 0
src/pages/mandatoryleave/Edit.jsx Näytä tiedosto

@@ -0,0 +1,143 @@
1
+import React, { useCallback, useEffect, useRef, useState } from "react";
2
+import Edit from "@/components/Page/Edit";
3
+import { Radio, Form } from "antd";
4
+import {
5
+  ProForm,
6
+  ProFormDatePicker,
7
+  ProFormText,
8
+  ProFormSelect,
9
+  ProFormTextArea,
10
+  ProFormDigit,
11
+  ProFormRadio,
12
+} from "@ant-design/pro-components/es";
13
+import {
14
+  postTaMandatoryLeave,
15
+  putTaMandatoryLeave,
16
+  getTaMandatoryLeaveById,
17
+} from "@/service/taMandatoryLeave";
18
+import { getSysPosition } from "@/service/sysposition";
19
+import { queryDict } from "@/utils/request";
20
+import { getSysOrg } from "@/service/sysorg";
21
+import { useSearchParams } from "react-router-dom";
22
+
23
+const queryPosition = queryDict(getSysPosition, {
24
+  labelKey: "name",
25
+  valueKey: "positionId",
26
+});
27
+const getOrgId = queryDict(getSysOrg, {
28
+  labelKey: "orgId",
29
+  valueKey: "orgId",
30
+});
31
+const getOrgName = queryDict(getSysOrg, {
32
+  labelKey: "name",
33
+  valueKey: "name",
34
+});
35
+const request = {
36
+  save: postTaMandatoryLeave,
37
+  update: putTaMandatoryLeave,
38
+  get: getTaMandatoryLeaveById,
39
+};
40
+
41
+export default (props) => {
42
+  const formRef = useRef();
43
+  const [params] = useSearchParams();
44
+  const disabled = params.get("disabled");
45
+  const selectChange = [
46
+    { label: "是", value: "是" },
47
+    { label: "否", value: "否" },
48
+  ];
49
+
50
+  const counterInstitutionsLevelOpt = [
51
+    { label: "1.总行部门、直属机构", value: "1.总行部门、直属机构" },
52
+    { label: "2.分行各级管理机构本部", value: "2.分行各级管理机构本部" },
53
+    { label: "3.基层营业网点(含营业部)", value: "3.基层营业网点(含营业部)" },
54
+  ];
55
+
56
+  const onChangeOrg = (value, e) => {
57
+    formRef.current.setFieldValue("orgNum", e.orgId);
58
+    formRef.current.setFieldValue("orgName", e.name);
59
+  };
60
+
61
+  return (
62
+    <Edit
63
+      formRef={formRef}
64
+      request={request}
65
+      disabled={disabled}
66
+      rowKey="leaveId"
67
+      labelCol={{ span: 6 }}
68
+      renderItems={() => (
69
+        <>
70
+          {/* <ProFormDigit name="sort" label=" 序 号 :" /> */}
71
+          <ProFormText name="headOfficeOrg" label="总 行 部 门" />
72
+          <ProFormText
73
+            name="employeeNum"
74
+            label="员 工 工 号"
75
+            rules={[{ required: true }]}
76
+          />
77
+          <ProFormText
78
+            name="employeeName"
79
+            label="员 工 姓 名"
80
+            rules={[{ required: true }]}
81
+          />
82
+          <ProFormSelect
83
+            onChange={onChangeOrg}
84
+            request={getOrgId}
85
+            name="orgNum"
86
+            label="原单位机构号"
87
+          />
88
+          <ProFormSelect
89
+            rules={[{ required: true }]}
90
+            onChange={onChangeOrg}
91
+            name="orgName"
92
+            label="原单位名称"
93
+            request={getOrgName}
94
+          />
95
+
96
+          <ProFormSelect
97
+            name="counterInstitutionsLevel"
98
+            label="对应机构层次"
99
+            options={counterInstitutionsLevelOpt}
100
+          />
101
+          <ProFormText name="currentPosition" label="当 前 职 位" />
102
+
103
+          <ProFormSelect
104
+            name="counterImpPost"
105
+            label="对应重要岗位"
106
+            request={queryPosition}
107
+          />
108
+          <ProFormText
109
+            name="counterHeadOfficeLine"
110
+            label="对应总行条线"
111
+            // options={[{ label: "个人数字金融部", value: "个人数字金融部" }]}
112
+          />
113
+          <ProFormDatePicker
114
+            name="startTimeVacation"
115
+            label="休假起始时间"
116
+            width="100%"
117
+          />
118
+          <ProFormDatePicker
119
+            name="vacationDeadline"
120
+            label="休假截止时间"
121
+            width="100%"
122
+          />
123
+          <ProFormDigit name="mandatoryVacationDay" label="已强制休假天数" />
124
+          <ProFormText
125
+            name="depSbtDepartureCheck"
126
+            label="开展离岗检查/代职检查情况"
127
+          />
128
+          <ProFormTextArea name="remark" label="备注填写" />
129
+          <ProFormRadio.Group
130
+            name="checkChange"
131
+            label="立 查 立 改:"
132
+            options={selectChange}
133
+          />
134
+          <ProFormRadio.Group
135
+            name="checkCorrect"
136
+            label="自 查 自 纠:"
137
+            options={selectChange}
138
+          />
139
+        </>
140
+      )}
141
+    />
142
+  );
143
+};

+ 222
- 0
src/pages/mandatoryleave/index.jsx Näytä tiedosto

@@ -0,0 +1,222 @@
1
+import React, { useEffect, useRef, useState } from "react";
2
+import { Button, Divider, Upload, Row, Col, Form, Input } from "antd";
3
+import List from "@/components/Page/List";
4
+import { useNavigate } from "react-router-dom";
5
+import {
6
+  deleteTaMandatoryLeave,
7
+  getTaMandatoryLeave,
8
+} from "@/service/taMandatoryLeave";
9
+import ModalForms from "@/components/Upload/ModalForms";
10
+import Massage from "@/components/Massage";
11
+import { DownloadOutlined } from "@ant-design/icons";
12
+import { uploadFileTaMandatoryLeave } from "@/components/Upload/request";
13
+import { getSysPosition } from "@/service/sysposition";
14
+import { queryDict } from "@/utils/request";
15
+import { getSysOrg } from "@/service/sysorg";
16
+const getPosition = queryDict(getSysPosition, {
17
+  labelKey: "name",
18
+  valueKey: "positionId",
19
+});
20
+const getOrg = queryDict(getSysOrg, {
21
+  labelKey: "name",
22
+  valueKey: "name",
23
+});
24
+
25
+export default (props) => {
26
+  const columns = [
27
+    {
28
+      title: "序号",
29
+      dataIndex: "sort",
30
+      search: false,
31
+    },
32
+    {
33
+      title: "总行部门",
34
+      dataIndex: "headOfficeOrg",
35
+    },
36
+    {
37
+      title: "员工工号",
38
+      dataIndex: "employeeNum",
39
+    },
40
+    {
41
+      title: "员工姓名",
42
+      dataIndex: "employeeName",
43
+    },
44
+    {
45
+      title: "原单位机构号",
46
+      dataIndex: "orgNum",
47
+      search: false,
48
+    },
49
+    {
50
+      title: "原单位名称",
51
+      dataIndex: "orgName",
52
+      request: getOrg,
53
+      fieldProps: {
54
+        showSearch: true,
55
+        filterOption: (x, opt) => opt.label?.includes(x),
56
+      },
57
+    },
58
+    {
59
+      title: "对应机构层次",
60
+      dataIndex: "counterInstitutionsLevel",
61
+      search: false,
62
+    },
63
+    {
64
+      title: "当前职位",
65
+      dataIndex: "currentPosition",
66
+      search: false,
67
+    },
68
+    {
69
+      title: "对应重要岗位",
70
+      dataIndex: "counterImpPost",
71
+      valueType: "select",
72
+      request: getPosition,
73
+      fieldProps: {
74
+        showSearch: true,
75
+        filterOption: (x, opt) => opt.label?.includes(x),
76
+      },
77
+    },
78
+    {
79
+      title: "对应总行条线",
80
+      dataIndex: "counterHeadOfficeLine",
81
+      search: false,
82
+    },
83
+    {
84
+      title: "休假起始时间",
85
+      dataIndex: "startTimeVacation",
86
+      valueType: "date",
87
+      search: false,
88
+    },
89
+    {
90
+      title: "休假起始时间",
91
+      dataIndex: "startTimeVacation",
92
+      valueType: "dateRange",
93
+      hideInTable: true,
94
+      search: {
95
+        transform: (val) => {
96
+          return {
97
+            startVacation: val[0],
98
+            endVacation: val[1],
99
+          };
100
+        },
101
+      },
102
+    },
103
+    {
104
+      title: "休假截止时间",
105
+      dataIndex: "vacationDeadline",
106
+      valueType: "date",
107
+      search: false,
108
+    },
109
+    {
110
+      title: "休假截止时间",
111
+      dataIndex: "vacationDeadline",
112
+      valueType: "dateRange",
113
+      hideInTable: true,
114
+      search: {
115
+        transform: (val) => {
116
+          return {
117
+            startVacation: val[0],
118
+            endVacation: val[1],
119
+          };
120
+        },
121
+      },
122
+    },
123
+    {
124
+      title: "已强制休假天数(工作日)",
125
+      dataIndex: "mandatoryVacationDay",
126
+      search: false,
127
+      width: 200,
128
+    },
129
+    {
130
+      title: "开展离岗检查/代职检查情况",
131
+      dataIndex: "depSbtDepartureCheck",
132
+      search: false,
133
+      width: 200,
134
+    },
135
+    {
136
+      title: "备注",
137
+      dataIndex: "remark",
138
+      search: false,
139
+    },
140
+    {
141
+      title: "立查立改(是/否)",
142
+      dataIndex: "checkChange",
143
+      search: false,
144
+    },
145
+    {
146
+      title: "自查自纠 (是/否)",
147
+      dataIndex: "checkCorrect",
148
+      search: false,
149
+    },
150
+  ];
151
+  const navigate = useNavigate();
152
+  const [visible, setVisible] = useState(false);
153
+  const ref = useRef();
154
+  const [open, setOpen] = useState(false);
155
+  const [errList, setErrList] = useState([]);
156
+
157
+  const onEdit = (it) => {
158
+    navigate(`/mandatoryLeaveMange/mandatoryleave/edit?id=${it.leaveId}`);
159
+  };
160
+  const onAdd = () => {
161
+    navigate("/mandatoryLeaveMange/mandatoryleave/edit");
162
+  };
163
+  const onFinish = () => {
164
+    ref.current?.reload();
165
+  };
166
+  const onSuccess = (x) => {
167
+    if (x) {
168
+      const res = x.split("读取文件失败:");
169
+      if ((res[0] = "读取文件失败")) {
170
+        setOpen(true);
171
+        const errlist = res[1].split("\n").filter((x) => x);
172
+        setErrList(errlist);
173
+      }
174
+    }
175
+  };
176
+
177
+  const onDelete = (it) => {
178
+    deleteTaMandatoryLeave(it.leaveId).then(() => {
179
+      ref.current?.reload();
180
+    });
181
+  };
182
+  return (
183
+    <>
184
+      <List
185
+        ref={ref}
186
+        rowKey="leaveId"
187
+        search={{ labelWidth: 130 }}
188
+        scroll={{ x: 2600 }}
189
+        columns={columns}
190
+        // pagination={{
191
+        //   pageSize: 10,
192
+        // }}
193
+        onEdit={onEdit}
194
+        onAdd={onAdd}
195
+        onDelete={onDelete}
196
+        request={getTaMandatoryLeave}
197
+        toolBarRender={() => [
198
+          <Button key="2" href={mandatoryleave} icon={<DownloadOutlined />}>
199
+            下载模板
200
+          </Button>,
201
+          <Button type="primary" key="1" onClick={() => setVisible(true)}>
202
+            导入
203
+          </Button>,
204
+        ]}
205
+      />
206
+
207
+      <ModalForms
208
+        visible={visible}
209
+        setVisible={setVisible}
210
+        onFinish={onFinish}
211
+        onSuccess={onSuccess}
212
+        uploadFile={uploadFileTaMandatoryLeave}
213
+      />
214
+      <Massage
215
+        title="读取错误提示"
216
+        open={open}
217
+        setOpen={setOpen}
218
+        errList={errList}
219
+      />
220
+    </>
221
+  );
222
+};

+ 58
- 0
src/pages/rotationManage/compatibilityQuery/components/SearchResult.jsx Näytä tiedosto

@@ -0,0 +1,58 @@
1
+import React, { useEffect, useRef, useState } from "react";
2
+import { Button, Divider, Card } from "antd";
3
+
4
+export default (props) => {
5
+  const { repelmsg, postIdName } = props;
6
+  const [allRulesCompatible, setAllRulesCompatible] = useState(false);
7
+  useEffect(() => {
8
+    if (repelmsg) {
9
+      const allRulesCompatible = (repelmsg || [])?.every(
10
+        (x) => x.rules == "根据不相容设置这两个岗位是相容的"
11
+      );
12
+      setAllRulesCompatible(allRulesCompatible);
13
+    }
14
+  }, [repelmsg]);
15
+
16
+  console.log(allRulesCompatible);
17
+  return (
18
+    <Card style={{ height: "100%" }}>
19
+      {repelmsg &&
20
+        (allRulesCompatible ? (
21
+          <h3>根据不相容设置这两个岗位是相容的</h3>
22
+        ) : (
23
+          <h3>根据不相容设置这两个岗位是不相容的</h3>
24
+        ))}
25
+      {(repelmsg || []).map((x, index) => {
26
+        return (
27
+          <div key={index}>
28
+            {x.primaryPost && postIdName && (
29
+              <div>原岗位:{postIdName[x.primaryPost]}</div>
30
+            )}
31
+            {x.targetPost && postIdName && (
32
+              <div>目标岗位:{postIdName[x.targetPost]}</div>
33
+            )}
34
+
35
+            {/* <h3>{x.rules}</h3> */}
36
+            {x.employeeNum && <div>员工工号:{x.employeeNum}</div>}
37
+            {x.employeeName && <div>员工姓名:{x.employeeName}</div>}
38
+            {x.counterImpPositions && postIdName && (
39
+              <div>原岗位:{postIdName[x.counterImpPositions]}</div>
40
+            )}
41
+
42
+            {x.specificOrgNum && <div>原网点号:{x.specificOrgNum}</div>}
43
+            {x.specificOrgName && <div>原网点名称:{x.specificOrgName}</div>}
44
+            {x.counterImpPost && postIdName && (
45
+              <div>目标岗位:{postIdName[x.counterImpPost]}</div>
46
+            )}
47
+            {x.orgNumJobRotation && (
48
+              <div>目标网点号:{x.orgNumJobRotation}</div>
49
+            )}
50
+            {x.specificNameOrgRotation && (
51
+              <div>目标网点名称:{x.specificNameOrgRotation}</div>
52
+            )}
53
+          </div>
54
+        );
55
+      })}
56
+    </Card>
57
+  );
58
+};

+ 144
- 0
src/pages/rotationManage/compatibilityQuery/index.jsx Näytä tiedosto

@@ -0,0 +1,144 @@
1
+import React, { useEffect, useRef, useState } from "react";
2
+import { Button, Divider, Select, Row, Col, Form, Input, Card } from "antd";
3
+import {
4
+  ProForm,
5
+  ProFormSelect,
6
+  ProFormText,
7
+} from "@ant-design/pro-components";
8
+import Page from "@/components/Page";
9
+import { getTaIncompatibleRule } from "@/service/taIncompatible";
10
+import { getSysPosition } from "@/service/sysposition";
11
+import { queryDict } from "@/utils/request";
12
+import SearchResult from "./components/SearchResult";
13
+import dayjs from "dayjs";
14
+import { getSysOrg } from "@/service/sysorg";
15
+
16
+const queryPosition = queryDict(getSysPosition, {
17
+  labelKey: "name",
18
+  valueKey: "positionId",
19
+});
20
+const queryOrg = queryDict(getSysOrg, {
21
+  labelKey: "orgId",
22
+  valueKey: "orgId",
23
+  show: true,
24
+});
25
+
26
+export default (props) => {
27
+  const formRef = useRef();
28
+  const [repelmsg, setRepelmsg] = useState();
29
+  const [postIdName, setPostIdName] = useState();
30
+  const onFinish = (values) => {
31
+    let time = dayjs().format("YYYY-MM-DD");
32
+    const data = {
33
+      ...values,
34
+      passTime: time,
35
+    };
36
+
37
+    queryPosition().then((res) => {
38
+      const data = {};
39
+      (res || []).map((x) => {
40
+        data[x.positionId] = x.label;
41
+      });
42
+      setPostIdName(data);
43
+    });
44
+
45
+    getTaIncompatibleRule(data).then((res) => {
46
+      setRepelmsg(res);
47
+    });
48
+  };
49
+  return (
50
+    <Page>
51
+      <Row gutter={24}>
52
+        <Col span={12}>
53
+          <Card>
54
+            <ProForm
55
+              formRef={formRef}
56
+              layout="horizontal"
57
+              size="large"
58
+              onFinish={onFinish}
59
+              labelCol={{ span: 8 }}
60
+              wrapperCol={{ span: 12 }}
61
+              submitter={false}
62
+            >
63
+              <div style={{ paddingBottom: "24px" }}>
64
+                变更前:
65
+                <ProFormText
66
+                  name="oldOrgCode"
67
+                  label="当前归属机构号"
68
+                  request={queryOrg}
69
+                />
70
+                <ProFormSelect
71
+                  label="当前岗位名称"
72
+                  name="primaryPost"
73
+                  request={queryPosition}
74
+                />
75
+              </div>
76
+              <div>
77
+                变更后:
78
+                <ProFormText
79
+                  name="newOrgCode"
80
+                  label="拟建归属机构号"
81
+                  request={queryOrg}
82
+                />
83
+                <ProFormSelect
84
+                  name="targetPost"
85
+                  label="拟建岗位名称"
86
+                  request={queryPosition}
87
+                />
88
+              </div>
89
+              <Form.Item style={{ display: "flex", justifyContent: "center" }}>
90
+                <Button
91
+                  type="primary"
92
+                  htmlType="submit"
93
+                  style={{ width: "6vw" }}
94
+                >
95
+                  查询
96
+                </Button>
97
+              </Form.Item>
98
+            </ProForm>
99
+          </Card>
100
+        </Col>
101
+        <Col span={12}>
102
+          {/* <Card style={{ height: "100%" }}>
103
+            {(repelmsg || []).map((x, index) => {
104
+              return (
105
+                <div key={index}>
106
+                  <h3>{x.regularRules}</h3>
107
+                  {x.primaryPost && postIdName && (
108
+                    <div>原岗位:{postIdName[x.primaryPost]}</div>
109
+                  )}
110
+                  {x.targetPost && postIdName && (
111
+                    <div>目标岗位:{postIdName[x.targetPost]}</div>
112
+                  )}
113
+
114
+                  <h3>{x.monthRules}</h3>
115
+                  {x.monthRules && <div>{x.monthRules}</div>}
116
+                  {x.employeeNum && <div>员工工号:{x.employeeNum}</div>}
117
+                  {x.employeeName && <div>员工姓名:{x.employeeName}</div>}
118
+                  {x.counterImpPositions && postIdName && (
119
+                    <div>原岗位:{postIdName[x.counterImpPositions]}</div>
120
+                  )}
121
+
122
+                  {x.specificOrgNum && <div>原网点号:{x.specificOrgNum}</div>}
123
+                  {x.specificOrgName && (
124
+                    <div>原网点名称:{x.specificOrgName}</div>
125
+                  )}
126
+                  {x.counterImpPost && postIdName && (
127
+                    <div>目标岗位:{postIdName[x.counterImpPost]}</div>
128
+                  )}
129
+                  {x.orgNumJobRotation && (
130
+                    <div>目标网点号:{x.orgNumJobRotation}</div>
131
+                  )}
132
+                  {x.specificNameOrgRotation && (
133
+                    <div>目标网点名称:{x.specificNameOrgRotation}</div>
134
+                  )}
135
+                </div>
136
+              );
137
+            })}
138
+          </Card> */}
139
+          <SearchResult repelmsg={repelmsg} postIdName={postIdName} />
140
+        </Col>
141
+      </Row>
142
+    </Page>
143
+  );
144
+};

+ 227
- 0
src/pages/rotationManage/rotationList/Edit.jsx Näytä tiedosto

@@ -0,0 +1,227 @@
1
+import React, { useCallback, useEffect, useRef, useState } from "react";
2
+import Edit from "@/components/Page/Edit";
3
+import {
4
+  ProForm,
5
+  ProFormDatePicker,
6
+  ProFormDigit,
7
+  ProFormRadio,
8
+  ProFormRate,
9
+  ProFormSelect,
10
+  ProFormText,
11
+  ProFormTextArea,
12
+} from "@ant-design/pro-components/es";
13
+import {
14
+  getTaRotationById,
15
+  postTaRotation,
16
+  putTaRotation,
17
+} from "@/service/taRotation";
18
+import dayjs from "dayjs";
19
+import { queryDict } from "@/utils/request";
20
+import { getSysPosition } from "@/service/sysposition";
21
+import { getSysOrg } from "@/service/sysorg";
22
+import { useNavigate, useSearchParams } from "react-router-dom";
23
+import { message } from "antd";
24
+import Massage from "@/components/Massage";
25
+
26
+const request = {
27
+  get: getTaRotationById,
28
+  save: postTaRotation,
29
+  update: putTaRotation,
30
+};
31
+const getPosition = queryDict(getSysPosition, {
32
+  labelKey: "name",
33
+  valueKey: "positionId",
34
+});
35
+const getOrgId = queryDict(getSysOrg, {
36
+  labelKey: "orgId",
37
+  valueKey: "orgId",
38
+});
39
+const getOrgName = queryDict(getSysOrg, {
40
+  labelKey: "name",
41
+  valueKey: "name",
42
+});
43
+
44
+export default (props) => {
45
+  const [params] = useSearchParams();
46
+  const disabled = params.get("disabled");
47
+  const formRef = useRef();
48
+  const selectChange = [
49
+    { label: "是", value: "是" },
50
+    { label: "否", value: "否" },
51
+  ];
52
+  const rules = {
53
+    // headOfficeOrg: [{ required: true, message: "请输入总行部门" }],
54
+    employeeNum: [{ required: true, message: "请输入员工工号" }],
55
+    employeeName: [{ required: true, message: "请输入员工姓名" }],
56
+    // specificOrgNum: [{ required: true, message: "请输入具体单位机构号" }],
57
+    specificOrgName: [{ required: true, message: "请输入所在单位具体名称" }],
58
+    counterImpPositions: [{ required: true, message: "请输入对应重要岗位" }],
59
+    rotationCycle: [{ required: true, message: "请输入轮岗周期要求" }],
60
+    rotationDeadline: [{ required: true, message: "请输入轮岗截止时间" }],
61
+    counterRotationMethod: [{ required: true, message: "请输入轮岗方法" }],
62
+    orgNumJobRotation: [{ required: true, message: "请输入机构轮岗" }],
63
+    specificNameOrgRotation: [
64
+      { required: true, message: "请输入轮岗后单位具体名称" },
65
+    ],
66
+    rotationExeTime: [{ required: true, message: "请输入轮岗执行时间" }],
67
+  };
68
+  const onChangeOrg = (value, e) => {
69
+    formRef.current.setFieldValue("specificOrgNum", e.orgId);
70
+    formRef.current.setFieldValue("specificOrgName", e.name);
71
+  };
72
+
73
+  const onChangeOrgAfter = (value, e) => {
74
+    formRef.current.setFieldValue("orgNumJobRotation", e.orgId);
75
+    formRef.current.setFieldValue("specificNameOrgRotation", e.name);
76
+  };
77
+  const onChangeTime = (value, e) => {
78
+    const durationOfEmployment = formRef.current.getFieldValue(
79
+      "durationOfEmployment"
80
+    );
81
+    if (durationOfEmployment) {
82
+      const rotationDeadline = dayjs(durationOfEmployment).add(value, "year");
83
+      formRef.current.setFieldValue("rotationDeadline", rotationDeadline);
84
+    }
85
+  };
86
+
87
+  return (
88
+    <Edit
89
+      formRef={formRef}
90
+      request={request}
91
+      // initialValues={initialValues}
92
+      labelCol={{ span: 6 }}
93
+      disabled={disabled}
94
+      renderItems={() => (
95
+        <>
96
+          <ProFormText name="headOfficeOrg" label="总 行 部  门:" />
97
+          <ProFormText
98
+            name="employeeNum"
99
+            label="员 工 工 号:"
100
+            required
101
+            rules={rules.employeeNum}
102
+          />
103
+          <ProFormText
104
+            name="employeeName"
105
+            label="员 工 姓 名:"
106
+            required
107
+            rules={rules.employeeName}
108
+          />
109
+          <ProFormText
110
+            name="specificOrgNum"
111
+            label="原单位机构号:"
112
+            onChange={onChangeOrg}
113
+            request={getOrgId}
114
+
115
+            // required
116
+            // rules={rules.specificOrgNum}
117
+          />
118
+          <ProFormText
119
+            name="specificOrgName"
120
+            label="原单位具体名称:"
121
+            request={getOrgName}
122
+            onChange={onChangeOrg}
123
+            required
124
+            rules={rules.specificOrgName}
125
+          />
126
+          <ProFormText name="counterInstitutionsLevel" label="对应机构层次:" />
127
+          <ProFormText name="currentPosition" label="当 前 职 位:" />
128
+          <ProFormText
129
+            name="counterImpPositions"
130
+            label="对应重要岗位:"
131
+            request={getPosition}
132
+            required
133
+            rules={rules.counterImpPositions}
134
+          />
135
+          <ProFormText
136
+            name="counterHeadOfficeLine"
137
+            label="对应总行条线:"
138
+            // required
139
+            // rules={rules.counterImpPositions}
140
+          />
141
+          <ProFormDatePicker
142
+            name="durationOfEmployment"
143
+            label="从事岗位日期:"
144
+            width="100%"
145
+            // style={{ width: "800px" }}
146
+          />
147
+          <ProFormDigit
148
+            name="rotationCycle"
149
+            label="轮岗周期要求(年):"
150
+            onChange={onChangeTime}
151
+            min={1}
152
+            // required
153
+            // rules={rules.rotationCycle}
154
+          />
155
+          <ProFormDatePicker
156
+            name="rotationDeadline"
157
+            label="轮岗截止日期:"
158
+            width="100%"
159
+            required
160
+            rules={rules.rotationDeadline}
161
+          />
162
+
163
+          <ProFormText
164
+            name="counterRotationMethod"
165
+            label="对应轮岗方式:"
166
+            // required
167
+            // rules={rules.counterRotationMethod}
168
+          />
169
+          <ProFormText
170
+            name="orgNumJobRotation"
171
+            label="轮岗后单位机构号:"
172
+            request={getOrgId}
173
+            onChange={onChangeOrgAfter}
174
+            // required
175
+            // rules={rules.orgNumJobRotation}
176
+          />
177
+          <ProFormText
178
+            name="specificNameOrgRotation"
179
+            label="轮岗后单位具体名称:"
180
+            request={getOrgName}
181
+            onChange={onChangeOrgAfter}
182
+
183
+            // required
184
+            // rules={rules.specificNameOrgRotation}
185
+          />
186
+
187
+          <ProFormText name="jobRotationPost" label="轮 岗 后 职 位:" />
188
+          <ProFormText
189
+            name="counterImpPostRotation"
190
+            label="轮岗后对应重要岗位:"
191
+            request={getPosition}
192
+          />
193
+          <ProFormText
194
+            name="counterOfficeLineRotation"
195
+            label="轮岗后对应总行条线:"
196
+          />
197
+          <ProFormDatePicker
198
+            name="rotationExeTime"
199
+            label="轮岗执行时间:"
200
+            width="100%"
201
+            required
202
+            rules={rules.rotationExeTime}
203
+          />
204
+          <ProFormRadio.Group
205
+            name="checkChange"
206
+            label="立 查 立 改:"
207
+            options={selectChange}
208
+          />
209
+          <ProFormText name="accountability" label="是 否 问 责:" />
210
+          <ProFormText name="factConfirmationNum" label="事实确认书编号:" />
211
+          <ProFormRadio.Group
212
+            name="checkCorrect"
213
+            label="自 查 自 纠:"
214
+            options={selectChange}
215
+          />
216
+          <ProFormDatePicker
217
+            name="planRotationDate"
218
+            label="计划轮岗日期:"
219
+            width="100%"
220
+          />
221
+
222
+          <ProFormTextArea name="remark" label="备 注:" />
223
+        </>
224
+      )}
225
+    />
226
+  );
227
+};

+ 261
- 0
src/pages/rotationManage/rotationList/index.jsx Näytä tiedosto

@@ -0,0 +1,261 @@
1
+import React, { useEffect, useRef, useState } from "react";
2
+import { Button, Divider, Upload, Row, Col, Form, Input } from "antd";
3
+import List from "@/components/Page/List";
4
+import { deleteTaRotation, getTaRotation } from "@/service/taRotation";
5
+import { useNavigate } from "react-router-dom";
6
+import { DownloadOutlined } from "@ant-design/icons";
7
+import ModalForms from "@/components/Upload/ModalForms";
8
+import Massage from "@/components/Massage";
9
+import { uploadFile } from "@/components/Upload/request";
10
+import { queryDict, queryTable } from "@/utils/request";
11
+import { getSysPosition } from "@/service/sysposition";
12
+import { getSysOrg } from "@/service/sysorg";
13
+import dayjs from "dayjs";
14
+const getPosition = queryDict(getSysPosition, {
15
+  labelKey: "name",
16
+  valueKey: "positionId",
17
+});
18
+const getOrg = queryDict(getSysOrg, {
19
+  labelKey: "name",
20
+  valueKey: "name",
21
+});
22
+
23
+export default (props) => {
24
+  const columns = [
25
+    {
26
+      title: "序号",
27
+      dataIndex: "sort",
28
+      search: false,
29
+    },
30
+    {
31
+      title: "总行部门",
32
+      dataIndex: "headOfficeOrg",
33
+    },
34
+    {
35
+      title: "员工工号",
36
+      dataIndex: "employeeNum",
37
+    },
38
+    {
39
+      title: "员工姓名",
40
+      dataIndex: "employeeName",
41
+    },
42
+    {
43
+      title: "原单位机构号",
44
+      dataIndex: "specificOrgNum",
45
+      search: false,
46
+    },
47
+    {
48
+      title: "原单位具体名称",
49
+      dataIndex: "specificOrgName",
50
+      request: getOrg,
51
+      fieldProps: {
52
+        showSearch: true,
53
+        filterOption: (x, opt) => opt.label?.includes(x),
54
+      },
55
+    },
56
+    {
57
+      title: "对应机构层次",
58
+      dataIndex: "counterInstitutionsLevel",
59
+      search: false,
60
+    },
61
+    {
62
+      title: "当前职位",
63
+      dataIndex: "currentPosition",
64
+      search: false,
65
+    },
66
+
67
+    {
68
+      title: "对应重要岗位",
69
+      dataIndex: "counterImpPositions",
70
+      valueType: "select",
71
+      request: getPosition,
72
+      fieldProps: {
73
+        showSearch: true,
74
+        filterOption: (x, opt) => opt.label?.includes(x),
75
+      },
76
+    },
77
+    {
78
+      title: "对应总行条线",
79
+      dataIndex: "counterHeadOfficeLine",
80
+      search: false,
81
+    },
82
+    {
83
+      title: "从事岗位日期",
84
+      dataIndex: "durationOfEmployment",
85
+      valueType: "date",
86
+      search: false,
87
+    },
88
+    {
89
+      title: "轮岗周期要求(年)",
90
+      dataIndex: "rotationCycle",
91
+      search: false,
92
+    },
93
+    {
94
+      title: "轮岗截止日期",
95
+      dataIndex: "rotationDeadline",
96
+      valueType: "date",
97
+      search: false,
98
+    },
99
+    {
100
+      title: "轮岗截止日期",
101
+      dataIndex: "rotationDeadline",
102
+      valueType: "dateRange",
103
+      hideInTable:true,
104
+      search: {
105
+        transform: (val) => {
106
+          return {
107
+            startDeadline: val[0],
108
+            endDeadline: val[1],
109
+          };
110
+        },
111
+      },
112
+    },
113
+    {
114
+      title: "对应轮岗方式",
115
+      dataIndex: "counterRotationMethod",
116
+      search: false,
117
+    },
118
+    {
119
+      title: "轮岗后单位机构号",
120
+      dataIndex: "orgNumJobRotation",
121
+      search: false,
122
+    },
123
+    {
124
+      title: "轮岗后单位具体名称",
125
+      dataIndex: "specificNameOrgRotation",
126
+      search: false,
127
+    },
128
+    {
129
+      title: "轮岗后职位",
130
+      dataIndex: "jobRotationPost",
131
+      search: false,
132
+    },
133
+    {
134
+      title: "轮岗后对应重要岗位",
135
+      dataIndex: "counterImpPostRotation",
136
+      search: false,
137
+      valueType: "select",
138
+      request: getPosition,
139
+    },
140
+    {
141
+      title: "轮岗后对应总行条线",
142
+      dataIndex: "counterOfficeLineRotation",
143
+      search: false,
144
+    },
145
+    {
146
+      title: "轮岗执行时间",
147
+      dataIndex: "rotationExeTime",
148
+      valueType: "date",
149
+      search: false,
150
+    },
151
+    {
152
+      title: "轮岗执行时间",
153
+      dataIndex: "rotationExeTime",
154
+      valueType: "dateRange",
155
+      hideInTable:false,
156
+      search: {
157
+        transform: (val) => {
158
+          return {
159
+            startExeTime: val[0],
160
+            endExeTime: val[1],
161
+          };
162
+        },
163
+      },
164
+    },
165
+    {
166
+      title: "立查立改(是/否)",
167
+      dataIndex: "checkChange",
168
+      search: false,
169
+    },
170
+    {
171
+      title: "是否问责",
172
+      dataIndex: "accountability",
173
+      search: false,
174
+    },
175
+    {
176
+      title: "事实确认书编号",
177
+      dataIndex: "factConfirmationNum",
178
+      search: false,
179
+    },
180
+    {
181
+      title: "自查自纠(是/否)",
182
+      dataIndex: "checkCorrect",
183
+      search: false,
184
+    },
185
+    {
186
+      title: "计划轮岗日期",
187
+      dataIndex: "planRotationDate",
188
+      valueType: "date",
189
+      search: false,
190
+    },
191
+  ];
192
+  const [visible, setVisible] = useState(false);
193
+  const [open, setOpen] = useState(false);
194
+  const [errList, setErrList] = useState([]);
195
+  const ref = useRef();
196
+
197
+  const navigate = useNavigate();
198
+
199
+  const onEdit = (r) => {
200
+    navigate(`/rotationMange/rotationList/edit?id=${r.rotationId}`);
201
+  };
202
+  const onAdd = (r) => {
203
+    navigate("/rotationMange/rotationList/edit");
204
+  };
205
+
206
+  const onFinish = () => {
207
+    ref.current?.reload();
208
+  };
209
+
210
+  const onSuccess = (x) => {
211
+    if (x) {
212
+      const res = x.split("读取文件失败:");
213
+      if ((res[0] = "读取文件失败")) {
214
+        setOpen(true);
215
+        const errlist = res[1].split("\n").filter((x) => x);
216
+        setErrList(errlist);
217
+      }
218
+    }
219
+  };
220
+  const onDelete = (it) => {
221
+    deleteTaRotation(it.rotationId).then(() => {
222
+      ref.current?.reload();
223
+    });
224
+  };
225
+  return (
226
+    <>
227
+      <List
228
+        ref={ref}
229
+        rowKey="rotationId"
230
+        search={{ labelWidth: 130 }}
231
+        columns={columns}
232
+        request={getTaRotation}
233
+        onEdit={onEdit}
234
+        onAdd={onAdd}
235
+        onDelete={onDelete}
236
+        toolBarRender={() => [
237
+          <Button key="2" href={rotation} icon={<DownloadOutlined />}>
238
+            下载模板
239
+          </Button>,
240
+          <Button type="primary" key="1" onClick={() => setVisible(true)}>
241
+            导入
242
+          </Button>,
243
+        ]}
244
+      />
245
+      <ModalForms
246
+        visible={visible}
247
+        setVisible={setVisible}
248
+        onFinish={onFinish}
249
+        onSuccess={onSuccess}
250
+        uploadFile={uploadFile}
251
+      />
252
+
253
+      <Massage
254
+        title="读取错误提示"
255
+        open={open}
256
+        setOpen={setOpen}
257
+        errList={errList}
258
+      />
259
+    </>
260
+  );
261
+};

+ 79
- 0
src/pages/system/Incompatible/Edit.jsx Näytä tiedosto

@@ -0,0 +1,79 @@
1
+import React, { useCallback, useEffect, useRef } from "react";
2
+import Edit from "@/components/Page/Edit";
3
+import { Radio, Form } from 'antd';
4
+import {
5
+  ProForm,
6
+  ProFormDatePicker,
7
+  ProFormText,
8
+  ProFormSelect,
9
+  ProFormRadio
10
+} from "@ant-design/pro-components/es";
11
+import { postTaIncompatible, putTaIncompatible, getTaIncompatibleById } from "@/service/taIncompatible";
12
+import { getSysPosition } from "@/service/sysposition";
13
+import { queryDict } from "@/utils/request";
14
+
15
+const queryPosition = queryDict(getSysPosition, { labelKey: "name", valueKey: "positionId" });
16
+
17
+const request = {
18
+  save: postTaIncompatible,
19
+  update: putTaIncompatible,
20
+  get: getTaIncompatibleById
21
+};
22
+
23
+export default (props) => {
24
+  const formRef = useRef();
25
+
26
+  const initialValues = { status: 1 };
27
+
28
+  const statusOpt = [
29
+    { label: "正常", value: 1 },
30
+    { label: "不正常", value: 0 },
31
+  ];
32
+  const ruleOpt = [
33
+    { label: "普通规则", value: "regularRules" },
34
+    { label: "三个月规则", value: "monthRules" },
35
+  ]
36
+
37
+  return (
38
+    <Edit
39
+      formRef={formRef}
40
+      request={request}
41
+      initialValues={initialValues}
42
+      renderItems={() => (
43
+        <>
44
+          <ProFormSelect
45
+            labelCol={{ span: 6 }}
46
+            name="primaryPost"
47
+            label="原岗位"
48
+            request={queryPosition}
49
+          />
50
+          <ProFormSelect
51
+            labelCol={{ span: 6 }}
52
+            name="targetPost"
53
+            label="目标岗位"
54
+            request={queryPosition}
55
+          />
56
+          {/* <ProFormText labelCol={{ span: 6 }} name="rule" label="规则" /> */}
57
+          <ProFormRadio.Group
58
+            labelCol={{ span: 6 }}
59
+            name="rule"
60
+            label="规则"
61
+            options={ruleOpt}
62
+          />
63
+          <ProFormRadio.Group
64
+            labelCol={{ span: 6 }}
65
+            name="status"
66
+            label="状态"
67
+            options={statusOpt}
68
+          />
69
+          {/* <Form.Item labelCol={{ span: 6 }} label="状态" name="status">
70
+            <Radio.Group>
71
+              <Radio value={1}>正常</Radio>
72
+              <Radio value={0}>不正常</Radio>
73
+            </Radio.Group>
74
+          </Form.Item> */}
75
+        </>
76
+      )}
77
+    />
78
+  )
79
+}

+ 80
- 0
src/pages/system/Incompatible/index.jsx Näytä tiedosto

@@ -0,0 +1,80 @@
1
+import React from 'react'
2
+import { useNavigate } from "react-router-dom";
3
+import List from "@/components/Page/List";
4
+import { Button, message, Popconfirm } from "antd";
5
+import { getTaIncompatible, deleteTaIncompatible } from "@/service/taIncompatible";
6
+import { queryDict } from "@/utils/request";
7
+import { getSysPosition } from "@/service/sysposition";
8
+
9
+const queryPosition = queryDict(getSysPosition, { labelKey: "name", valueKey: "positionId" });
10
+
11
+export default (props) => {
12
+  const actionRef = React.useRef();
13
+  const navigate = useNavigate();
14
+
15
+  const columns = [
16
+    {
17
+      title: "原岗位",
18
+      dataIndex: "primaryPost",
19
+      valueType: "select",
20
+      request: queryPosition,
21
+      key: "primaryPost",
22
+    },
23
+    {
24
+      title: "目标岗位",
25
+      dataIndex: "targetPost",
26
+      valueType: "select",
27
+      request: queryPosition,
28
+      key: "targetPost",
29
+    },
30
+    {
31
+      title: "规则",
32
+      dataIndex: "rule",
33
+      key: "rule",
34
+      valueEnum: {
35
+        "regularRules": "普通规则",
36
+        "monthRules": "三个月规则",
37
+      },
38
+    },
39
+    {
40
+      title: "状态",
41
+      dataIndex: "status",
42
+      key: "status",
43
+      search: false,
44
+      valueEnum: {
45
+        1: {
46
+          text: "正常",
47
+          status: "Processing",
48
+        },
49
+        0: {
50
+          text: "禁用",
51
+          status: "Error",
52
+        },
53
+      },
54
+    }
55
+  ]
56
+
57
+  const onEdit = (it) => {
58
+    navigate(`/system/incompatible/edit?id=${it.incompatibleId}`);
59
+  };
60
+  const onAdd = () => {
61
+    navigate("/system/incompatible/edit");
62
+  };
63
+  const onDelete = (it) => {
64
+    deleteTaIncompatible(it.incompatibleId).then((res) => {
65
+      actionRef.current?.reload()
66
+    })
67
+  };
68
+
69
+  return (
70
+    <List
71
+      actionRef={actionRef}
72
+      rowKey="incompatibleId"
73
+      columns={columns}
74
+      onEdit={onEdit}
75
+      onAdd={onAdd}
76
+      onDelete={onDelete}
77
+      request={getTaIncompatible}
78
+    />
79
+  )
80
+}

+ 56
- 0
src/pages/system/log/index.jsx Näytä tiedosto

@@ -0,0 +1,56 @@
1
+import React from 'react';
2
+import Page from '@/components/Page';
3
+import { ProTable } from '@ant-design/pro-components';
4
+import { getSysLog } from '@/service/syslog';
5
+import { queryTable } from '@/utils/request';
6
+
7
+export default (props) => {
8
+
9
+  const columns = [
10
+    {
11
+      title: "时间",
12
+      dataIndex: "createDate",
13
+      valueType: 'dateRange',
14
+      render: (_, row) => row.createDate ? row.createDate.replace('T', ' ') : '-',
15
+      search: {
16
+        transform: (val) => {
17
+          return {
18
+            startDate: val[0],
19
+            endDate: val[1],
20
+          }
21
+        }
22
+      }
23
+    },
24
+    {
25
+      title: "操作人",
26
+      dataIndex: "userName",
27
+    },
28
+    {
29
+      title: "模块",
30
+      dataIndex: "moduleName",
31
+    },
32
+    {
33
+      title: "操作",
34
+      dataIndex: "action",
35
+      search: false,
36
+    },
37
+    {
38
+      title: "IP",
39
+      dataIndex: "ip",
40
+      search: false,
41
+    },
42
+    {
43
+      title: "User Agent",
44
+      dataIndex: "ua",
45
+      search: false,
46
+    },
47
+  ]
48
+
49
+  return (
50
+    <ProTable
51
+      rowKey="logId"
52
+      columns={columns}
53
+      request={queryTable(getSysLog)}
54
+    />
55
+  )
56
+}

+ 101
- 0
src/pages/system/org/components/Form.jsx Näytä tiedosto

@@ -0,0 +1,101 @@
1
+import React from "react";
2
+import { Button, Card, Form, Input, InputNumber, Select } from "antd";
3
+import useBool from "@/utils/hooks/useBool";
4
+import { postSysOrg, putSysOrg, getSysOrgById } from "@/service/sysorg";
5
+import { formItemLayout, tailFormItemLayout } from "@/utils/form";
6
+import { ProFormSelect } from "@ant-design/pro-components";
7
+
8
+export default (props) => {
9
+  const { org, list, parentId, onChange } = props;
10
+
11
+  const [submiting, startSubmit, cancelSubmit] = useBool();
12
+  const [form] = Form.useForm();
13
+
14
+  const onFinish = (values) => {
15
+    startSubmit();
16
+
17
+    if (org?.orgId) {
18
+      // 修改
19
+      putSysOrg(org.orgId, values)
20
+        .then((res) => {
21
+          cancelSubmit();
22
+          onChange(res);
23
+        })
24
+        .catch(() => {
25
+          cancelSubmit();
26
+        });
27
+    } else {
28
+      // 新增
29
+      postSysOrg(values)
30
+        .then((res) => {
31
+          cancelSubmit();
32
+          onChange(res);
33
+        })
34
+        .catch(() => {
35
+          cancelSubmit();
36
+        });
37
+    }
38
+  };
39
+
40
+  console.log(org);
41
+  React.useEffect(() => {
42
+    form.resetFields();
43
+    if (org) {
44
+      form.setFieldsValue(org);
45
+    }
46
+    form.setFieldValue("parentId", parentId);
47
+  }, [org, parentId]);
48
+
49
+  return (
50
+    <Form
51
+      form={form}
52
+      {...formItemLayout}
53
+      style={{ maxWidth: "800px" }}
54
+      onFinish={onFinish}
55
+    >
56
+      <Form.Item
57
+        name="name"
58
+        label="机构名称"
59
+        rules={[{ required: true, message: "请填写机构名称" }]}
60
+        getValueFromEvent={(e) => e.target.value.replace(/(^\s*)|(\s*$)/g, "")}
61
+      >
62
+        <Input />
63
+      </Form.Item>
64
+      <Form.Item
65
+        name="orgCode"
66
+        label="机构号"
67
+        rules={[{ required: true, message: "请填写机构号" }]}
68
+        getValueFromEvent={(e) => e.target.value.replace(/(^\s*)|(\s*$)/g, "")}
69
+      >
70
+        <Input />
71
+      </Form.Item>
72
+      <Form.Item name="parentId" label="上级单位">
73
+        <Select disabled={true}>
74
+          {(list || []).map((x) => (
75
+            <Select.Option key={x.orgId}>{x.name}</Select.Option>
76
+          ))}
77
+        </Select>
78
+      </Form.Item>
79
+
80
+      <Form.Item name="status" label="状态">
81
+        <Select style={{ width: "100%" }} placeholder="请选择状态">
82
+          <Select.Option value={0}>不正常</Select.Option>
83
+          <Select.Option value={1}>正常</Select.Option>
84
+        </Select>
85
+      </Form.Item>
86
+
87
+      {/* <Form.Item name="isSeven" label="7天全班">
88
+        <Select style={{ width: "100%" }} placeholder="请选择">
89
+          <Select.Option value={0}>否</Select.Option>
90
+          <Select.Option value={1}>是</Select.Option>
91
+        </Select>
92
+      </Form.Item> */}
93
+
94
+      <Form.Item {...tailFormItemLayout}>
95
+        <Button loading={submiting} type="primary" htmlType="submit">
96
+          保存
97
+        </Button>
98
+      </Form.Item>
99
+    </Form>
100
+  );
101
+};

+ 163
- 0
src/pages/system/org/index.jsx Näytä tiedosto

@@ -0,0 +1,163 @@
1
+import React from "react";
2
+import { PlusOutlined, DeleteOutlined } from "@ant-design/icons";
3
+import { Button, Card, Row, Col, Tree, Tooltip, Popconfirm, Spin } from "antd";
4
+import Page from "@/components/Page";
5
+import { getSysOrg, deleteSysOrg } from "@/service/sysorg";
6
+import { arr2Tree } from "@/utils/array";
7
+import useBool from "@/utils/hooks/useBool";
8
+import Form from "./components/Form";
9
+
10
+import styles from "./styles.module.less";
11
+
12
+export default (props) => {
13
+  const [loading, startLoading, stopLoading] = useBool();
14
+  const [list, setList] = React.useState([]);
15
+  const [current, setCurrernt] = React.useState({});
16
+  const [parentId, setParentId] = React.useState();
17
+
18
+  const [parentList, treeData] = React.useMemo(() => {
19
+    const plist = [{ orgId: "-1", name: "根节点" }].concat(list);
20
+    const [tree] = arr2Tree(
21
+      (list || []).map((x) => ({
22
+        title: x.name,
23
+        key: x.orgId,
24
+        parentId: x.parentId,
25
+        raw: x,
26
+      }))
27
+    );
28
+    return [plist, tree];
29
+  }, [list]);
30
+
31
+  const changeCurrent = (org) => {
32
+    setCurrernt(org);
33
+    setParentId(org?.orgPId || "-1");
34
+  };
35
+
36
+  const onSelect = (selectedKeys, e) => {
37
+    changeCurrent(e.node.raw);
38
+  };
39
+
40
+  const onClick = (org) => {
41
+    changeCurrent(org);
42
+  };
43
+
44
+  const onAdd = (parent = "-1") => {
45
+    console.log(111)
46
+    const a={}
47
+    setParentId(parent);
48
+    setCurrernt(a);
49
+  };
50
+
51
+  const onDelete = (org) => {
52
+    deleteSysOrg(org.orgId).then(() => {
53
+      queryList();
54
+      // setList(list.filter((x) => x.orgId !== org.orgId));
55
+    });
56
+  };
57
+
58
+  const queryList = React.useCallback(() => {
59
+    startLoading();
60
+    getSysOrg({ pageSize: 500 }).then((res) => {
61
+      setList(res.records || []);
62
+      stopLoading();
63
+      // changeCurrent();
64
+      // const list = [];
65
+    });
66
+  }, []);
67
+
68
+  const onFormChange = () => {
69
+    // 重新查一次数据
70
+    queryList();
71
+  };
72
+
73
+  React.useEffect(() => {
74
+    queryList();
75
+  }, []);
76
+
77
+  return (
78
+    <Page>
79
+      <Row gutter={24}    className={styles.treeRow}>
80
+        <Col span={8} style={{ overflow: "hidden" }}>
81
+          <Card
82
+         
83
+            title="单位"
84
+            onSelect={onSelect}
85
+            extra={
86
+              <Button type="link" onClick={() => onAdd()}>
87
+                新增
88
+              </Button>
89
+            }
90
+          >
91
+            <Spin spinning={loading}>
92
+              <Tree
93
+                height="100%"
94
+                blockNode
95
+                defaultExpandParent={true}
96
+                treeData={treeData}
97
+                // onSelect={onSelect}
98
+                titleRender={(node) => (
99
+                  <div
100
+                    style={{ display: "flex" }}
101
+                    onClick={(e) => e.stopPropagation()}
102
+                  >
103
+                    <div
104
+                      style={{
105
+                        lineHeight: "32px",
106
+                        flex: 1,
107
+                        width: 0,
108
+                      }}
109
+                      onClick={() => onClick(node.raw)}
110
+                    >
111
+                      <div
112
+                        style={{
113
+                          overflow: "hidden",
114
+                          whiteSpace: "nowrap",
115
+                          textOverflow: "ellipsis",
116
+                        }}
117
+                      >
118
+                        {node.title}
119
+                      </div>
120
+                    </div>
121
+                    <div style={{ width: "80px", flex: "none" }}>
122
+                      <Tooltip title="新增子节点">
123
+                        <Button
124
+                          type="link"
125
+                          icon={<PlusOutlined />}
126
+                          onClick={() => onAdd(node.raw.orgId)}
127
+                        ></Button>
128
+                      </Tooltip>
129
+                      <Tooltip title="删除节点">
130
+                        <Popconfirm
131
+                          title="确认进行删除操作?"
132
+                          onConfirm={() => onDelete(node.raw)}
133
+                        >
134
+                          <Button
135
+                            type="link"
136
+                            danger
137
+                            icon={<DeleteOutlined />}
138
+                          ></Button>
139
+                        </Popconfirm>
140
+                      </Tooltip>
141
+                    </div>
142
+                  </div>
143
+                )}
144
+              />
145
+            </Spin>
146
+          </Card>
147
+        </Col>
148
+        <Col span={16}>
149
+          {parentId && (
150
+            <Card>
151
+              <Form
152
+                org={current}
153
+                parentId={parentId}
154
+                list={parentList}
155
+                onChange={onFormChange}
156
+              />
157
+            </Card>
158
+          )}
159
+        </Col>
160
+      </Row>
161
+    </Page>
162
+  );
163
+};

+ 23
- 0
src/pages/system/org/styles.module.less Näytä tiedosto

@@ -0,0 +1,23 @@
1
+.treeRow {
2
+    overflow: hidden;
3
+    max-height: 100%;
4
+    // min-height: 0%;
5
+    min-height: -webkit-fill-available;
6
+    min-height: -moz-available; /* Firefox */
7
+    :global(.ant-col-8) {
8
+        // height: 100%;
9
+        // overflow: hidden;
10
+
11
+        :global(.ant-card) {
12
+            height: 70% !important;
13
+            overflow: hidden;
14
+
15
+            :global(.ant-card-body) {
16
+                height: 90% !important;
17
+                overflow-y: auto;
18
+            }
19
+        }
20
+    }
21
+
22
+
23
+}

+ 130
- 0
src/pages/system/position/edit/index.jsx Näytä tiedosto

@@ -0,0 +1,130 @@
1
+import React, { useRef, useEffect, useState } from "react";
2
+import { Button, Card, Form, Input, Select, TreeSelect } from "antd";
3
+import useBool from "@/utils/hooks/useBool";
4
+import Page from "@/components/Page";
5
+import { arr2Tree } from "@/utils/array";
6
+import {
7
+  getSysPosition,
8
+  postSysPosition,
9
+  putSysPosition,
10
+  getSysPositionById,
11
+} from "@/service/sysposition";
12
+import { getSysOrg } from "@/service/sysorg";
13
+import { useSearchParams, useNavigate } from "react-router-dom";
14
+import { formItemLayout, tailFormItemLayout } from "@/utils/form";
15
+
16
+const { Option } = Select;
17
+export default (props) => {
18
+  const [searchParams, setSearchParams] = useSearchParams();
19
+  const id = searchParams.get("id");
20
+  const navigate = useNavigate();
21
+  const [submiting, startSubmit, cancelSubmit] = useBool();
22
+
23
+  const [orgTreeData, setOrgTreeData] = useState([]);
24
+
25
+  const [form] = Form.useForm();
26
+
27
+  useEffect(() => {
28
+    if (id) {
29
+      getSysPositionById(id).then((res) => {
30
+        form.setFieldsValue(res);
31
+      });
32
+    }
33
+  }, [id]);
34
+
35
+  useEffect(() => {
36
+    // getSysPosition({ pageSize: 999 }).then((res) => {
37
+    //   setPositionList(res.records || []);
38
+    // });
39
+    getSysOrg({ pageSize: 999 }).then((res) => {
40
+      const [tree] = arr2Tree(
41
+        (res.records || []).map((x) => ({
42
+          title: x.name,
43
+          key: x.orgId,
44
+          value: x.orgId,
45
+          parentId: x.parentId,
46
+          raw: x,
47
+        }))
48
+        // "1"
49
+      );
50
+      setOrgTreeData(tree);
51
+    });
52
+  }, []);
53
+
54
+  const onFinish = (values) => {
55
+    startSubmit();
56
+
57
+    if (id) {
58
+      // 修改
59
+      putSysPosition(id, values)
60
+        .then((res) => {
61
+          navigate(-1);
62
+        })
63
+        .catch(() => {
64
+          cancelSubmit();
65
+        });
66
+    } else {
67
+      // 新增
68
+      postSysPosition(values)
69
+        .then((res) => {
70
+          navigate(-1);
71
+        })
72
+        .catch(() => {
73
+          cancelSubmit();
74
+        });
75
+    }
76
+  };
77
+
78
+  return (
79
+    <Page back>
80
+      <Card>
81
+        <Form
82
+          onFinish={onFinish}
83
+          form={form}
84
+          {...formItemLayout}
85
+          scrollToFirstError
86
+          style={{ maxWidth: "1000px" }}
87
+        >
88
+          <Form.Item
89
+            name="name"
90
+            label="岗位名称"
91
+            getValueFromEvent={(e) =>
92
+              e.target.value.replace(/(^\s*)|(\s*$)/g, "")
93
+            }
94
+          >
95
+            <Input />
96
+          </Form.Item>
97
+          {/* <Form.Item
98
+            name="orgId"
99
+            label="所属机构"
100
+            rules={[{ required: true, message: "请填写机构" }]}
101
+          >
102
+            <TreeSelect
103
+              showSearch
104
+              // style={{ width: "100%", maxWidth: 300 }}
105
+              dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
106
+              placeholder="请选择机构"
107
+              treeDefaultExpandAll
108
+              treeData={orgTreeData}
109
+            ></TreeSelect>
110
+          </Form.Item> */}
111
+
112
+          <Form.Item name="status" label="状态">
113
+            <Select style={{ width: "100%" }} placeholder="请选择状态">
114
+              <Option value={0}>不正常</Option>
115
+              <Option value={1}>正常</Option>
116
+            </Select>
117
+          </Form.Item>
118
+          <Form.Item {...tailFormItemLayout}>
119
+            <Button loading={submiting} type="primary" htmlType="submit">
120
+              保存
121
+            </Button>
122
+            <Button style={{ marginLeft: "2em" }} onClick={() => navigate(-1)}>
123
+              返回
124
+            </Button>
125
+          </Form.Item>
126
+        </Form>
127
+      </Card>
128
+    </Page>
129
+  );
130
+};

+ 112
- 0
src/pages/system/position/index.jsx Näytä tiedosto

@@ -0,0 +1,112 @@
1
+import React from "react";
2
+import { useNavigate } from "react-router-dom";
3
+import { queryTable, queryDict } from "@/utils/request";
4
+import Page from "@/components/Page";
5
+import { ProTable } from "@ant-design/pro-components";
6
+import { Button, message, Popconfirm } from "antd";
7
+import { getSysPosition, deleteSysPosition } from "@/service/sysposition";
8
+import { getSysOrg } from "@/service/sysorg";
9
+
10
+const querySysPositionList = queryTable(getSysPosition);
11
+const queryOrg = queryDict(getSysOrg, { labelKey: "name", valueKey: "orgId" });
12
+
13
+export default (props) => {
14
+  const actionRef = React.useRef();
15
+  const navigate = useNavigate();
16
+
17
+  const handleDelete = (id) => {
18
+    if (id) {
19
+      deleteSysPosition(id).then((res) => {
20
+        actionRef.current.reload();
21
+      });
22
+    }
23
+  };
24
+
25
+  const columns = [
26
+    {
27
+      title: "岗位名称",
28
+      dataIndex: "name",
29
+    },
30
+    {
31
+      title: "所属机构",
32
+      dataIndex: "orgId",
33
+      valueType: "select",
34
+      request: queryOrg,
35
+    },
36
+    {
37
+      title: "状态",
38
+      dataIndex: "status",
39
+      search: false,
40
+      valueEnum: {
41
+        1: {
42
+          text: "正常",
43
+          status: "Processing",
44
+        },
45
+        0: {
46
+          text: "禁用",
47
+          status: "Error",
48
+        },
49
+      },
50
+    },
51
+    {
52
+      title: "操作",
53
+      valueType: "option",
54
+      width: 200,
55
+      render: (_, record) => [
56
+        // <Button
57
+        //   key={1}
58
+        //   style={{ padding: 0 }}
59
+        //   type="link"
60
+        //   onClick={() => {
61
+        //     updateStatus(record);
62
+        //   }}
63
+        // >
64
+        //   {record.status === 1 ? "禁用" : "启用"}
65
+        // </Button>,
66
+        <Button
67
+          key={2}
68
+          style={{ padding: 0 }}
69
+          type="link"
70
+          onClick={() => {
71
+            navigate(`/system/position/edit?id=${record.positionId}`);
72
+          }}
73
+        >
74
+          编辑
75
+        </Button>,
76
+        <Popconfirm
77
+          key={3}
78
+          title="您是否确认删除 ?"
79
+          onConfirm={() => handleDelete(record.positionId)}
80
+          okText="确定"
81
+          cancelText="取消"
82
+        >
83
+          <Button style={{ padding: 0 }} type="link">
84
+            删除
85
+          </Button>
86
+        </Popconfirm>,
87
+      ],
88
+    },
89
+  ];
90
+
91
+  return (
92
+    <Page>
93
+      <ProTable
94
+        actionRef={actionRef}
95
+        rowKey="positionId"
96
+        toolBarRender={() => [
97
+          <Button
98
+            key="1"
99
+            type="primary"
100
+            onClick={() => {
101
+              navigate("/system/position/edit");
102
+            }}
103
+          >
104
+            新增
105
+          </Button>,
106
+        ]}
107
+        request={querySysPositionList}
108
+        columns={columns}
109
+      />
110
+    </Page>
111
+  );
112
+};

+ 23
- 0
src/pages/system/role/index.jsx Näytä tiedosto

@@ -0,0 +1,23 @@
1
+import React from 'react';
2
+import { Row, Col } from 'antd';
3
+import Page from '@/components/Page';
4
+import RoleList from './list';
5
+import MenuList from './menus';
6
+
7
+export default (props) => {
8
+
9
+  const [current, setCurrent] = React.useState();
10
+
11
+  return (
12
+    <Page>
13
+      <Row gutter={24}>
14
+        <Col span={8}>
15
+          <RoleList onChange={setCurrent} current={current} />
16
+        </Col>
17
+        <Col span={16}>
18
+          <MenuList role={current} />
19
+        </Col>
20
+      </Row>
21
+    </Page>
22
+  )
23
+}

+ 169
- 0
src/pages/system/role/list.jsx Näytä tiedosto

@@ -0,0 +1,169 @@
1
+import React from "react";
2
+import { Button, Input, List, Popconfirm, Spin, Modal, message } from "antd";
3
+import useBool from "@/utils/hooks/useBool";
4
+import classNames from "classnames";
5
+import {
6
+  getSysRole,
7
+  postSysRole,
8
+  putSysRole,
9
+  deleteSysRole,
10
+} from "@/service/sysrole";
11
+import "./style.less";
12
+
13
+const Header = (props) => {
14
+  return (
15
+    <div className="role-list-header">
16
+      <div className="role-list-header-body">{props.children}</div>
17
+      <div className="role-list-header-action">{props.action}</div>
18
+    </div>
19
+  );
20
+};
21
+
22
+export default (props) => {
23
+  const { current, onChange } = props;
24
+
25
+  const [loading, startLoading, cancelLoading] = useBool();
26
+  const [visible, openModel, hideModel] = useBool();
27
+  const [finished, setFinished] = React.useState(false);
28
+  const [inText, setInText] = React.useState();
29
+  const [list, setList] = React.useState([]);
30
+
31
+  const onAdd = () => {
32
+    openModel();
33
+    setInText();
34
+    onChange();
35
+  };
36
+
37
+  const onEdit = (item) => {
38
+    openModel();
39
+    setInText(item.name);
40
+    onChange(item);
41
+  };
42
+
43
+  const onDelete = (item) => {
44
+    startLoading();
45
+    // 删除角色
46
+    deleteSysRole(item.roleId)
47
+      .then(() => {
48
+        setList(list.filter((x) => x.roleId !== item.roleId));
49
+        cancelLoading();
50
+        hideModel();
51
+        onChange();
52
+      })
53
+      .catch(() => {
54
+        cancelLoading();
55
+      });
56
+  };
57
+
58
+  const handleOk = () => {
59
+    if (!inText) {
60
+      message.warn("请输入角色名称");
61
+      return;
62
+    }
63
+    startLoading();
64
+    const data = { name: inText };
65
+
66
+    if (current) {
67
+      // 修改角色
68
+      putSysRole(current.roleId, data)
69
+        .then((res) => {
70
+          setList(list.map((x) => (x.roleId === current.roleId ? res : x)));
71
+          cancelLoading();
72
+          hideModel();
73
+        })
74
+        .catch(() => {
75
+          cancelLoading();
76
+        });
77
+    } else {
78
+      // 新增角色
79
+      postSysRole(data)
80
+        .then((res) => {
81
+          setList([res].concat(list));
82
+          cancelLoading();
83
+          hideModel();
84
+          onChange(res);
85
+        })
86
+        .catch(() => {
87
+          cancelLoading();
88
+        });
89
+    }
90
+  };
91
+
92
+  const handleCancel = () => {
93
+    if (loading) {
94
+      return;
95
+    }
96
+
97
+    hideModel();
98
+  };
99
+
100
+  const queryRoles = React.useCallback((params) => {
101
+    startLoading();
102
+    getSysRole(params)
103
+      .then((res) => {
104
+        setList(res.records);
105
+        cancelLoading();
106
+        setFinished(res.current >= res.pages);
107
+      })
108
+      .catch(() => {
109
+        cancelLoading();
110
+      });
111
+  }, []);
112
+
113
+  React.useEffect(() => {
114
+    queryRoles({ pageNum: 1, pageSize: 500 });
115
+  }, []);
116
+
117
+  return (
118
+    <Spin spinning={loading}>
119
+      <List
120
+        header={
121
+          <Header
122
+            action={
123
+              <Button type="primary" onClick={onAdd}>
124
+                新增
125
+              </Button>
126
+            }
127
+          >
128
+            角色列表
129
+          </Header>
130
+        }
131
+        style={{ background: "#fff" }}
132
+        bordered
133
+        dataSource={list}
134
+        renderItem={(item) => (
135
+          <List.Item
136
+            className={classNames("role-list-item", {
137
+              active: current && current.roleId === item.roleId,
138
+            })}
139
+            onClick={() => onChange(item)}
140
+            actions={[
141
+              <Button key="edit" type="link" onClick={() => onEdit(item)}>
142
+                编辑
143
+              </Button>,
144
+              <Popconfirm
145
+                key="delete"
146
+                title="确认删除?"
147
+                onConfirm={() => onDelete(item)}
148
+              >
149
+                <Button type="link" danger>
150
+                  删除
151
+                </Button>
152
+              </Popconfirm>,
153
+            ]}
154
+          >
155
+            {item.name}
156
+          </List.Item>
157
+        )}
158
+      />
159
+      <Modal
160
+        title="请输入角色名称"
161
+        open={visible}
162
+        onOk={handleOk}
163
+        onCancel={handleCancel}
164
+      >
165
+        <Input value={inText} onChange={(e) => setInText(e.target.value)} />
166
+      </Modal>
167
+    </Spin>
168
+  );
169
+};

+ 156
- 0
src/pages/system/role/menus.jsx Näytä tiedosto

@@ -0,0 +1,156 @@
1
+import React from "react";
2
+import { Button, Card, Tree, message } from "antd";
3
+import useBool from "@/utils/hooks/useBool";
4
+import { arr2Tree } from "@/utils/array";
5
+import { getSysMenu } from "@/service/sysmenu";
6
+import {
7
+  getSysRoleResource,
8
+  postSysRoleResource,
9
+} from "@/service/sysroleresource";
10
+
11
+export default (props) => {
12
+  const { role } = props;
13
+
14
+  const [loading, startLoading, cancelLoading] = useBool();
15
+  const [list, setList] = React.useState([]);
16
+  const [expandedKeys, setExpandedKeys] = React.useState([]);
17
+  const [treeData, setTreeData] = React.useState([]);
18
+  const treeDictRef = React.useRef();
19
+  const [checkedKeys, setCheckedKeys] = React.useState({
20
+    checked: [],
21
+    halfChecked: [],
22
+  });
23
+  const keys = checkedKeys.checked.concat(checkedKeys.halfChecked);
24
+
25
+  const title = role ? `${role.name} - 授权菜单` : "未选角色 - 授权菜单";
26
+
27
+  const getListData = () => {
28
+    return new Promise((resolve, reject) => {
29
+      getSysMenu({ pageSize: 999 })
30
+        .then((res = []) => {
31
+          setList(res?.records);
32
+          resolve(res?.records);
33
+        })
34
+        .catch(reject);
35
+    });
36
+  };
37
+
38
+  const onCheck = (keys, { halfCheckedKeys }) => {
39
+    setCheckedKeys({ checked: keys, halfChecked: halfCheckedKeys });
40
+  };
41
+
42
+  const onSubmit = () => {
43
+    // 如果一个都不选,代表清空授权
44
+    const data = [];
45
+    const roleId = role.roleId;
46
+    for (let resourceId of keys) {
47
+      data.push({ roleId, resourceId, resourceType: 'menu' });
48
+    }
49
+    startLoading();
50
+    postSysRoleResource(roleId, data)
51
+      .then((res) => {
52
+        cancelLoading();
53
+      })
54
+      .catch(() => {
55
+        cancelLoading();
56
+      });
57
+  };
58
+
59
+  React.useEffect(() => {
60
+    const p = !list.length ? getListData : () => Promise.resolve(list);
61
+    if (!role || !role.roleId) {
62
+      p();
63
+    } else {
64
+      startLoading();
65
+      p()
66
+        .then((allData) => {
67
+          getSysRoleResource({ roleId: role.roleId, resourceType: 'menu', pageSize: 9999 })
68
+            .then((res = []) => {
69
+              cancelLoading();
70
+
71
+              const { records = [] } = res;
72
+
73
+              // 数据分为2部分, 一部分叶子节点, 一部分非叶子节点
74
+              const checked = [],
75
+                halfChecked = [];
76
+
77
+                for (let item of records) {
78
+                  // 获取子节点, 没有的话则为叶子节点, 否则为非叶子节点
79
+                  const children = allData.filter(
80
+                    (x) => x.parentId === item.resourceId
81
+                  );
82
+                  if (!children.length) {
83
+                    // 没有子节点, 则为叶子节点, 算入选中节点
84
+                    checked.push(item.resourceId);
85
+                  } else {
86
+                    // 获取选中子节点
87
+                    const checkedChildren = records.filter(
88
+                      (x) => x.parentId === item.resourceId
89
+                    );
90
+                    if (checkedChildren.length === children.length) {
91
+                      // 非叶子节点如果 子节点全部包含, 那么算入选中节点
92
+                      checked.push(item.resourceId);
93
+                    } else {
94
+                      halfChecked.push(item.resourceId);
95
+                    }
96
+                  }
97
+                }
98
+
99
+              setCheckedKeys({ checked, halfChecked });
100
+            })
101
+            .catch(() => {
102
+              cancelLoading();
103
+            });
104
+        })
105
+        .catch(() => {
106
+          cancelLoading();
107
+        });
108
+    }
109
+  }, [role]);
110
+
111
+  React.useEffect(() => {
112
+    // 展开节点
113
+    setExpandedKeys(
114
+      list.filter((x) => x.parentId === "-1").map((x) => x.resourceId)
115
+    );
116
+    // 先转为需要的格式
117
+    const arr = list.map((x) => ({
118
+      title: x.name,
119
+      key: x.menuId,
120
+      parentId: x.parentId,
121
+    }));
122
+    // 再转为 tree
123
+    const [tree, dict] = arr2Tree(arr);
124
+    setTreeData(tree);
125
+    treeDictRef.current = dict;
126
+  }, [list]);
127
+
128
+  return (
129
+    <Card
130
+      loading={loading}
131
+      title={title}
132
+      extra={
133
+        <Button
134
+          type="primary"
135
+          disabled={!role || !role.roleId}
136
+          ghost
137
+          onClick={onSubmit}
138
+        >
139
+          保存
140
+        </Button>
141
+      }
142
+    >
143
+      <Tree
144
+        checkable
145
+        autoExpandParent
146
+        expandedKeys={expandedKeys}
147
+        checkStrictly={false}
148
+        selectable={false}
149
+        checkedKeys={checkedKeys}
150
+        onCheck={onCheck}
151
+        onExpand={setExpandedKeys}
152
+        treeData={treeData}
153
+      />
154
+    </Card>
155
+  );
156
+};

+ 19
- 0
src/pages/system/role/style.less Näytä tiedosto

@@ -0,0 +1,19 @@
1
+
2
+.role-list-header {
3
+  width: 100%;
4
+  display: flex;
5
+
6
+  .role-list-header-body {
7
+    flex: 1;
8
+  }
9
+
10
+  .role-list-header-action {
11
+    flex: 0
12
+  }
13
+}
14
+
15
+.role-list-item {
16
+  &.active {
17
+    background: #e6f7ff;
18
+  }
19
+}

+ 162
- 0
src/pages/system/user/Edit.jsx Näytä tiedosto

@@ -0,0 +1,162 @@
1
+import React, { useMemo } from "react";
2
+import { Button, Card, Form, Input, Select, Switch } from "antd";
3
+import useBool from "@/utils/hooks/useBool";
4
+import { postSysUser,getSysUserById } from "@/service/sysuser";
5
+// import { postSysUser } from "@/service/authCenter";
6
+import { useSearchParams, useNavigate } from "react-router-dom";
7
+import Page from "@/components/Page";
8
+import { formItemLayout, tailFormItemLayout } from "@/utils/form";
9
+import { getSysOrg } from "@/service/sysorg";
10
+import UserDrawer from "./components/UserDrawer";
11
+import SelectRole from "./components/SelectRole";
12
+import { ProFormSwitch } from "@ant-design/pro-components";
13
+
14
+const { Option } = Select;
15
+
16
+export default (props) => {
17
+  const [loading, startLoading, cancelLoading] = useBool();
18
+  const [submiting, startSubmit, cancelSubmit] = useBool();
19
+  const [open, setOpen] = React.useState(false);
20
+  const [orgList, setOrgList] = React.useState([]);
21
+  const [posList, setPosList] = React.useState([]);
22
+  const [searchParams] = useSearchParams();
23
+  const [form] = Form.useForm();
24
+  const [user, setUser] = React.useState();
25
+  const staffRef = React.useRef();
26
+  const navigate = useNavigate();
27
+
28
+  const id = searchParams.get("id");
29
+
30
+  const onFinish = (values) => {
31
+    startSubmit();
32
+
33
+    const staffData = staffRef.current
34
+      ? {
35
+          hxId: staffRef.current?.hxId,
36
+          name: staffRef.current?.name,
37
+          avatar: staffRef.current?.avatar,
38
+          phone: staffRef.current?.phone,
39
+        }
40
+      : {};
41
+
42
+    // 把时间去掉
43
+    const rolesList = (values.rolesList || []).map((x) => ({
44
+      ...x,
45
+      createDate: undefined,
46
+      updateDate: undefined,
47
+      createUser: undefined,
48
+      updateUser: undefined,
49
+    }));
50
+
51
+    postSysUser({
52
+      ...values,
53
+      rolesList,
54
+      userId: id || staffRef.current?.userId,
55
+      ...staffData,
56
+    })
57
+      .then((res) => {
58
+        cancelSubmit();
59
+        navigate(-1);
60
+      })
61
+      .catch(() => {
62
+        cancelSubmit();
63
+      });
64
+  };
65
+
66
+  const onChange = (userId, staff) => {
67
+    if (!userId) return;
68
+
69
+    staffRef.current = staff;
70
+
71
+    const data = {
72
+      ...(user || {}),
73
+      userId,
74
+      name: staff.name,
75
+      phone: staff.phone,
76
+      hxId: staff.hxId,
77
+    };
78
+
79
+    setUser(data);
80
+    form.setFieldsValue(data);
81
+  };
82
+
83
+  React.useEffect(() => {
84
+    getSysOrg({ pageSize: 500 }).then((res) => setOrgList(res.records || []));
85
+  }, []);
86
+
87
+  React.useEffect(() => {
88
+    if (id) {
89
+      startLoading();
90
+      getSysUserById(id)
91
+        .then((res) => {
92
+          setUser(res);
93
+          form.setFieldsValue(res);
94
+          cancelLoading();
95
+        })
96
+        .catch((err) => {
97
+          console.error(err);
98
+          cancelLoading();
99
+        });
100
+    } else {
101
+      // form.setFieldsValue({ password: '123456' });
102
+    }
103
+  }, [id]);
104
+
105
+  return (
106
+    <Page back>
107
+      <UserDrawer open={open} onChange={onChange} onOpenChange={setOpen} />
108
+      <Card loading={loading}>
109
+        <Form
110
+          onFinish={onFinish}
111
+          form={form}
112
+          {...formItemLayout}
113
+          scrollToFirstError
114
+          style={{ maxWidth: "1000px" }}
115
+        >
116
+          <Form.Item name="name" label="姓名">
117
+            {user?.userId ? (
118
+              <Input readOnly onClick={() => setOpen(true)} />
119
+            ) : (
120
+              <Button type="primary" onClick={() => setOpen(true)}>
121
+                选择人员
122
+              </Button>
123
+            )}
124
+          </Form.Item>
125
+          <Form.Item name="phone" label="手机号">
126
+            <Input readOnly />
127
+          </Form.Item>
128
+          <Form.Item name="hxId" label="EHR">
129
+            <Input readOnly />
130
+          </Form.Item>
131
+          <Form.Item name="rolesList" label="角色">
132
+            <SelectRole />
133
+          </Form.Item>
134
+          <Form.Item
135
+            name="isAdmin"
136
+            label="是否管理员"
137
+            valuePropName="checked"
138
+            normalize={(x) => (x ? 1 : 0)}
139
+          >
140
+            <Switch checkedChildren="开启" unCheckedChildren="关闭" />
141
+          </Form.Item>
142
+          <Form.Item name="orgId" label="部门">
143
+            <Select placeholder="请选择部门" allowClear>
144
+              {orgList.map((x) => (
145
+                <Option key={x.orgId}>{x.name}</Option>
146
+              ))}
147
+            </Select>
148
+          </Form.Item>
149
+
150
+          <Form.Item {...tailFormItemLayout}>
151
+            <Button loading={submiting} type="primary" htmlType="submit">
152
+              保存
153
+            </Button>
154
+            <Button style={{ marginLeft: "2em" }} onClick={() => navigate(-1)}>
155
+              返回
156
+            </Button>
157
+          </Form.Item>
158
+        </Form>
159
+      </Card>
160
+    </Page>
161
+  );
162
+};

+ 36
- 0
src/pages/system/user/components/SelectRole.jsx Näytä tiedosto

@@ -0,0 +1,36 @@
1
+import React from 'react';
2
+import { Select } from 'antd';
3
+import { getSysRole } from '@/service/sysrole';
4
+
5
+
6
+export default (props) => {
7
+  const { value, onChange, ...leftProps } = props;
8
+
9
+  const [list, setList] = React.useState([]);
10
+  const data = React.useMemo(() => (value || []).map(x => x.roleId), [value]);
11
+  const options = React.useMemo(() => list.map(x => ({ label: x.name, value: x.roleId })), [list]);
12
+
13
+  const handleChange = (values = []) => {
14
+    if (onChange) {
15
+      const dts = values.map(x => list.filter(y => y.roleId === x)[0]);
16
+      onChange(dts);
17
+    }
18
+  }
19
+
20
+  React.useEffect(() => {
21
+    getSysRole({ pageSize: 500 }).then(res => {
22
+      setList(res.records || []);
23
+    })
24
+  }, []);
25
+
26
+  return (
27
+    <Select
28
+      mode="multiple"
29
+      allowClear
30
+      value={data}
31
+      onChange={handleChange}
32
+      options={options}
33
+      {...leftProps}
34
+    />
35
+  )
36
+}

+ 49
- 0
src/pages/system/user/components/UserDrawer.jsx Näytä tiedosto

@@ -0,0 +1,49 @@
1
+import React from 'react';
2
+import { Button, Drawer, Space } from 'antd';
3
+import StaffSearch from '@/components/Staff/StaffSearch';
4
+
5
+export default (props) => {
6
+
7
+  const { open, onOpenChange, onChange } = props;
8
+
9
+  const [vals, setVals] = React.useState([]);
10
+
11
+  const onClose = () => {
12
+    if (onOpenChange) {
13
+      onOpenChange(false);
14
+    }
15
+  }
16
+
17
+  const handleChange = (...args) => {
18
+    setVals(args);
19
+  }
20
+
21
+  const onSubmit = () => {
22
+    onOpenChange(false);
23
+    if (onChange) {
24
+      onChange(...(vals || []));
25
+    }
26
+  }
27
+
28
+  return (
29
+    <Drawer
30
+      title="请选择员工"
31
+      placement="right"
32
+      open={open}
33
+      width={720}
34
+      bodyStyle={{ paddingBottom: 80 }}
35
+      onClose={onClose}
36
+      extra={
37
+        <Space>
38
+          <Button onClick={onClose}>Cancel</Button>
39
+          <Button onClick={onSubmit} type="primary">
40
+            确定
41
+          </Button>
42
+        </Space>
43
+      }
44
+    >
45
+      <StaffSearch onChange={handleChange} />
46
+    </Drawer>
47
+  )
48
+}
49
+

+ 140
- 0
src/pages/system/user/index.jsx Näytä tiedosto

@@ -0,0 +1,140 @@
1
+import React from "react";
2
+import { useNavigate } from "react-router-dom";
3
+import { Button, Popconfirm, message } from "antd";
4
+import { ProTable } from "@ant-design/pro-components";
5
+import { queryTable, queryDict } from "@/utils/request";
6
+import { getSysUser, updateUserStatus, deleteSysUser } from "@/service/sysuser";
7
+import { getSysOrg } from "@/service/sysorg";
8
+import Page from "@/components/Page";
9
+
10
+const queryUserList = queryTable(getSysUser);
11
+const queryOrg = queryDict(getSysOrg, { labelKey: "name", valueKey: "orgId" });
12
+// const queryPosition = queryDict(getSysPosition, { labelKey: "name", valueKey: "positionId" });
13
+
14
+export default (props) => {
15
+  const actionRef = React.useRef();
16
+  const [open, setOpen] = React.useState(false);
17
+  const navigate = useNavigate();
18
+
19
+  const updateStatus = (user) => {
20
+    const status = user.status === 1 ? 0 : 1;
21
+    const hide = message.loading("请稍候...", 0);
22
+    updateUserStatus(user.userId, status)
23
+      .then((res) => {
24
+        hide();
25
+        actionRef.current.reload();
26
+      })
27
+      .catch(() => {
28
+        hide();
29
+      });
30
+  };
31
+
32
+  const handleDelete = (id) => {
33
+    if (id) {
34
+      deleteSysUser(id).then((res) => {
35
+        actionRef.current.reload();
36
+      });
37
+    }
38
+  };
39
+
40
+  const columns = [
41
+    {
42
+      title: "EHR",
43
+      search: false,
44
+      dataIndex: "hxId",
45
+    },
46
+    {
47
+      title: "姓名",
48
+      dataIndex: "name",
49
+    },
50
+    {
51
+      title: "手机号",
52
+      search: false,
53
+      dataIndex: "phone",
54
+    },
55
+    {
56
+      title: "网点/部门",
57
+      dataIndex: "orgId",
58
+      valueType: "select",
59
+      request: queryOrg,
60
+      fieldProps: {
61
+        showSearch: true,
62
+        filterOption: (x, opt) => opt.label?.includes(x),
63
+      },
64
+    },
65
+    {
66
+      title: "状态",
67
+      dataIndex: "status",
68
+      search: false,
69
+      valueEnum: {
70
+        1: {
71
+          text: "正常",
72
+          status: "Processing",
73
+        },
74
+        0: {
75
+          text: "禁用",
76
+          status: "Error",
77
+        },
78
+      },
79
+    },
80
+    {
81
+      title: "操作",
82
+      valueType: "option",
83
+      width: 200,
84
+      render: (_, record) => [
85
+        <Button
86
+          key={1}
87
+          style={{ padding: 0 }}
88
+          type="link"
89
+          onClick={() => {
90
+            updateStatus(record);
91
+          }}
92
+        >
93
+          {record.status === 1 ? "禁用" : "启用"}
94
+        </Button>,
95
+        <Button
96
+          key={2}
97
+          style={{ padding: 0 }}
98
+          type="link"
99
+          onClick={() => navigate(`/system/user/edit?id=${record.userId}`)}
100
+        >
101
+          编辑
102
+        </Button>,
103
+        <Popconfirm
104
+          key={4}
105
+          title="您是否确认删除 ?"
106
+          onConfirm={() => handleDelete(record.userId)}
107
+          okText="确定"
108
+          cancelText="取消"
109
+        >
110
+          {/* manualPush */}
111
+          <Button style={{ padding: 0 }} danger type="link">
112
+            删除
113
+          </Button>
114
+        </Popconfirm>,
115
+      ],
116
+    },
117
+  ];
118
+
119
+  return (
120
+    <Page>
121
+      <ProTable
122
+        actionRef={actionRef}
123
+        rowKey="userId"
124
+        toolBarRender={() => [
125
+          <Button
126
+            key="1"
127
+            type="primary"
128
+            onClick={() => {
129
+              navigate("/system/user/edit");
130
+            }}
131
+          >
132
+            新增
133
+          </Button>,
134
+        ]}
135
+        request={queryUserList}
136
+        columns={columns}
137
+      />
138
+    </Page>
139
+  );
140
+};

+ 0
- 0
src/pages/work/index.jsx Näytä tiedosto


Some files were not shown because too many files changed in this diff