From a2ffb0ad462db675e0b0922711f6bcdd2eb7c260 Mon Sep 17 00:00:00 2001 From: NanGua-QWQ Date: Fri, 13 Feb 2026 22:05:57 +0800 Subject: [PATCH 1/3] FIX --- src/renderer/src/services/AutoScoreService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/services/AutoScoreService.ts b/src/renderer/src/services/AutoScoreService.ts index 16c4306..b378c56 100644 --- a/src/renderer/src/services/AutoScoreService.ts +++ b/src/renderer/src/services/AutoScoreService.ts @@ -1,7 +1,7 @@ import React from 'react' import { Service } from '../../../shared/kernel' import { ClientContext } from '../ClientContext' -import { Input, Select, Radio } from 'tdesign-react' +import { Input, Select } from 'tdesign-react' export interface AutoScoreRule { id: number From db3f05c59d478ceec7f8406673b883bbbc68618a Mon Sep 17 00:00:00 2001 From: NanGua-QWQ Date: Sun, 15 Feb 2026 22:44:32 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat=EF=BC=9A=E8=87=AA=E5=8A=A8=E5=8A=A0?= =?UTF-8?q?=E5=88=86=E6=98=BE=E7=A4=BAjson?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- pnpm-lock.yaml | 182 +----------------- src/renderer/src/App.tsx | 8 +- .../src/components/AutoScoreManager.tsx | 37 +++- src/renderer/src/components/Code.tsx | 40 ++++ 5 files changed, 79 insertions(+), 190 deletions(-) create mode 100644 src/renderer/src/components/Code.tsx diff --git a/package.json b/package.json index 128044d..525620a 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "mica-electron": "^1.5.16", "os": "^0.1.2", "pinyin-pro": "^3.27.0", + "prismjs": "^1.30.0", "react-router-dom": "^6.28.0", - "react-syntax-highlighter": "^16.1.0", "reflect-metadata": "^0.2.2", "tdesign-react": "^1.16.3", "typeorm": "^0.3.27", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38ce7e1..a5cd097 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,12 +33,12 @@ importers: pinyin-pro: specifier: ^3.27.0 version: 3.27.0 + prismjs: + specifier: ^1.30.0 + version: 1.30.0 react-router-dom: specifier: ^6.28.0 version: 6.30.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react-syntax-highlighter: - specifier: ^16.1.0 - version: 16.1.0(react@19.2.3) reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -211,10 +211,6 @@ packages: resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -915,9 +911,6 @@ packages: '@types/fs-extra@9.0.13': resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} - '@types/hast@3.0.4': - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/http-cache-semantics@4.0.4': resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} @@ -936,9 +929,6 @@ packages: '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} - '@types/prismjs@1.26.5': - resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} - '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -956,12 +946,6 @@ packages: '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/uuid@11.0.0': resolution: {integrity: sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==} deprecated: This is a stub types definition. uuid provides its own type definitions, so you do not need this installed. @@ -1278,15 +1262,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -1366,9 +1341,6 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} @@ -1451,9 +1423,6 @@ packages: supports-color: optional: true - decode-named-character-reference@1.3.0: - resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -1776,9 +1745,6 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fault@1.0.4: - resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} - fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -1837,10 +1803,6 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1999,24 +1961,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - - hastscript@9.0.1: - resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - highlight.js@10.7.3: - resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - - highlightjs-vue@1.0.0: - resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} - hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -2097,12 +2047,6 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -2135,9 +2079,6 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2158,9 +2099,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -2339,9 +2277,6 @@ packages: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} - lowlight@1.20.0: - resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2620,9 +2555,6 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-entities@4.0.2: - resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -2723,9 +2655,6 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -2791,12 +2720,6 @@ packages: peerDependencies: react: '>=16.8' - react-syntax-highlighter@16.1.0: - resolution: {integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==} - engines: {node: '>= 16.20.2'} - peerDependencies: - react: '>= 0.14.0' - react-transition-group@4.4.5: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -2826,9 +2749,6 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - refractor@5.0.0: - resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} - regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -3035,9 +2955,6 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -3623,8 +3540,6 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 - '@babel/runtime@7.28.6': {} - '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -4199,10 +4114,6 @@ snapshots: dependencies: '@types/node': 22.19.5 - '@types/hast@3.0.4': - dependencies: - '@types/unist': 3.0.3 - '@types/http-cache-semantics@4.0.4': {} '@types/json-schema@7.0.15': {} @@ -4223,8 +4134,6 @@ snapshots: xmlbuilder: 15.1.1 optional: true - '@types/prismjs@1.26.5': {} - '@types/react-dom@19.2.3(@types/react@19.2.8)': dependencies: '@types/react': 19.2.8 @@ -4241,10 +4150,6 @@ snapshots: '@types/triple-beam@1.3.5': {} - '@types/unist@2.0.11': {} - - '@types/unist@3.0.3': {} - '@types/uuid@11.0.0': dependencies: uuid: 13.0.0 @@ -4687,12 +4592,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -4760,8 +4659,6 @@ snapshots: dependencies: delayed-stream: 1.0.0 - comma-separated-tokens@2.0.3: {} - commander@5.1.0: {} commander@9.5.0: @@ -4828,10 +4725,6 @@ snapshots: dependencies: ms: 2.1.3 - decode-named-character-reference@1.3.0: - dependencies: - character-entities: 2.0.2 - decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -5367,10 +5260,6 @@ snapshots: fast-levenshtein@2.0.6: {} - fault@1.0.4: - dependencies: - format: 0.2.2 - fd-slicer@1.1.0: dependencies: pend: 1.2.0 @@ -5437,8 +5326,6 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - format@0.2.2: {} - forwarded@0.2.0: {} frac@1.1.2: {} @@ -5621,28 +5508,12 @@ snapshots: dependencies: function-bind: 1.1.2 - hast-util-parse-selector@4.0.0: - dependencies: - '@types/hast': 3.0.4 - - hastscript@9.0.1: - dependencies: - '@types/hast': 3.0.4 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 4.0.0 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - hermes-estree@0.25.1: {} hermes-parser@0.25.1: dependencies: hermes-estree: 0.25.1 - highlight.js@10.7.3: {} - - highlightjs-vue@1.0.0: {} - hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -5726,13 +5597,6 @@ snapshots: ipaddr.js@1.9.1: {} - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -5773,8 +5637,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-decimal@2.0.1: {} - is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -5795,8 +5657,6 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-hexadecimal@2.0.1: {} - is-interactive@1.0.0: {} is-map@2.0.3: {} @@ -5965,11 +5825,6 @@ snapshots: lowercase-keys@2.0.0: {} - lowlight@1.20.0: - dependencies: - fault: 1.0.4 - highlight.js: 10.7.3 - lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -6253,16 +6108,6 @@ snapshots: dependencies: callsites: 3.1.0 - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.3.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -6351,8 +6196,6 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 - property-information@7.1.0: {} - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -6416,16 +6259,6 @@ snapshots: '@remix-run/router': 1.23.2 react: 19.2.3 - react-syntax-highlighter@16.1.0(react@19.2.3): - dependencies: - '@babel/runtime': 7.28.6 - highlight.js: 10.7.3 - highlightjs-vue: 1.0.0 - lowlight: 1.20.0 - prismjs: 1.30.0 - react: 19.2.3 - refractor: 5.0.0 - react-transition-group@4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@babel/runtime': 7.26.10 @@ -6464,13 +6297,6 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - refractor@5.0.0: - dependencies: - '@types/hast': 3.0.4 - '@types/prismjs': 1.26.5 - hastscript: 9.0.1 - parse-entities: 4.0.2 - regenerator-runtime@0.14.1: {} regexp.prototype.flags@1.5.4: @@ -6749,8 +6575,6 @@ snapshots: source-map@0.6.1: {} - space-separated-tokens@2.0.2: {} - sprintf-js@1.1.3: optional: true diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index dae5638..3d502a8 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -7,10 +7,12 @@ import { Wizard } from './components/Wizard' import { ThemeProvider } from './contexts/ThemeContext' import { ThemeEditorProvider } from './contexts/ThemeEditorContext' import { ThemeEditor } from './components/ThemeEditor' +import { useTheme } from './contexts/ThemeContext'; function MainContent(): React.JSX.Element { const navigate = useNavigate() const location = useLocation() + const { currentTheme } = useTheme(); useEffect(() => { if (!(window as any).api) return @@ -153,9 +155,9 @@ function MainContent(): React.JSX.Element { >

@@ -163,7 +165,7 @@ function MainContent(): React.JSX.Element {

{ )} )) - return (

自动化加分管理

@@ -671,11 +669,36 @@ export const AutoScoreManager: React.FC = () => { - - - println("这是一个示例代码块,展示如何使用自动化加分功能的API接口") - + { + if (editingRuleId !== null) { + // 显示当前编辑的规则 + const values = form.getFieldsValue(true) as unknown as AutoScoreRuleFormValues; + const studentNames = Array.isArray(values.studentNames) ? values.studentNames : []; + const triggersPayload = triggerList.map((t) => ({ event: t.eventName, value: t.value })); + const actionsPayload = actionList.map((a) => ({ + event: a.eventName, + value: a.value, + reason: a.reason + })); + + const currentRule = { + id: editingRuleId, + enabled: true, + name: values.name || '', + studentNames, + triggers: triggersPayload, + actions: actionsPayload + }; + + return JSON.stringify(currentRule, null, 2); + } else { + // 显示所有规则 + return JSON.stringify(rules, null, 2); + } + })()} + language={'json'}/> {/*

使用说明

diff --git a/src/renderer/src/components/Code.tsx b/src/renderer/src/components/Code.tsx new file mode 100644 index 0000000..2683f9f --- /dev/null +++ b/src/renderer/src/components/Code.tsx @@ -0,0 +1,40 @@ +import { useEffect } from "react"; + +import Prism from "prismjs"; +import { useTheme } from '../contexts/ThemeContext'; + +// 预先导入所有主题 +import "prismjs/themes/prism-coy.min.css"; +import "prismjs/themes/prism-okaidia.min.css"; + +const Code = ({ code, language }) => { + const { currentTheme } = useTheme(); + + useEffect(() => { + // 重新高亮代码 + Prism.highlightAll(); + }, [currentTheme, code, language]); + + // 根据主题设置不同的类名 + const isDark = currentTheme?.mode === 'dark'; + + return ( +
+
+                {code}
+            
+
+ ); +}; + +export default Code; \ No newline at end of file From 5837a2a6f2e38a365a99b92147a17f10adadbb57 Mon Sep 17 00:00:00 2001 From: NanGua-QWQ Date: Sat, 21 Feb 2026 15:57:07 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E9=87=8D=E6=9E=84AutoScoreManager=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B9=E4=BE=BF=E6=B7=BB=E5=8A=A0actions=E5=92=8Ctr?= =?UTF-8?q?iggers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/services/AutoScoreService.ts | 145 +++++-- .../src/components/AutoScoreManager.tsx | 368 ++++++------------ .../com.automatically/ActionItem.tsx | 52 +++ .../com.automatically/TriggerItem.tsx | 43 ++ .../actions/AddScoreAction.tsx | 29 ++ .../actions/AddTagAction.tsx | 21 + .../actions/SendNotificationAction.tsx | 21 + .../actions/SetStudentStatusAction.tsx | 28 ++ .../com.automatically/actions/index.ts | 70 ++++ .../src/components/com.automatically/index.ts | 17 + .../components/com.automatically/registry.ts | 66 ++++ .../triggers/IntervalTimeTrigger.tsx | 41 ++ .../triggers/RandomTimeTrigger.tsx | 77 ++++ .../triggers/StudentTagTrigger.tsx | 50 +++ .../com.automatically/triggers/index.ts | 55 +++ .../src/components/com.automatically/types.ts | 51 +++ src/renderer/src/services/AutoScoreService.ts | 144 +------ src/shared/triggers/IntervalTimeTrigger.ts | 52 +++ src/shared/triggers/RandomTimeTrigger.ts | 65 ++++ src/shared/triggers/StudentTagTrigger.ts | 30 ++ src/shared/triggers/index.ts | 17 + src/shared/triggers/types.ts | 32 ++ tsconfig.node.json | 2 +- 23 files changed, 1063 insertions(+), 413 deletions(-) create mode 100644 src/renderer/src/components/com.automatically/ActionItem.tsx create mode 100644 src/renderer/src/components/com.automatically/TriggerItem.tsx create mode 100644 src/renderer/src/components/com.automatically/actions/AddScoreAction.tsx create mode 100644 src/renderer/src/components/com.automatically/actions/AddTagAction.tsx create mode 100644 src/renderer/src/components/com.automatically/actions/SendNotificationAction.tsx create mode 100644 src/renderer/src/components/com.automatically/actions/SetStudentStatusAction.tsx create mode 100644 src/renderer/src/components/com.automatically/actions/index.ts create mode 100644 src/renderer/src/components/com.automatically/index.ts create mode 100644 src/renderer/src/components/com.automatically/registry.ts create mode 100644 src/renderer/src/components/com.automatically/triggers/IntervalTimeTrigger.tsx create mode 100644 src/renderer/src/components/com.automatically/triggers/RandomTimeTrigger.tsx create mode 100644 src/renderer/src/components/com.automatically/triggers/StudentTagTrigger.tsx create mode 100644 src/renderer/src/components/com.automatically/triggers/index.ts create mode 100644 src/renderer/src/components/com.automatically/types.ts create mode 100644 src/shared/triggers/IntervalTimeTrigger.ts create mode 100644 src/shared/triggers/RandomTimeTrigger.ts create mode 100644 src/shared/triggers/StudentTagTrigger.ts create mode 100644 src/shared/triggers/index.ts create mode 100644 src/shared/triggers/types.ts diff --git a/src/main/services/AutoScoreService.ts b/src/main/services/AutoScoreService.ts index 7392959..7d19ced 100644 --- a/src/main/services/AutoScoreService.ts +++ b/src/main/services/AutoScoreService.ts @@ -1,6 +1,7 @@ import { Service } from '../../shared/kernel' import { MainContext } from '../context' import { student } from '../repos/StudentRepository' +import { getTriggerLogic } from '../../shared/triggers' interface AutoScoreRule { id: number @@ -129,7 +130,6 @@ export class AutoScoreService extends Service { const data = await fs.readJsonFile(RULES_FILE_NAME, 'automatic') if (data && data.rules) { this.rules = data.rules.map((rule: any) => { - // 数据迁移:将旧格式转换为新格式 const migratedRule = this.migrateRule(rule) return { ...migratedRule, @@ -138,7 +138,6 @@ export class AutoScoreService extends Service { : undefined } }) - // 如果有数据迁移,保存新格式 if ( data.rules.some( (rule: any) => rule.intervalMinutes !== undefined || rule.scoreValue !== undefined @@ -159,12 +158,10 @@ export class AutoScoreService extends Service { } private migrateRule(rule: any): AutoScoreRule { - // 如果已经是新格式,直接返回 if (!rule.intervalMinutes && !rule.scoreValue) { return rule } - // 迁移旧格式到新格式 const migratedRule: AutoScoreRule = { id: rule.id, enabled: rule.enabled, @@ -175,7 +172,6 @@ export class AutoScoreService extends Service { actions: rule.actions || [] } - // 将intervalMinutes迁移到triggers if ( rule.intervalMinutes && !migratedRule.triggers?.find((t) => t.event === 'interval_time_passed') @@ -187,7 +183,6 @@ export class AutoScoreService extends Service { }) } - // 将scoreValue和reason迁移到actions if ( rule.scoreValue !== undefined && !migratedRule.actions?.find((a) => a.event === 'add_score') @@ -280,25 +275,28 @@ export class AutoScoreService extends Service { this.timers.delete(rule.id) } - // 从triggers中读取间隔时间 - const intervalTrigger = rule.triggers?.find((t) => t.event === 'interval_time_passed') - const intervalMinutes = intervalTrigger?.value ? parseInt(intervalTrigger.value, 10) : 0 + const now = new Date() + let delayMs = 0 + let primaryTrigger: { event: string; value?: string } | undefined - if (!intervalMinutes || intervalMinutes <= 0) { - this.logger.warn(`Rule ${rule.name} has no valid interval time, skipping timer`) + for (const trigger of rule.triggers || []) { + const logic = getTriggerLogic(trigger.event) + if (logic?.calculateNextTime) { + const result = logic.calculateNextTime(trigger.value || '', rule.lastExecuted, now) + if (delayMs === 0 || result.delayMs < delayMs) { + delayMs = result.delayMs + primaryTrigger = trigger + } + } + } + + if (!primaryTrigger) { + this.logger.warn(`Rule ${rule.name} has no valid triggers with timing logic, skipping`) return } - const now = new Date() - const intervalMs = intervalMinutes * 60 * 1000 - - let delayMs = intervalMs - if (rule.lastExecuted) { - const timeSinceLastExecution = now.getTime() - rule.lastExecuted.getTime() - delayMs = intervalMs - (timeSinceLastExecution % intervalMs) - if (timeSinceLastExecution >= intervalMs) { - delayMs = 0 - } + if (delayMs < 0) { + delayMs = 0 } const timer = setTimeout(() => { @@ -307,21 +305,32 @@ export class AutoScoreService extends Service { }, delayMs) this.timers.set(rule.id, timer) + this.logger.info(`Rule ${rule.name} scheduled to execute in ${delayMs}ms`) } private setRuleInterval(rule: AutoScoreRule) { - // 从triggers中读取间隔时间 - const intervalTrigger = rule.triggers?.find((t) => t.event === 'interval_time_passed') - const intervalMinutes = intervalTrigger?.value ? parseInt(intervalTrigger.value, 10) : 0 + const now = new Date() + let minDelayMs = Infinity + let primaryTrigger: { event: string; value?: string } | undefined - if (!intervalMinutes || intervalMinutes <= 0) { + for (const trigger of rule.triggers || []) { + const logic = getTriggerLogic(trigger.event) + if (logic?.calculateNextTime) { + const result = logic.calculateNextTime(trigger.value || '', rule.lastExecuted, now) + if (result.delayMs < minDelayMs) { + minDelayMs = result.delayMs + primaryTrigger = trigger + } + } + } + + if (!primaryTrigger || minDelayMs === Infinity) { return } - const intervalMs = intervalMinutes * 60 * 1000 const timer = setInterval(() => { this.executeRule(rule) - }, intervalMs) + }, minDelayMs) this.timers.set(rule.id, timer) } @@ -331,7 +340,6 @@ export class AutoScoreService extends Service { this.logger.info(`Executing auto score rule: ${rule.name}`) const studentRepo = this.mainCtx.students - const eventRepo = this.mainCtx.events let studentsToScore: student[] = [] if (rule.studentNames.length === 0) { @@ -347,17 +355,30 @@ export class AutoScoreService extends Service { } } - // 从actions中读取分数和理由 - const scoreAction = rule.actions?.find((a) => a.event === 'add_score') - const scoreValue = scoreAction?.value ? parseInt(scoreAction.value, 10) : 0 - const reason = scoreAction?.reason || `自动化加分 - ${rule.name}` + for (const trigger of rule.triggers || []) { + const logic = getTriggerLogic(trigger.event) + if (logic?.check) { + const context = { + students: studentsToScore, + events: [], + rule: { + id: rule.id, + name: rule.name, + studentNames: rule.studentNames, + triggers: rule.triggers, + actions: rule.actions + }, + now: new Date() + } + const result = logic.check(context, trigger.value || '') + if (result.matchedStudents && result.matchedStudents.length > 0) { + studentsToScore = result.matchedStudents + } + } + } - for (const student of studentsToScore) { - await eventRepo.create({ - student_name: student.name, - reason_content: reason, - delta: scoreValue - }) + for (const action of rule.actions || []) { + await this.executeAction(action, studentsToScore, rule.name) } rule.lastExecuted = new Date() @@ -371,6 +392,54 @@ export class AutoScoreService extends Service { } } + private async executeAction( + action: { event: string; value?: string; reason?: string }, + students: student[], + ruleName: string + ) { + const eventRepo = this.mainCtx.events + + switch (action.event) { + case 'add_score': { + const scoreValue = action.value ? parseInt(action.value, 10) : 0 + const reason = action.reason || `自动化加分 - ${ruleName}` + for (const student of students) { + await eventRepo.create({ + student_name: student.name, + reason_content: reason, + delta: scoreValue + }) + } + break + } + case 'add_tag': { + const tagName = action.value + if (tagName) { + const studentRepo = this.mainCtx.students + for (const student of students) { + const currentTags = student.tags || [] + if (!currentTags.includes(tagName)) { + await studentRepo.update(student.id, { + tags: [...currentTags, tagName] + }) + } + } + } + break + } + case 'send_notification': { + this.logger.info(`Notification action: ${action.value}`) + break + } + case 'set_student_status': { + this.logger.info(`Set student status action: ${action.value} (not implemented - student type has no status field)`) + break + } + default: + this.logger.warn(`Unknown action event: ${action.event}`) + } + } + private stopRules() { for (const [timer] of this.timers) { clearTimeout(timer) diff --git a/src/renderer/src/components/AutoScoreManager.tsx b/src/renderer/src/components/AutoScoreManager.tsx index d36f1be..2d6e14d 100644 --- a/src/renderer/src/components/AutoScoreManager.tsx +++ b/src/renderer/src/components/AutoScoreManager.tsx @@ -1,6 +1,9 @@ import React, { useState, useEffect } from 'react' -import { AddIcon, Delete1Icon, MoveIcon } from 'tdesign-icons-react' -import { allTriggers, allActions, TriggerItem, ActionItem } from '../services/AutoScoreService' +import { AddIcon, MoveIcon } from 'tdesign-icons-react' +import { allTriggers, allActions, triggerRegistry, actionRegistry } from './com.automatically' +import type { TriggerItem, ActionItem } from './com.automatically/types' +import TriggerItemComponent from './com.automatically/TriggerItem' +import ActionItemComponent from './com.automatically/ActionItem' import { Card, Form, @@ -12,12 +15,11 @@ import { Space, Switch, Popconfirm, - Radio, Select, TooltipLite } from 'tdesign-react' +import Code from './Code' -import Code from './Code'; interface AutoScoreRule { id: number enabled: boolean @@ -41,6 +43,8 @@ export const AutoScoreManager: React.FC = () => { const [pageSize, setPageSize] = useState(50) const [form] = Form.useForm() const [editingRuleId, setEditingRuleId] = useState(null) + const [triggerList, setTriggerList] = useState([]) + const [actionList, setActionList] = useState([]) const fetchRules = async () => { if (!(window as any).api) return @@ -92,35 +96,16 @@ export const AutoScoreManager: React.FC = () => { return } - // 验证触发器必填项 if (triggerList.length === 0) { MessagePlugin.warning('请至少添加一个触发器') return } - for (const t of triggerList) { - const def = allTriggers.find((a) => a.eventName === t.eventName) - if (def?.valueType && (!t.value || String(t.value).trim() === '')) { - MessagePlugin.warning(`触发器 ${def.label} 需要填写 value`) - return - } - } - - // 验证行动必填项 if (actionList.length === 0) { MessagePlugin.warning('请至少添加一个行动') return } - for (const a of actionList) { - const def = allActions.find((action) => action.eventName === a.eventName) - if (def?.valueType && (!a.value || String(a.value).trim() === '')) { - MessagePlugin.warning(`行动 ${def.label} 需要填写 value`) - return - } - } - - // 确保 studentNames 是数组类型 const studentNames = Array.isArray(values.studentNames) ? values.studentNames : [] const triggersPayload = triggerList.map((t) => ({ event: t.eventName, value: t.value })) @@ -138,7 +123,6 @@ export const AutoScoreManager: React.FC = () => { actions: actionsPayload } - // 权限检查:仅管理员可创建/更新自动化 try { const authRes = await (window as any).api.authGetStatus() if (!authRes || !authRes.success || authRes.data?.permission !== 'admin') { @@ -152,19 +136,16 @@ export const AutoScoreManager: React.FC = () => { try { let res if (editingRuleId !== null) { - // 更新现有自动化 res = await (window as any).api.invoke('auto-score:updateRule', { id: editingRuleId, ...ruleData }) } else { - // 创建新自动化 res = await (window as any).api.invoke('auto-score:addRule', ruleData) } if (res.success) { MessagePlugin.success(editingRuleId !== null ? '自动化更新成功' : '自动化创建成功') - // 手动清空表单字段,避免 form.reset() 导致的栈溢出 form.setFieldsValue({ name: '', studentNames: '' @@ -172,7 +153,7 @@ export const AutoScoreManager: React.FC = () => { setEditingRuleId(null) setTriggerList([]) setActionList([]) - fetchRules() // 刷新自动化列表 + fetchRules() } else { MessagePlugin.error( res.message || (editingRuleId !== null ? '更新自动化失败' : '创建自动化失败') @@ -183,39 +164,30 @@ export const AutoScoreManager: React.FC = () => { MessagePlugin.error(editingRuleId !== null ? '更新自动化失败' : '创建自动化失败') } } + const handleEdit = (rule: AutoScoreRule) => { setEditingRuleId(rule.id) form.setFieldsValue({ name: rule.name, studentNames: rule.studentNames.join(', ') }) - // 如果后端返回了 triggers 字段,把它加载到 triggerList if (rule.triggers && Array.isArray(rule.triggers)) { - const mapped = rule.triggers.map((t, idx) => { - const found = allTriggers.find((a) => a.eventName === t.event) - return { - id: idx + 1, - eventName: t.event, - haveValue: !!found?.valueType, - value: t.value ?? '' - } - }) + const mapped = rule.triggers.map((t, idx) => ({ + id: idx + 1, + eventName: t.event, + value: t.value ?? '' + })) setTriggerList(mapped) } else { setTriggerList([]) } - // 如果后端返回了 actions 字段,把它加载到 actionList if (rule.actions && Array.isArray(rule.actions)) { - const mapped = rule.actions.map((a, idx) => { - const found = allActions.find((action) => action.eventName === a.event) - return { - id: idx + 1, - eventName: a.event, - valueType: found?.valueType, - value: a.value ?? '', - reason: a.reason - } - }) + const mapped = rule.actions.map((a, idx) => ({ + id: idx + 1, + eventName: a.event, + value: a.value ?? '', + reason: a.reason ?? '' + })) setActionList(mapped) } else { setActionList([]) @@ -224,7 +196,6 @@ export const AutoScoreManager: React.FC = () => { const handleDelete = async (ruleId: number) => { if (!(window as any).api) return - // 权限检查 try { const authRes = await (window as any).api.authGetStatus() if (!authRes || !authRes.success || authRes.data?.permission !== 'admin') { @@ -239,7 +210,7 @@ export const AutoScoreManager: React.FC = () => { const res = await (window as any).api.invoke('auto-score:deleteRule', ruleId) if (res.success) { MessagePlugin.success('自动化删除成功') - fetchRules() // 刷新自动化列表 + fetchRules() } else { MessagePlugin.error(res.message || '删除自动化失败') } @@ -251,7 +222,6 @@ export const AutoScoreManager: React.FC = () => { const handleToggle = async (ruleId: number, enabled: boolean) => { if (!(window as any).api) return - // 权限检查 try { const authRes = await (window as any).api.authGetStatus() if (!authRes || !authRes.success || authRes.data?.permission !== 'admin') { @@ -266,7 +236,7 @@ export const AutoScoreManager: React.FC = () => { const res = await (window as any).api.invoke('auto-score:toggleRule', { ruleId, enabled }) if (res.success) { MessagePlugin.success(enabled ? '自动化已启用' : '自动化已禁用') - fetchRules() // 刷新自动化列表 + fetchRules() } else { MessagePlugin.error(res.message || (enabled ? '启用自动化失败' : '禁用自动化失败')) } @@ -277,7 +247,6 @@ export const AutoScoreManager: React.FC = () => { } const handleResetForm = () => { - // 手动清空表单字段,避免 form.reset() 导致的栈溢出 form.setFieldsValue({ name: '', studentNames: '' @@ -287,6 +256,73 @@ export const AutoScoreManager: React.FC = () => { setActionList([]) } + const handleAddTrigger = () => { + const nextId = triggerList.length ? Math.max(...triggerList.map((t) => t.id)) + 1 : 1 + const defaultTrigger = allTriggers.list[0] + if (!defaultTrigger) { + MessagePlugin.error('没有可用的触发器类型,请检查配置') + return + } + setTriggerList((prev) => [ + ...prev, + { + id: nextId, + eventName: defaultTrigger.eventName, + value: '' + } + ]) + } + + const handleDeleteTrigger = (id: number) => { + setTriggerList((prev) => prev.filter((t) => t.id !== id)) + } + + const handleTriggerChange = (id: number, eventName: string) => { + setTriggerList((prev) => + prev.map((t) => (t.id === id ? { ...t, eventName, value: '' } : t)) + ) + } + + const handleTriggerValueChange = (id: number, value: string) => { + setTriggerList((prev) => prev.map((t) => (t.id === id ? { ...t, value } : t))) + } + + const handleAddAction = () => { + const nextId = actionList.length ? Math.max(...actionList.map((a) => a.id)) + 1 : 1 + const defaultAction = allActions.list[0] + if (!defaultAction) { + MessagePlugin.error('没有可用的行动类型,请检查配置') + return + } + setActionList((prev) => [ + ...prev, + { + id: nextId, + eventName: defaultAction.eventName, + value: '', + reason: '' + } + ]) + } + + const handleDeleteAction = (id: number) => { + setActionList((prev) => prev.filter((a) => a.id !== id)) + } + + const handleActionChange = (id: number, eventName: string) => { + setActionList((prev) => + prev.map((a) => (a.id === id ? { ...a, eventName, value: '' } : a)) + ) + } + + const handleActionValueChange = (id: number, value: string) => { + setActionList((prev) => prev.map((a) => (a.id === id ? { ...a, value } : a))) + } + + const handleActionReasonChange = (id: number, reason: string) => { + setActionList((prev) => prev.map((a) => (a.id === id ? { ...a, reason } : a))) + } + const columns: PrimaryTableCol[] = [ { colKey: 'drag', @@ -316,7 +352,7 @@ export const AutoScoreManager: React.FC = () => { return } const triggerLabels = row.triggers.map((t) => { - const def = allTriggers.find((tr) => tr.eventName === t.event) + const def = triggerRegistry.get(t.event) return def?.label || t.event }) return ( @@ -340,7 +376,7 @@ export const AutoScoreManager: React.FC = () => { return } const actionLabels = row.actions.map((a) => { - const def = allActions.find((ac) => ac.eventName === a.event) + const def = actionRegistry.get(a.event) return def?.label || a.event }) return ( @@ -403,180 +439,34 @@ export const AutoScoreManager: React.FC = () => { ) } ] + const onDragSort = (params: any) => setRules(params.newData) - const triggerOptions = allTriggers.map((t) => ({ label: t.label, value: t.eventName })) - - const [triggerList, setTriggerList] = useState([]) - const [actionList, setActionList] = useState([]) - - const handleTriggerChange = (id: number, value: string) => { - const found = allTriggers.find((a) => a.eventName === value) - setTriggerList((prev) => - prev.map((t) => - t.id === id ? { ...t, eventName: value, valueType: found?.valueType, value: '' } : t - ) - ) - } - - const handleValueChange = (id: number, val: string) => { - setTriggerList((prev) => prev.map((t) => (t.id === id ? { ...t, value: val } : t))) - } - - const handleDeleteTrigger = (id: number) => { - setTriggerList((prev) => prev.filter((t) => t.id !== id)) - } - - const handleAddTrigger = () => { - const nextId = triggerList.length ? Math.max(...triggerList.map((t) => t.id)) + 1 : 1 - const defaultTrigger = allTriggers[0] - setTriggerList((prev) => [ - ...prev, - { - id: nextId, - eventName: defaultTrigger.eventName, - valueType: defaultTrigger.valueType, - value: '' - } - ]) - } - - // 行动管理相关函数 - const handleActionChange = (id: number, value: string) => { - const found = allActions.find((a) => a.eventName === value) - setActionList((prev) => - prev.map((t) => - t.id === id ? { ...t, eventName: value, valueType: found?.valueType, value: '' } : t - ) - ) - } - - const handleActionValueChange = (id: number, val: string) => { - setActionList((prev) => prev.map((t) => (t.id === id ? { ...t, value: val } : t))) - } - - const handleDeleteAction = (id: number) => { - setActionList((prev) => prev.filter((t) => t.id !== id)) - } - - const handleAddAction = () => { - const nextId = actionList.length ? Math.max(...actionList.map((t) => t.id)) + 1 : 1 - const defaultAction = allActions[0] - setActionList((prev) => [ - ...prev, - { - id: nextId, - eventName: defaultAction.eventName, - valueType: defaultAction.valueType, - value: '' - } - ]) - } - const triggerItems = triggerList .filter((t) => t.eventName !== null) - .map((triggerTest) => ( -
-
+ .map((item) => ( + )) + return (

自动化加分管理

@@ -670,19 +560,18 @@ export const AutoScoreManager: React.FC = () => { - { + { if (editingRuleId !== null) { - // 显示当前编辑的规则 - const values = form.getFieldsValue(true) as unknown as AutoScoreRuleFormValues; - const studentNames = Array.isArray(values.studentNames) ? values.studentNames : []; - const triggersPayload = triggerList.map((t) => ({ event: t.eventName, value: t.value })); + const values = form.getFieldsValue(true) as unknown as AutoScoreRuleFormValues + const studentNames = Array.isArray(values.studentNames) ? values.studentNames : [] + const triggersPayload = triggerList.map((t) => ({ event: t.eventName, value: t.value })) const actionsPayload = actionList.map((a) => ({ event: a.eventName, value: a.value, reason: a.reason - })); - + })) + const currentRule = { id: editingRuleId, enabled: true, @@ -690,25 +579,16 @@ export const AutoScoreManager: React.FC = () => { studentNames, triggers: triggersPayload, actions: actionsPayload - }; - - return JSON.stringify(currentRule, null, 2); + } + + return JSON.stringify(currentRule, null, 2) } else { - // 显示所有规则 - return JSON.stringify(rules, null, 2); + return JSON.stringify(rules, null, 2) } })()} - language={'json'}/> + language={'json'} + /> - {/*
-

使用说明

-
    -
  • 自动化加分功能会按照设定的时间间隔自动为学生加分
  • -
  • 间隔时间以分钟为单位,例如1440表示每24小时(一天)执行一次
  • -
  • 如果"适用学生"字段为空,则自动化适用于所有学生
  • -
  • 可以随时启用/禁用自动化,不会影响已保存的自动化配置
  • -
-
*/}
) } diff --git a/src/renderer/src/components/com.automatically/ActionItem.tsx b/src/renderer/src/components/com.automatically/ActionItem.tsx new file mode 100644 index 0000000..83d3d35 --- /dev/null +++ b/src/renderer/src/components/com.automatically/ActionItem.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import { Button, Select } from 'tdesign-react' +import { Delete1Icon } from 'tdesign-icons-react' +import { actionRegistry, allActions } from './registry' +import type { ActionItem as ActionItemType } from './types' + +interface ActionItemProps { + item: ActionItemType + onDelete: (id: number) => void + onChange: (id: number, eventName: string) => void + onValueChange: (id: number, value: string) => void + onReasonChange: (id: number, reason: string) => void +} + +const ActionItem: React.FC = ({ + item, + onDelete, + onChange, + onValueChange, + onReasonChange +}) => { + const definition = actionRegistry.get(item.eventName) + const Component = definition?.component + + return ( +
+
+ ) +} + +export default TriggerItem diff --git a/src/renderer/src/components/com.automatically/actions/AddScoreAction.tsx b/src/renderer/src/components/com.automatically/actions/AddScoreAction.tsx new file mode 100644 index 0000000..62f1631 --- /dev/null +++ b/src/renderer/src/components/com.automatically/actions/AddScoreAction.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { Input } from 'tdesign-react' +import type { ActionComponentProps } from '../types' + +export const eventName = 'add_score' +export const label = '添加分数' +export const description = '为学生添加分数' +export const hasReason = true + +const AddScoreAction: React.FC = ({ value, reason, onChange, onReasonChange }) => { + return ( + <> + onChange(v ? String(v) : '')} + /> + onReasonChange?.(v ? String(v) : '')} + /> + + ) +} + +export default AddScoreAction diff --git a/src/renderer/src/components/com.automatically/actions/AddTagAction.tsx b/src/renderer/src/components/com.automatically/actions/AddTagAction.tsx new file mode 100644 index 0000000..dd6afb7 --- /dev/null +++ b/src/renderer/src/components/com.automatically/actions/AddTagAction.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { Input } from 'tdesign-react' +import type { ActionComponentProps } from '../types' + +export const eventName = 'add_tag' +export const label = '添加标签' +export const description = '为学生添加标签' +export const hasReason = false + +const AddTagAction: React.FC = ({ value, onChange }) => { + return ( + onChange(v ? String(v) : '')} + /> + ) +} + +export default AddTagAction diff --git a/src/renderer/src/components/com.automatically/actions/SendNotificationAction.tsx b/src/renderer/src/components/com.automatically/actions/SendNotificationAction.tsx new file mode 100644 index 0000000..326a501 --- /dev/null +++ b/src/renderer/src/components/com.automatically/actions/SendNotificationAction.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { Input } from 'tdesign-react' +import type { ActionComponentProps } from '../types' + +export const eventName = 'send_notification' +export const label = '发送通知' +export const description = '向学生发送通知' +export const hasReason = false + +const SendNotificationAction: React.FC = ({ value, onChange }) => { + return ( + onChange(v ? String(v) : '')} + /> + ) +} + +export default SendNotificationAction diff --git a/src/renderer/src/components/com.automatically/actions/SetStudentStatusAction.tsx b/src/renderer/src/components/com.automatically/actions/SetStudentStatusAction.tsx new file mode 100644 index 0000000..312cb00 --- /dev/null +++ b/src/renderer/src/components/com.automatically/actions/SetStudentStatusAction.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { Select } from 'tdesign-react' +import type { ActionComponentProps } from '../types' + +export const eventName = 'set_student_status' +export const label = '设置学生状态' +export const description = '设置学生的状态' +export const hasReason = false + +const statusOptions = [ + { label: '活跃', value: 'active' }, + { label: '不活跃', value: 'inactive' }, + { label: '请假', value: 'leave' } +] + +const SetStudentStatusAction: React.FC = ({ value, onChange }) => { + return ( + onChange(v ? String(v) : '')} + options={tags} + filterable + clearable + /> + ) +} + +export default StudentTagTrigger diff --git a/src/renderer/src/components/com.automatically/triggers/index.ts b/src/renderer/src/components/com.automatically/triggers/index.ts new file mode 100644 index 0000000..6251240 --- /dev/null +++ b/src/renderer/src/components/com.automatically/triggers/index.ts @@ -0,0 +1,55 @@ +import { triggerRegistry } from '../registry' +import type { TriggerDefinition } from '../types' + +import IntervalTimeTrigger, { + eventName as intervalEventName, + label as intervalLabel, + description as intervalDescription, + triggerLogic as intervalTriggerLogic +} from './IntervalTimeTrigger' + +import StudentTagTrigger, { + eventName as studentTagEventName, + label as studentTagLabel, + description as studentTagDescription, + triggerLogic as studentTagTriggerLogic +} from './StudentTagTrigger' + +import RandomTimeTrigger, { + eventName as randomTimeEventName, + label as randomTimeLabel, + description as randomTimeDescription, + triggerLogic as randomTimeTriggerLogic +} from './RandomTimeTrigger' + +const triggerDefinitions: TriggerDefinition[] = [ + { + eventName: intervalEventName, + label: intervalLabel, + description: intervalDescription, + component: IntervalTimeTrigger, + triggerLogic: intervalTriggerLogic + }, + { + eventName: studentTagEventName, + label: studentTagLabel, + description: studentTagDescription, + component: StudentTagTrigger, + triggerLogic: studentTagTriggerLogic + }, + { + eventName: randomTimeEventName, + label: randomTimeLabel, + description: randomTimeDescription, + component: RandomTimeTrigger, + triggerLogic: randomTimeTriggerLogic + } +] + +triggerDefinitions.forEach((def) => triggerRegistry.register(def)) + +export { + IntervalTimeTrigger, + StudentTagTrigger, + RandomTimeTrigger +} diff --git a/src/renderer/src/components/com.automatically/types.ts b/src/renderer/src/components/com.automatically/types.ts new file mode 100644 index 0000000..47ccbb8 --- /dev/null +++ b/src/renderer/src/components/com.automatically/types.ts @@ -0,0 +1,51 @@ +import React from 'react' + +export interface TriggerComponentProps { + value: string + onChange: (value: string) => void +} + +export interface ActionComponentProps { + value: string + reason: string + onChange: (value: string) => void + onReasonChange: (reason: string) => void +} + +export interface TriggerLogic { + eventName: string + label: string + description: string + validate: (value: string) => { valid: boolean; message?: string } + calculateNextTime?: (value: string, lastExecuted: Date | undefined, now: Date) => { delayMs: number; nextExecuteTime: Date } + check?: (context: any, value: string) => { shouldExecute: boolean; message?: string } +} + +export interface TriggerDefinition { + eventName: string + label: string + description: string + component: React.FC + triggerLogic?: TriggerLogic +} + +export interface ActionDefinition { + eventName: string + label: string + description: string + component: React.FC + hasReason?: boolean +} + +export interface TriggerItem { + id: number + eventName: string + value: string +} + +export interface ActionItem { + id: number + eventName: string + value: string + reason: string +} diff --git a/src/renderer/src/services/AutoScoreService.ts b/src/renderer/src/services/AutoScoreService.ts index b378c56..3b92c8a 100644 --- a/src/renderer/src/services/AutoScoreService.ts +++ b/src/renderer/src/services/AutoScoreService.ts @@ -1,7 +1,7 @@ -import React from 'react' import { Service } from '../../../shared/kernel' import { ClientContext } from '../ClientContext' -import { Input, Select } from 'tdesign-react' +import { allTriggers, allActions } from '../components/com.automatically' +import type { TriggerItem, ActionItem } from '../components/com.automatically/types' export interface AutoScoreRule { id: number @@ -27,123 +27,8 @@ declare module '../../../shared/kernel' { } } -type RenderConfig = { - component?: React.ElementType - props?: Record - render?: (props: any) => React.ReactNode -} - -type TriggerDef = { - id: number - label: string - description: string - eventName: string - valueType?: React.ElementType - renderConfig?: RenderConfig -} - -export const allTriggers: TriggerDef[] = [ - { - id: 1, - label: '根据间隔时间触发', - description: '当学生注册时触发自动化', - eventName: 'interval_time_passed', - valueType: Input - }, - { - id: 2, - label: '按照学生标签触发', - description: '当学生完成作业时触发自动化', - eventName: 'student_tag_matched', - valueType: Input - }, - { - id: 3, - label: '随机时间触发', - description: '当随机时间到达时触发自动化', - eventName: 'random_time_reached' - } -] - -// 触发器和行动项类型定义 -export type TriggerItem = { - id: number - eventName: string - value?: string - valueType?: React.ElementType -} - -export type ActionItem = { - id: number - eventName: string - value?: string - reason?: string - valueType?: React.ElementType -} - -export const allActions: TriggerDef[] = [ - { - id: 1, - label: '添加分数', - description: '为学生添加分数', - eventName: 'add_score', - valueType: Input, - renderConfig: { - component: Input, - props: { - placeholder: '请输入分数', - style: { width: '150px' } - } - } - }, - { - id: 2, - label: '添加标签', - description: '为学生添加标签', - eventName: 'add_tag', - valueType: Input, - renderConfig: { - component: Input, - props: { - placeholder: '请输入标签', - style: { width: '150px' } - } - } - }, - { - id: 3, - label: '发送通知', - description: '向学生发送通知', - eventName: 'send_notification', - valueType: Input, - renderConfig: { - component: Input, - props: { - placeholder: '请输入通知内容', - style: { width: '150px' } - } - } - }, - { - id: 4, - label: '设置学生状态', - description: '设置学生的状态', - eventName: 'set_student_status', - valueType: Select, - renderConfig: { - component: Select, - props: { - placeholder: '请选择状态', - style: { width: '150px' }, - options: [ - { label: '活跃', value: 'active' }, - { label: '不活跃', value: 'inactive' }, - { label: '请假', value: 'leave' } - ] - } - } - } -] +export { allTriggers, allActions } +export type { TriggerItem, ActionItem } export class AutoScoreService extends Service { constructor(ctx: ClientContext) { @@ -183,22 +68,19 @@ export class AutoScoreService extends Service { return await (window as any).api.invoke('auto-score:getStatus', {}) } - // 触发器管理相关函数 createTriggerItem(triggerList: TriggerItem[]): TriggerItem { const nextId = triggerList.length ? Math.max(...triggerList.map((t) => t.id)) + 1 : 1 - const defaultTrigger = allTriggers[0] + const defaultTrigger = allTriggers.list[0] return { id: nextId, eventName: defaultTrigger.eventName, - valueType: defaultTrigger.valueType, value: '' } } updateTriggerEvent(triggerList: TriggerItem[], id: number, eventName: string): TriggerItem[] { - const found = allTriggers.find((a) => a.eventName === eventName) return triggerList.map((t) => - t.id === id ? { ...t, eventName, valueType: found?.valueType, value: '' } : t + t.id === id ? { ...t, eventName, value: '' } : t ) } @@ -210,22 +92,20 @@ export class AutoScoreService extends Service { return triggerList.filter((t) => t.id !== id) } - // 行动管理相关函数 createActionItem(actionList: ActionItem[]): ActionItem { const nextId = actionList.length ? Math.max(...actionList.map((a) => a.id)) + 1 : 1 - const defaultAction = allActions[0] + const defaultAction = allActions.list[0] return { id: nextId, eventName: defaultAction.eventName, - valueType: defaultAction.valueType, - value: '' + value: '', + reason: '' } } updateActionEvent(actionList: ActionItem[], id: number, eventName: string): ActionItem[] { - const found = allActions.find((a) => a.eventName === eventName) return actionList.map((a) => - a.id === id ? { ...a, eventName, valueType: found?.valueType, value: '' } : a + a.id === id ? { ...a, eventName, value: '' } : a ) } @@ -233,6 +113,10 @@ export class AutoScoreService extends Service { return actionList.map((a) => (a.id === id ? { ...a, value } : a)) } + updateActionReason(actionList: ActionItem[], id: number, reason: string): ActionItem[] { + return actionList.map((a) => (a.id === id ? { ...a, reason } : a)) + } + deleteActionItem(actionList: ActionItem[], id: number): ActionItem[] { return actionList.filter((a) => a.id !== id) } diff --git a/src/shared/triggers/IntervalTimeTrigger.ts b/src/shared/triggers/IntervalTimeTrigger.ts new file mode 100644 index 0000000..afe927e --- /dev/null +++ b/src/shared/triggers/IntervalTimeTrigger.ts @@ -0,0 +1,52 @@ +import type { TriggerLogic } from './types' + +export const intervalTimeTrigger: TriggerLogic = { + eventName: 'interval_time_passed', + label: '根据间隔时间触发', + description: '当间隔时间到达时触发自动化', + + validate: (value: string) => { + const minutes = parseInt(value, 10) + if (isNaN(minutes) || minutes <= 0) { + return { valid: false, message: '请输入有效的时间间隔(分钟)' } + } + return { valid: true } + }, + + calculateNextTime: (value: string, lastExecuted: Date | undefined, now: Date) => { + const intervalMinutes = parseInt(value, 10) + if (isNaN(intervalMinutes) || intervalMinutes <= 0) { + return { delayMs: 0, nextExecuteTime: now } + } + + const intervalMs = intervalMinutes * 60 * 1000 + + let delayMs = intervalMs + if (lastExecuted) { + const timeSinceLastExecution = now.getTime() - lastExecuted.getTime() + delayMs = intervalMs - (timeSinceLastExecution % intervalMs) + if (timeSinceLastExecution >= intervalMs) { + delayMs = 0 + } + } + + const nextExecuteTime = new Date(now.getTime() + delayMs) + return { delayMs, nextExecuteTime } + }, + + check: (context, value) => { + const result = intervalTimeTrigger.calculateNextTime!( + value, + context.rule.triggers?.find((t) => t.event === 'interval_time_passed') + ? context.now + : undefined, + context.now + ) + return { + shouldExecute: result.delayMs === 0, + matchedStudents: context.students, + nextExecuteTime: result.nextExecuteTime, + delayMs: result.delayMs + } + } +} diff --git a/src/shared/triggers/RandomTimeTrigger.ts b/src/shared/triggers/RandomTimeTrigger.ts new file mode 100644 index 0000000..ddab590 --- /dev/null +++ b/src/shared/triggers/RandomTimeTrigger.ts @@ -0,0 +1,65 @@ +import type { TriggerLogic } from './types' + +export const randomTimeTrigger: TriggerLogic = { + eventName: 'random_time_reached', + label: '随机时间触发', + description: '当随机时间到达时触发自动化', + + validate: (value: string) => { + if (!value) { + return { valid: true } + } + try { + const config = JSON.parse(value) + if (config.minHour !== undefined || config.maxHour !== undefined) { + const minHour = config.minHour ?? 0 + const maxHour = config.maxHour ?? 23 + if (minHour < 0 || minHour > 23 || maxHour < 0 || maxHour > 23) { + return { valid: false, message: '小时范围必须在0-23之间' } + } + if (minHour > maxHour) { + return { valid: false, message: '最小小时不能大于最大小时' } + } + } + return { valid: true } + } catch { + return { valid: false, message: '配置格式错误' } + } + }, + + calculateNextTime: (value: string, _lastExecuted: Date | undefined, now: Date) => { + let config = { minHour: 9, maxHour: 18 } + try { + if (value) { + const parsed = JSON.parse(value) + config = { ...config, ...parsed } + } + } catch {} + + const minHour = config.minHour ?? 0 + const maxHour = config.maxHour ?? 23 + + const randomHour = Math.floor(Math.random() * (maxHour - minHour + 1)) + minHour + const randomMinute = Math.floor(Math.random() * 60) + + let targetDate = new Date(now) + targetDate.setHours(randomHour, randomMinute, 0, 0) + + if (targetDate.getTime() <= now.getTime()) { + targetDate.setDate(targetDate.getDate() + 1) + } + + const delayMs = targetDate.getTime() - now.getTime() + return { delayMs, nextExecuteTime: targetDate } + }, + + check: (context, value) => { + const result = randomTimeTrigger.calculateNextTime!(value, undefined, context.now) + return { + shouldExecute: result.delayMs === 0, + matchedStudents: context.students, + nextExecuteTime: result.nextExecuteTime, + delayMs: result.delayMs + } + } +} diff --git a/src/shared/triggers/StudentTagTrigger.ts b/src/shared/triggers/StudentTagTrigger.ts new file mode 100644 index 0000000..8cb503e --- /dev/null +++ b/src/shared/triggers/StudentTagTrigger.ts @@ -0,0 +1,30 @@ +import type { TriggerLogic } from './types' + +export const studentTagTrigger: TriggerLogic = { + eventName: 'student_tag_matched', + label: '按照学生标签触发', + description: '当学生标签匹配时触发自动化', + + validate: (value: string) => { + if (!value || value.trim() === '') { + return { valid: false, message: '请输入标签名称' } + } + return { valid: true } + }, + + check: (context, value) => { + const tagName = value.trim().toLowerCase() + const matchedStudents = context.students.filter((student: any) => { + if (student.tags && Array.isArray(student.tags)) { + return student.tags.some((tag: string) => tag.toLowerCase() === tagName) + } + return false + }) + + return { + shouldExecute: matchedStudents.length > 0, + matchedStudents, + nextExecuteTime: context.now + } + } +} diff --git a/src/shared/triggers/index.ts b/src/shared/triggers/index.ts new file mode 100644 index 0000000..52f5b2f --- /dev/null +++ b/src/shared/triggers/index.ts @@ -0,0 +1,17 @@ +import type { TriggerLogic } from './types' +import { intervalTimeTrigger } from './IntervalTimeTrigger' +import { studentTagTrigger } from './StudentTagTrigger' +import { randomTimeTrigger } from './RandomTimeTrigger' + +export * from './types' +export { intervalTimeTrigger, studentTagTrigger, randomTimeTrigger } + +export const allTriggerLogics: TriggerLogic[] = [ + intervalTimeTrigger, + studentTagTrigger, + randomTimeTrigger +] + +export function getTriggerLogic(eventName: string): TriggerLogic | undefined { + return allTriggerLogics.find((t) => t.eventName === eventName) +} diff --git a/src/shared/triggers/types.ts b/src/shared/triggers/types.ts new file mode 100644 index 0000000..9cd41c8 --- /dev/null +++ b/src/shared/triggers/types.ts @@ -0,0 +1,32 @@ +export interface TriggerContext { + students: any[] + events: any[] + rule: { + id: number + name: string + studentNames: string[] + triggers?: { event: string; value?: string }[] + actions?: { event: string; value?: string; reason?: string }[] + } + now: Date +} + +export interface TriggerResult { + shouldExecute: boolean + matchedStudents?: any[] + nextExecuteTime?: Date + delayMs?: number +} + +export interface TriggerLogic { + eventName: string + label: string + description: string + validate: (value: string) => { valid: boolean; message?: string } + calculateNextTime?: ( + value: string, + lastExecuted: Date | undefined, + now: Date + ) => { delayMs: number; nextExecuteTime: Date } + check?: (context: TriggerContext, value: string) => TriggerResult +} diff --git a/tsconfig.node.json b/tsconfig.node.json index 81d8aec..792885f 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -1,6 +1,6 @@ { "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", - "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"], + "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/shared/**/*"], "compilerOptions": { "composite": true, "types": ["electron-vite/node"],