feat: 使用react-awesome-query-builder实现自动加分规则构建器

This commit is contained in:
NanGua-QWQ
2026-03-27 21:50:22 +08:00
parent a1d93e1d76
commit f70df2a1c8
10 changed files with 478 additions and 705 deletions
-5
View File
@@ -19,8 +19,6 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^6.1.0", "@ant-design/icons": "^6.1.0",
"@react-awesome-query-builder/antd": "6.7.0-alpha.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", "@tauri-apps/api": "^2.5.0",
"antd": "^6.3.1", "antd": "^6.3.1",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
@@ -28,9 +26,6 @@
"json-rules-engine": "^7.3.1", "json-rules-engine": "^7.3.1",
"pinyin-pro": "^3.27.0", "pinyin-pro": "^3.27.0",
"react": "^19.2.1", "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-dom": "^19.2.1",
"react-i18next": "^16.5.6", "react-i18next": "^16.5.6",
"react-querybuilder": "^8.14.0", "react-querybuilder": "^8.14.0",
+18 -122
View File
@@ -14,12 +14,6 @@ importers:
'@react-awesome-query-builder/antd': '@react-awesome-query-builder/antd':
specifier: 6.7.0-alpha.0 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) 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': '@tauri-apps/api':
specifier: ^2.5.0 specifier: ^2.5.0
version: 2.10.1 version: 2.10.1
@@ -41,15 +35,6 @@ importers:
react: react:
specifier: ^19.2.1 specifier: ^19.2.1
version: 19.2.4 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: react-dom:
specifier: ^19.2.1 specifier: ^19.2.1
version: 19.2.4(react@19.2.4) 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: ^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-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': '@react-querybuilder/core@8.14.0':
resolution: {integrity: sha512-j1pIY0Yyn/dXu9ZST/DVY7TqRmIO1hY/mZ8653DaeHaDzUF37tOdkm/NQDU9RfM0KXIWsJY5zlvYAR1DypZ+7g==} resolution: {integrity: sha512-j1pIY0Yyn/dXu9ZST/DVY7TqRmIO1hY/mZ8653DaeHaDzUF37tOdkm/NQDU9RfM0KXIWsJY5zlvYAR1DypZ+7g==}
peerDependencies: peerDependencies:
@@ -823,20 +790,6 @@ packages:
sequelize: sequelize:
optional: true 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': '@reduxjs/toolkit@2.11.2':
resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==}
peerDependencies: peerDependencies:
@@ -889,66 +842,79 @@ packages:
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.59.0': '@rollup/rollup-linux-arm-musleabihf@4.59.0':
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.59.0': '@rollup/rollup-linux-arm64-gnu@4.59.0':
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.59.0': '@rollup/rollup-linux-arm64-musl@4.59.0':
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.59.0': '@rollup/rollup-linux-loong64-gnu@4.59.0':
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.59.0': '@rollup/rollup-linux-loong64-musl@4.59.0':
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.59.0': '@rollup/rollup-linux-ppc64-gnu@4.59.0':
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.59.0': '@rollup/rollup-linux-ppc64-musl@4.59.0':
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.59.0': '@rollup/rollup-linux-riscv64-gnu@4.59.0':
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.59.0': '@rollup/rollup-linux-riscv64-musl@4.59.0':
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.59.0': '@rollup/rollup-linux-s390x-gnu@4.59.0':
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.59.0': '@rollup/rollup-linux-x64-gnu@4.59.0':
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.59.0': '@rollup/rollup-linux-x64-musl@4.59.0':
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.59.0': '@rollup/rollup-openbsd-x64@4.59.0':
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
@@ -1012,30 +978,35 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-arm64-musl@2.10.1': '@tauri-apps/cli-linux-arm64-musl@2.10.1':
resolution: {integrity: sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==} resolution: {integrity: sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@tauri-apps/cli-linux-riscv64-gnu@2.10.1': '@tauri-apps/cli-linux-riscv64-gnu@2.10.1':
resolution: {integrity: sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==} resolution: {integrity: sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-x64-gnu@2.10.1': '@tauri-apps/cli-linux-x64-gnu@2.10.1':
resolution: {integrity: sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==} resolution: {integrity: sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-x64-musl@2.10.1': '@tauri-apps/cli-linux-x64-musl@2.10.1':
resolution: {integrity: sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==} resolution: {integrity: sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@tauri-apps/cli-win32-arm64-msvc@2.10.1': '@tauri-apps/cli-win32-arm64-msvc@2.10.1':
resolution: {integrity: sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==} resolution: {integrity: sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==}
@@ -1372,9 +1343,6 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dnd-core@16.0.1:
resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==}
doctrine@2.1.0: doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -2029,27 +1997,6 @@ packages:
react: '>=16.9.0' react: '>=16.9.0'
react-dom: '>=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: react-dom@19.2.4:
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
peerDependencies: peerDependencies:
@@ -3210,20 +3157,6 @@ snapshots:
- '@types/react-dom' - '@types/react-dom'
- react-native - 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)': '@react-querybuilder/core@8.14.0(json-logic-js@2.0.5)':
dependencies: dependencies:
'@ts-jison/lexer': 0.4.1-alpha.1 '@ts-jison/lexer': 0.4.1-alpha.1
@@ -3233,15 +3166,6 @@ snapshots:
optionalDependencies: optionalDependencies:
json-logic-js: 2.0.5 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)': '@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: dependencies:
'@standard-schema/spec': 1.1.0 '@standard-schema/spec': 1.1.0
@@ -3811,12 +3735,6 @@ snapshots:
has-property-descriptors: 1.0.2 has-property-descriptors: 1.0.2
object-keys: 1.1.1 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: doctrine@2.1.0:
dependencies: dependencies:
esutils: 2.0.3 esutils: 2.0.3
@@ -4594,28 +4512,6 @@ snapshots:
react-dom: 19.2.4(react@19.2.4) react-dom: 19.2.4(react@19.2.4)
react-is: 18.3.1 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): react-dom@19.2.4(react@19.2.4):
dependencies: dependencies:
react: 19.2.4 react: 19.2.4
+3 -39
View File
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { HolderOutlined } from "@ant-design/icons" import { HolderOutlined } from "@ant-design/icons"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import RuleComponent from "./autoScore/ruleComponent" import DemoQueryBuilder from "./autoScore/aaa"
import { import {
Card, Card,
Form, Form,
@@ -426,44 +426,8 @@ export const AutoScoreManager: React.FC = () => {
</Form> </Form>
</Card> </Card>
<RuleComponent /> <DemoQueryBuilder />
{/* <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> */}
<div style={{ marginBottom: "24px", display: "flex", gap: "12px" }}> <div style={{ marginBottom: "24px", display: "flex", gap: "12px" }}>
<Button type="primary" onClick={handleSubmit}> <Button type="primary" onClick={handleSubmit}>
{editingRuleId !== null ? t("autoScore.updateAutomation") : t("autoScore.addAutomation")} {editingRuleId !== null ? t("autoScore.updateAutomation") : t("autoScore.addAutomation")}
+149
View File
@@ -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 "{}"
}
}
+204
View File
@@ -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
+104
View File
@@ -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