mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
feat: 使用react-awesome-query-builder实现自动加分规则构建器
This commit is contained in:
@@ -19,8 +19,6 @@
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@react-awesome-query-builder/antd": "6.7.0-alpha.0",
|
||||
"@react-querybuilder/antd": "^8.14.0",
|
||||
"@react-querybuilder/dnd": "^8.14.0",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"antd": "^6.3.1",
|
||||
"dayjs": "^1.11.20",
|
||||
@@ -28,9 +26,6 @@
|
||||
"json-rules-engine": "^7.3.1",
|
||||
"pinyin-pro": "^3.27.0",
|
||||
"react": "^19.2.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-touch-backend": "^16.0.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"react-i18next": "^16.5.6",
|
||||
"react-querybuilder": "^8.14.0",
|
||||
|
||||
Generated
+18
-122
@@ -14,12 +14,6 @@ importers:
|
||||
'@react-awesome-query-builder/antd':
|
||||
specifier: 6.7.0-alpha.0
|
||||
version: 6.7.0-alpha.0(@ant-design/icons@6.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(antd@6.3.2(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(dayjs@1.11.20)(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@react-querybuilder/antd':
|
||||
specifier: ^8.14.0
|
||||
version: 8.14.0(@ant-design/icons@6.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(antd@6.3.2(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(dayjs@1.11.20)(react-querybuilder@8.14.0(@types/react@19.2.14)(json-logic-js@2.0.5)(react@19.2.4)(redux@5.0.1))(react@19.2.4)
|
||||
'@react-querybuilder/dnd':
|
||||
specifier: ^8.14.0
|
||||
version: 8.14.0(react-dnd-html5-backend@16.0.1)(react-dnd-touch-backend@16.0.1)(react-dnd@16.0.1(@types/hoist-non-react-statics@3.3.7(@types/react@19.2.14))(@types/node@22.19.15)(@types/react@19.2.14)(react@19.2.4))(react-querybuilder@8.14.0(@types/react@19.2.14)(json-logic-js@2.0.5)(react@19.2.4)(redux@5.0.1))(react@19.2.4)
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2.5.0
|
||||
version: 2.10.1
|
||||
@@ -41,15 +35,6 @@ importers:
|
||||
react:
|
||||
specifier: ^19.2.1
|
||||
version: 19.2.4
|
||||
react-dnd:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1(@types/hoist-non-react-statics@3.3.7(@types/react@19.2.14))(@types/node@22.19.15)(@types/react@19.2.14)(react@19.2.4)
|
||||
react-dnd-html5-backend:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1
|
||||
react-dnd-touch-backend:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1
|
||||
react-dom:
|
||||
specifier: ^19.2.1
|
||||
version: 19.2.4(react@19.2.4)
|
||||
@@ -791,24 +776,6 @@ packages:
|
||||
react: ^16.8.4 || ^17.0.1 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.4 || ^17.0.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-dnd/asap@5.0.2':
|
||||
resolution: {integrity: sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==}
|
||||
|
||||
'@react-dnd/invariant@4.0.2':
|
||||
resolution: {integrity: sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==}
|
||||
|
||||
'@react-dnd/shallowequal@4.0.2':
|
||||
resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==}
|
||||
|
||||
'@react-querybuilder/antd@8.14.0':
|
||||
resolution: {integrity: sha512-H6SMCmKtciOIJyXo29oLMMDIG1+ydE7eoIqAIhItsvJN7iLcAPhvKd5+CuHyOWj9tokEAHGpdm4BP+KPFAxTNw==}
|
||||
peerDependencies:
|
||||
'@ant-design/icons': '>=5'
|
||||
antd: '>=5.11.0'
|
||||
dayjs: '>=1'
|
||||
react: '>=18'
|
||||
react-querybuilder: 8.14.0
|
||||
|
||||
'@react-querybuilder/core@8.14.0':
|
||||
resolution: {integrity: sha512-j1pIY0Yyn/dXu9ZST/DVY7TqRmIO1hY/mZ8653DaeHaDzUF37tOdkm/NQDU9RfM0KXIWsJY5zlvYAR1DypZ+7g==}
|
||||
peerDependencies:
|
||||
@@ -823,20 +790,6 @@ packages:
|
||||
sequelize:
|
||||
optional: true
|
||||
|
||||
'@react-querybuilder/dnd@8.14.0':
|
||||
resolution: {integrity: sha512-g0BHs9Vtbm7OhnhIq1pxJNY3JXNmSbA7NVTlOuqoAfFl0D46rZfGInVzoBfpwVpoLtQIXPO25Y5N/1/LcWuSNg==}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dnd: '>=14.0.0'
|
||||
react-dnd-html5-backend: '>=14.0.0'
|
||||
react-dnd-touch-backend: '>=14.0.0'
|
||||
react-querybuilder: 8.14.0
|
||||
peerDependenciesMeta:
|
||||
react-dnd-html5-backend:
|
||||
optional: true
|
||||
react-dnd-touch-backend:
|
||||
optional: true
|
||||
|
||||
'@reduxjs/toolkit@2.11.2':
|
||||
resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==}
|
||||
peerDependencies:
|
||||
@@ -889,66 +842,79 @@ packages:
|
||||
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
|
||||
@@ -1012,30 +978,35 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.10.1':
|
||||
resolution: {integrity: sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tauri-apps/cli-linux-riscv64-gnu@2.10.1':
|
||||
resolution: {integrity: sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.10.1':
|
||||
resolution: {integrity: sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tauri-apps/cli-linux-x64-musl@2.10.1':
|
||||
resolution: {integrity: sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.10.1':
|
||||
resolution: {integrity: sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==}
|
||||
@@ -1372,9 +1343,6 @@ packages:
|
||||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
dnd-core@16.0.1:
|
||||
resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==}
|
||||
|
||||
doctrine@2.1.0:
|
||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2029,27 +1997,6 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
react-dnd-html5-backend@16.0.1:
|
||||
resolution: {integrity: sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==}
|
||||
|
||||
react-dnd-touch-backend@16.0.1:
|
||||
resolution: {integrity: sha512-NonoCABzzjyWGZuDxSG77dbgMZ2Wad7eQiCd/ECtsR2/NBLTjGksPUx9UPezZ1nQ/L7iD130Tz3RUshL/ClKLA==}
|
||||
|
||||
react-dnd@16.0.1:
|
||||
resolution: {integrity: sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==}
|
||||
peerDependencies:
|
||||
'@types/hoist-non-react-statics': '>= 3.3.1'
|
||||
'@types/node': '>= 12'
|
||||
'@types/react': '>= 16'
|
||||
react: '>= 16.14'
|
||||
peerDependenciesMeta:
|
||||
'@types/hoist-non-react-statics':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-dom@19.2.4:
|
||||
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
|
||||
peerDependencies:
|
||||
@@ -3210,20 +3157,6 @@ snapshots:
|
||||
- '@types/react-dom'
|
||||
- react-native
|
||||
|
||||
'@react-dnd/asap@5.0.2': {}
|
||||
|
||||
'@react-dnd/invariant@4.0.2': {}
|
||||
|
||||
'@react-dnd/shallowequal@4.0.2': {}
|
||||
|
||||
'@react-querybuilder/antd@8.14.0(@ant-design/icons@6.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(antd@6.3.2(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(dayjs@1.11.20)(react-querybuilder@8.14.0(@types/react@19.2.14)(json-logic-js@2.0.5)(react@19.2.4)(redux@5.0.1))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@ant-design/icons': 6.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
antd: 6.3.2(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
dayjs: 1.11.20
|
||||
react: 19.2.4
|
||||
react-querybuilder: 8.14.0(@types/react@19.2.14)(json-logic-js@2.0.5)(react@19.2.4)(redux@5.0.1)
|
||||
|
||||
'@react-querybuilder/core@8.14.0(json-logic-js@2.0.5)':
|
||||
dependencies:
|
||||
'@ts-jison/lexer': 0.4.1-alpha.1
|
||||
@@ -3233,15 +3166,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
json-logic-js: 2.0.5
|
||||
|
||||
'@react-querybuilder/dnd@8.14.0(react-dnd-html5-backend@16.0.1)(react-dnd-touch-backend@16.0.1)(react-dnd@16.0.1(@types/hoist-non-react-statics@3.3.7(@types/react@19.2.14))(@types/node@22.19.15)(@types/react@19.2.14)(react@19.2.4))(react-querybuilder@8.14.0(@types/react@19.2.14)(json-logic-js@2.0.5)(react@19.2.4)(redux@5.0.1))(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react-dnd: 16.0.1(@types/hoist-non-react-statics@3.3.7(@types/react@19.2.14))(@types/node@22.19.15)(@types/react@19.2.14)(react@19.2.4)
|
||||
react-querybuilder: 8.14.0(@types/react@19.2.14)(json-logic-js@2.0.5)(react@19.2.4)(redux@5.0.1)
|
||||
optionalDependencies:
|
||||
react-dnd-html5-backend: 16.0.1
|
||||
react-dnd-touch-backend: 16.0.1
|
||||
|
||||
'@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
@@ -3811,12 +3735,6 @@ snapshots:
|
||||
has-property-descriptors: 1.0.2
|
||||
object-keys: 1.1.1
|
||||
|
||||
dnd-core@16.0.1:
|
||||
dependencies:
|
||||
'@react-dnd/asap': 5.0.2
|
||||
'@react-dnd/invariant': 4.0.2
|
||||
redux: 4.2.1
|
||||
|
||||
doctrine@2.1.0:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
@@ -4594,28 +4512,6 @@ snapshots:
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-is: 18.3.1
|
||||
|
||||
react-dnd-html5-backend@16.0.1:
|
||||
dependencies:
|
||||
dnd-core: 16.0.1
|
||||
|
||||
react-dnd-touch-backend@16.0.1:
|
||||
dependencies:
|
||||
'@react-dnd/invariant': 4.0.2
|
||||
dnd-core: 16.0.1
|
||||
|
||||
react-dnd@16.0.1(@types/hoist-non-react-statics@3.3.7(@types/react@19.2.14))(@types/node@22.19.15)(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
'@react-dnd/invariant': 4.0.2
|
||||
'@react-dnd/shallowequal': 4.0.2
|
||||
dnd-core: 16.0.1
|
||||
fast-deep-equal: 3.1.3
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/hoist-non-react-statics': 3.3.7(@types/react@19.2.14)
|
||||
'@types/node': 22.19.15
|
||||
'@types/react': 19.2.14
|
||||
|
||||
react-dom@19.2.4(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from "react"
|
||||
import { HolderOutlined } from "@ant-design/icons"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import RuleComponent from "./autoScore/ruleComponent"
|
||||
import DemoQueryBuilder from "./autoScore/aaa"
|
||||
import {
|
||||
Card,
|
||||
Form,
|
||||
@@ -426,44 +426,8 @@ export const AutoScoreManager: React.FC = () => {
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<RuleComponent />
|
||||
|
||||
{/* <Card
|
||||
style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}
|
||||
title={t('autoScore.whenTriggered')}
|
||||
>
|
||||
<Space orientation="vertical" style={{ width: '100%' }}>
|
||||
{triggerList.map((trigger, index) => renderTriggerItem(trigger, index))}
|
||||
{' '}
|
||||
|
||||
<Button
|
||||
type="dashed"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddTrigger}
|
||||
style={{ fontWeight: 'bolder', fontSize: 15 }}
|
||||
>
|
||||
{t('autoScore.addTrigger')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Card> */}
|
||||
|
||||
{/* <Card
|
||||
style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}
|
||||
title={t('autoScore.triggeredActions')}
|
||||
>
|
||||
<Space orientation="vertical" style={{ width: '100%' }}>
|
||||
{actionList.map((action) => renderActionItem(action))}
|
||||
<Button
|
||||
type="dashed"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddAction}
|
||||
style={{ fontWeight: 'bolder', fontSize: 15 }}
|
||||
>
|
||||
{t('autoScore.addAction')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Card> */}
|
||||
|
||||
<DemoQueryBuilder />
|
||||
|
||||
<div style={{ marginBottom: "24px", display: "flex", gap: "12px" }}>
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
{editingRuleId !== null ? t("autoScore.updateAutomation") : t("autoScore.addAutomation")}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import type { Config, JsonGroup } from "@react-awesome-query-builder/antd"
|
||||
import { Utils as QbUtils, AntdConfig } from "@react-awesome-query-builder/antd"
|
||||
|
||||
const InitialConfig = AntdConfig
|
||||
|
||||
export interface AutoScoreRuleConfig {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
type: "interval_time" | "student_tag"
|
||||
config: Config
|
||||
defaultQuery: JsonGroup
|
||||
}
|
||||
|
||||
export const intervalTimeConfig: Config = {
|
||||
...InitialConfig,
|
||||
ctx: InitialConfig.ctx,
|
||||
fields: {
|
||||
interval_minutes: {
|
||||
label: "间隔时间(分钟)",
|
||||
type: "number",
|
||||
fieldSettings: {
|
||||
min: 1,
|
||||
max: 1440,
|
||||
},
|
||||
valueSources: ["value"],
|
||||
preferWidgets: ["number"],
|
||||
},
|
||||
score_value: {
|
||||
label: "加分数值",
|
||||
type: "number",
|
||||
fieldSettings: {
|
||||
min: -100,
|
||||
max: 100,
|
||||
},
|
||||
valueSources: ["value"],
|
||||
preferWidgets: ["number"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const studentTagConfig: Config = {
|
||||
...InitialConfig,
|
||||
ctx: InitialConfig.ctx,
|
||||
fields: {
|
||||
tag_name: {
|
||||
label: "学生标签",
|
||||
type: "select",
|
||||
valueSources: ["value"],
|
||||
fieldSettings: {
|
||||
listValues: [
|
||||
{ value: "优秀", title: "优秀" },
|
||||
{ value: "进步", title: "进步" },
|
||||
{ value: "活跃", title: "活跃" },
|
||||
{ value: "负责", title: "负责" },
|
||||
{ value: "需关注", title: "需关注" },
|
||||
],
|
||||
},
|
||||
},
|
||||
score_value: {
|
||||
label: "加分数值",
|
||||
type: "number",
|
||||
fieldSettings: {
|
||||
min: -100,
|
||||
max: 100,
|
||||
},
|
||||
valueSources: ["value"],
|
||||
preferWidgets: ["number"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const defaultIntervalTimeQuery: JsonGroup = {
|
||||
id: QbUtils.uuid(),
|
||||
type: "group",
|
||||
children1: [
|
||||
{
|
||||
id: QbUtils.uuid(),
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: "interval_minutes",
|
||||
operator: "greater",
|
||||
value: [30],
|
||||
valueSrc: ["value"],
|
||||
valueType: ["number"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const defaultStudentTagQuery: JsonGroup = {
|
||||
id: QbUtils.uuid(),
|
||||
type: "group",
|
||||
children1: [
|
||||
{
|
||||
id: QbUtils.uuid(),
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: "tag_name",
|
||||
operator: "select_equals",
|
||||
value: ["优秀"],
|
||||
valueSrc: ["value"],
|
||||
valueType: ["select"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const ruleConfigs: AutoScoreRuleConfig[] = [
|
||||
{
|
||||
id: "interval_time_rule",
|
||||
name: "间隔时间加分",
|
||||
description: "根据学生上次加分后的间隔时间自动加分",
|
||||
type: "interval_time",
|
||||
config: intervalTimeConfig,
|
||||
defaultQuery: defaultIntervalTimeQuery,
|
||||
},
|
||||
{
|
||||
id: "student_tag_rule",
|
||||
name: "学生标签加分",
|
||||
description: "根据学生拥有的标签自动加分",
|
||||
type: "student_tag",
|
||||
config: studentTagConfig,
|
||||
defaultQuery: defaultStudentTagQuery,
|
||||
},
|
||||
]
|
||||
|
||||
export const getRuleConfig = (
|
||||
type: "interval_time" | "student_tag"
|
||||
): AutoScoreRuleConfig | undefined => {
|
||||
return ruleConfigs.find((r) => r.type === type)
|
||||
}
|
||||
|
||||
export const validateQuery = (tree: any, config: Config): boolean => {
|
||||
try {
|
||||
return QbUtils.isValidTree(tree, config)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const exportQueryToJsonLogic = (tree: any, config: Config): string => {
|
||||
try {
|
||||
const result = QbUtils.jsonLogicFormat(tree, config)
|
||||
return JSON.stringify(result.logic, null, 2)
|
||||
} catch {
|
||||
return "{}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import React, { useState, useCallback } from "react"
|
||||
import { Card, Tabs, Button, Space, message, Form, Input, Select } from "antd"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import type {
|
||||
Config,
|
||||
ImmutableTree,
|
||||
BuilderProps,
|
||||
JsonGroup,
|
||||
} from "@react-awesome-query-builder/antd"
|
||||
import { Utils as QbUtils, Query, Builder } from "@react-awesome-query-builder/antd"
|
||||
import "@react-awesome-query-builder/antd/css/styles.css"
|
||||
import { ruleConfigs, getRuleConfig, exportQueryToJsonLogic } from "./BuilderUtils"
|
||||
|
||||
interface RuleProps {
|
||||
onRuleSubmit?: (ruleData: {
|
||||
type: string
|
||||
name: string
|
||||
query: JsonGroup
|
||||
jsonLogic: string
|
||||
studentNames: string[]
|
||||
}) => void
|
||||
}
|
||||
|
||||
export const Rule: React.FC<RuleProps> = ({ onRuleSubmit }) => {
|
||||
const { t } = useTranslation()
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
const [form] = Form.useForm()
|
||||
const [activeTab, setActiveTab] = useState<string>("interval_time")
|
||||
const [students, setStudents] = useState<{ id: number; name: string }[]>([])
|
||||
|
||||
const [intervalTimeState, setIntervalTimeState] = useState(() => {
|
||||
const config = getRuleConfig("interval_time")
|
||||
return {
|
||||
tree: QbUtils.loadTree(config?.defaultQuery || { id: QbUtils.uuid(), type: "group" }),
|
||||
config: config?.config || ({} as Config),
|
||||
}
|
||||
})
|
||||
|
||||
const [studentTagState, setStudentTagState] = useState(() => {
|
||||
const config = getRuleConfig("student_tag")
|
||||
return {
|
||||
tree: QbUtils.loadTree(config?.defaultQuery || { id: QbUtils.uuid(), type: "group" }),
|
||||
config: config?.config || ({} as Config),
|
||||
}
|
||||
})
|
||||
|
||||
const fetchStudents = useCallback(async () => {
|
||||
if (!(window as any).api) return
|
||||
try {
|
||||
const res = await (window as any).api.queryStudents({})
|
||||
if (res.success) {
|
||||
setStudents(res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch students:", error)
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchStudents()
|
||||
}, [fetchStudents])
|
||||
|
||||
const handleIntervalTimeChange = useCallback((immutableTree: ImmutableTree, config: Config) => {
|
||||
setIntervalTimeState((prev) => ({ ...prev, tree: immutableTree, config }))
|
||||
}, [])
|
||||
|
||||
const handleStudentTagChange = useCallback((immutableTree: ImmutableTree, config: Config) => {
|
||||
setStudentTagState((prev) => ({ ...prev, tree: immutableTree, config }))
|
||||
}, [])
|
||||
|
||||
const renderBuilder = useCallback(
|
||||
(props: BuilderProps) => (
|
||||
<div className="query-builder-container" style={{ padding: "10px" }}>
|
||||
<div className="query-builder qb-lite">
|
||||
<Builder {...props} />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const values = form.getFieldsValue(true)
|
||||
if (!values.name) {
|
||||
messageApi.warning(t("autoScore.nameRequired"))
|
||||
return
|
||||
}
|
||||
|
||||
const ruleConfig = getRuleConfig(activeTab as "interval_time" | "student_tag")
|
||||
if (!ruleConfig) {
|
||||
messageApi.error(t("autoScore.ruleConfigNotFound"))
|
||||
return
|
||||
}
|
||||
|
||||
const currentState = activeTab === "interval_time" ? intervalTimeState : studentTagState
|
||||
const jsonLogic = exportQueryToJsonLogic(currentState.tree, currentState.config)
|
||||
|
||||
if (onRuleSubmit) {
|
||||
onRuleSubmit({
|
||||
type: activeTab,
|
||||
name: values.name,
|
||||
query: QbUtils.getTree(currentState.tree) as unknown as JsonGroup,
|
||||
jsonLogic,
|
||||
studentNames: values.studentNames || [],
|
||||
})
|
||||
}
|
||||
|
||||
messageApi.success(t("autoScore.ruleSaved"))
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
form.resetFields()
|
||||
const intervalConfig = getRuleConfig("interval_time")
|
||||
const tagConfig = getRuleConfig("student_tag")
|
||||
|
||||
setIntervalTimeState({
|
||||
tree: QbUtils.loadTree(intervalConfig?.defaultQuery || { id: QbUtils.uuid(), type: "group" }),
|
||||
config: intervalConfig?.config || ({} as Config),
|
||||
})
|
||||
|
||||
setStudentTagState({
|
||||
tree: QbUtils.loadTree(tagConfig?.defaultQuery || { id: QbUtils.uuid(), type: "group" }),
|
||||
config: tagConfig?.config || ({} as Config),
|
||||
})
|
||||
}
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
key: "interval_time",
|
||||
label: t("autoScore.intervalTimeRule"),
|
||||
children: (
|
||||
<div>
|
||||
<p style={{ marginBottom: 16, color: "var(--ss-text-secondary)" }}>
|
||||
{ruleConfigs[0].description}
|
||||
</p>
|
||||
<Query
|
||||
{...intervalTimeState.config}
|
||||
value={intervalTimeState.tree}
|
||||
onChange={handleIntervalTimeChange}
|
||||
renderBuilder={renderBuilder}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "student_tag",
|
||||
label: t("autoScore.studentTagRule"),
|
||||
children: (
|
||||
<div>
|
||||
<p style={{ marginBottom: 16, color: "var(--ss-text-secondary)" }}>
|
||||
{ruleConfigs[1].description}
|
||||
</p>
|
||||
<Query
|
||||
{...studentTagState.config}
|
||||
value={studentTagState.tree}
|
||||
onChange={handleStudentTagChange}
|
||||
renderBuilder={renderBuilder}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
{contextHolder}
|
||||
<Card style={{ marginBottom: 24, backgroundColor: "var(--ss-card-bg)" }}>
|
||||
<Form form={form} layout="vertical">
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24 }}>
|
||||
<Form.Item
|
||||
label={t("autoScore.ruleName")}
|
||||
name="name"
|
||||
rules={[{ required: true, message: t("autoScore.nameRequired") }]}
|
||||
>
|
||||
<Input placeholder={t("autoScore.ruleNamePlaceholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("autoScore.applicableStudents")} name="studentNames">
|
||||
<Select
|
||||
mode="multiple"
|
||||
showSearch
|
||||
placeholder={t("autoScore.studentPlaceholder")}
|
||||
options={students.map((student) => ({ label: student.name, value: student.name }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<Card style={{ marginBottom: 24, backgroundColor: "var(--ss-card-bg)" }}>
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
|
||||
</Card>
|
||||
|
||||
<Space>
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
{t("autoScore.saveRule")}
|
||||
</Button>
|
||||
<Button onClick={handleReset}>{t("autoScore.resetForm")}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Rule
|
||||
@@ -0,0 +1,104 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
|
||||
// >>>
|
||||
import type { JsonGroup, Config, ImmutableTree, BuilderProps } from '@react-awesome-query-builder/antd';
|
||||
import { Utils as QbUtils, Query, Builder, AntdConfig } from '@react-awesome-query-builder/antd';
|
||||
const InitialConfig = AntdConfig;
|
||||
// <<<
|
||||
|
||||
// You need to provide your own config. See below 'Config format'
|
||||
const config: Config = {
|
||||
...InitialConfig,
|
||||
fields: {
|
||||
...InitialConfig.fields,
|
||||
qty: {
|
||||
type: 'number',
|
||||
label: 'Quantity',
|
||||
fieldSettings: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
}
|
||||
},
|
||||
// complex
|
||||
user: {
|
||||
type: '!struct', // special keyword for complex fields
|
||||
label: 'User',
|
||||
subfields: {
|
||||
// subfields of complex field
|
||||
name: {
|
||||
type: 'text',
|
||||
label: 'Name',
|
||||
label2: 'User name', //optional, see below
|
||||
fieldSettings: {
|
||||
validateValue: (val: string, _fieldSettings: any) => (val.length <= 20),
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// You can load query value from your backend storage (for saving see `Query.onChange()`)
|
||||
const queryValue: JsonGroup = { id: QbUtils.uuid(), type: "group" };
|
||||
|
||||
const DemoQueryBuilder: React.FC = () => {
|
||||
const [state, setState] = useState({
|
||||
tree: QbUtils.loadTree(queryValue),
|
||||
config: config
|
||||
});
|
||||
|
||||
const onChange = useCallback((immutableTree: ImmutableTree, config: Config) => {
|
||||
// Tip: for better performance you can apply `throttle` - see `packages/examples/src/demo`
|
||||
setState(prevState => ({ ...prevState, tree: immutableTree, config: config }));
|
||||
|
||||
const jsonTree = QbUtils.getTree(immutableTree);
|
||||
console.log(jsonTree);
|
||||
// `jsonTree` can be saved to backend, and later loaded to `queryValue`
|
||||
}, []);
|
||||
|
||||
const renderBuilder = useCallback((props: BuilderProps) => (
|
||||
<div className="query-builder-container" style={{ padding: "10px" }}>
|
||||
<div className="query-builder qb-lite">
|
||||
<Builder {...props} />
|
||||
</div>
|
||||
</div>
|
||||
), []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Query
|
||||
{...config}
|
||||
value={state.tree}
|
||||
onChange={onChange}
|
||||
renderBuilder={renderBuilder}
|
||||
/>
|
||||
<div className="query-builder-result">
|
||||
<div>
|
||||
Query string:{" "}
|
||||
<pre>
|
||||
{JSON.stringify(QbUtils.queryString(state.tree, state.config))}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
MongoDb query:{" "}
|
||||
<pre>
|
||||
{JSON.stringify(QbUtils.mongodbFormat(state.tree, state.config))}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
SQL where:{" "}
|
||||
<pre>
|
||||
{JSON.stringify(QbUtils.sqlFormat(state.tree, state.config))}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
JsonLogic:{" "}
|
||||
<pre>
|
||||
{JSON.stringify(QbUtils.jsonLogicFormat(state.tree, state.config))}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default DemoQueryBuilder;
|
||||
@@ -1,107 +0,0 @@
|
||||
import { Space, Input, InputNumber, Select, Button } from "antd"
|
||||
import { PlusOutlined, DeleteOutlined } from "@ant-design/icons"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
const SCORE_RANGE = { MIN: -999, MAX: 999 }
|
||||
|
||||
const ACTION_DEFINITIONS = [
|
||||
{ eventName: "add_score", labelKey: "autoScore.actionAddScore" },
|
||||
{ eventName: "add_tag", labelKey: "autoScore.actionAddTag" },
|
||||
]
|
||||
|
||||
interface ActionItem {
|
||||
id: number
|
||||
eventName: string
|
||||
value?: string
|
||||
reason?: string
|
||||
}
|
||||
|
||||
interface ActionComponentProps {
|
||||
actions: ActionItem[]
|
||||
onAdd: () => void
|
||||
onRemove: (id: number) => void
|
||||
onChange: (id: number, field: keyof ActionItem, value: string | number) => void
|
||||
}
|
||||
|
||||
export const ActionComponent: React.FC<ActionComponentProps> = ({
|
||||
actions,
|
||||
onAdd,
|
||||
onRemove,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleActionChange = (id: number, field: keyof ActionItem, value: string | number) => {
|
||||
onChange(id, field, value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
{actions.map((action) => (
|
||||
<div
|
||||
key={action.id}
|
||||
style={{
|
||||
padding: "12px",
|
||||
border: "1px solid #d9d9d9",
|
||||
borderRadius: "6px",
|
||||
backgroundColor: "var(--ss-card-bg)",
|
||||
}}
|
||||
>
|
||||
<Space style={{ width: "100%" }} wrap>
|
||||
<Select
|
||||
value={action.eventName}
|
||||
onChange={(value) => handleActionChange(action.id, "eventName", value)}
|
||||
style={{ width: 150 }}
|
||||
options={ACTION_DEFINITIONS.map((d) => ({
|
||||
label: t(d.labelKey),
|
||||
value: d.eventName,
|
||||
}))}
|
||||
/>
|
||||
|
||||
{action.eventName === "add_score" && (
|
||||
<InputNumber
|
||||
value={action.value ? parseInt(action.value) : undefined}
|
||||
onChange={(value) => handleActionChange(action.id, "value", String(value || 0))}
|
||||
placeholder={t("autoScore.scorePlaceholder")}
|
||||
min={SCORE_RANGE.MIN}
|
||||
max={SCORE_RANGE.MAX}
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{action.eventName === "add_tag" && (
|
||||
<Input
|
||||
value={action.value}
|
||||
onChange={(e) => handleActionChange(action.id, "value", e.target.value)}
|
||||
placeholder={t("autoScore.tagNamePlaceholder")}
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Input
|
||||
value={action.reason}
|
||||
onChange={(e) => handleActionChange(action.id, "reason", e.target.value)}
|
||||
placeholder={t("autoScore.reasonPlaceholder")}
|
||||
style={{ flex: 1, minWidth: 200 }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => onRemove(action.id)}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button type="dashed" icon={<PlusOutlined />} onClick={onAdd} block>
|
||||
{t("autoScore.addAction")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ActionComponent
|
||||
@@ -1,251 +0,0 @@
|
||||
.ruleGroup {
|
||||
background-color: var(--ss-card-bg, #fff);
|
||||
border: 1px solid var(--ss-border-color, #d9d9d9);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ruleGroup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ruleGroup-combinators,
|
||||
.ruleGroup-addRule,
|
||||
.ruleGroup-addGroup {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.ruleGroup-body {
|
||||
padding-left: 12px;
|
||||
border-left: 2px solid var(--ss-primary-color, #1677ff);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.rule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--ss-item-bg, rgba(0, 0, 0, 0.02));
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.rule:hover {
|
||||
background-color: var(--ss-item-hover, rgba(0, 0, 0, 0.04));
|
||||
}
|
||||
|
||||
.rule-fields,
|
||||
.rule-operators,
|
||||
.rule-value,
|
||||
.rule-subfields {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.rule-fields .ant-select-selector,
|
||||
.rule-operators .ant-select-selector,
|
||||
.rule-subfields .ant-select-selector {
|
||||
background-color: var(--ss-input-bg, #fff) !important;
|
||||
border-color: var(--ss-border-color, #d9d9d9) !important;
|
||||
color: var(--ss-text-main, rgba(0, 0, 0, 0.88)) !important;
|
||||
}
|
||||
|
||||
.rule-fields .ant-select-selector:hover,
|
||||
.rule-operators .ant-select-selector:hover,
|
||||
.rule-subfields .ant-select-selector:hover {
|
||||
border-color: var(--ss-primary-color, #1677ff) !important;
|
||||
}
|
||||
|
||||
.rule-fields .ant-select-focused .ant-select-selector,
|
||||
.rule-operators .ant-select-focused .ant-select-selector,
|
||||
.rule-subfields .ant-select-focused .ant-select-selector {
|
||||
border-color: var(--ss-primary-color, #1677ff) !important;
|
||||
box-shadow: 0 0 0 2px rgba(var(--ss-primary-rgb, 22, 119, 255), 0.1) !important;
|
||||
}
|
||||
|
||||
.rule-value .ant-input,
|
||||
.rule-value .ant-input-number,
|
||||
.rule-value .ant-select-selector {
|
||||
background-color: var(--ss-input-bg, #fff) !important;
|
||||
border-color: var(--ss-border-color, #d9d9d9) !important;
|
||||
color: var(--ss-text-main, rgba(0, 0, 0, 0.88)) !important;
|
||||
}
|
||||
|
||||
.rule-value .ant-input:hover,
|
||||
.rule-value .ant-input-number:hover,
|
||||
.rule-value .ant-select-selector:hover {
|
||||
border-color: var(--ss-primary-color, #1677ff) !important;
|
||||
}
|
||||
|
||||
.rule-value .ant-input:focus,
|
||||
.rule-value .ant-input-number-focused,
|
||||
.rule-value .ant-select-focused .ant-select-selector {
|
||||
border-color: var(--ss-primary-color, #1677ff) !important;
|
||||
box-shadow: 0 0 0 2px rgba(var(--ss-primary-rgb, 22, 119, 255), 0.1) !important;
|
||||
}
|
||||
|
||||
.rule-value .ant-input-number-input {
|
||||
color: var(--ss-text-main, rgba(0, 0, 0, 0.88)) !important;
|
||||
}
|
||||
|
||||
.rule-remove,
|
||||
.ruleGroup-remove {
|
||||
color: #fff !important;
|
||||
background-color: var(--ant-color-error, #ff4d4f) !important;
|
||||
border-color: var(--ant-color-error, #ff4d4f) !important;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.rule-remove:hover,
|
||||
.ruleGroup-remove:hover {
|
||||
color: #fff !important;
|
||||
background-color: #ff7875 !important;
|
||||
border-color: #ff7875 !important;
|
||||
}
|
||||
|
||||
.ruleGroup-addRule,
|
||||
.ruleGroup-addGroup {
|
||||
color: var(--ss-primary-color, #1677ff) !important;
|
||||
border-color: var(--ss-primary-color, #1677ff) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.ruleGroup-addRule:hover,
|
||||
.ruleGroup-addGroup:hover {
|
||||
color: #fff !important;
|
||||
background-color: var(--ss-primary-color, #1677ff) !important;
|
||||
}
|
||||
|
||||
.betweenRules {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 8px 0;
|
||||
color: var(--ss-text-secondary, rgba(0, 0, 0, 0.45));
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.betweenRules::before,
|
||||
.betweenRules::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background-color: var(--ss-border-color, #d9d9d9);
|
||||
}
|
||||
|
||||
.betweenRules::before {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.betweenRules::after {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.dragHandle {
|
||||
cursor: grab;
|
||||
color: var(--ss-text-secondary, rgba(0, 0, 0, 0.45));
|
||||
padding: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.dragHandle:hover {
|
||||
color: var(--ss-text-main, rgba(0, 0, 0, 0.88));
|
||||
}
|
||||
|
||||
.queryBuilder .ant-select-dropdown {
|
||||
background-color: var(--ss-card-bg, #fff);
|
||||
}
|
||||
|
||||
.queryBuilder .ant-select-item {
|
||||
color: var(--ss-text-main, rgba(0, 0, 0, 0.88));
|
||||
}
|
||||
|
||||
.queryBuilder .ant-select-item-option-active {
|
||||
background-color: var(--ss-item-hover, rgba(0, 0, 0, 0.04));
|
||||
}
|
||||
|
||||
.queryBuilder .ant-select-item-option-selected {
|
||||
background-color: rgba(var(--ss-primary-rgb, 22, 119, 255), 0.1);
|
||||
color: var(--ss-primary-color, #1677ff);
|
||||
}
|
||||
|
||||
.queryBuilder .ant-btn {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.queryBuilder .ant-select-selector,
|
||||
.queryBuilder .ant-input,
|
||||
.queryBuilder .ant-input-number {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .ruleGroup {
|
||||
background-color: var(--ss-card-bg, #1f1f1f);
|
||||
border-color: var(--ss-border-color, #424242);
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .rule {
|
||||
background-color: var(--ss-item-bg, rgba(255, 255, 255, 0.04));
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .rule:hover {
|
||||
background-color: var(--ss-item-hover, rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .rule-fields .ant-select-selector,
|
||||
[theme-mode="dark"] .rule-operators .ant-select-selector,
|
||||
[theme-mode="dark"] .rule-subfields .ant-select-selector,
|
||||
[theme-mode="dark"] .rule-value .ant-input,
|
||||
[theme-mode="dark"] .rule-value .ant-input-number,
|
||||
[theme-mode="dark"] .rule-value .ant-select-selector {
|
||||
background-color: var(--ss-input-bg, #141414) !important;
|
||||
border-color: var(--ss-border-color, #424242) !important;
|
||||
color: var(--ss-text-main, rgba(255, 255, 255, 0.85)) !important;
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .rule-value .ant-input-number-input {
|
||||
color: var(--ss-text-main, rgba(255, 255, 255, 0.85)) !important;
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .betweenRules {
|
||||
color: var(--ss-text-secondary, rgba(255, 255, 255, 0.45));
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .betweenRules::before,
|
||||
[theme-mode="dark"] .betweenRules::after {
|
||||
background-color: var(--ss-border-color, #424242);
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .dragHandle {
|
||||
color: var(--ss-text-secondary, rgba(255, 255, 255, 0.45));
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .dragHandle:hover {
|
||||
color: var(--ss-text-main, rgba(255, 255, 255, 0.85));
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .queryBuilder .ant-select-dropdown {
|
||||
background-color: var(--ss-card-bg, #1f1f1f);
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .queryBuilder .ant-select-item {
|
||||
color: var(--ss-text-main, rgba(255, 255, 255, 0.85));
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .queryBuilder .ant-select-item-option-active {
|
||||
background-color: var(--ss-item-hover, rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
|
||||
[theme-mode="dark"] .queryBuilder .ant-select-item-option-selected {
|
||||
background-color: rgba(var(--ss-primary-rgb, 22, 119, 255), 0.2);
|
||||
}
|
||||
|
||||
.ruleGroup-header .ant-select-selector {
|
||||
min-width: 80px;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import type { RuleGroupType, Field, RuleType } from "react-querybuilder"
|
||||
import { defaultOperators } from "react-querybuilder"
|
||||
import { fetchAllTags } from "../TagEditorDialog"
|
||||
|
||||
const tags = await fetchAllTags()
|
||||
|
||||
export interface AutoScoreTrigger {
|
||||
event: string
|
||||
value?: string
|
||||
relation?: "AND" | "OR"
|
||||
}
|
||||
|
||||
export interface AutoScoreAction {
|
||||
event: string
|
||||
value?: string
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export interface AutoScoreRuleData {
|
||||
triggers: AutoScoreTrigger[]
|
||||
actions: AutoScoreAction[]
|
||||
}
|
||||
|
||||
export const validator = (r: RuleType) => !!r.value
|
||||
|
||||
export const getFields = (t: (key: string) => string): Field[] => [
|
||||
{
|
||||
name: "student_has_tag",
|
||||
label: t("triggers.studentTag.label"),
|
||||
placeholder: t("triggers.studentTag.placeholder"),
|
||||
valueEditorType: "multiselect",
|
||||
values: tags.map((tag: { id: number; name: string }) => tag.name),
|
||||
defaultValue: tags.length > 0 ? [tags[0].name] : [],
|
||||
operators: defaultOperators.filter((op) => op.name === "in"),
|
||||
},
|
||||
{
|
||||
name: "interval_time_passed",
|
||||
label: t("triggers.intervalTime.label"),
|
||||
matchModes: ["all"],
|
||||
subproperties: [
|
||||
{
|
||||
name: "month",
|
||||
label: t("triggers.intervalTime.monthLabel"),
|
||||
inputType: "number",
|
||||
datatype: "month",
|
||||
operators: ["="],
|
||||
},
|
||||
/* { name: 'week', label: t('triggers.intervalTime.weekLabel'), inputType: 'week', datatype: 'week', operators: ['='] },
|
||||
*/ {
|
||||
name: "time",
|
||||
label: t("triggers.intervalTime.timeLabel"),
|
||||
inputType: "time",
|
||||
datatype: "time",
|
||||
operators: ["="],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const defaultQuery: RuleGroupType = {
|
||||
combinator: "and",
|
||||
rules: [{ field: "interval_time_passed", operator: "=", value: "1440" }],
|
||||
}
|
||||
|
||||
export function queryToAutoScoreRule(): AutoScoreRuleData {
|
||||
const triggers: AutoScoreTrigger[] = []
|
||||
|
||||
/* const processRuleGroup = (group: RuleGroupType, relation: 'AND' | 'OR' = 'AND') => {
|
||||
group.rules.forEach((rule, index) => {
|
||||
if ('rules' in rule) {
|
||||
processRuleGroup(rule, group.combinator === 'and' ? 'AND' : 'OR')
|
||||
} else {
|
||||
const trigger: AutoScoreTrigger = {
|
||||
event: rule.field,
|
||||
value: rule.value as string
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
trigger.relation = relation
|
||||
}
|
||||
|
||||
triggers.push(trigger)
|
||||
}
|
||||
})
|
||||
} */
|
||||
|
||||
/* processRuleGroup(query) */
|
||||
|
||||
return {
|
||||
triggers,
|
||||
actions: [],
|
||||
}
|
||||
}
|
||||
|
||||
export function autoScoreRuleToQuery(ruleData: AutoScoreRuleData): RuleGroupType {
|
||||
const rules: any[] = []
|
||||
let currentCombinator: "and" | "or" = "and"
|
||||
|
||||
ruleData.triggers.forEach((trigger, index) => {
|
||||
if (index === 0) {
|
||||
currentCombinator = "and"
|
||||
} else if (trigger.relation === "OR") {
|
||||
currentCombinator = "or"
|
||||
}
|
||||
|
||||
rules.push({
|
||||
field: trigger.event,
|
||||
operator: "=",
|
||||
value: trigger.value || "",
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
combinator: currentCombinator,
|
||||
rules: rules.length > 0 ? rules : defaultQuery.rules,
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { formatQuery, QueryBuilder, RuleGroupType } from "react-querybuilder"
|
||||
import { useState, useEffect } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { QueryBuilderDnD } from "@react-querybuilder/dnd"
|
||||
import * as ReactDnD from "react-dnd"
|
||||
import { QueryBuilderAntD } from "@react-querybuilder/antd"
|
||||
import * as ReactDndHtml5Backend from "react-dnd-html5-backend"
|
||||
import * as ReactDndTouchBackend from "react-dnd-touch-backend"
|
||||
import {
|
||||
getFields,
|
||||
defaultQuery,
|
||||
autoScoreRuleToQuery,
|
||||
queryToAutoScoreRule,
|
||||
type AutoScoreRuleData,
|
||||
} from "./ruleBuilderUtils"
|
||||
import { Card } from "antd"
|
||||
|
||||
import "./ruleBuilderOverride.css"
|
||||
|
||||
interface RuleComponentProps {
|
||||
initialData?: AutoScoreRuleData
|
||||
onChange?: (data: AutoScoreRuleData) => void
|
||||
}
|
||||
|
||||
export const RuleComponent: React.FC<RuleComponentProps> = ({ initialData, onChange }) => {
|
||||
const { t } = useTranslation()
|
||||
const [query, setQuery] = useState<RuleGroupType>(
|
||||
initialData ? autoScoreRuleToQuery(initialData) : defaultQuery
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
setQuery(autoScoreRuleToQuery(initialData))
|
||||
}
|
||||
}, [initialData])
|
||||
|
||||
const handleQueryChange = (newQuery: RuleGroupType) => {
|
||||
setQuery(newQuery)
|
||||
if (onChange) {
|
||||
const ruleData = queryToAutoScoreRule()
|
||||
onChange(ruleData)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card
|
||||
title={t("autoScore.triggerCondition")}
|
||||
style={{ marginBottom: "24px", backgroundColor: "var(--ss-card-bg)" }}
|
||||
>
|
||||
<QueryBuilderDnD dnd={{ ...ReactDnD, ...ReactDndHtml5Backend, ...ReactDndTouchBackend }}>
|
||||
<QueryBuilderAntD>
|
||||
<QueryBuilder fields={getFields(t)} query={query} onQueryChange={handleQueryChange} />
|
||||
</QueryBuilderAntD>
|
||||
</QueryBuilderDnD>
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<pre style={{ fontSize: "12px", color: "#999" }}>{formatQuery(query, "json")}</pre>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RuleComponent
|
||||
Reference in New Issue
Block a user