mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2026-02-04 07:53:11 +00:00
Compare commits
5 Commits
8a8ea05d81
...
fb4da655c6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb4da655c6 | ||
|
|
3a6f6f5fdc | ||
|
|
f96b939a39 | ||
|
|
aa2c45be25 | ||
|
|
f6b8d76906 |
@ -1,5 +1,5 @@
|
|||||||
# Classworks KV 默认服务器域名
|
# Classworks KV 默认服务器域名
|
||||||
VITE_DEFAULT_KV_SERVER=https://kv.wuyuan.dev
|
VITE_DEFAULT_KV_SERVER=https://kv-service.houlang.cloud
|
||||||
|
|
||||||
# Classworks KV 授权服务器域名
|
# Classworks KV 授权服务器域名
|
||||||
VITE_DEFAULT_AUTH_SERVER=https://kv.houlang.cloud
|
VITE_DEFAULT_AUTH_SERVER=https://kv.houlang.cloud
|
||||||
|
|||||||
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
VITE_APP_ID: d158067f53627d2b98babe8bffd2fd7d
|
VITE_APP_ID: d158067f53627d2b98babe8bffd2fd7d
|
||||||
VITE_DEFAULT_KV_SERVER: https://kv.wuyuan.dev
|
VITE_DEFAULT_KV_SERVER: https://kv-service.houlang.cloud
|
||||||
VITE_DEFAULT_AUTH_SERVER: https://kv.houlang.cloud
|
VITE_DEFAULT_AUTH_SERVER: https://kv.houlang.cloud
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm install
|
||||||
|
|||||||
@ -10,8 +10,6 @@
|
|||||||
"lint": "eslint . --fix"
|
"lint": "eslint . --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@examaware-cs/core": "^1.0.0",
|
|
||||||
"@examaware-cs/player": "^1.0.2",
|
|
||||||
"@fingerprintjs/fingerprintjs": "^5.0.1",
|
"@fingerprintjs/fingerprintjs": "^5.0.1",
|
||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"@microsoft/clarity": "^1.0.2",
|
"@microsoft/clarity": "^1.0.2",
|
||||||
|
|||||||
132
pnpm-lock.yaml
generated
132
pnpm-lock.yaml
generated
@ -8,12 +8,6 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@examaware-cs/core':
|
|
||||||
specifier: ^1.0.0
|
|
||||||
version: 1.0.0
|
|
||||||
'@examaware-cs/player':
|
|
||||||
specifier: ^1.0.2
|
|
||||||
version: 1.0.2(tdesign-vue-next@1.17.5(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3))
|
|
||||||
'@fingerprintjs/fingerprintjs':
|
'@fingerprintjs/fingerprintjs':
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
@ -865,15 +859,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
|
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@examaware-cs/core@1.0.0':
|
|
||||||
resolution: {integrity: sha512-JSnyYe4/wpKfoPdA1CVNjLyBSpFSL+H/rKTZlbJyFCymtBRlXthzswKzLCwioyw7y/C3lioUFXLJ2g+CwDnS6w==}
|
|
||||||
|
|
||||||
'@examaware-cs/player@1.0.2':
|
|
||||||
resolution: {integrity: sha512-Fid58JL0X5TP/O3DNp8QY0B5B8Svv1I8Qi8AObw5HK571/UU6/ri1YQS03ujxt6yupzd7qhiX+7C7EO4nLglJg==}
|
|
||||||
peerDependencies:
|
|
||||||
tdesign-vue-next: ^1.15.5
|
|
||||||
vue: ^3.0.0
|
|
||||||
|
|
||||||
'@fingerprintjs/fingerprintjs@5.0.1':
|
'@fingerprintjs/fingerprintjs@5.0.1':
|
||||||
resolution: {integrity: sha512-KbaeE/rk2WL8MfpRP6jTI4lSr42SJPjvkyrjP3QU6uUDkOMWWYC2Ts1sNSYcegHC8avzOoYTHBj+2fTqvZWQBA==}
|
resolution: {integrity: sha512-KbaeE/rk2WL8MfpRP6jTI4lSr42SJPjvkyrjP3QU6uUDkOMWWYC2Ts1sNSYcegHC8avzOoYTHBj+2fTqvZWQBA==}
|
||||||
|
|
||||||
@ -1132,9 +1117,6 @@ packages:
|
|||||||
'@polka/url@1.0.0-next.29':
|
'@polka/url@1.0.0-next.29':
|
||||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||||
|
|
||||||
'@popperjs/core@2.11.8':
|
|
||||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
|
||||||
|
|
||||||
'@quansync/fs@0.1.5':
|
'@quansync/fs@0.1.5':
|
||||||
resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==}
|
resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==}
|
||||||
|
|
||||||
@ -1328,27 +1310,12 @@ packages:
|
|||||||
'@types/json5@0.0.29':
|
'@types/json5@0.0.29':
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
|
|
||||||
'@types/lodash-es@4.17.12':
|
|
||||||
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
|
|
||||||
|
|
||||||
'@types/lodash@4.17.21':
|
|
||||||
resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==}
|
|
||||||
|
|
||||||
'@types/resolve@1.20.2':
|
'@types/resolve@1.20.2':
|
||||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||||
|
|
||||||
'@types/sortablejs@1.15.9':
|
|
||||||
resolution: {integrity: sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==}
|
|
||||||
|
|
||||||
'@types/tinycolor2@1.4.6':
|
|
||||||
resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==}
|
|
||||||
|
|
||||||
'@types/trusted-types@2.0.7':
|
'@types/trusted-types@2.0.7':
|
||||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||||
|
|
||||||
'@types/validator@13.15.10':
|
|
||||||
resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==}
|
|
||||||
|
|
||||||
'@types/web-bluetooth@0.0.21':
|
'@types/web-bluetooth@0.0.21':
|
||||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||||
|
|
||||||
@ -1725,9 +1692,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
dayjs@1.11.19:
|
|
||||||
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
|
||||||
|
|
||||||
debug@3.2.7:
|
debug@3.2.7:
|
||||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2510,9 +2474,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
lodash-es@4.17.21:
|
|
||||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
|
||||||
|
|
||||||
lodash.debounce@4.0.8:
|
lodash.debounce@4.0.8:
|
||||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||||
|
|
||||||
@ -2594,9 +2555,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
misans@4.1.0:
|
|
||||||
resolution: {integrity: sha512-CcIRrIVhnt+OpGXvw1Q8llGBVAy5P2mdov/kJ0gGa81sJ0RY7mZp2fNAt2ySTCeZos+wo7ZnzDZxl1In//7FdA==}
|
|
||||||
|
|
||||||
mitt@3.0.1:
|
mitt@3.0.1:
|
||||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||||
|
|
||||||
@ -2667,9 +2625,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
ogl@1.0.11:
|
|
||||||
resolution: {integrity: sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==}
|
|
||||||
|
|
||||||
open@10.2.0:
|
open@10.2.0:
|
||||||
resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
|
resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -3105,9 +3060,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
simple-keyboard@3.8.92:
|
|
||||||
resolution: {integrity: sha512-7VrUeYwQBuh4Lgi/l6qwtExKQT/muGy18080ft8rOaC6oSfQafoAk759vihvjQi9eJ08/uVQ38ZFukzbfRmfAg==}
|
|
||||||
|
|
||||||
simple-swizzle@0.2.4:
|
simple-swizzle@0.2.4:
|
||||||
resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==}
|
resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==}
|
||||||
|
|
||||||
@ -3126,9 +3078,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
|
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
sortablejs@1.15.6:
|
|
||||||
resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==}
|
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -3240,17 +3189,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
tdesign-icons-vue-next@0.4.1:
|
|
||||||
resolution: {integrity: sha512-uDPuTLRORnGcTyVGNoentNaK4V+ZcBmhYwcY3KqDaQQ5rrPeLMxu0ZVmgOEf0JtF2QZiqAxY7vodNEiLUdoRKA==}
|
|
||||||
peerDependencies:
|
|
||||||
vue: ^3.0.0
|
|
||||||
|
|
||||||
tdesign-vue-next@1.17.5:
|
|
||||||
resolution: {integrity: sha512-eXnkD6dvX/CTHLaF18ThXXiT6UI3iSyTy+xvevV8jEo/F4YDAWbD/URHyOpyvL39TwZzHGv3TnEpUBRcefV9Zg==}
|
|
||||||
engines: {node: '>= 18'}
|
|
||||||
peerDependencies:
|
|
||||||
vue: '>=3.1.0'
|
|
||||||
|
|
||||||
temp-dir@2.0.0:
|
temp-dir@2.0.0:
|
||||||
resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
|
resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -3264,9 +3202,6 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
tinycolor2@1.6.0:
|
|
||||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@ -3455,10 +3390,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
|
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
validator@13.15.23:
|
|
||||||
resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==}
|
|
||||||
engines: {node: '>= 0.10'}
|
|
||||||
|
|
||||||
varint@6.0.0:
|
varint@6.0.0:
|
||||||
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
|
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
|
||||||
|
|
||||||
@ -4554,17 +4485,6 @@ snapshots:
|
|||||||
'@eslint/core': 0.17.0
|
'@eslint/core': 0.17.0
|
||||||
levn: 0.4.1
|
levn: 0.4.1
|
||||||
|
|
||||||
'@examaware-cs/core@1.0.0': {}
|
|
||||||
|
|
||||||
'@examaware-cs/player@1.0.2(tdesign-vue-next@1.17.5(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3))':
|
|
||||||
dependencies:
|
|
||||||
'@examaware-cs/core': 1.0.0
|
|
||||||
misans: 4.1.0
|
|
||||||
ogl: 1.0.11
|
|
||||||
simple-keyboard: 3.8.92
|
|
||||||
tdesign-vue-next: 1.17.5(vue@3.5.25(typescript@5.9.3))
|
|
||||||
vue: 3.5.25(typescript@5.9.3)
|
|
||||||
|
|
||||||
'@fingerprintjs/fingerprintjs@5.0.1': {}
|
'@fingerprintjs/fingerprintjs@5.0.1': {}
|
||||||
|
|
||||||
'@humanfs/core@0.19.1': {}
|
'@humanfs/core@0.19.1': {}
|
||||||
@ -4771,8 +4691,6 @@ snapshots:
|
|||||||
|
|
||||||
'@polka/url@1.0.0-next.29': {}
|
'@polka/url@1.0.0-next.29': {}
|
||||||
|
|
||||||
'@popperjs/core@2.11.8': {}
|
|
||||||
|
|
||||||
'@quansync/fs@0.1.5':
|
'@quansync/fs@0.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
quansync: 0.2.11
|
quansync: 0.2.11
|
||||||
@ -4916,22 +4834,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
|
|
||||||
'@types/lodash-es@4.17.12':
|
|
||||||
dependencies:
|
|
||||||
'@types/lodash': 4.17.21
|
|
||||||
|
|
||||||
'@types/lodash@4.17.21': {}
|
|
||||||
|
|
||||||
'@types/resolve@1.20.2': {}
|
'@types/resolve@1.20.2': {}
|
||||||
|
|
||||||
'@types/sortablejs@1.15.9': {}
|
|
||||||
|
|
||||||
'@types/tinycolor2@1.4.6': {}
|
|
||||||
|
|
||||||
'@types/trusted-types@2.0.7': {}
|
'@types/trusted-types@2.0.7': {}
|
||||||
|
|
||||||
'@types/validator@13.15.10': {}
|
|
||||||
|
|
||||||
'@types/web-bluetooth@0.0.21': {}
|
'@types/web-bluetooth@0.0.21': {}
|
||||||
|
|
||||||
'@vite-pwa/assets-generator@1.0.2':
|
'@vite-pwa/assets-generator@1.0.2':
|
||||||
@ -5394,8 +5300,6 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
is-data-view: 1.0.2
|
is-data-view: 1.0.2
|
||||||
|
|
||||||
dayjs@1.11.19: {}
|
|
||||||
|
|
||||||
debug@3.2.7:
|
debug@3.2.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
@ -6247,8 +6151,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
|
|
||||||
lodash-es@4.17.21: {}
|
|
||||||
|
|
||||||
lodash.debounce@4.0.8: {}
|
lodash.debounce@4.0.8: {}
|
||||||
|
|
||||||
lodash.merge@4.6.2: {}
|
lodash.merge@4.6.2: {}
|
||||||
@ -6316,8 +6218,6 @@ snapshots:
|
|||||||
|
|
||||||
minipass@7.1.2: {}
|
minipass@7.1.2: {}
|
||||||
|
|
||||||
misans@4.1.0: {}
|
|
||||||
|
|
||||||
mitt@3.0.1: {}
|
mitt@3.0.1: {}
|
||||||
|
|
||||||
mlly@1.8.0:
|
mlly@1.8.0:
|
||||||
@ -6388,8 +6288,6 @@ snapshots:
|
|||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
ogl@1.0.11: {}
|
|
||||||
|
|
||||||
open@10.2.0:
|
open@10.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
default-browser: 5.4.0
|
default-browser: 5.4.0
|
||||||
@ -6865,8 +6763,6 @@ snapshots:
|
|||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
|
|
||||||
simple-keyboard@3.8.92: {}
|
|
||||||
|
|
||||||
simple-swizzle@0.2.4:
|
simple-swizzle@0.2.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish: 0.3.4
|
is-arrayish: 0.3.4
|
||||||
@ -6897,8 +6793,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
sortablejs@1.15.6: {}
|
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
source-map-support@0.5.21:
|
||||||
@ -7020,28 +6914,6 @@ snapshots:
|
|||||||
|
|
||||||
tapable@2.3.0: {}
|
tapable@2.3.0: {}
|
||||||
|
|
||||||
tdesign-icons-vue-next@0.4.1(vue@3.5.25(typescript@5.9.3)):
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.28.4
|
|
||||||
vue: 3.5.25(typescript@5.9.3)
|
|
||||||
|
|
||||||
tdesign-vue-next@1.17.5(vue@3.5.25(typescript@5.9.3)):
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.28.4
|
|
||||||
'@popperjs/core': 2.11.8
|
|
||||||
'@types/lodash-es': 4.17.12
|
|
||||||
'@types/sortablejs': 1.15.9
|
|
||||||
'@types/tinycolor2': 1.4.6
|
|
||||||
'@types/validator': 13.15.10
|
|
||||||
dayjs: 1.11.19
|
|
||||||
lodash-es: 4.17.21
|
|
||||||
mitt: 3.0.1
|
|
||||||
sortablejs: 1.15.6
|
|
||||||
tdesign-icons-vue-next: 0.4.1(vue@3.5.25(typescript@5.9.3))
|
|
||||||
tinycolor2: 1.6.0
|
|
||||||
validator: 13.15.23
|
|
||||||
vue: 3.5.25(typescript@5.9.3)
|
|
||||||
|
|
||||||
temp-dir@2.0.0: {}
|
temp-dir@2.0.0: {}
|
||||||
|
|
||||||
tempy@0.6.0:
|
tempy@0.6.0:
|
||||||
@ -7058,8 +6930,6 @@ snapshots:
|
|||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
source-map-support: 0.5.21
|
source-map-support: 0.5.21
|
||||||
|
|
||||||
tinycolor2@1.6.0: {}
|
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@ -7292,8 +7162,6 @@ snapshots:
|
|||||||
|
|
||||||
uuid@13.0.0: {}
|
uuid@13.0.0: {}
|
||||||
|
|
||||||
validator@13.15.23: {}
|
|
||||||
|
|
||||||
varint@6.0.0: {}
|
varint@6.0.0: {}
|
||||||
|
|
||||||
vite-hot-client@2.1.0(vite@5.4.21(sass-embedded@1.93.3)(sass@1.94.2)(terser@5.44.1)):
|
vite-hot-client@2.1.0(vite@5.4.21(sass-embedded@1.93.3)(sass@1.94.2)(terser@5.44.1)):
|
||||||
|
|||||||
@ -23,6 +23,12 @@ onMounted(() => {
|
|||||||
// 应用保存的主题设置
|
// 应用保存的主题设置
|
||||||
const savedTheme = getSetting("theme.mode");
|
const savedTheme = getSetting("theme.mode");
|
||||||
theme.global.name.value = savedTheme;
|
theme.global.name.value = savedTheme;
|
||||||
|
|
||||||
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
window.deferredPwaPrompt = e;
|
||||||
|
window.dispatchEvent(new Event('pwa-prompt-ready'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -108,7 +108,18 @@
|
|||||||
variant="elevated"
|
variant="elevated"
|
||||||
@click="downloadAsEa2"
|
@click="downloadAsEa2"
|
||||||
>
|
>
|
||||||
部署到 ExamAware2 知试
|
.ea2 文件
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
:disabled="!isValidConfig"
|
||||||
|
class="text-none"
|
||||||
|
color="secondary"
|
||||||
|
prepend-icon="mdi-play-circle"
|
||||||
|
variant="elevated"
|
||||||
|
@click="openInEa2Player"
|
||||||
|
>
|
||||||
|
拉起EA2播放器
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-tooltip
|
<v-tooltip
|
||||||
@ -1830,6 +1841,42 @@ export default {
|
|||||||
this.deleting = false;
|
this.deleting = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉起EA2播放器
|
||||||
|
* 将配置JSON转换为base64并通过examaware://协议打开
|
||||||
|
*/
|
||||||
|
openInEa2Player() {
|
||||||
|
try {
|
||||||
|
// 获取存储格式的JSON字符串
|
||||||
|
const configToSave = {
|
||||||
|
examName: this.localConfig.examName,
|
||||||
|
message: this.localConfig.message,
|
||||||
|
room: this.localConfig.room,
|
||||||
|
examInfos: this.localConfig.examInfos.map((info) => ({
|
||||||
|
name: info.name,
|
||||||
|
start: this.formatDisplayDateTime(info.start),
|
||||||
|
end: this.formatDisplayDateTime(info.end),
|
||||||
|
alertTime: parseInt(info.alertTime) || 15,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonString = JSON.stringify(configToSave);
|
||||||
|
|
||||||
|
// 转换为base64
|
||||||
|
const base64Data = btoa(unescape(encodeURIComponent(jsonString)));
|
||||||
|
|
||||||
|
// 构建examaware://协议URL
|
||||||
|
const ea2Url = `examaware://player?data=${base64Data}`;
|
||||||
|
|
||||||
|
// 尝试打开
|
||||||
|
window.location.href = ea2Url;
|
||||||
|
|
||||||
|
this.$message?.success('正在拉起 ExamAware2 播放器...');
|
||||||
|
} catch (err) {
|
||||||
|
this.error = '拉起播放器失败: ' + err.message;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
350
src/components/PwaInstallCard.vue
Normal file
350
src/components/PwaInstallCard.vue
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
<template>
|
||||||
|
<v-card
|
||||||
|
v-if="showCard"
|
||||||
|
class="mb-4"
|
||||||
|
color="surface-variant"
|
||||||
|
variant="tonal"
|
||||||
|
>
|
||||||
|
<div class="d-flex flex-no-wrap justify-space-between">
|
||||||
|
<div class="pe-4">
|
||||||
|
<v-card-title class="text-h6">
|
||||||
|
安装应用与授权
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-subtitle class="pb-1">
|
||||||
|
手动点选下方项目请求安装和权限,也可以直接关闭
|
||||||
|
</v-card-subtitle>
|
||||||
|
|
||||||
|
<v-card-text class="pt-0 pb-1">
|
||||||
|
<v-list density="comfortable" lines="two">
|
||||||
|
<v-list-item
|
||||||
|
v-for="item in chipList"
|
||||||
|
:key="item.key"
|
||||||
|
:disabled="isRequesting"
|
||||||
|
@click="() => handleSingleRequest(item.key)"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<v-avatar :color="chipColors[item.status]" size="32" variant="tonal">
|
||||||
|
<v-icon :icon="statusIcons[item.status]"></v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-list-item-title>{{ item.label }}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
|
||||||
|
|
||||||
|
<template #append>
|
||||||
|
<v-chip :color="chipColors[item.status]" size="small" variant="tonal" class="me-2">
|
||||||
|
{{ statusText[item.status] }}
|
||||||
|
</v-chip>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
icon="mdi-information"
|
||||||
|
size="small"
|
||||||
|
:disabled="isRequesting"
|
||||||
|
@click.stop="() => openHelp(item.key)"
|
||||||
|
></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
class="ms-2"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="dismiss"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="ms-2"
|
||||||
|
variant="elevated"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
:prepend-icon="isRequesting ? 'mdi-timer-sand' : 'mdi-shield-check'"
|
||||||
|
:disabled="!hasPendingRequests || isRequesting"
|
||||||
|
@click="handleRequest"
|
||||||
|
>
|
||||||
|
{{ isRequesting ? "处理中" : "一次处理全部" }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-avatar
|
||||||
|
class="ma-3"
|
||||||
|
size="100"
|
||||||
|
rounded="0"
|
||||||
|
>
|
||||||
|
<v-icon icon="mdi-monitor-cellphone" size="80"></v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-dialog v-model="helpDialog" max-width="520">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h6">{{ helpContent.title }}</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<p class="mb-3">{{ helpContent.message }}</p>
|
||||||
|
<v-list density="comfortable">
|
||||||
|
<v-list-item
|
||||||
|
v-for="(link, index) in helpContent.links"
|
||||||
|
:key="index"
|
||||||
|
:href="link.href"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<v-list-item-title>{{ link.text }}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>{{ link.desc }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn variant="text" @click="helpDialog = false">我知道了</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
import { getSetting, setSetting, requestNotificationPermission, requestPersistentStorage } from "@/utils/settings";
|
||||||
|
|
||||||
|
const showCard = ref(false);
|
||||||
|
const isRequesting = ref(false);
|
||||||
|
const helpDialog = ref(false);
|
||||||
|
const helpContent = reactive({
|
||||||
|
title: "",
|
||||||
|
message: "",
|
||||||
|
links: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusText = {
|
||||||
|
pending: "待授权",
|
||||||
|
granted: "已完成",
|
||||||
|
denied: "已拒绝",
|
||||||
|
unavailable: "不可用",
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusIcons = {
|
||||||
|
pending: "mdi-progress-clock",
|
||||||
|
granted: "mdi-check-circle",
|
||||||
|
denied: "mdi-close-circle",
|
||||||
|
unavailable: "mdi-help-circle",
|
||||||
|
};
|
||||||
|
|
||||||
|
const chipColors = {
|
||||||
|
pending: "primary",
|
||||||
|
granted: "success",
|
||||||
|
denied: "error",
|
||||||
|
unavailable: "surface-variant",
|
||||||
|
};
|
||||||
|
|
||||||
|
const permissionStates = reactive({
|
||||||
|
pwa: { label: "安装应用", description: "将网站安装为独立应用,便于快速启动", status: "pending" },
|
||||||
|
notification: { label: "通知权限", description: "允许接收作业、考试等通知提醒", status: "pending" },
|
||||||
|
storage: { label: "离线存储", description: "启用持久化存储以获得更稳健的离线体验", status: "pending" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const chipList = computed(() => [
|
||||||
|
{ key: "pwa", ...permissionStates.pwa },
|
||||||
|
{ key: "notification", ...permissionStates.notification },
|
||||||
|
{ key: "storage", ...permissionStates.storage },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hasPendingRequests = computed(() => chipList.value.some((item) => item.status === "pending"));
|
||||||
|
|
||||||
|
const helpLinks = {
|
||||||
|
pwa: [
|
||||||
|
{
|
||||||
|
text: "MDN - 安装 PWA 指南",
|
||||||
|
desc: "检查浏览器是否支持并手动触发安装",
|
||||||
|
href: "https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps/Guides/Installing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Microsoft Edge - PWA 体验",
|
||||||
|
desc: "Edge 浏览器安装与 UX 说明",
|
||||||
|
href: "https://learn.microsoft.com/zh-cn/microsoft-edge/progressive-web-apps/ux",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
notification: [
|
||||||
|
{
|
||||||
|
text: "MDN - 通知权限与用法",
|
||||||
|
desc: "浏览器通知权限的工作方式与调试",
|
||||||
|
href: "https://developer.mozilla.org/zh-CN/docs/Web/API/notification",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
storage: [
|
||||||
|
{
|
||||||
|
text: "MDN - Storage 持久化说明",
|
||||||
|
desc: "了解持久化存储的可用性与申请方式",
|
||||||
|
href: "https://developer.mozilla.org/zh-CN/docs/Web/API/StorageManager/persist",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let displayModeMedia;
|
||||||
|
|
||||||
|
const refreshStates = async () => {
|
||||||
|
const hideCard = getSetting("pwa.hideInstallCard");
|
||||||
|
if (hideCard) {
|
||||||
|
showCard.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
|
window.navigator.standalone === true;
|
||||||
|
|
||||||
|
if (isStandalone) {
|
||||||
|
permissionStates.pwa.status = "granted";
|
||||||
|
} else if (window.deferredPwaPrompt) {
|
||||||
|
permissionStates.pwa.status = "pending";
|
||||||
|
} else {
|
||||||
|
// 仍标记为待处理,允许点击后给出指导
|
||||||
|
permissionStates.pwa.status = "pending";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Notification === "undefined") {
|
||||||
|
permissionStates.notification.status = "unavailable";
|
||||||
|
} else {
|
||||||
|
const current = Notification.permission;
|
||||||
|
permissionStates.notification.status = current === "granted"
|
||||||
|
? "granted"
|
||||||
|
: current === "denied"
|
||||||
|
? "denied"
|
||||||
|
: "pending";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigator.storage?.persisted) {
|
||||||
|
const persisted = await navigator.storage.persisted();
|
||||||
|
permissionStates.storage.status = persisted ? "granted" : "pending";
|
||||||
|
} else {
|
||||||
|
permissionStates.storage.status = "unavailable";
|
||||||
|
}
|
||||||
|
|
||||||
|
const stillNeedAction = chipList.value.some((item) => item.status !== "granted");
|
||||||
|
showCard.value = stillNeedAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestPwaInstall = async () => {
|
||||||
|
const promptEvent = window.deferredPwaPrompt;
|
||||||
|
if (!promptEvent) {
|
||||||
|
permissionStates.pwa.status = "pending";
|
||||||
|
openHelp("pwa", "浏览器没有提供安装提示,可按文档手动安装。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promptEvent.prompt();
|
||||||
|
const { outcome } = await promptEvent.userChoice;
|
||||||
|
|
||||||
|
permissionStates.pwa.status = outcome === "accepted" ? "granted" : "denied";
|
||||||
|
if (outcome !== "accepted") {
|
||||||
|
openHelp("pwa", "如果未出现安装弹窗,或被拒绝,请按说明手动安装。");
|
||||||
|
}
|
||||||
|
window.deferredPwaPrompt = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestNotification = async () => {
|
||||||
|
if (typeof Notification === "undefined") {
|
||||||
|
permissionStates.notification.status = "unavailable";
|
||||||
|
openHelp("notification", "当前环境不支持通知 API,可查看说明手动开启或更换浏览器。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const granted = await requestNotificationPermission();
|
||||||
|
permissionStates.notification.status = granted ? "granted" : "denied";
|
||||||
|
if (!granted) {
|
||||||
|
openHelp("notification", "通知请求未被授予,请按说明检查浏览器或系统设置。");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestStorage = async () => {
|
||||||
|
if (!navigator.storage?.persist) {
|
||||||
|
permissionStates.storage.status = "unavailable";
|
||||||
|
openHelp("storage", "当前浏览器不支持持久化存储,可查看说明或更换浏览器。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const persisted = await requestPersistentStorage();
|
||||||
|
permissionStates.storage.status = persisted ? "granted" : "denied";
|
||||||
|
if (!persisted) {
|
||||||
|
openHelp("storage", "未能启用持久化存储,可按说明检查浏览器或系统设置。");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRequest = async () => {
|
||||||
|
if (!hasPendingRequests.value || isRequesting.value) return;
|
||||||
|
isRequesting.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await requestPwaInstall();
|
||||||
|
await requestNotification();
|
||||||
|
await requestStorage();
|
||||||
|
} finally {
|
||||||
|
isRequesting.value = false;
|
||||||
|
await refreshStates();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSingleRequest = async (key) => {
|
||||||
|
if (isRequesting.value) return;
|
||||||
|
isRequesting.value = true;
|
||||||
|
try {
|
||||||
|
if (key === "pwa") {
|
||||||
|
await requestPwaInstall();
|
||||||
|
} else if (key === "notification") {
|
||||||
|
await requestNotification();
|
||||||
|
} else if (key === "storage") {
|
||||||
|
await requestStorage();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isRequesting.value = false;
|
||||||
|
await refreshStates();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openHelp = (key, message = "") => {
|
||||||
|
if (key === "pwa") {
|
||||||
|
helpContent.title = "如何安装为应用";
|
||||||
|
} else if (key === "notification") {
|
||||||
|
helpContent.title = "如何开启通知";
|
||||||
|
} else {
|
||||||
|
helpContent.title = "如何启用离线存储";
|
||||||
|
}
|
||||||
|
|
||||||
|
helpContent.message = message || "查看以下步骤获取更多说明。";
|
||||||
|
helpContent.links = helpLinks[key] || [];
|
||||||
|
helpDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dismiss = () => {
|
||||||
|
setSetting("pwa.hideInstallCard", true);
|
||||||
|
showCard.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPromptReady = () => {
|
||||||
|
if (permissionStates.pwa.status !== "granted") {
|
||||||
|
permissionStates.pwa.status = "pending";
|
||||||
|
}
|
||||||
|
refreshStates();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDisplayModeChange = () => {
|
||||||
|
refreshStates();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
refreshStates();
|
||||||
|
window.addEventListener('pwa-prompt-ready', onPromptReady);
|
||||||
|
displayModeMedia = window.matchMedia('(display-mode: standalone)');
|
||||||
|
displayModeMedia.addEventListener('change', onDisplayModeChange);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('pwa-prompt-ready', onPromptReady);
|
||||||
|
if (displayModeMedia) {
|
||||||
|
displayModeMedia.removeEventListener('change', onDisplayModeChange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -1,216 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-alert
|
|
||||||
v-if="error"
|
|
||||||
border="start"
|
|
||||||
class="mb-4"
|
|
||||||
closable
|
|
||||||
type="error"
|
|
||||||
variant="tonal"
|
|
||||||
@click:close="error = ''"
|
|
||||||
>
|
|
||||||
{{ error }}
|
|
||||||
</v-alert>
|
|
||||||
|
|
||||||
<v-skeleton-loader v-if="loading" type="article"/>
|
|
||||||
|
|
||||||
<div v-else-if="!config">
|
|
||||||
<v-alert border="start" type="warning" variant="tonal">
|
|
||||||
缺少配置,请通过 URL 参数 id 或 url 传入配置。
|
|
||||||
</v-alert>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<div ref="playerRef" class="player">
|
|
||||||
<ExamPlayer
|
|
||||||
v-model:room-number="roomNumberLocal"
|
|
||||||
:config="playerConfigObj"
|
|
||||||
:exam-config="config"
|
|
||||||
:show-action-bar="true"
|
|
||||||
:time-sync-status="'电脑时间'"
|
|
||||||
@exit="exit()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {ref, computed, onMounted, watch} from "vue";
|
|
||||||
import {useRoute, useRouter} from "vue-router";
|
|
||||||
import dataProvider from "@/utils/dataProvider";
|
|
||||||
import {ExamPlayer} from "@examaware-cs/player";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "ExamPlayerPage",
|
|
||||||
components: {ExamPlayer},
|
|
||||||
setup() {
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const loading = ref(true);
|
|
||||||
const error = ref("");
|
|
||||||
const config = ref(null);
|
|
||||||
|
|
||||||
// 播放器配置:支持从查询参数读取考场号
|
|
||||||
const roomNumber = computed(() => {
|
|
||||||
return (route.query.roomNumber || route.query.room || "01") + "";
|
|
||||||
});
|
|
||||||
const roomNumberLocal = ref(roomNumber.value);
|
|
||||||
const playerConfigObj = computed(() => ({
|
|
||||||
roomNumber: roomNumberLocal.value,
|
|
||||||
timeSync: true,
|
|
||||||
refreshInterval: 1000,
|
|
||||||
fullscreen: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
async function loadConfig() {
|
|
||||||
loading.value = true;
|
|
||||||
error.value = "";
|
|
||||||
config.value = null;
|
|
||||||
try {
|
|
||||||
const url = route.query.url || route.query.configUrl;
|
|
||||||
const id = route.query.id;
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
const resp = await fetch(url);
|
|
||||||
if (!resp.ok) throw new Error("拉取配置失败: " + resp.status);
|
|
||||||
const json = await resp.json();
|
|
||||||
config.value = normalizeConfig(json);
|
|
||||||
} else if (id) {
|
|
||||||
const data = await dataProvider.loadData(`es_${id}`);
|
|
||||||
if (!data) throw new Error("未找到该配置");
|
|
||||||
config.value = normalizeConfig(data);
|
|
||||||
} else {
|
|
||||||
// 没有参数
|
|
||||||
config.value = null;
|
|
||||||
}
|
|
||||||
// ExamPlayer 组件会自行响应传入的 exam-config
|
|
||||||
} catch (e) {
|
|
||||||
error.value = e?.message || String(e);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function exit() {
|
|
||||||
// 返回上一页
|
|
||||||
router.push("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeConfig(raw) {
|
|
||||||
// 保障字段存在与正确类型
|
|
||||||
return {
|
|
||||||
examName: raw?.examName || "未命名考试",
|
|
||||||
message: raw?.message || "",
|
|
||||||
// ExamAware 需要 examInfos: { name, start, end, alertTime? }
|
|
||||||
examInfos: Array.isArray(raw?.examInfos)
|
|
||||||
? raw.examInfos.map((i) => ({
|
|
||||||
name: i?.name || "未命名科目",
|
|
||||||
start: i?.start || "",
|
|
||||||
end: i?.end || "",
|
|
||||||
alertTime: typeof i?.alertTime === "number" ? i.alertTime : 15,
|
|
||||||
}))
|
|
||||||
: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(loadConfig);
|
|
||||||
watch(
|
|
||||||
() => [
|
|
||||||
route.query.id,
|
|
||||||
route.query.url,
|
|
||||||
route.query.configUrl,
|
|
||||||
route.query.room,
|
|
||||||
route.query.roomNumber,
|
|
||||||
],
|
|
||||||
loadConfig
|
|
||||||
);
|
|
||||||
watch(roomNumber, (v) => {
|
|
||||||
roomNumberLocal.value = v;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
config,
|
|
||||||
roomNumberLocal,
|
|
||||||
playerConfigObj,
|
|
||||||
exit,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
/* 自定义字体定义 */
|
|
||||||
@font-face {
|
|
||||||
font-family: "TCloudNumber";
|
|
||||||
src: url("../assets/fonts/TCloudNumberVF.ttf") format("truetype-variations"),
|
|
||||||
url("../assets/fonts/TCloudNumberVF.ttf") format("truetype");
|
|
||||||
font-weight: 100 900;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#app {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: "MiSans", MiSans, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
||||||
Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans",
|
|
||||||
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 设置主题为深色 */
|
|
||||||
html {
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--td-bg-color-page, #0a0a0a);
|
|
||||||
color: var(--td-text-color-primary, #ffffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 通用样式 */
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 确保 TDesign 组件样式正常工作 */
|
|
||||||
.t-button {
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义滚动条样式 */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: var(--td-bg-color-container, #1a1a1a);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--td-bg-color-component, #333333);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--td-bg-color-component-hover, #444444);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -169,6 +169,8 @@
|
|||||||
@add-exam-card="showAddExamDialog = true"
|
@add-exam-card="showAddExamDialog = true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<pwa-install-card />
|
||||||
|
|
||||||
<!-- 推荐添加考试提示 -->
|
<!-- 推荐添加考试提示 -->
|
||||||
<v-alert
|
<v-alert
|
||||||
v-if="upcomingExams.length > 0 && !hasExamCard"
|
v-if="upcomingExams.length > 0 && !hasExamCard"
|
||||||
@ -441,6 +443,7 @@ import AttendanceSidebar from "@/components/attendance/AttendanceSidebar.vue";
|
|||||||
import AttendanceManagementDialog from "@/components/attendance/AttendanceManagementDialog.vue";
|
import AttendanceManagementDialog from "@/components/attendance/AttendanceManagementDialog.vue";
|
||||||
import HomeworkGrid from "@/components/home/HomeworkGrid.vue";
|
import HomeworkGrid from "@/components/home/HomeworkGrid.vue";
|
||||||
import HomeActions from "@/components/home/HomeActions.vue";
|
import HomeActions from "@/components/home/HomeActions.vue";
|
||||||
|
import PwaInstallCard from "@/components/PwaInstallCard.vue";
|
||||||
import ExamScheduleCard from "@/components/home/ExamScheduleCard.vue";
|
import ExamScheduleCard from "@/components/home/ExamScheduleCard.vue";
|
||||||
import ExamConfigEditor from "@/components/ExamConfigEditor.vue";
|
import ExamConfigEditor from "@/components/ExamConfigEditor.vue";
|
||||||
import HitokotoCard from "@/components/HitokotoCard.vue";
|
import HitokotoCard from "@/components/HitokotoCard.vue";
|
||||||
@ -485,6 +488,7 @@ export default {
|
|||||||
AttendanceManagementDialog,
|
AttendanceManagementDialog,
|
||||||
HomeworkGrid,
|
HomeworkGrid,
|
||||||
HomeActions,
|
HomeActions,
|
||||||
|
PwaInstallCard,
|
||||||
ExamScheduleCard,
|
ExamScheduleCard,
|
||||||
ExamConfigEditor,
|
ExamConfigEditor,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,4 +5,3 @@ export const useAppStore = defineStore('app', {
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
//
|
//
|
||||||
}),
|
}),
|
||||||
})
|
|
||||||
|
|||||||
@ -143,7 +143,7 @@ export default {
|
|||||||
* // 成功时返回:
|
* // 成功时返回:
|
||||||
* {
|
* {
|
||||||
* success: true,
|
* success: true,
|
||||||
* url: "https://kv.wuyuan.dev/device-uuid-123/exam_configs?token=abc123", // 私有访问时包含token
|
* url: "https://kv-service.houlang.cloud/device-uuid-123/exam_configs?token=abc123", // 私有访问时包含token
|
||||||
* migrated: true, // 是否成功迁移了本地数据
|
* migrated: true, // 是否成功迁移了本地数据
|
||||||
* configured: false // 是否自动配置了云端设置
|
* configured: false // 是否自动配置了云端设置
|
||||||
* }
|
* }
|
||||||
@ -151,7 +151,7 @@ export default {
|
|||||||
* // 公开访问时返回:
|
* // 公开访问时返回:
|
||||||
* {
|
* {
|
||||||
* success: true,
|
* success: true,
|
||||||
* url: "https://kv.wuyuan.dev/device-uuid-123/exam_configs", // 公开访问不包含token
|
* url: "https://kv-service.houlang.cloud/device-uuid-123/exam_configs", // 公开访问不包含token
|
||||||
* migrated: false,
|
* migrated: false,
|
||||||
* configured: true
|
* configured: true
|
||||||
* }
|
* }
|
||||||
@ -183,7 +183,7 @@ export default {
|
|||||||
if (autoConfigureCloud) {
|
if (autoConfigureCloud) {
|
||||||
// 使用classworksCloudDefaults配置
|
// 使用classworksCloudDefaults配置
|
||||||
const classworksCloudDefaults = {
|
const classworksCloudDefaults = {
|
||||||
"server.domain": import.meta.env.VITE_DEFAULT_KV_SERVER || "https://kv.wuyuan.dev",
|
"server.domain": import.meta.env.VITE_DEFAULT_KV_SERVER || "https://kv-service.houlang.cloud",
|
||||||
"server.siteKey": "",
|
"server.siteKey": "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// 请求通知权限
|
// 请求通知权限
|
||||||
async function requestNotificationPermission() {
|
async function requestNotificationPermission() {
|
||||||
if (Notification && Notification.requestPermission) {
|
if (typeof Notification !== "undefined" && Notification.requestPermission) {
|
||||||
const permission = await Notification.requestPermission();
|
const permission = await Notification.requestPermission();
|
||||||
if (permission === "granted") {
|
if (permission === "granted") {
|
||||||
console.log("通知权限已授予");
|
console.log("通知权限已授予");
|
||||||
@ -45,10 +45,7 @@ async function initializeStorage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在页面加载时初始化
|
// 初始化将由显式触发方调用,避免页面加载时立即请求权限
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
window.addEventListener("load", initializeStorage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置项定义
|
* 配置项定义
|
||||||
@ -68,7 +65,7 @@ const SETTINGS_STORAGE_KEY = "Classworks_settings";
|
|||||||
|
|
||||||
// 新增: Classworks云端存储的默认设置
|
// 新增: Classworks云端存储的默认设置
|
||||||
const classworksCloudDefaults = {
|
const classworksCloudDefaults = {
|
||||||
"server.domain": import.meta.env.VITE_DEFAULT_KV_SERVER || "https://kv.wuyuan.dev",
|
"server.domain": import.meta.env.VITE_DEFAULT_KV_SERVER || "https://kv-service.houlang.cloud",
|
||||||
//"server.domain": "http://localhost:3030",
|
//"server.domain": "http://localhost:3030",
|
||||||
"server.siteKey": "",
|
"server.siteKey": "",
|
||||||
};
|
};
|
||||||
@ -462,6 +459,14 @@ const settingsDefinitions = {
|
|||||||
description: "学号模式最小值",
|
description: "学号模式最小值",
|
||||||
icon: "mdi-numeric-negative-1",
|
icon: "mdi-numeric-negative-1",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// PWA 设置
|
||||||
|
"pwa.hideInstallCard": {
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
description: "不显示PWA安装卡片",
|
||||||
|
icon: "mdi-download-off",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -752,4 +757,6 @@ export {
|
|||||||
watchSettings,
|
watchSettings,
|
||||||
getSettingDefinition,
|
getSettingDefinition,
|
||||||
exportSettingsAsKeyValue,
|
exportSettingsAsKeyValue,
|
||||||
|
requestNotificationPermission,
|
||||||
|
requestPersistentStorage,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user