Compare commits
452 Commits
Author | SHA1 | Date |
---|---|---|
zouyf | 8fc1a22d2f | |
“zouyf” | c625fb5787 | |
朱浩 | 7f525d4a98 | |
zhengdegang | b6ab46e4a0 | |
zdg | dbf2874282 | |
zdg | c584e3fec9 | |
zhengdegang | 21323ffac0 | |
zdg | c5e1e00190 | |
zdg | 1d10dff4dc | |
朱浩 | b73a057a9b | |
朱浩 | d07944e4ad | |
zhangxuelin | 2463feb0b7 | |
zhangxuelin | d866f546bc | |
baigl | 991bbd179f | |
白了个白 | bf39850d2f | |
白了个白 | 0f51892d96 | |
zhengdegang | 2224de7144 | |
zdg | 14cf608c62 | |
zdg | eca310928e | |
zhangxuelin | 35e63e3587 | |
zhengdegang | 52f9a1493d | |
zdg | 0acde1952c | |
zdg | 719693bf63 | |
“zouyf” | 6da745ced8 | |
“zouyf” | f065762663 | |
zhangxuelin | 1999104ee4 | |
zhangxuelin | 65643279ce | |
“zouyf” | bde39cc2e6 | |
“zouyf” | 48cd120eed | |
baigl | 53ccb2236e | |
白了个白 | 00119e4a49 | |
白了个白 | 4bbe89ed31 | |
zhangxuelin | 49e16c458f | |
zhangxuelin | 5fb447d5e6 | |
yangws | 9aa17ed786 | |
小杨 | 2a7fbc3577 | |
白了个白 | ccbafea3d4 | |
白了个白 | 1f9111a89d | |
“zouyf” | be78520410 | |
“zouyf” | 4b935b9110 | |
zhangxuelin | d0cbb5cbcd | |
zhangxuelin | 5686669563 | |
zhangxuelin | eb7d6df355 | |
白了个白 | 1a3598d7c0 | |
白了个白 | 2fdd997c5e | |
lyc | 960ab581c3 | |
lyc | 0d6767170d | |
“zouyf” | 96e01ae711 | |
“zouyf” | d8c4876119 | |
zhangxuelin | ae839825d4 | |
zhangxuelin | ef670dee99 | |
朱浩 | c81f393715 | |
zhangxuelin | 06bb6284c4 | |
朱浩 | b5caa7ba80 | |
朱浩 | 53ff508635 | |
“zouyf” | d9a0f2f8c3 | |
“zouyf” | 89cba04b8c | |
zouyf | 9cce9c224c | |
“zouyf” | 171dc72ee7 | |
zhangxuelin | c2750e3da6 | |
zhangxuelin | 5550ca570c | |
“zouyf” | dd0160865b | |
“zouyf” | ff0dd3600c | |
lyc | 62a419356c | |
lyc | 4b2d13db8e | |
zhangxuelin | ab9bad467a | |
zhengdegang | f63bb69919 | |
zdg | b3ffa9bdaa | |
zdg | 5510021566 | |
lyc | 89e0b2f97c | |
lyc | 406e581182 | |
lyc | ea0e1c91cf | |
lyc | d4dce61555 | |
lyc | d6d5d13232 | |
zhengdegang | 33e38f8992 | |
zdg | 11968879d8 | |
zdg | db86299523 | |
zdg | c152a3d3ee | |
zhangxuelin | a366d5d4d6 | |
zhangxuelin | e0ec22c3ac | |
lyc | 9c8872579f | |
lyc | 4f32927b49 | |
zhangxuelin | e132f3927b | |
朱浩 | c290c170d3 | |
朱浩 | 3867a9603f | |
zhangxuelin | f732520d8d | |
lyc | 01fbb9d05c | |
lyc | 1334becb54 | |
lyc | c95508ed59 | |
“zouyf” | 63b8a0d5c6 | |
zhangxuelin | cf540d73df | |
baigl | 35bf246975 | |
白了个白 | e3c4706880 | |
白了个白 | c507331fb5 | |
lyc | abd7be0698 | |
lyc | 92e8aa64d7 | |
“zouyf” | f6b08169b6 | |
zhangxuelin | 83d3cd5df8 | |
朱浩 | b1407706a8 | |
朱浩 | 996b4006a2 | |
朱浩 | 4531533382 | |
朱浩 | a601a9f8dd | |
zhengdegang | 086836e016 | |
zdg | acb0231304 | |
baigl | 94e23c3d69 | |
白了个白 | ea5ed4b6ac | |
白了个白 | 6b45fa61dd | |
白了个白 | 7aa75794dc | |
朱浩 | a2c2c4eec7 | |
CYS | 4095390f45 | |
朱浩 | 1983dafbba | |
朱浩 | 6c7284383a | |
白了个白 | f7a3aa6e53 | |
baigl | 28f79f7ab0 | |
白了个白 | 1488d7cc03 | |
白了个白 | 045b44b833 | |
baigl | b47a3bb63a | |
白了个白 | 5e954bab0b | |
白了个白 | 7322056dad | |
zhengdegang | 18370129df | |
zdg | 0bf94af5a3 | |
zdg | 7b042fd001 | |
CYS | 3bf381f5c5 | |
baigl | 016e87277b | |
白了个白 | 502c772b46 | |
白了个白 | 57cd8ea45a | |
白了个白 | 8bbd01e2b2 | |
白了个白 | 622a71253f | |
“zouyf” | af60c5cc17 | |
“zouyf” | e67756192a | |
zouyf | a2591c60bc | |
“zouyf” | da8d6c4b32 | |
“zouyf” | 73d4ee03ba | |
“zouyf” | 8863e05a1a | |
朱浩 | 795c4d82c7 | |
朱浩 | 8e0b9f72d5 | |
白了个白 | 4ad59a8b4e | |
白了个白 | 3de2ddb094 | |
zouyf | 6dbd140f80 | |
“zouyf” | 7bca2fdc2d | |
lyc | ee96250af2 | |
lyc | 54be3cad4f | |
“zouyf” | debd5e1942 | |
zouyf | 90161e0396 | |
“zouyf” | 3a6c7dfa47 | |
zhengdegang | c0e82cd329 | |
zdg | e5667fac2d | |
zdg | f9bb7ed672 | |
朱浩 | 0ab34e7aa3 | |
朱浩 | 7751692061 | |
朱浩 | c69ae97a48 | |
朱浩 | 051dbdf4a2 | |
zhengdegang | d9b4b5c886 | |
zdg | bc195e1f51 | |
yangws | ba35677947 | |
lyc | 4b393ecec9 | |
lyc | 2565481306 | |
lyc | f708e2f741 | |
lyc | 94276db542 | |
zhengdegang | 6ef5d4575e | |
zdg | dfc10aebbe | |
“zouyf” | c8b8c21875 | |
“zouyf” | c8729a7008 | |
zdg | ff6fab0bcc | |
zdg | 45abab7a41 | |
朱浩 | 6128f267e1 | |
朱浩 | e0a406c497 | |
lyc | cebf864b82 | |
lyc | 0ed69394ef | |
lyc | 0b6039f9d8 | |
lyc | 627c70d0e1 | |
zhangxuelin | 64272467a2 | |
zhengdegang | 792ab3284f | |
zdg | 38a50d18bf | |
zdg | 6fa0aa6e5f | |
zdg | b0fca4ad9b | |
lyc | 9381785991 | |
lyc | 28561b5016 | |
lyc | 2e2ebbd47f | |
“zouyf” | 2bdac6e2ea | |
“zouyf” | 058509b29c | |
baigl | 2b5b365e2f | |
白了个白 | 90ac7a49c7 | |
白了个白 | 75ddbf6f26 | |
白了个白 | de9235751f | |
zhengdegang | c33c0d923e | |
zdg | 94fa1a2457 | |
zdg | abce344d42 | |
zdg | 0d38a12094 | |
白了个白 | dfd53637be | |
白了个白 | c01a29bf3f | |
zhangxuelin | 436bdfe7c8 | |
zhangxuelin | 2d0be935bf | |
zhangxuelin | d6dfe966c0 | |
zdg | cf7f985020 | |
zdg | 5d6c946e08 | |
“zouyf” | 9a2a55c62b | |
“zouyf” | 19efe185c5 | |
“zouyf” | 78ecf6ea55 | |
yangws | 55efc7f3e1 | |
小杨 | 122c5341e9 | |
朱浩 | c2a4f2c708 | |
yangws | eb18344b6b | |
小杨 | e080e39e1c | |
小杨 | 25f09cb4e0 | |
朱浩 | 3b67c34ccb | |
朱浩 | 187c222fe5 | |
lyc | a255b1af71 | |
lyc | 546c8ad72f | |
zhengdegang | e58cb334e4 | |
zdg | c298e1c0a2 | |
朱浩 | ed8051d0e0 | |
朱浩 | 628055fb58 | |
zhengdegang | 1698ce30dd | |
zdg | a973d296fe | |
zhangxuelin | bf4b857eb7 | |
zhangxuelin | 79839458f0 | |
zhangxuelin | d018a28200 | |
zhengdegang | 2214c5d805 | |
zdg | 9ad959dfb7 | |
lyc | 0b9fce21f3 | |
lyc | 37d0592dcc | |
yangws | a421ca94bc | |
小杨 | 6eda37c8c9 | |
lyc | 2a11173874 | |
lyc | 88869e9a8a | |
lyc | 08db70a3f2 | |
yangws | f9efb76282 | |
小杨 | ec3d728896 | |
yangws | cb0ab7f2e9 | |
小杨 | 43e9c9f44e | |
yangws | 809c639ce9 | |
小杨 | 243561dfd8 | |
zhengdegang | e5bc693f66 | |
zdg | 29888b0674 | |
zdg | 645f9ab120 | |
朱浩 | 74767b44df | |
朱浩 | ef4ec36bbe | |
zdg | c4a15e9c53 | |
yangws | 15b9f38810 | |
小杨 | a34883abbb | |
小杨 | 8f8016cb0b | |
lyc | a3079a87d0 | |
lyc | e43573bae2 | |
lyc | 83d885bcbe | |
lyc | b5669a97f1 | |
“zouyf” | cccf109499 | |
“zouyf” | 4a0e9b0b84 | |
lyc | be2afadd43 | |
朱浩 | a62dde1b93 | |
lyc | b4864bc109 | |
lyc | a379176496 | |
zdg | 6a9392d498 | |
zdg | e8ecc27441 | |
zhengdegang | c2d5f2e99a | |
lyc | eaa14b1666 | |
lyc | 99c26145d8 | |
zdg | e7990dc175 | |
zdg | 31de0da3c5 | |
朱浩 | 82548ce937 | |
朱浩 | aa3be39721 | |
baigl | c3b975fee9 | |
白了个白 | ea2a8ebfda | |
白了个白 | 308465adb4 | |
yangws | 469116701b | |
小杨 | 7861727e92 | |
zdg | 80b66ffb28 | |
zhangxuelin | c5e71e6e7b | |
zhangxuelin | c7ebc6de65 | |
zhangxuelin | b906a1e688 | |
baigl | f75ab621d2 | |
白了个白 | a711eb2745 | |
白了个白 | 363b284b7c | |
zdg | 75b5884210 | |
zdg | 03f160464c | |
baigl | dc4c0fd334 | |
白了个白 | fb3ffd5dda | |
白了个白 | 5771533039 | |
zhangxuelin | 75ea11e171 | |
zhangxuelin | 851a39897b | |
zhangxuelin | 69bfc68b28 | |
zhangxuelin | 590b318757 | |
CYS | 4421ee7fea | |
baigl | 25b6da7f26 | |
白了个白 | 892b4ee3b0 | |
zdg | 354e0b44c9 | |
zdg | 5e04a4c2c2 | |
zdg | ca93eb72ac | |
yangws | 3d8f7c2a3a | |
小杨 | 78cc5edd39 | |
朱浩 | 9f4e232d6f | |
朱浩 | afc9fe89d1 | |
朱浩 | 894ed269ce | |
zdg | b15fa29ffa | |
zhangxuelin | 64d2dbcabb | |
zhangxuelin | f4cb3a8916 | |
yangws | 69db6bd8b1 | |
小杨 | e522fc8ee5 | |
yangws | 5b50404d3c | |
小杨 | bbed835ffe | |
zdg | 9ef4812acb | |
zhangxuelin | d9bdd5424f | |
zhangxuelin | 979621b515 | |
zdg | d313f901a3 | |
zdg | 8990ffdc58 | |
zdg | 13b1a895a4 | |
朱浩 | 9c7aeb1a27 | |
lyc | 7545e68a62 | |
lyc | dad772713f | |
朱浩 | a9faeea3fd | |
朱浩 | 5493de7a61 | |
朱浩 | 02ab1ffa8e | |
朱浩 | 10e76add35 | |
lyc | c1325db9e7 | |
lyc | cbc12a3d43 | |
baigl | 44b341f7ac | |
白了个白 | c8dd19382e | |
zhangxuelin | f2faa882a8 | |
zhangxuelin | e4afb26e4e | |
zhangxuelin | d60d8822a6 | |
朱浩 | 90137bc3a2 | |
朱浩 | 82c61a3fa7 | |
朱浩 | 48d631a17c | |
zhangxuelin | 1835f76e56 | |
zhangxuelin | 432c1ff71d | |
朱浩 | 7f595c09a9 | |
朱浩 | c8e10d4fe1 | |
朱浩 | f916660156 | |
zhengdegang | e0107814ea | |
zdg | bb0aa82e82 | |
zdg | 8ed13a3146 | |
zhangxuelin | 82b70558c2 | |
zhangxuelin | bace41e12e | |
zhangxuelin | 04e204928d | |
zdg | 9e5609fbdd | |
lyc | 50aff2f158 | |
lyc | a1a4e11de7 | |
lyc | 20fe5be502 | |
lyc | 7e9ac2bf74 | |
lyc | 013669dd35 | |
yangws | 3ca20cd327 | |
小杨 | c159d6be1a | |
yangws | cc44a86437 | |
小杨 | e7c6ab9e8d | |
小杨 | 85d06f306a | |
朱浩 | 656a58693f | |
zouyf | 34cfcf5a6f | |
“zouyf” | 9603406a0b | |
“zouyf” | 10a7e73c64 | |
“zouyf” | 2c238b5706 | |
“zouyf” | b47feb4a3a | |
朱浩 | 772b196b31 | |
zhangxuelin | 91e9867b68 | |
zhangxuelin | 80ac4a6e1c | |
朱浩 | 757edb0f67 | |
“zouyf” | c79911cc99 | |
zhangxuelin | 1987783fda | |
“zouyf” | 755cfc615a | |
baigl | fa776d2a8c | |
白了个白 | 122487cf8b | |
白了个白 | b7f11c5338 | |
白了个白 | c78233a2f4 | |
“zouyf” | 2e81c706b1 | |
“zouyf” | 2360d95f1c | |
zdg | 4f68ae27b3 | |
zdg | 38c041465e | |
zhangxuelin | f035cf87ea | |
zhangxuelin | e56eb059be | |
yangws | ffd6d6fab9 | |
小杨 | 23e59531ea | |
小杨 | 9e021edc67 | |
zouyf | 238f08d9ac | |
“zouyf” | d90b7c695a | |
“zouyf” | f936e726c0 | |
“zouyf” | d07dd4455c | |
zouyf | b319aeb4b2 | |
“zouyf” | 23c397e18a | |
“zouyf” | 78858111bb | |
zhengdegang | 3c6ac1f77d | |
zdg | 97962d591f | |
zdg | 86154148c6 | |
zdg | 44092a21bf | |
“zouyf” | 728ce16c8b | |
小杨 | 5b1d921378 | |
lyc | f2637c7b15 | |
lyc | 93fa916084 | |
lyc | 24d6d5887f | |
朱浩 | 222b0f54f8 | |
朱浩 | e0a56b37ef | |
baigl | 045bc5e198 | |
白了个白 | 464f39dac8 | |
白了个白 | 8e72b50c68 | |
小杨 | 0aa09e9d9a | |
lyc | 97f55ca333 | |
lyc | cb5445f9a9 | |
lyc | 560e6f0e70 | |
“zouyf” | bbe2281781 | |
“zouyf” | 66881b0025 | |
baigl | 4010411c9f | |
“zouyf” | e43c9fd038 | |
白了个白 | da1c80406f | |
白了个白 | e3199b43de | |
朱浩 | a78497b263 | |
朱浩 | 65670af54f | |
zhengdegang | e5be55f12e | |
zdg | 209ab7fafc | |
朱浩 | 87dcd9d5c3 | |
zouyf | 1f96b9b09e | |
小杨 | 0e18d74bb9 | |
小杨 | 399c4b5461 | |
“zouyf” | 35c088c25a | |
“zouyf” | 028eb0f752 | |
zhengdegang | 9a68661fb0 | |
zdg | b28abdff50 | |
zdg | b428260703 | |
lyc | b5f824ceea | |
lyc | d3b3e3bcb5 | |
zhengdegang | dcabb80757 | |
zdg | 53f26d96d4 | |
zdg | 7d7b50fa3e | |
baigl | eae2171c70 | |
白了个白 | 5fe9359d64 | |
白了个白 | be9d33fcd3 | |
lyc | 09e3264ee7 | |
lyc | 7845d9bb1a | |
zhengdegang | 2fb6828154 | |
zdg | 03d1a683be | |
zdg | d542064ee3 | |
朱浩 | 9803c09c43 | |
zhengdegang | 57fdba4578 | |
zdg | 0e5d84fcd2 | |
朱浩 | 7811bbfe3e | |
zhengdegang | 7ddf71c044 | |
zdg | 91ceb712bf | |
zdg | a192640899 | |
zdg | 08a16929f7 | |
lyc | 42e8149443 | |
lyc | 8b429a4174 | |
白了个白 | 23795f2419 | |
白了个白 | ea204de407 | |
白了个白 | a31b8d7376 | |
小杨 | 8d03c927b9 | |
小杨 | edabc0336f | |
白了个白 | df474db4a5 | |
白了个白 | 328e623db7 | |
白了个白 | 8ad041e963 | |
zhengdegang | c22b360e0f | |
zdg | 3b851887ca | |
zdg | e6333e49f0 | |
CYS | cee81102da | |
lyc | 3ad4391e10 | |
“zouyf” | 7b59d78ce3 |
|
@ -16,4 +16,10 @@ VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktx
|
||||||
|
|
||||||
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'
|
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'
|
||||||
|
|
||||||
|
# websocket 地址
|
||||||
|
# VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
|
||||||
|
VITE_APP_WS_URL = 'wss://file.ysaix.com:7868'
|
||||||
|
# VITE_APP_WS_URL = 'ws://192.168.2.16:7865'
|
||||||
|
|
||||||
|
# 是否显示开发工具
|
||||||
VITE_SHOW_DEV_TOOLS = 'true'
|
VITE_SHOW_DEV_TOOLS = 'true'
|
||||||
|
|
|
@ -18,4 +18,8 @@ VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktx
|
||||||
|
|
||||||
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||||
|
|
||||||
|
# websocket 地址
|
||||||
|
VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
|
||||||
|
|
||||||
|
# 是否显示开发工具
|
||||||
VITE_SHOW_DEV_TOOLS = 'false'
|
VITE_SHOW_DEV_TOOLS = 'false'
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# 页面标题
|
||||||
|
VITE_APP_TITLE = 文枢课堂
|
||||||
|
|
||||||
|
# 生产环境配置
|
||||||
|
VITE_APP_ENV = 'production'
|
||||||
|
|
||||||
|
# AIx融合数字管理系统/生产环境
|
||||||
|
VITE_APP_BASE_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||||
|
|
||||||
|
VITE_APP_DOMAIN = 'prev.ysaix.com'
|
||||||
|
|
||||||
|
VITE_APP_UPLOAD_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||||
|
|
||||||
|
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||||
|
VITE_BUILD_COMPRESS = gzip
|
||||||
|
|
||||||
|
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||||
|
|
||||||
|
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||||
|
|
||||||
|
# websocket 地址
|
||||||
|
VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
|
||||||
|
|
||||||
|
# 是否显示开发工具
|
||||||
|
VITE_SHOW_DEV_TOOLS = 'false'
|
|
@ -0,0 +1,25 @@
|
||||||
|
# 页面标题
|
||||||
|
VITE_APP_TITLE = 实训教学
|
||||||
|
|
||||||
|
# 生产环境配置
|
||||||
|
VITE_APP_ENV = 'production'
|
||||||
|
|
||||||
|
# AIx融合数字管理系统/生产环境
|
||||||
|
VITE_APP_BASE_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||||
|
|
||||||
|
VITE_APP_DOMAIN = 'prev.ysaix.com'
|
||||||
|
|
||||||
|
VITE_APP_UPLOAD_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||||
|
|
||||||
|
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||||
|
VITE_BUILD_COMPRESS = gzip
|
||||||
|
|
||||||
|
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||||
|
|
||||||
|
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||||
|
|
||||||
|
# websocket 地址
|
||||||
|
VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
|
||||||
|
|
||||||
|
# 是否显示开发工具
|
||||||
|
VITE_SHOW_DEV_TOOLS = 'false'
|
|
@ -0,0 +1,54 @@
|
||||||
|
appId: com.electron.app.yc
|
||||||
|
productName: 文枢课堂
|
||||||
|
directories:
|
||||||
|
output: dist
|
||||||
|
buildResources: build
|
||||||
|
win:
|
||||||
|
executableName: 文枢课堂
|
||||||
|
icon: resources/yc-logo.png
|
||||||
|
files:
|
||||||
|
- '!**/.vscode/*'
|
||||||
|
- '!src/*'
|
||||||
|
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||||
|
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||||
|
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||||
|
asarUnpack:
|
||||||
|
- resources/**
|
||||||
|
nsis:
|
||||||
|
oneClick: false
|
||||||
|
allowToChangeInstallationDirectory: true
|
||||||
|
artifactName: ${name}-yc-${version}-setup.${ext}
|
||||||
|
shortcutName: ${productName}
|
||||||
|
uninstallDisplayName: ${productName}
|
||||||
|
createDesktopShortcut: always
|
||||||
|
mac:
|
||||||
|
entitlementsInherit: build/entitlements.mac.plist
|
||||||
|
extendInfo:
|
||||||
|
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||||
|
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||||
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
|
notarize: false
|
||||||
|
dmg:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
linux:
|
||||||
|
target:
|
||||||
|
- AppImage
|
||||||
|
- snap
|
||||||
|
- deb
|
||||||
|
maintainer: electronjs.org
|
||||||
|
category: Utility
|
||||||
|
appImage:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
npmRebuild: false
|
||||||
|
publish:
|
||||||
|
provider: generic
|
||||||
|
url: https://prev.ysaix.com:7868/src/assets/smarttalkyc/
|
||||||
|
electronDownload:
|
||||||
|
mirror: https://npmmirror.com/mirrors/electron/
|
||||||
|
# 额外依赖打包到输出目录
|
||||||
|
extraFiles:
|
||||||
|
- from: ./node_modules/im_electron_sdk/lib/
|
||||||
|
to: ./resources
|
||||||
|
filter:
|
||||||
|
- '**/*'
|
|
@ -0,0 +1,54 @@
|
||||||
|
appId: com.electron.app.yc2
|
||||||
|
productName: 实训教学
|
||||||
|
directories:
|
||||||
|
output: dist
|
||||||
|
buildResources: build
|
||||||
|
win:
|
||||||
|
executableName: 实训教学
|
||||||
|
icon: resources/yc-logo.png
|
||||||
|
files:
|
||||||
|
- '!**/.vscode/*'
|
||||||
|
- '!src/*'
|
||||||
|
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||||
|
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||||
|
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||||
|
asarUnpack:
|
||||||
|
- resources/**
|
||||||
|
nsis:
|
||||||
|
oneClick: false
|
||||||
|
allowToChangeInstallationDirectory: true
|
||||||
|
artifactName: ${name}-ycsx-${version}-setup.${ext}
|
||||||
|
shortcutName: ${productName}
|
||||||
|
uninstallDisplayName: ${productName}
|
||||||
|
createDesktopShortcut: always
|
||||||
|
mac:
|
||||||
|
entitlementsInherit: build/entitlements.mac.plist
|
||||||
|
extendInfo:
|
||||||
|
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||||
|
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||||
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
|
notarize: false
|
||||||
|
dmg:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
linux:
|
||||||
|
target:
|
||||||
|
- AppImage
|
||||||
|
- snap
|
||||||
|
- deb
|
||||||
|
maintainer: electronjs.org
|
||||||
|
category: Utility
|
||||||
|
appImage:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
npmRebuild: false
|
||||||
|
publish:
|
||||||
|
provider: generic
|
||||||
|
url: https://prev.ysaix.com:7868/src/assets/smarttalkycsx/
|
||||||
|
electronDownload:
|
||||||
|
mirror: https://npmmirror.com/mirrors/electron/
|
||||||
|
# 额外依赖打包到输出目录
|
||||||
|
extraFiles:
|
||||||
|
- from: ./node_modules/im_electron_sdk/lib/
|
||||||
|
to: ./resources
|
||||||
|
filter:
|
||||||
|
- '**/*'
|
|
@ -33,8 +33,9 @@ export default defineConfig({
|
||||||
proxy: {
|
proxy: {
|
||||||
'/dev-api': {
|
'/dev-api': {
|
||||||
target: 'http://27.128.240.72:7865',
|
target: 'http://27.128.240.72:7865',
|
||||||
|
// target: 'https://prev.ysaix.com:7868/prod-api/',
|
||||||
// target: 'http://36.134.181.164:7863',
|
// target: 'http://36.134.181.164:7863',
|
||||||
// target: 'http://192.168.2.52:7863',
|
// target: 'http://192.168.0.102:7865',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
declare module '*.vue' {
|
||||||
|
import { ComponentOptions } from 'vue'
|
||||||
|
const componentOptions: ComponentOptions
|
||||||
|
export default componentOptions
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "aix-win-ws",
|
"name": "aix-win-ws",
|
||||||
"version": "2.5.4",
|
"version": "2.5.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "上海交大重庆人工智能研究院",
|
"author": "上海交大重庆人工智能研究院",
|
||||||
|
@ -16,6 +16,8 @@
|
||||||
"build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
|
"build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
|
||||||
"build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml",
|
"build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml",
|
||||||
"build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml",
|
"build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml",
|
||||||
|
"build:yc": "electron-vite build --mode yc && electron-builder --win --config ./electron-builder-yc.yml",
|
||||||
|
"build:yc2": "electron-vite build --mode yc2 && electron-builder --win --config ./electron-builder-yc2.yml",
|
||||||
"build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml",
|
"build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml",
|
||||||
"build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml",
|
"build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml",
|
||||||
"build:linux": "npm run build && electron-builder --linux"
|
"build:linux": "npm run build && electron-builder --linux"
|
||||||
|
@ -57,6 +59,7 @@
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"hfmath": "^0.0.2",
|
"hfmath": "^0.0.2",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"im_electron_sdk": "^8.0.5904",
|
"im_electron_sdk": "^8.0.5904",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
|
@ -91,6 +94,8 @@
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
"tinymce": "6.8.3",
|
"tinymce": "6.8.3",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
"v-viewer": "^3.0.11",
|
||||||
|
"viewerjs": "^1.11.7",
|
||||||
"vite-plugin-electron": "^0.28.8",
|
"vite-plugin-electron": "^0.28.8",
|
||||||
"vue": "^3.4.34",
|
"vue": "^3.4.34",
|
||||||
"vue-cropper": "1.0.3",
|
"vue-cropper": "1.0.3",
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
|
@ -13,6 +13,34 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
||||||
const appTempFilePath = userDataPath + '\\tempFile\\'
|
const appTempFilePath = userDataPath + '\\tempFile\\'
|
||||||
let Spark = new SparkMD5.ArrayBuffer()
|
let Spark = new SparkMD5.ArrayBuffer()
|
||||||
|
|
||||||
|
ipcMain.on('remove-local-file-list', (e, list) => {
|
||||||
|
let filePath = appRootFilePath
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
let item = list[i];
|
||||||
|
if (!isAccess(filePath + item.fileNewName)) {
|
||||||
|
e.reply('remove-local-file-list-not', item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(filePath + item.fileNewName);
|
||||||
|
console.log(`${filePath} 已成功删除`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`删除文件时出错:`, err);
|
||||||
|
e.reply('remove-local-file-list-error', item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.reply('remove-local-file-list-reply')
|
||||||
|
})
|
||||||
|
|
||||||
|
const isAccess = (filePath) => {
|
||||||
|
try {
|
||||||
|
fs.accessSync(filePath);
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ipcMain.on('upload-file-change', (e, { id, fileNewName, cookie, fileType }) => {
|
ipcMain.on('upload-file-change', (e, { id, fileNewName, cookie, fileType }) => {
|
||||||
let filePath = appRootFilePath + fileNewName
|
let filePath = appRootFilePath + fileNewName
|
||||||
//执行更新,上传文件
|
//执行更新,上传文件
|
||||||
|
@ -58,7 +86,6 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
||||||
}
|
}
|
||||||
//倒数十秒提交更改,十秒之内有继续修改则重置倒数
|
//倒数十秒提交更改,十秒之内有继续修改则重置倒数
|
||||||
uploadId = setTimeout(() => {
|
uploadId = setTimeout(() => {
|
||||||
console.log(223)
|
|
||||||
//执行更新,上传文件
|
//执行更新,上传文件
|
||||||
let formData = new FormData()
|
let formData = new FormData()
|
||||||
formData.append('id', id)
|
formData.append('id', id)
|
||||||
|
@ -89,8 +116,12 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
||||||
|
|
||||||
function getFileMsg(path) {
|
function getFileMsg(path) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
const stats = fs.statSync(path)
|
const stats = fs.statSync(path)
|
||||||
return resolve(stats.mtime.getTime())
|
resolve(stats.mtime.getTime())
|
||||||
|
}catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,17 +334,20 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
||||||
},
|
},
|
||||||
onDownloadCancelled: async () => {
|
onDownloadCancelled: async () => {
|
||||||
console.log("取消")
|
console.log("取消")
|
||||||
reject({type:"取消了下载"})
|
resolve({type:"取消了下载"})
|
||||||
},
|
},
|
||||||
onDownloadInterrupted: async () => {
|
onDownloadInterrupted: async () => {
|
||||||
console.log('中断')
|
console.log('中断')
|
||||||
reject({type:"下载被中断"})
|
resolve({type:"下载被中断"})
|
||||||
},
|
},
|
||||||
onError: (err, data) => {
|
onError: (err, data) => {
|
||||||
console.log(err.toString())
|
console.log(err.toString())
|
||||||
reject({type:"下载出错",err})
|
resolve({type:"下载出错",err})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}).catch(err=>{
|
||||||
|
console.log(err)
|
||||||
|
resolve({type:"下载出错",err})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -360,6 +394,7 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
console.log(error)
|
||||||
e.reply('download-file-default' + fileName, false)
|
e.reply('download-file-default' + fileName, false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Logger from './logger' // 日志封装
|
||||||
import chat from './chat' // chat封装
|
import chat from './chat' // chat封装
|
||||||
import Store from './store' // Store封装
|
import Store from './store' // Store封装
|
||||||
import updateInit from './update'
|
import updateInit from './update'
|
||||||
|
|
||||||
// 代理 electron/remote
|
// 代理 electron/remote
|
||||||
// 第一步:引入remote
|
// 第一步:引入remote
|
||||||
import remote from '@electron/remote/main'
|
import remote from '@electron/remote/main'
|
||||||
|
@ -41,19 +42,19 @@ if(!gotTheLock){
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
let logoIco = import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?'../../resources/yc-logo.png':'../../resources/logo2.ico'
|
||||||
//登录窗口
|
//登录窗口
|
||||||
function createLoginWindow() {
|
function createLoginWindow() {
|
||||||
if (loginWindow) return
|
if (loginWindow) return
|
||||||
loginWindow = new BrowserWindow({
|
loginWindow = new BrowserWindow({
|
||||||
width: 888,
|
width: import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?1060:888,
|
||||||
height: 520,
|
height: 520,
|
||||||
show: false,
|
show: false,
|
||||||
frame: false,
|
frame: false,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
icon: join(__dirname, '../../resources/logo2.ico'),
|
icon: join(__dirname, logoIco),
|
||||||
...(process.platform === 'linux' ? { icon } : {}),
|
...(process.platform === 'linux' ? { icon } : {}),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
defaultEncoding: 'utf-8',
|
defaultEncoding: 'utf-8',
|
||||||
|
@ -95,7 +96,7 @@ function createMainWindow() {
|
||||||
frame: false, // 无边框
|
frame: false, // 无边框
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
icon: join(__dirname, '../../resources/logo2.ico'),
|
icon: join(__dirname, logoIco),
|
||||||
...(process.platform === 'linux' ? { icon } : {}),
|
...(process.platform === 'linux' ? { icon } : {}),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
defaultEncoding: 'utf-8',
|
defaultEncoding: 'utf-8',
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
||||||
/> -->
|
/> -->
|
||||||
<meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; default-src 'self'; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net;" />
|
<meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; frame-src 'self' *; default-src 'self' https://wzyzoss.eos-chongqing-3.cmecloud.cn/; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net;" />
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -13097,15 +13097,15 @@ const PDFViewerApplication = {
|
||||||
}
|
}
|
||||||
if (isValidSpreadMode(spread)) {
|
if (isValidSpreadMode(spread)) {
|
||||||
//默认双页
|
//默认双页
|
||||||
// this.pdfViewer.spreadMode = spread;
|
this.pdfViewer.spreadMode = spread;
|
||||||
this.pdfViewer.spreadMode = 1;
|
// this.pdfViewer.spreadMode = 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.isInitialViewSet = true;
|
this.isInitialViewSet = true;
|
||||||
this.pdfSidebar?.setInitialView(sidebarView);
|
this.pdfSidebar?.setInitialView(sidebarView);
|
||||||
//默认双页
|
//默认双页
|
||||||
// setViewerModes(scrollMode, spreadMode);
|
setViewerModes(scrollMode, spreadMode);
|
||||||
setViewerModes(scrollMode, 1);
|
// setViewerModes(scrollMode, 1);
|
||||||
if (this.initialBookmark) {
|
if (this.initialBookmark) {
|
||||||
setRotation(this.initialRotation);
|
setRotation(this.initialRotation);
|
||||||
delete this.initialRotation;
|
delete this.initialRotation;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import 'animate.css'
|
||||||
import { ref, onMounted, watch, onBeforeMount } from 'vue'
|
import { ref, onMounted, watch, onBeforeMount } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from './store'
|
import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from './store'
|
||||||
|
@ -27,8 +28,9 @@ import msgUtils from '@/plugins/modal' // 消息工具
|
||||||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||||
import { PPTApi } from './api'
|
import { PPTApi } from './api'
|
||||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||||
import './api/watcher' // 监听
|
import watcher from './api/watcher' // 监听
|
||||||
|
import emitter from '@/utils/mitt' //mitt 事件总线
|
||||||
|
watcher() // 监听启动
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const _isPC = isPC()
|
const _isPC = isPC()
|
||||||
|
|
||||||
|
@ -38,8 +40,8 @@ const slidesStore = useSlidesStore()
|
||||||
const { databaseId } = storeToRefs(mainStore)
|
const { databaseId } = storeToRefs(mainStore)
|
||||||
const { screening } = storeToRefs(useScreenStore())
|
const { screening } = storeToRefs(useScreenStore())
|
||||||
|
|
||||||
if (import.meta.env.MODE !== 'development') {
|
if (import.meta.env.MODE === 'development') {
|
||||||
window.onbeforeunload = () => false
|
// window.onbeforeunload = () => false
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
@ -60,14 +62,6 @@ window.addEventListener('unload', () => {
|
||||||
const newDiscardedDB = JSON.stringify(discardedDBList)
|
const newDiscardedDB = JSON.stringify(discardedDBList)
|
||||||
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
|
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
|
||||||
})
|
})
|
||||||
/** 接口类型 */
|
|
||||||
interface Result {
|
|
||||||
code?: number,
|
|
||||||
msg?: string,
|
|
||||||
data?: any
|
|
||||||
rows?: Array<any>,
|
|
||||||
total?: number
|
|
||||||
}
|
|
||||||
// 获取参数
|
// 获取参数
|
||||||
const initLoad: Function = () => {
|
const initLoad: Function = () => {
|
||||||
// 获取缓存的ppt 资源数据
|
// 获取缓存的ppt 资源数据
|
||||||
|
@ -89,5 +83,8 @@ const initLoad: Function = () => {
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
svg, canvas, img, audio, video, iframe {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* 统一处理消息 发送 避免找不到
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ChatWs from '@/plugins/socket' // 聊天socket
|
||||||
|
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||||
|
import { useClasscourseStore } from '../store'
|
||||||
|
import * as API_classcourse from '@/api/teaching/classcourse' // 后端api
|
||||||
|
import { MsgEnum } from './types'
|
||||||
|
// import msgUtils from '@/plugins/modal' // 消息工具
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
|
||||||
|
const courseId = classcourse?.id // 课堂id
|
||||||
|
const timgroupid = classcourse?.timgroupid // 群组id
|
||||||
|
const classcourseStore = useClasscourseStore() // 课堂信息-状态管理
|
||||||
|
// 上课状态才-初始化socket
|
||||||
|
if (!ChatWs.ws && !!courseId) ChatWs.init()
|
||||||
|
// 开课消息
|
||||||
|
const startCourse = async() => {
|
||||||
|
// await API_classcourse.updateClasscourse({ id: classcourse.id, status: 'open' })
|
||||||
|
ChatWs.sendMsg('open', {id: courseId})
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
// 下课消息
|
||||||
|
const exitCourse = async() => {
|
||||||
|
if(!timgroupid) throw new Error('未获取到群组ID')
|
||||||
|
await API_classcourse.updateClasscourse({ id: courseId, status: 'closed' })
|
||||||
|
return ChatWs.closedCourse(timgroupid)
|
||||||
|
}
|
||||||
|
// 翻页消息
|
||||||
|
const slideFlapping = (msg:object) => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const isWs = !!ChatWs.ws && ChatWs.ws.readyState === 1 // 是否有socket连接
|
||||||
|
if(!timgroupid) return reject('未获取到群组ID')
|
||||||
|
else if(!isWs) return reject('信异常,请重试!')
|
||||||
|
const {current: paging, animationSteps: cartoonTimes} = msg || {}
|
||||||
|
const head = MsgEnum.HEADS.MSG_slideFlapping
|
||||||
|
ChatWs.sendMsg(head, msg) // 发送消息
|
||||||
|
API_classcourse.setPaging({ id: courseId, paging, cartoonTimes})
|
||||||
|
// 更新本地缓存
|
||||||
|
sessionStore.set('curr.classcourse.paging', paging)
|
||||||
|
sessionStore.set('curr.classcourse.cartoonTimes', cartoonTimes)
|
||||||
|
classcourseStore.classcourse.paging = paging
|
||||||
|
classcourseStore.classcourse.cartoonTimes = cartoonTimes
|
||||||
|
return resolve(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
groupid: timgroupid,
|
||||||
|
classcourse,
|
||||||
|
exitCourse,
|
||||||
|
slideFlapping,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
* @author zdg
|
||||||
|
* @description 上课相关内容
|
||||||
|
*/
|
||||||
|
// import type { Classcourse } from './types'
|
||||||
|
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||||
|
import * as useStore from '../store' // pptist-状态管理
|
||||||
|
import ChatWs from '@/plugins/socket' // 聊天socket
|
||||||
|
import msgUtils from '@/plugins/modal' // 消息工具
|
||||||
|
import emitter from '@/utils/mitt' //mitt 事件总线
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
|
||||||
|
const slidesStore = useStore.useSlidesStore() // 幻灯片-状态管理
|
||||||
|
const screenStore = useStore.useScreenStore() // 全屏-状态管理
|
||||||
|
const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理
|
||||||
|
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
|
||||||
|
const isPublic = sessionStore.get('curr.isPublic') // 是否公屏开课
|
||||||
|
|
||||||
|
export class Classcourse {
|
||||||
|
msgObj:ElMessageBox = null // 提示消息对象
|
||||||
|
classcourse:any = null // 课堂信息
|
||||||
|
id: number|string = null // 课堂id
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.load()
|
||||||
|
}
|
||||||
|
// 延时
|
||||||
|
sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
/**
|
||||||
|
* @description 加载
|
||||||
|
*/
|
||||||
|
async load() {
|
||||||
|
console.log('classcourse-load', classcourse)
|
||||||
|
// 打开全屏
|
||||||
|
const isCourse = !!classcourse
|
||||||
|
screenStore.setScreening(isCourse)
|
||||||
|
// 如果课堂信息有值,则连接socket
|
||||||
|
if (isCourse) {
|
||||||
|
// 连接socket
|
||||||
|
ChatWs.id = classcourse.timgroupid // 群组id
|
||||||
|
if (!ChatWs.ws) {
|
||||||
|
ChatWs.init().then(_ => {
|
||||||
|
isPublic && ChatWs.sendMsg('open', {id: classcourse.id})
|
||||||
|
// isPublic && console.log('socket-开课消息-已发送')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.classcourse = classcourse // 课堂信息
|
||||||
|
this.id = classcourse.id // 课堂id
|
||||||
|
// 如果课堂信息有paging,则更新当前页码
|
||||||
|
const { paging, cartoonTimes } = classcourse
|
||||||
|
const isPaging = !!paging || paging === 0
|
||||||
|
// 如果课堂信息有paging,则更新动画播放状态
|
||||||
|
const isAnim = !!cartoonTimes || cartoonTimes === 0
|
||||||
|
if (isPaging) slidesStore.updateSlideIndex(paging)
|
||||||
|
if (isAnim) slidesStore.updateAnimationIndex(cartoonTimes)
|
||||||
|
// 课堂信息-状态管理
|
||||||
|
classcourseStore.setClasscourse(classcourse)
|
||||||
|
// 待上课提示
|
||||||
|
if (!classcourse.status) {
|
||||||
|
this.msgObj = {
|
||||||
|
type: 'success',
|
||||||
|
title: '系统提示',
|
||||||
|
message: '公屏课堂已准备完毕,等待老师开启课堂...',
|
||||||
|
center: true,
|
||||||
|
showClose: false,
|
||||||
|
showCancelButton: false,
|
||||||
|
showConfirmButton: false,
|
||||||
|
beforeClose: () => {}
|
||||||
|
}
|
||||||
|
msgUtils.ElMessageBox(this.msgObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Classcourse()
|
|
@ -0,0 +1,47 @@
|
||||||
|
export default class gridPic {
|
||||||
|
private static Instance: gridPic | null = null;
|
||||||
|
private gridPicRef: any = null;
|
||||||
|
|
||||||
|
constructor(elRef?: any) {
|
||||||
|
if (elRef) {
|
||||||
|
this.gridPicRef = elRef;
|
||||||
|
}
|
||||||
|
if (!gridPic.Instance) {
|
||||||
|
gridPic.Instance = this;
|
||||||
|
}
|
||||||
|
return gridPic.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
init(elRef) {
|
||||||
|
if (elRef) {
|
||||||
|
this.gridPicRef = elRef;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPIc(data) {
|
||||||
|
if (this.gridPicRef && this.gridPicRef.value && typeof this.gridPicRef.value.addPic === 'function') {
|
||||||
|
this.gridPicRef.value.addPic(data);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态方法 - 初始化
|
||||||
|
static init(elRef) {
|
||||||
|
if (!gridPic.Instance) {
|
||||||
|
gridPic.Instance = new gridPic(elRef);
|
||||||
|
} else {
|
||||||
|
gridPic.Instance.init(elRef);
|
||||||
|
}
|
||||||
|
return gridPic.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态方法 - 打开推图上屏幕
|
||||||
|
static addPIc(data) {
|
||||||
|
if (gridPic.Instance) {
|
||||||
|
return gridPic.Instance.addPIc(data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,16 +3,25 @@
|
||||||
* @author zdg
|
* @author zdg
|
||||||
* @date 2024-11-26
|
* @date 2024-11-26
|
||||||
*/
|
*/
|
||||||
import { toRaw } from 'vue'
|
import { toRaw, nextTick } from 'vue'
|
||||||
|
import type { Result } from './types' // 接口类型
|
||||||
import msgUtils from '@/plugins/modal' // 消息工具
|
import msgUtils from '@/plugins/modal' // 消息工具
|
||||||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||||
|
import * as API_smarttalk from '@/api/file' // 相关api
|
||||||
|
import * as API_classwork from '@/api/teaching/classwork' // 相关api
|
||||||
import * as useStore from '../store' // pptist-状态管理
|
import * as useStore from '../store' // pptist-状态管理
|
||||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||||
import useUserStore from '@/store/modules/user' // 外部-用户信息
|
import useUserStore from '@/store/modules/user' // 外部-用户信息
|
||||||
|
import * as Api_server from '@/api/apiService' // 相关api
|
||||||
|
import * as commUtils from '@/utils/comm.js' // 工具
|
||||||
|
import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
|
||||||
const slidesStore = useStore.useSlidesStore()
|
const slidesStore = useStore.useSlidesStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
import { getClassWorkList,getStudentClassWorkData } from '@/views/tool/createHomework'
|
||||||
|
import {createWindow} from '@/utils/tool'
|
||||||
|
import { useToolState } from '@/store/modules/tool'
|
||||||
|
const toolStore = useToolState()
|
||||||
/** 工具类 */
|
/** 工具类 */
|
||||||
export class Utils {
|
export class Utils {
|
||||||
static mxData: any = {
|
static mxData: any = {
|
||||||
|
@ -41,6 +50,8 @@ export class Utils {
|
||||||
}, delay)
|
}, delay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 延时
|
||||||
|
static sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ppt相关后端接口处理 */
|
/** ppt相关后端接口处理 */
|
||||||
|
@ -48,12 +59,14 @@ export class PPTApi {
|
||||||
// 变量
|
// 变量
|
||||||
static isUpdate = true // 是否更新数据
|
static isUpdate = true // 是否更新数据
|
||||||
|
|
||||||
// 获取所有幻灯片列表
|
// 获取所有幻灯片列表 isUpdate为true不更新
|
||||||
static getSlideList(parentid: (Number | String)): Promise<Boolean> {
|
static getSlideList(parentid: (Number | String),isUpdate?:Boolean): Promise<Boolean> {
|
||||||
|
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
|
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
|
||||||
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
|
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
|
if(!isUpdate){
|
||||||
const slides = (res.rows || []).map(o => {
|
const slides = (res.rows || []).map(o => {
|
||||||
if (!!o.datacontent) {
|
if (!!o.datacontent) {
|
||||||
const json = JSON.parse(o.datacontent)
|
const json = JSON.parse(o.datacontent)
|
||||||
|
@ -63,21 +76,51 @@ export class PPTApi {
|
||||||
// 如果没有数据,默认空白页
|
// 如果没有数据,默认空白页
|
||||||
return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}}
|
return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}}
|
||||||
})
|
})
|
||||||
slidesStore.updateSlideIndex(0) // 下标0 为第一页
|
// slidesStore.updateSlideIndex(0) // 下标0 为第一页
|
||||||
slidesStore.setSlides(slides) // 写入数据
|
slidesStore.setSlides(slides) // 写入数据
|
||||||
|
}
|
||||||
|
// 活动列表处理
|
||||||
|
// const workList = (res.rows || []).map(o => o.activityContent)
|
||||||
|
const workItem = res.rows ? [...res.rows] : []
|
||||||
|
// 获取所有的pptlist的数据
|
||||||
|
slidesStore.setWorkItem(workItem)
|
||||||
|
// 没有上课时调用-作业列表
|
||||||
|
if(!classcourse) this.updateWorkList()
|
||||||
|
// 没有上课时调用-批量更新缩略图
|
||||||
|
if(!classcourse) {
|
||||||
|
Utils.sleep(1500).then(() => {
|
||||||
|
this.batchUpdateThumUrl()
|
||||||
|
})
|
||||||
|
}
|
||||||
resolve(true)
|
resolve(true)
|
||||||
} else msgUtils.msgError(res.msg || '获取数据失败');resolve(false)
|
} else msgUtils.msgError(res.msg || '获取数据失败');resolve(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 批量插入slide
|
||||||
|
* @param slides 批量新增的幻灯片
|
||||||
|
* @param slideAll 所有幻灯片
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static async addSlideServer(slides: object[], slideAll: object[]) {
|
||||||
|
const resource = sessionStore.get('curr.resource')||{}
|
||||||
|
for(const slide of slides){
|
||||||
|
slide.id = resource.id // 覆盖默认随机id
|
||||||
|
await this.addSlide(slide)
|
||||||
|
}
|
||||||
|
await this.batchUpdateSlides(slideAll, true) // 批量更新-排序
|
||||||
|
return PPTApi.getSlideList(resource.id) // 更新幻灯片列表以及活动相关
|
||||||
|
}
|
||||||
|
|
||||||
// 新增幻灯片
|
// 新增幻灯片
|
||||||
static addSlide(data: object): Promise<Boolean> {
|
static addSlide(data: object): Promise<Boolean> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const enpt = sessionStore.get('curr.entp')||{}
|
const enpt = sessionStore.get('curr.entp')||{}
|
||||||
const resource = sessionStore.get('curr.resource')||{}
|
// const resource = sessionStore.get('curr.resource')||{}
|
||||||
const {id, ...content} = data
|
const {id, ...content} = data
|
||||||
const params = {
|
const params = {
|
||||||
parentid: resource.id,
|
parentid: id,
|
||||||
entpid: userStore.user.deptId,
|
entpid: userStore.user.deptId,
|
||||||
entpcourseid: enpt.id,
|
entpcourseid: enpt.id,
|
||||||
ppttype: 'file',
|
ppttype: 'file',
|
||||||
|
@ -99,7 +142,7 @@ export class PPTApi {
|
||||||
// msgUtils.msgSuccess('新增成功')
|
// msgUtils.msgSuccess('新增成功')
|
||||||
this.isUpdate = false // 新增后会触发监听,不再更新数据
|
this.isUpdate = false // 新增后会触发监听,不再更新数据
|
||||||
resolve(true)
|
resolve(true)
|
||||||
} else msgUtils.msgError('新增失败');resolve(false)
|
} else msgUtils.msgError('新增失败');reject(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -117,21 +160,29 @@ export class PPTApi {
|
||||||
|
|
||||||
const currentSlide = toRaw(slidesStore.currentSlide)
|
const currentSlide = toRaw(slidesStore.currentSlide)
|
||||||
const isAdd = !/^\d+$/.test(currentSlide.id) // 是否新增
|
const isAdd = !/^\d+$/.test(currentSlide.id) // 是否新增
|
||||||
if (isAdd) { // 新增的幻灯片(id 为非数字,说明是新增的幻灯片)
|
const currInd = toRaw(slidesStore.slideIndex) // 当前页索引-new
|
||||||
const bool = await this.addSlide(currentSlide)
|
const oldInd = oldData.findIndex(o => o.id == currentSlide.id) // 当前页索引-old
|
||||||
bool && this.batchUpdateSlides(newData, true) // 批量更新-排序
|
const isBatch = oldVal && oldVal.length && currInd != oldInd // 是否批量更新-排序
|
||||||
} else { // 防抖-更新
|
if (isAdd) return // 新增-这里不处理 状态管理-处理
|
||||||
|
// 防抖-更新
|
||||||
if (!this.isUpdate) return this.isUpdate = true // 下次更新数据
|
if (!this.isUpdate) return this.isUpdate = true // 下次更新数据
|
||||||
|
if (isBatch) { // 批量更新-排序
|
||||||
|
this.batchUpdateSlides(newData, true)
|
||||||
|
} else { // 更新当前页幻灯片
|
||||||
const params = {
|
const params = {
|
||||||
id: currentSlide.id,
|
id: currentSlide.id,
|
||||||
datacontent: JSON.stringify(currentSlide),
|
datacontent: JSON.stringify(currentSlide),
|
||||||
}
|
}
|
||||||
Utils.mxThrottle(() => {this.updateSlide(params)}, 1000, 2)
|
Utils.mxThrottle(() => {this.updateSlide(params)}, 200, 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 更新幻灯片
|
// 更新幻灯片 isThum 是否更新缩略图
|
||||||
static updateSlide(data: object): Promise<Boolean> {
|
static updateSlide(data: object, isThum = true): Promise<Boolean> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
if (isThum) { // 更新缩略图
|
||||||
|
const thumUrl = await this.getSlideThumUrl()
|
||||||
|
data.base64Code = thumUrl // 更新缩略图
|
||||||
|
}
|
||||||
const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data)
|
const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
resolve(true)
|
resolve(true)
|
||||||
|
@ -166,6 +217,101 @@ export class PPTApi {
|
||||||
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
|
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新-备课资源 标题
|
||||||
|
static updateSmarttalk(data: object): Promise<Boolean> {
|
||||||
|
return API_smarttalk.updateSmarttalk(data).then(res => {
|
||||||
|
if (res.code === 200) return true
|
||||||
|
else msgUtils.msgError(res.msg || '更新失败');return false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新-活动列表
|
||||||
|
static async updateWorkList(): Promise<Boolean> {
|
||||||
|
const resolveData = (resolve, data = []) => {
|
||||||
|
slidesStore.setWorkList(data)
|
||||||
|
return resolve()
|
||||||
|
}
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const workItem = slidesStore.workItem // 所有的pptlist的数据-原始数据
|
||||||
|
const currentSlide = slidesStore.currentSlide // 当前页幻灯片
|
||||||
|
const slideId = currentSlide.id // 当前页幻灯片id
|
||||||
|
if (!slideId) return resolveData(resolve)
|
||||||
|
// slide详情获取-作业id
|
||||||
|
// const res = await API_entpcoursefile.getEntpcoursefile(slideId)
|
||||||
|
// const workIds = res?.data?.activityContent||''
|
||||||
|
// workItem-获取作业id
|
||||||
|
const workIds = workItem.find(o => o.id == slideId)?.activityContent
|
||||||
|
if (!workIds) return resolveData(resolve)
|
||||||
|
// 获取作业列表
|
||||||
|
const resW = await API_classwork.homeworklist({ ids: workIds, pageSize: 1000 })
|
||||||
|
if (resW && resW.rows) return resolveData(resolve, resW.rows)
|
||||||
|
else msgUtils.msgError(resW.msg || '更新失败');return resolveData(resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新缩略图-异步
|
||||||
|
static batchUpdateThumUrl() {
|
||||||
|
return nextTick().then(async () => {
|
||||||
|
const list = slidesStore.workItem || []
|
||||||
|
if (!list.length) return
|
||||||
|
const upList = []
|
||||||
|
for (const [ind,o] of list.entries()) {
|
||||||
|
const isCreate = !o.fileurl // 是否创建
|
||||||
|
if (isCreate) {
|
||||||
|
const thumUrl = await this.getSlideThumUrl(ind)
|
||||||
|
upList.push({ id: o.id, fileurl: thumUrl })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!upList.length) return
|
||||||
|
// 批量更新
|
||||||
|
return await API_entpcoursefile.batchUpdateNew(upList)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// thumbnail-slide thumbnail 缩略图
|
||||||
|
static getSlideThumUrl(index?:number): Promise<Boolean> {
|
||||||
|
return nextTick().then(async() => {
|
||||||
|
const slideIndex = index ?? slidesStore.slideIndex
|
||||||
|
const elements = document.querySelectorAll('.thumbnail-slide')
|
||||||
|
if (elements.length && slideIndex >= 0) {
|
||||||
|
const element = elements[slideIndex]
|
||||||
|
return await toPng(element)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 图片|音频|视频 转换为在线地址
|
||||||
|
static toRousrceUrl =async (o:any) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', o)
|
||||||
|
formData.append('ral', true)
|
||||||
|
const res = await Api_server.Other.uploadFile(formData)
|
||||||
|
if (res && res.code == 200){
|
||||||
|
const url = res?.url
|
||||||
|
url &&(o.src = url)
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Homework{
|
||||||
|
static win: null // 作业弹窗
|
||||||
|
// 作业弹窗
|
||||||
|
static async showHomework(id: any) {
|
||||||
|
let result = await getClassWorkList(id)
|
||||||
|
result = await getStudentClassWorkData()
|
||||||
|
localStorage.setItem('teachClassWorkItem', JSON.stringify(result[0]));
|
||||||
|
toolStore.isTaskWin=true; // 设置打开批改窗口
|
||||||
|
// emit('closeActive')
|
||||||
|
// 重复打开,先关闭弹窗
|
||||||
|
// if (this.win) this.win?.close?.()
|
||||||
|
this.win = await createWindow('open-taskwin',{url:'/teachClassTask'})
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
static closeHomework() {
|
||||||
|
if (this.win) this.win?.close?.()
|
||||||
|
this.win = null
|
||||||
|
}
|
||||||
|
}
|
||||||
export default PPTApi
|
export default PPTApi
|
|
@ -2,6 +2,7 @@
|
||||||
* @description api 无store循环引用
|
* @description api 无store循环引用
|
||||||
* @author zdg
|
* @author zdg
|
||||||
*/
|
*/
|
||||||
|
import type { Result } from './types' // 接口类型
|
||||||
import msgUtils from '@/plugins/modal' // 消息工具
|
import msgUtils from '@/plugins/modal' // 消息工具
|
||||||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
/** 返回-接口类型 */
|
||||||
|
export interface Result {
|
||||||
|
code?: number,
|
||||||
|
msg?: string,
|
||||||
|
data?: any
|
||||||
|
rows?: Array<any>,
|
||||||
|
total?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 课程信息 */
|
||||||
|
export interface Classcourse {
|
||||||
|
id?: number|string, // 课程id
|
||||||
|
coursetitle?: string, // 课程名称
|
||||||
|
coursetype?: string, // 课程类型
|
||||||
|
courseverid?: string, // 课程版本id
|
||||||
|
coursedesc?: string, // 课程描述
|
||||||
|
status?: number, // 课程状态
|
||||||
|
teacherid?: number|string, // 教师id
|
||||||
|
entpcoursefileid?: number|string, // 课程文件id
|
||||||
|
classid?: number|string, // 班级id
|
||||||
|
entpcourseid?: number|string, // 章节中间表id
|
||||||
|
timgroupid?: number|string, // ws 群组id
|
||||||
|
plandate?: string, // 计划时间
|
||||||
|
opendate?: string, // 开课时间
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 消息枚举
|
||||||
|
* @author zdg
|
||||||
|
* @date 2021-07-05 14:07:01
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class MsgEnum {
|
||||||
|
/**
|
||||||
|
* @description: 消息类型
|
||||||
|
*
|
||||||
|
* | 名称 | 含义 | 值(enum) |
|
||||||
|
* | ---- | ---- | ---- |
|
||||||
|
* | SYSTEM | 系统消息 | system |
|
||||||
|
* | TEACHER | 老师消息 | teacher |
|
||||||
|
* | STUDENT | 学生消息 | student |
|
||||||
|
* | NOTICE | 通知消息 | notice |
|
||||||
|
*/
|
||||||
|
static TYPES = {
|
||||||
|
/** @desc: 系统消息 */
|
||||||
|
SYSTEM: 'system',
|
||||||
|
/** @desc: 老师消息 */
|
||||||
|
TEACHER: 'teacher',
|
||||||
|
/** @desc: 学生消息 */
|
||||||
|
STUDENT: 'student',
|
||||||
|
/** @desc: 通知消息 */
|
||||||
|
NOTICE: 'notice'
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description: 消息头-类型
|
||||||
|
*
|
||||||
|
* | 名称 | 含义 | 值(enum) |
|
||||||
|
* | ---- | ---- | ---- |
|
||||||
|
* | --- | 以下为旧定义-消息头 | --- |
|
||||||
|
* | MSG_closed | 结束课程(下课) | closed |
|
||||||
|
* | MSG_onlineStatus | 在线状态 | onlineStatus |
|
||||||
|
* | MSG_pushQuizOfClassWorkdata2Public | 老师端:把选中的学生习题作业,推到大屏 | pushQuizOfClassWorkdata2Public |
|
||||||
|
* | MSG_pushClassWorkdata2Public | 老师端:把选中的学生作业,推到大屏 | pushClassWorkdata2Public |
|
||||||
|
* | MSG_shareStudentPresentdata2All | 把某个学生的展示成果数据推给全班所有学生 | shareStudentPresentdata2All |
|
||||||
|
* | MSG_pushStudentPresentdata2Public | 老师端:课堂展示活动,把选中的学生展示数据,推到大屏 | pushStudentPresentdata2Public |
|
||||||
|
* | MSG_pushClassWorkPresentList2Public | 老师端:课堂展示活动,任务列表,推到大屏 | pushClassWorkPresentList2Public |
|
||||||
|
* | MSG_activePageType | 课标研读-分页切换 | activePageType |
|
||||||
|
* | MSG_slideFlapping | 幻灯片-切换 | slideFlapping |
|
||||||
|
* | MSG_anmationclick | 幻灯片-动画切换 | anmationclick |
|
||||||
|
* | MSG_classcourseopen | 群组创建成功 | classcourseopen |
|
||||||
|
* | MSG_classquizfeedback | 学生的测练结果反馈 | classquizfeedback |
|
||||||
|
* | MSG_classtaskfeedback | 老师端:接收到学生反馈消息-课堂测练中的其他任务 | classtaskfeedback |
|
||||||
|
* | MSG_studentfeedback | 老师端:学生反馈的消息,具体要看其中的feedbackkey,类别较繁杂 | studentfeedback |
|
||||||
|
* | MSG_studentfeedbackcancel | 老师端:学生反馈的消息取消,如取消学会了,取消困惑 | studentfeedbackcancel |
|
||||||
|
* | MSG_classshowdata | 学生提交的课堂展示数据-要在老师端显示,再由老师选择推送到公屏上 | classshowdata |
|
||||||
|
* | MSG_classWorkOfPresentDataUpdate | 学生在公屏上展示并完善后,保存后,老师端要更新 | classWorkOfPresentDataUpdate |
|
||||||
|
* | MSG_classlecturePagesrc | 课堂讲授活动,选择不同的内容 | classlecturePagesrc |
|
||||||
|
* | --- | 以下为新定义-消息头 | --- |
|
||||||
|
* | MSG_0001 | 点赞 | 0x0001 |
|
||||||
|
* | MSG_0002 | xx | 0x0002 |
|
||||||
|
* | MSG_0003 | xx | 0x0003 |
|
||||||
|
*/
|
||||||
|
static HEADS = {
|
||||||
|
// === 旧定义-消息头(兼容以前) ===
|
||||||
|
/** @desc: 开课 */
|
||||||
|
MSG_open : 'open',
|
||||||
|
/** @desc: 结束课程(下课) */
|
||||||
|
MSG_closed : 'closed',
|
||||||
|
/** @desc: 在线状态 */
|
||||||
|
MSG_onlineStatus : 'onlineStatus',
|
||||||
|
/** @desc: 老师端:把选中的学生习题作业,推到大屏 */
|
||||||
|
MSG_pushQuizOfClassWorkdata2Public : 'pushQuizOfClassWorkdata2Public',
|
||||||
|
/** @desc: 老师端:把选中的学生作业,推到大屏 */
|
||||||
|
MSG_pushClassWorkdata2Public : 'pushClassWorkdata2Public',
|
||||||
|
/** @desc: 把某个学生的展示成果数据推给全班所有学生 */
|
||||||
|
MSG_shareStudentPresentdata2All : 'shareStudentPresentdata2All',
|
||||||
|
/** @desc: 老师端:课堂展示活动,把选中的学生展示数据,推到大屏 */
|
||||||
|
MSG_pushStudentPresentdata2Public : 'pushStudentPresentdata2Public',
|
||||||
|
/** @desc: 老师端:课堂展示活动,任务列表,推到大屏 */
|
||||||
|
MSG_pushClassWorkPresentList2Public : 'pushClassWorkPresentList2Public',
|
||||||
|
/** @desc: 课标研读-分页切换 */
|
||||||
|
MSG_activePageType : 'activePageType',
|
||||||
|
/** @desc: 幻灯片-切换 */
|
||||||
|
MSG_slideFlapping : 'slideFlapping',
|
||||||
|
/** @desc: 幻灯片-动画切换 */
|
||||||
|
MSG_anmationclick : 'anmationclick',
|
||||||
|
/** @desc: 群组创建成功 */
|
||||||
|
MSG_classcourseopen : 'classcourseopen',
|
||||||
|
/** @desc: 学生提交作业 */
|
||||||
|
MSG_finishHomework : 'finishHomework',
|
||||||
|
/** @desc: 学生的测练结果反馈 */
|
||||||
|
MSG_classquizfeedback : 'classquizfeedback',
|
||||||
|
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
|
||||||
|
MSG_classtaskfeedback : 'classtaskfeedback',
|
||||||
|
/** @desc: 老师端:学生反馈的消息,具体要看其中的feedbackkey,类别较繁杂 */
|
||||||
|
MSG_studentfeedback : 'studentfeedback',
|
||||||
|
/** @desc: 老师端:学生反馈的消息取消,如取消学会了,取消困惑 */
|
||||||
|
MSG_studentfeedbackcancel : 'studentfeedbackcancel',
|
||||||
|
/** @desc: 学生提交的课堂展示数据-要在老师端显示,再由老师选择推送到公屏上 */
|
||||||
|
MSG_classshowdata : 'classshowdata',
|
||||||
|
/** @desc: 学生在公屏上展示并完善后,保存后,老师端要更新 */
|
||||||
|
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
|
||||||
|
/** @desc: 课堂讲授活动,选择不同的内容 */
|
||||||
|
MSG_classlecturePagesrc : 'classlecturePagesrc',
|
||||||
|
/** @desc: 课堂作业|活动 */
|
||||||
|
MSG_homework : 'HOMEWORK',
|
||||||
|
/** @desc: 公屏 - 课堂作业|活动 */
|
||||||
|
MSG_pushSreen_work : 'pushSreen_work',
|
||||||
|
/** @desc: 公屏 - 实验 */
|
||||||
|
MSG_pushSreen_experiment : 'pushSreen_experiment',
|
||||||
|
/** @desc: 点赞 */
|
||||||
|
MSG_dz : 'dz',
|
||||||
|
/** @desc: 疑惑 */
|
||||||
|
MSG_yh : 'yh',
|
||||||
|
/** @desc: 推图片上屏 */
|
||||||
|
MSG_pushSreen_ImgList : 'pushSreen_ImgList',
|
||||||
|
// === 新定义-消息头 ===
|
||||||
|
/** @desc: 课程创建-待开课 */
|
||||||
|
MSG_0000: 0x0000,
|
||||||
|
/** @desc: 点赞 */
|
||||||
|
MSG_0001: 0x0001,
|
||||||
|
/** @desc: 疑惑 */
|
||||||
|
MSG_0002: 0x0002,
|
||||||
|
MSG_0003: 0x0003,
|
||||||
|
MSG_0004: 0x0004,
|
||||||
|
MSG_0005: 0x0005,
|
||||||
|
MSG_0006: 0x0006,
|
||||||
|
MSG_0007: 0x0007,
|
||||||
|
MSG_0008: 0x0008,
|
||||||
|
MSG_0009: 0x0009,
|
||||||
|
MSG_0010: 0x000a,
|
||||||
|
MSG_0011: 0x000b,
|
||||||
|
MSG_0012: 0x000c,
|
||||||
|
MSG_0013: 0x000d,
|
||||||
|
MSG_0014: 0x000e,
|
||||||
|
MSG_0015: 0x000f,
|
||||||
|
/** @desc: 作业推送 */
|
||||||
|
MSG_0016: 0x0010,
|
||||||
|
MSG_0017: 0x0011,
|
||||||
|
MSG_0018: 0x0012,
|
||||||
|
MSG_0019: 0x0013,
|
||||||
|
MSG_0020: 0x0014,
|
||||||
|
MSG_0021: 0x0015,
|
||||||
|
MSG_0022: 0x0016,
|
||||||
|
MSG_0023: 0x0017,
|
||||||
|
MSG_0024: 0x0018,
|
||||||
|
MSG_0025: 0x0019,
|
||||||
|
MSG_0026: 0x001a,
|
||||||
|
MSG_0027: 0x001b,
|
||||||
|
MSG_0028: 0x001c,
|
||||||
|
MSG_0029: 0x001d,
|
||||||
|
MSG_0030: 0x001e,
|
||||||
|
MSG_0031: 0x001f,
|
||||||
|
MSG_0032: 0x0020,
|
||||||
|
MSG_0033: 0x0021,
|
||||||
|
MSG_0034: 0x0022,
|
||||||
|
MSG_0035: 0x0023,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* 点赞组件-相关
|
||||||
|
*/
|
||||||
|
export default class Upvote {
|
||||||
|
instance: any = null // 自身实例
|
||||||
|
upvoteRef: any = null // 点赞组件
|
||||||
|
constructor(elRef?: any) {
|
||||||
|
if(!!elRef) this.upvoteRef = elRef // 点赞组件
|
||||||
|
if (!Upvote.Instance) {
|
||||||
|
Upvote.Instance = this
|
||||||
|
}
|
||||||
|
return Upvote.Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
init(elRef) {
|
||||||
|
if(!!elRef) this.upvoteRef = elRef // 点赞组件
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
// 打开点赞或者疑问 1点赞 2疑问
|
||||||
|
trigger(type) {
|
||||||
|
this.upvoteRef?.value?.trigger?.(type)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
// 静态方法-初始化
|
||||||
|
static init(elRef) {
|
||||||
|
return new Upvote(elRef)
|
||||||
|
}
|
||||||
|
// 静态方法-打开点赞或者疑问 1点赞 2疑问
|
||||||
|
static trigger(type) {
|
||||||
|
return new Upvote().trigger(type)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,16 +2,30 @@
|
||||||
* @description 公共监听器
|
* @description 公共监听器
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { watch } from 'vue'
|
import { watch, render } from 'vue'
|
||||||
import { PPTApi } from './index'
|
import { PPTApi } from './index'
|
||||||
import * as store from '../store'
|
import * as store from '../store'
|
||||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||||
const slidesStore = store.useSlidesStore()
|
import { MsgEnum } from './types' // 消息枚举
|
||||||
const resource = sessionStore.get('curr.resource')
|
import ChatWs from '@/plugins/socket' // 聊天socket
|
||||||
|
import Classcourse from './classcourse' // 课程相关
|
||||||
|
import msgUtils from '@/plugins/modal' // 消息工具
|
||||||
|
import * as dialogUtils from '@/utils/dialog' // 弹窗-函数
|
||||||
|
import { Homework } from './index' // api-作业相关
|
||||||
|
import emitter from '@/utils/mitt' //mitt 事件总线
|
||||||
|
import useExecPlay from '../views/Screen/hooks/useExecPlay' // 播放控制
|
||||||
|
import hooksUpvote from './upvote' // 点赞-工具
|
||||||
|
import gridPic from './gridPic' // 上屏-工具
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 监听器
|
* @description 监听器
|
||||||
*/
|
*/
|
||||||
|
export default () => {
|
||||||
|
const slidesStore = store.useSlidesStore()
|
||||||
|
const classcourseStore = store.useClasscourseStore() // 课堂信息-状态管理
|
||||||
|
const resource = sessionStore.get('curr.resource') // apt 资源
|
||||||
|
const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源
|
||||||
|
const { execNext, turnPrevSlide } = useExecPlay(false) // 不加载钩子
|
||||||
// 监听幻灯片内容变化
|
// 监听幻灯片内容变化
|
||||||
watch(() => slidesStore.slides, (newVal, oldVal) => {
|
watch(() => slidesStore.slides, (newVal, oldVal) => {
|
||||||
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
|
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
|
||||||
|
@ -23,9 +37,116 @@ watch(() => slidesStore.title, (newVal, oldVal) => {
|
||||||
updatePPT({title: newVal})
|
updatePPT({title: newVal})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听幻灯片下标变化
|
||||||
|
watch(() => slidesStore.slideIndex, (newVal, oldVal) => {
|
||||||
|
if (!!Classcourse.id) return // 上课状态,不更新右侧作业列表
|
||||||
|
PPTApi.updateWorkList() // 更新作业列表
|
||||||
|
})
|
||||||
|
// 监听幻灯片下画布尺寸比例变化
|
||||||
|
watch(() => slidesStore.viewportRatio, (newVal, oldVal) => {
|
||||||
|
const width = slidesStore.viewportSize
|
||||||
|
const widthandration={width, ratio:newVal}
|
||||||
|
const data = { id: resource.id, parentContent: JSON.stringify(widthandration)}
|
||||||
|
PPTApi.updateSlide(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 消息监听ws
|
||||||
|
// console.log('监听器已开启', ChatWs)
|
||||||
|
if (!!ChatWs.ws) {
|
||||||
|
ChatWs.watch((data, e) => {
|
||||||
|
try {
|
||||||
|
handleMessage(JSON.parse(data)?.msg)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('socket 解析异常 ', error, e)
|
||||||
|
handleMessage(data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新ppt内容
|
||||||
const updatePPT = async (data) => {
|
const updatePPT = async (data) => {
|
||||||
if (!resource) return
|
if (!resource) return
|
||||||
data.id = resource.id
|
data.id = resource.id
|
||||||
await PPTApi.updateSlide(data) // 更新ppt内容
|
await PPTApi.updateSlide(data) // 更新ppt内容
|
||||||
sessionStore.set('curr.resource.title', data.title)
|
sessionStore.set('curr.resource.title', data.title)
|
||||||
|
// 更新smarttalk内容
|
||||||
|
if (!!smarttalk && !!data.title) {
|
||||||
|
const {id, fileFlag} = smarttalk
|
||||||
|
const params = { id, fileShowName: `${data.title}.${fileFlag}` }
|
||||||
|
await PPTApi.updateSmarttalk(params) // 更新ppt内容
|
||||||
|
sessionStore.set('curr.smarttalk.fileShowName', params.fileShowName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ws消息处理
|
||||||
|
const handleMessage = (msg) => {
|
||||||
|
console.log('ws消息处理', msg)
|
||||||
|
if (typeof msg === 'object'){
|
||||||
|
const { head, content, ...other } = msg
|
||||||
|
switch (head) {
|
||||||
|
case MsgEnum.HEADS.MSG_open: // 开课
|
||||||
|
// 课堂信息不一致
|
||||||
|
if (Classcourse.id !== content.id) {
|
||||||
|
msgUtils.alertError('老师开课信息异常,请重新进入公屏!')
|
||||||
|
.then(() => { // 点击确定按钮,关闭窗口
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
} else { // 正常更新数据
|
||||||
|
classcourseStore.classcourse.status = 'open'
|
||||||
|
sessionStore.set('curr.classcourse.status', 'open')
|
||||||
|
// 更新课堂信息-关闭警告框
|
||||||
|
Classcourse?.msgObj?.onVanish()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case MsgEnum.HEADS.MSG_slideFlapping: // 幻灯片翻页
|
||||||
|
render(null, document.body) //移除弹窗
|
||||||
|
emitter.emit('closegridPic') //如果有推图片窗口 就关闭
|
||||||
|
const slideIndex = content?.current || 0
|
||||||
|
const type = content?.animation // 上下动作
|
||||||
|
const steps = content?.animationSteps // 动画步骤
|
||||||
|
if (type === 'Nextsteps') execNext(true) // 下一步-异步动画
|
||||||
|
else if (type === 'Previoustep') turnPrevSlide() // 上一步清空-动画
|
||||||
|
else slidesStore.updateSlideIndex(slideIndex) // 更新幻灯片下标
|
||||||
|
// 更新本地缓存
|
||||||
|
sessionStore.set('curr.classcourse.paging', slideIndex)
|
||||||
|
sessionStore.set('curr.classcourse.cartoonTimes', steps)
|
||||||
|
classcourseStore.classcourse.paging = slideIndex
|
||||||
|
classcourseStore.classcourse.cartoonTimes = steps
|
||||||
|
break
|
||||||
|
// case MsgEnum.HEADS.MSG_homework: // 作业|活动-布置 不处理
|
||||||
|
case MsgEnum.HEADS.MSG_pushSreen_work: // 打开-作业|活动
|
||||||
|
if (!content.id) return
|
||||||
|
Homework.showHomework(content.id)
|
||||||
|
break
|
||||||
|
case MsgEnum.HEADS.MSG_pushSreen_experiment: // 打开实验:
|
||||||
|
if (!content.url) return
|
||||||
|
dialogUtils.openLink(content.url)
|
||||||
|
break
|
||||||
|
case MsgEnum.HEADS.MSG_closed: // 下课:
|
||||||
|
close()
|
||||||
|
break
|
||||||
|
case MsgEnum.HEADS.MSG_dz: // 点赞
|
||||||
|
hooksUpvote.trigger(1)
|
||||||
|
break
|
||||||
|
case MsgEnum.HEADS.MSG_yh: // 疑惑
|
||||||
|
hooksUpvote.trigger(2)
|
||||||
|
break
|
||||||
|
case MsgEnum.HEADS.MSG_pushSreen_ImgList: // 推图片上屏
|
||||||
|
const imgArray = content.ImgList.map((obj) => obj.url);
|
||||||
|
emitter.emit('opengridPic',{arr:imgArray}) // 打开推图片上屏窗口
|
||||||
|
break
|
||||||
|
case MsgEnum.HEADS.MSG_0010: // 备用
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 关闭窗口
|
||||||
|
const close = () => {
|
||||||
|
ChatWs?.close() // 关闭ws
|
||||||
|
setTimeout(() => {
|
||||||
|
window.close() // 关闭窗口
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -33,32 +33,6 @@ const { theme } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
const { addSlidesFromData } = useAddSlidesOrElements()
|
const { addSlidesFromData } = useAddSlidesOrElements()
|
||||||
const { isEmptySlide } = useSlideHandler()
|
const { isEmptySlide } = useSlideHandler()
|
||||||
export default () => {
|
|
||||||
|
|
||||||
const exporting = ref(false)
|
|
||||||
|
|
||||||
// 导入pptist文件
|
|
||||||
const importSpecificFile = (files: FileList, cover = false) => {
|
|
||||||
const file = files[0]
|
|
||||||
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.addEventListener('load', () => {
|
|
||||||
try {
|
|
||||||
const slides = JSON.parse(decrypt(reader.result as string))
|
|
||||||
if (cover) {
|
|
||||||
slidesStore.updateSlideIndex(0)
|
|
||||||
slidesStore.setSlides(slides)
|
|
||||||
}
|
|
||||||
else if (isEmptySlide.value) slidesStore.setSlides(slides)
|
|
||||||
else addSlidesFromData(slides)
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
message.error('无法正确读取 / 解析该文件')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
reader.readAsText(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseLineElement = (el: Shape) => {
|
const parseLineElement = (el: Shape) => {
|
||||||
let start: [number, number] = [0, 0]
|
let start: [number, number] = [0, 0]
|
||||||
let end: [number, number] = [0, 0]
|
let end: [number, number] = [0, 0]
|
||||||
|
@ -101,6 +75,33 @@ export default () => {
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
export default () => {
|
||||||
|
|
||||||
|
const exporting = ref(false)
|
||||||
|
|
||||||
|
// 导入pptist文件
|
||||||
|
const importSpecificFile = (files: FileList, cover = false) => {
|
||||||
|
const file = files[0]
|
||||||
|
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.addEventListener('load', () => {
|
||||||
|
try {
|
||||||
|
const slides = JSON.parse(decrypt(reader.result as string))
|
||||||
|
if (cover) {
|
||||||
|
slidesStore.updateSlideIndex(0)
|
||||||
|
slidesStore.setSlides(slides)
|
||||||
|
}
|
||||||
|
else if (isEmptySlide.value) slidesStore.setSlides(slides)
|
||||||
|
else addSlidesFromData(slides)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
message.error('无法正确读取 / 解析该文件')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
reader.readAsText(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 导入PPTX文件
|
// 导入PPTX文件
|
||||||
const importPPTXFile = (files: FileList) => {
|
const importPPTXFile = (files: FileList) => {
|
||||||
|
@ -493,6 +494,7 @@ export default () => {
|
||||||
importPPTXFile,
|
importPPTXFile,
|
||||||
PPTXFileToJson,
|
PPTXFileToJson,
|
||||||
exporting,
|
exporting,
|
||||||
|
parseLineElement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,6 +666,7 @@ export const PPTXFileToJson = (data: File|ArrayBuffer) => {
|
||||||
}
|
}
|
||||||
else if (el.type === 'shape') {
|
else if (el.type === 'shape') {
|
||||||
if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
|
if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
|
||||||
|
// 从返回对象中解构出 xx 函数并调用
|
||||||
const lineElement = parseLineElement(el)
|
const lineElement = parseLineElement(el)
|
||||||
slide.elements.push(lineElement)
|
slide.elements.push(lineElement)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { useScreenStore, useSlidesStore } from '../store'
|
import { useScreenStore, useSlidesStore, useClasscourseStore } from '../store'
|
||||||
import { enterFullscreen, exitFullscreen, isFullscreen } from '../utils/fullscreen'
|
import { enterFullscreen, exitFullscreen, isFullscreen } from '../utils/fullscreen'
|
||||||
|
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||||
|
import ChatWs from '@/plugins/socket' // 聊天socket
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const screenStore = useScreenStore()
|
const screenStore = useScreenStore()
|
||||||
const slidesStore = useSlidesStore()
|
const slidesStore = useSlidesStore()
|
||||||
|
const classcourseStore = useClasscourseStore() // 课堂信息
|
||||||
|
|
||||||
// 进入放映状态(从当前页开始)
|
// 进入放映状态(从当前页开始)
|
||||||
const enterScreening = () => {
|
const enterScreening = () => {
|
||||||
|
@ -19,7 +22,17 @@ export default () => {
|
||||||
|
|
||||||
// 退出放映状态
|
// 退出放映状态
|
||||||
const exitScreening = () => {
|
const exitScreening = () => {
|
||||||
screenStore.setScreening(false)
|
const classcourse = classcourseStore.classcourse
|
||||||
|
if (!!classcourse) { //DOTO 有课堂,执行退相关操作
|
||||||
|
console.log('退出放映状态')
|
||||||
|
ChatWs?.close() // 关闭ws
|
||||||
|
sessionStore.delete('curr.classcourse') // 清除课堂信息
|
||||||
|
sessionStore.delete('curr.resource') // 清除课件信息
|
||||||
|
sessionStore.delete('curr.isPublic') // 清除公屏状态
|
||||||
|
setTimeout(() => {
|
||||||
|
window.close() // 关闭窗口
|
||||||
|
}, 1000)
|
||||||
|
} else screenStore.setScreening(false)
|
||||||
if (isFullscreen()) exitFullscreen()
|
if (isFullscreen()) exitFullscreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,8 @@ import {
|
||||||
User,
|
User,
|
||||||
Switch,
|
Switch,
|
||||||
More,
|
More,
|
||||||
|
Material,
|
||||||
|
AddPicture
|
||||||
} from '@icon-park/vue-next'
|
} from '@icon-park/vue-next'
|
||||||
|
|
||||||
export interface Icons {
|
export interface Icons {
|
||||||
|
@ -255,6 +257,8 @@ export const icons: Icons = {
|
||||||
IconUser: User,
|
IconUser: User,
|
||||||
IconSwitch: Switch,
|
IconSwitch: Switch,
|
||||||
IconMore: More,
|
IconMore: More,
|
||||||
|
IconMaterial: Material,
|
||||||
|
IconAddPicture: AddPicture
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import type { Classcourse } from '../api/types'
|
||||||
|
|
||||||
|
export interface ClasscourseState {
|
||||||
|
classcourse: Classcourse | any, // 课堂信息
|
||||||
|
isEmit: boolean, // 是否加载监听事件(动画播放)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useClasscourseStore = defineStore('classcourse', {
|
||||||
|
state: (): ClasscourseState => ({
|
||||||
|
classcourse: null, // 课堂信息
|
||||||
|
isEmit: false, // 是否加载监听事件(动画播放)
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
setClasscourse(classcourse: Classcourse) {
|
||||||
|
this.classcourse = classcourse
|
||||||
|
},
|
||||||
|
setIsEmit(isEmit: boolean) {
|
||||||
|
this.isEmit = isEmit
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -3,6 +3,7 @@ import { useSlidesStore } from './slides'
|
||||||
import { useSnapshotStore } from './snapshot'
|
import { useSnapshotStore } from './snapshot'
|
||||||
import { useKeyboardStore } from './keyboard'
|
import { useKeyboardStore } from './keyboard'
|
||||||
import { useScreenStore } from './screen'
|
import { useScreenStore } from './screen'
|
||||||
|
import { useClasscourseStore } from './classcourse'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useMainStore,
|
useMainStore,
|
||||||
|
@ -10,4 +11,5 @@ export {
|
||||||
useSnapshotStore,
|
useSnapshotStore,
|
||||||
useKeyboardStore,
|
useKeyboardStore,
|
||||||
useScreenStore,
|
useScreenStore,
|
||||||
|
useClasscourseStore,
|
||||||
}
|
}
|
|
@ -1,17 +1,26 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
export interface ScreenState {
|
export interface ScreenState {
|
||||||
screening: boolean
|
screening: boolean,
|
||||||
|
like: number,
|
||||||
|
doubt: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useScreenStore = defineStore('screen', {
|
export const useScreenStore = defineStore('screen', {
|
||||||
state: (): ScreenState => ({
|
state: (): ScreenState => ({
|
||||||
screening: false, // 是否进入放映状态
|
screening: false, // 是否进入放映状态
|
||||||
|
like:0, // 点赞数量
|
||||||
|
doubt:0, // 疑问数量
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
setScreening(screening: boolean) {
|
setScreening(screening: boolean) {
|
||||||
this.screening = screening
|
this.screening = screening
|
||||||
},
|
},
|
||||||
|
// 打开点赞或疑问
|
||||||
|
openUpvote(type: 'like' | 'doubt'){
|
||||||
|
if (type === 'like') this.like++
|
||||||
|
else this.doubt++
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
|
@ -5,9 +5,12 @@ import type { Slide, SlideTheme, PPTElement, PPTAnimation } from '../types/slide
|
||||||
import { slides } from '../mocks/slides'
|
import { slides } from '../mocks/slides'
|
||||||
import { theme } from '../mocks/theme'
|
import { theme } from '../mocks/theme'
|
||||||
import { layouts } from '../mocks/layout'
|
import { layouts } from '../mocks/layout'
|
||||||
|
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||||
|
import useUserStore from '@/store/modules/user' // 外部-用户信息
|
||||||
|
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||||
|
|
||||||
import PPTApi from '../api/store'
|
import PPTApi from '../api/store'
|
||||||
|
const userStore = useUserStore()
|
||||||
interface RemovePropData {
|
interface RemovePropData {
|
||||||
id: string
|
id: string
|
||||||
propName: string | string[]
|
propName: string | string[]
|
||||||
|
@ -30,7 +33,10 @@ export interface SlidesState {
|
||||||
slides: Slide[]
|
slides: Slide[]
|
||||||
slideIndex: number
|
slideIndex: number
|
||||||
viewportSize: number
|
viewportSize: number
|
||||||
viewportRatio: number
|
viewportRatio: number,
|
||||||
|
animationIndex: number, // 不是从0开始
|
||||||
|
workList:Object[],
|
||||||
|
workItem:Object[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSlidesStore = defineStore('slides', {
|
export const useSlidesStore = defineStore('slides', {
|
||||||
|
@ -41,6 +47,9 @@ export const useSlidesStore = defineStore('slides', {
|
||||||
slideIndex: 0, // 当前页面索引
|
slideIndex: 0, // 当前页面索引
|
||||||
viewportSize: 1000, // 可视区域宽度基数
|
viewportSize: 1000, // 可视区域宽度基数
|
||||||
viewportRatio: 0.5625, // 可视区域比例,默认16:9
|
viewportRatio: 0.5625, // 可视区域比例,默认16:9
|
||||||
|
animationIndex: 0, // 不是从0开始
|
||||||
|
workList:[],// 活动的列表
|
||||||
|
workItem:[],// 获取到的所有pptlist
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -131,18 +140,26 @@ export const useSlidesStore = defineStore('slides', {
|
||||||
setSlides(slides: Slide[]) {
|
setSlides(slides: Slide[]) {
|
||||||
this.slides = slides
|
this.slides = slides
|
||||||
},
|
},
|
||||||
|
// 更新活动列表
|
||||||
|
setWorkList(list: Object[]) {
|
||||||
|
this.workList = list
|
||||||
|
},
|
||||||
|
setWorkItem(list: Object[]) {
|
||||||
|
this.workItem = list
|
||||||
|
},
|
||||||
|
|
||||||
addSlide(slide: Slide | Slide[]) {
|
async addSlide(slide: Slide | Slide[]) {
|
||||||
|
const { PPTApi } = await import('../api/index')
|
||||||
const slides = Array.isArray(slide) ? slide : [slide]
|
const slides = Array.isArray(slide) ? slide : [slide]
|
||||||
for (const slide of slides) {
|
for (const slide of slides) {
|
||||||
if (slide.sectionTag) delete slide.sectionTag
|
if (slide.sectionTag) delete slide.sectionTag
|
||||||
}
|
}
|
||||||
|
|
||||||
const addIndex = this.slideIndex + 1
|
const addIndex = this.slideIndex + 1
|
||||||
this.slides.splice(addIndex, 0, ...slides)
|
this.slides.splice(addIndex, 0, ...slides)
|
||||||
this.slideIndex = addIndex
|
this.slideIndex = addIndex
|
||||||
|
// 添加到服务器
|
||||||
|
PPTApi.addSlideServer(slides, this.slides)
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSlide(props: Partial<Slide>, slideId?: string) {
|
updateSlide(props: Partial<Slide>, slideId?: string) {
|
||||||
const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex
|
const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex
|
||||||
this.slides[slideIndex] = { ...this.slides[slideIndex], ...props }
|
this.slides[slideIndex] = { ...this.slides[slideIndex], ...props }
|
||||||
|
@ -177,6 +194,7 @@ export const useSlidesStore = defineStore('slides', {
|
||||||
const isDel = await PPTApi.delSlide(deletedId)
|
const isDel = await PPTApi.delSlide(deletedId)
|
||||||
if (isDel) {
|
if (isDel) {
|
||||||
// 后端删除成功,更新页面数据
|
// 后端删除成功,更新页面数据
|
||||||
|
this.workItem.splice(index, 1)
|
||||||
deleteSlidesIndex.push(index)
|
deleteSlidesIndex.push(index)
|
||||||
slides.splice(index, 1)
|
slides.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
@ -193,6 +211,9 @@ export const useSlidesStore = defineStore('slides', {
|
||||||
updateSlideIndex(index: number) {
|
updateSlideIndex(index: number) {
|
||||||
this.slideIndex = index
|
this.slideIndex = index
|
||||||
},
|
},
|
||||||
|
updateAnimationIndex(index: number) {
|
||||||
|
this.animationIndex = index
|
||||||
|
},
|
||||||
|
|
||||||
addElement(element: PPTElement | PPTElement[]) {
|
addElement(element: PPTElement | PPTElement[]) {
|
||||||
const elements = Array.isArray(element) ? element : [element]
|
const elements = Array.isArray(element) ? element : [element]
|
||||||
|
|
|
@ -6,4 +6,5 @@ export const enum ToolbarStates {
|
||||||
SLIDE_DESIGN = 'slideDesign',
|
SLIDE_DESIGN = 'slideDesign',
|
||||||
SLIDE_ANIMATION = 'slideAnimation',
|
SLIDE_ANIMATION = 'slideAnimation',
|
||||||
MULTI_POSITION = 'multiPosition',
|
MULTI_POSITION = 'multiPosition',
|
||||||
|
EL_ACTIVE = 'elActive',
|
||||||
}
|
}
|
|
@ -73,3 +73,54 @@ export const svg2File = (svg: string): File => {
|
||||||
const blob = new Blob([svg], { type: 'image/svg+xml' })
|
const blob = new Blob([svg], { type: 'image/svg+xml' })
|
||||||
return new File([blob], `${Date.now()}.svg`, { type: 'image/svg+xml' })
|
return new File([blob], `${Date.now()}.svg`, { type: 'image/svg+xml' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前的时间
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getTime=()=>{
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = ('0' + (now.getMonth() + 1)).slice(-2);
|
||||||
|
const day = ('0' + now.getDate()).slice(-2);
|
||||||
|
const hours = ('0' + now.getHours()).slice(-2);
|
||||||
|
const minutes = ('0' + now.getMinutes()).slice(-2);
|
||||||
|
const seconds = ('0' + now.getSeconds()).slice(-2);
|
||||||
|
return `${year}-${month}-${day}_${hours}:${minutes}:${seconds}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base64转图片File
|
||||||
|
* @param {String} base64 图片base64
|
||||||
|
* @param {String} fileName 图片名称| 默认 → myimg
|
||||||
|
* @returns File 返回转换后的file数据类型
|
||||||
|
*/
|
||||||
|
export const base64ToFile = (base64: string, fileName = '试题图片') => {
|
||||||
|
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
|
||||||
|
let data = base64.split(','),
|
||||||
|
// 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
|
||||||
|
type = data[0].match(/:(.*?);/)[1],
|
||||||
|
// 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp)
|
||||||
|
suffix = type.split('/')[1],
|
||||||
|
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
|
||||||
|
bstr = window.atob(data[1]),
|
||||||
|
// 获取解码结果字符串的长度
|
||||||
|
n = bstr.length,
|
||||||
|
// 根据解码结果字符串的长度创建一个等长的整形数字数组
|
||||||
|
// 但在创建时 所有元素初始值都为 0
|
||||||
|
u8arr = new Uint8Array(n)
|
||||||
|
|
||||||
|
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
|
||||||
|
while (n--) {
|
||||||
|
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
|
||||||
|
u8arr[n] = bstr.charCodeAt(n)
|
||||||
|
}
|
||||||
|
const filename = fileName+getTime()
|
||||||
|
// 利用构造函数创建File文件对象
|
||||||
|
// new File(bits, name, options)
|
||||||
|
const file = new File([u8arr], `${filename}.${suffix}`, {
|
||||||
|
type: type
|
||||||
|
})
|
||||||
|
// 返回file
|
||||||
|
return file
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
<template>
|
||||||
|
<header class="flex material-header">
|
||||||
|
<span>选择素材</span>
|
||||||
|
<i class="iconfont icon-guanbi" @click="onClose"></i>
|
||||||
|
</header>
|
||||||
|
<div class="flex material-list" v-loading="loading">
|
||||||
|
<div class="flex material-item" v-for="item in list" :key="item.id" >
|
||||||
|
<div class="flex">
|
||||||
|
<el-image :src="fileUrl(item)" class="img" />
|
||||||
|
<el-text truncated>{{ item.fileShowName }}</el-text>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" @click="onInsert(item)">插入</el-button>
|
||||||
|
</div>
|
||||||
|
<el-empty description="暂无素材" v-if="!list.length" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue';
|
||||||
|
import { sessionStore } from '@/utils/store'
|
||||||
|
import { getSmarttalkPage } from '@/api/file'
|
||||||
|
import * as commUtils from '@/utils/comm.js'
|
||||||
|
import { getFileSuffix } from '@/utils/ruoyi.js'
|
||||||
|
import { PPTApi } from '../../../api'
|
||||||
|
|
||||||
|
const emit = defineEmits(['insertMaterial', 'close'])
|
||||||
|
|
||||||
|
const curNode = reactive({})
|
||||||
|
let params = {
|
||||||
|
textbookId: '',
|
||||||
|
levelFirstId: '',
|
||||||
|
levelSecondId: null,
|
||||||
|
fileSource: '个人',
|
||||||
|
fileRoot: '备课',
|
||||||
|
orderByColumn: 'createTime',
|
||||||
|
isAsc: 'desc',
|
||||||
|
pageSize: 500
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffixAry = [ 'jpeg','jpg','png','gif','mp3','mp4','avi','mov']
|
||||||
|
const videoSuffix = ['mp3','mp4','avi','mov']
|
||||||
|
const list = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const init = () => {
|
||||||
|
loading.value = true
|
||||||
|
getSmarttalkPage(params).then(res => {
|
||||||
|
loading.value = false
|
||||||
|
if(res.rows && res.rows.length){
|
||||||
|
// 过滤出图片和视频
|
||||||
|
list.value = res.rows.filter( item => suffixAry.indexOf(getFileSuffix(item.fileShowName)) != -1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileUrl = computed(() => (item) =>{
|
||||||
|
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
|
||||||
|
return item.coverPic
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return item.fileFullPath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 插入
|
||||||
|
const onInsert = async (item) =>{
|
||||||
|
loading.value = true
|
||||||
|
const res = await fetch(item.fileFullPath)
|
||||||
|
const bolb = await res.blob()
|
||||||
|
const file = commUtils.blobToFile(bolb, item.fileShowName)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await PPTApi.toRousrceUrl(file)
|
||||||
|
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
|
||||||
|
emit('insertMaterial',{ type: 'video', data })
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
emit('insertMaterial',{ type: 'img', data })
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
const onClose = () =>{
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let data = sessionStore.get('subject.curNode')
|
||||||
|
Object.assign(curNode, data);
|
||||||
|
params.textbookId = curNode.rootid
|
||||||
|
if (curNode.parentNode) {
|
||||||
|
params.levelFirstId = curNode.parentNode.id
|
||||||
|
params.levelSecondId = curNode.id
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params.levelFirstId = curNode.id
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.material-header {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.icon-guanbi{
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.material-list{
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 150px;
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-item {
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
justify-content: space-between;
|
||||||
|
.img{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,19 +7,44 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<template v-if="type === 'video'">
|
<template v-if="type === 'video'">
|
||||||
<Input v-model:value="videoSrc" placeholder="请输入视频地址,e.g. https://xxx.mp4"></Input>
|
<el-tabs :tab-position="'left'" class="demo-tabs" v-model="tabvalue">
|
||||||
<div class="btns">
|
<el-tab-pane label="常规" >
|
||||||
|
<Input v-model:value="videoSrc" style="width:100%" placeholder="请输入视频地址,e.g. https://xxx.mp4"></Input>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="上传" >
|
||||||
|
<FileInput accept="video/*" @change="files => insertImageElementvideo(files)">
|
||||||
|
<div class="updivs">+点击上传视频</div>
|
||||||
|
</FileInput>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="btns" v-if="tabvalue=='0'">
|
||||||
<Button @click="emit('close')" style="margin-right: 10px;">取消</Button>
|
<Button @click="emit('close')" style="margin-right: 10px;">取消</Button>
|
||||||
<Button type="primary" @click="insertVideo()">确认</Button>
|
<Button type="primary" @click="insertVideo()">确认</Button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="type === 'audio'">
|
<template v-if="type === 'audio'">
|
||||||
<Input v-model:value="audioSrc" placeholder="请输入音频地址,e.g. https://xxx.mp3"></Input>
|
<el-tabs :tab-position="'left'" class="demo-tabs" v-model="tabvalue1">
|
||||||
<div class="btns">
|
<el-tab-pane label="常规" >
|
||||||
|
<Input v-model:value="audioSrc" style="width:100%" placeholder="请输入音频地址,e.g. https://xxx.mp3"></Input>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="上传" >
|
||||||
|
<FileInput accept="audio/*" @change="files => insertImageElementaudio(files)">
|
||||||
|
<div class="updivs">+点击上传音频</div>
|
||||||
|
</FileInput>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<div class="btns" v-if="tabvalue1=='0'">
|
||||||
<Button @click="emit('close')" style="margin-right: 10px;">取消</Button>
|
<Button @click="emit('close')" style="margin-right: 10px;">取消</Button>
|
||||||
<Button type="primary" @click="insertAudio()">确认</Button>
|
<Button type="primary" @click="insertAudio()">确认</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -30,6 +55,8 @@ import message from '../../../utils/message'
|
||||||
import Tabs from '../../../components/Tabs.vue'
|
import Tabs from '../../../components/Tabs.vue'
|
||||||
import Input from '../../../components/Input.vue'
|
import Input from '../../../components/Input.vue'
|
||||||
import Button from '../../../components/Button.vue'
|
import Button from '../../../components/Button.vue'
|
||||||
|
import FileInput from '../../../components/FileInput.vue'
|
||||||
|
import { PPTApi } from '../../../api'
|
||||||
|
|
||||||
type TypeKey = 'video' | 'audio'
|
type TypeKey = 'video' | 'audio'
|
||||||
interface TabItem {
|
interface TabItem {
|
||||||
|
@ -45,9 +72,33 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const type = ref<TypeKey>('video')
|
const type = ref<TypeKey>('video')
|
||||||
|
|
||||||
const videoSrc = ref('https://mazwai.com/videvo_files/video/free/2019-01/small_watermarked/181004_04_Dolphins-Whale_06_preview.webm')
|
const videoSrc = ref('')
|
||||||
const audioSrc = ref('https://freesound.org/data/previews/614/614107_11861866-lq.mp3')
|
const audioSrc = ref('')
|
||||||
|
// https://freesound.org/data/previews/614/614107_11861866-lq.mp3
|
||||||
|
const tabvalue = ref('0')
|
||||||
|
const tabvalue1 = ref('0')
|
||||||
|
|
||||||
|
const insertImageElementvideo = (files: FileList) => {
|
||||||
|
console.log('files', files)
|
||||||
|
const imageFile = files[0]
|
||||||
|
if (!imageFile) return
|
||||||
|
PPTApi.toRousrceUrl(imageFile).then(data=>{
|
||||||
|
videoSrc.value=data
|
||||||
|
insertVideo()
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertImageElementaudio = (files: FileList) => {
|
||||||
|
console.log('files', files)
|
||||||
|
const imageFile = files[0]
|
||||||
|
if (!imageFile) return
|
||||||
|
PPTApi.toRousrceUrl(imageFile).then(data=>{
|
||||||
|
videoSrc.value=data
|
||||||
|
insertAudio()
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
const tabs: TabItem[] = [
|
const tabs: TabItem[] = [
|
||||||
{ key: 'video', label: '视频' },
|
{ key: 'video', label: '视频' },
|
||||||
{ key: 'audio', label: '音频' },
|
{ key: 'audio', label: '音频' },
|
||||||
|
@ -74,4 +125,33 @@ const insertAudio = () => {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.updivs{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.demo-tabs{
|
||||||
|
:deep(.el-tabs__content){
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
div{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep( .el-tabs__item.is-active) {
|
||||||
|
color: #d14424;
|
||||||
|
}
|
||||||
|
:deep( .el-tabs__item:hover) {
|
||||||
|
color: #d14424;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__active-bar) {
|
||||||
|
background-color: #d14424;
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -81,6 +81,9 @@
|
||||||
</template>
|
</template>
|
||||||
<IconVideoTwo class="handler-item" v-tooltip="'插入音视频'" />
|
<IconVideoTwo class="handler-item" v-tooltip="'插入音视频'" />
|
||||||
</Popover>
|
</Popover>
|
||||||
|
<IconPreviewOpen class="handler-item" v-tooltip="'插入试题'" @click="classWorkTaskVisible = true" />
|
||||||
|
<IconMaterial class="handler-item" v-tooltip="'插入素材'" @click="materiaVisible = true"/>
|
||||||
|
<IconAddPicture class="handler-item" v-tooltip="'文生图'" @click="imgVisible = true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right-handler">
|
<div class="right-handler">
|
||||||
|
@ -110,6 +113,27 @@
|
||||||
@update="data => { createLatexElement(data); latexEditorVisible = false }"
|
@update="data => { createLatexElement(data); latexEditorVisible = false }"
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<!--插入试题-->
|
||||||
|
<el-dialog v-model="classWorkTaskVisible" append-to-body :show-close="false" width="70%">
|
||||||
|
<QuestToPPTist
|
||||||
|
class="class-work-task-modal"
|
||||||
|
@close="classWorkTaskVisible = false"
|
||||||
|
@update="data => { onhtml2canvas(data); classWorkTaskVisible = false }"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
|
<!--插入素材-->
|
||||||
|
<Modal
|
||||||
|
v-model:visible="materiaVisible"
|
||||||
|
:width="880">
|
||||||
|
<MaterialDialog @close="materiaVisible = false" @insertMaterial="insertMaterial"/>
|
||||||
|
</Modal>
|
||||||
|
<!--文生图-->
|
||||||
|
<Modal
|
||||||
|
v-model:visible="imgVisible"
|
||||||
|
:width="1300">
|
||||||
|
<TextCreateImg hasPPt @insertImg="insertImg" />
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -117,7 +141,7 @@
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSnapshotStore } from '../../../store'
|
import { useMainStore, useSnapshotStore } from '../../../store'
|
||||||
import { getImageDataURL } from '../../../utils/image'
|
import { getImageDataURL, base64ToFile } from '../../../utils/image'
|
||||||
import type { ShapePoolItem } from '../../../configs/shapes'
|
import type { ShapePoolItem } from '../../../configs/shapes'
|
||||||
import type { LinePoolItem } from '../../../configs/lines'
|
import type { LinePoolItem } from '../../../configs/lines'
|
||||||
import useScaleCanvas from '../../../hooks/useScaleCanvas'
|
import useScaleCanvas from '../../../hooks/useScaleCanvas'
|
||||||
|
@ -135,6 +159,12 @@ import Modal from '../../../components/Modal.vue'
|
||||||
import Divider from '../../../components/Divider.vue'
|
import Divider from '../../../components/Divider.vue'
|
||||||
import Popover from '../../../components/Popover.vue'
|
import Popover from '../../../components/Popover.vue'
|
||||||
import PopoverMenuItem from '../../../components/PopoverMenuItem.vue'
|
import PopoverMenuItem from '../../../components/PopoverMenuItem.vue'
|
||||||
|
import QuestToPPTist from '@/views/classTask/newClassTaskAssign/questToPPTist/index.vue'
|
||||||
|
import MaterialDialog from './MaterialDialog.vue'
|
||||||
|
import { PPTApi } from '../../../api'
|
||||||
|
import TextCreateImg from '@/components/ai-kolors/index.vue'
|
||||||
|
import { toPng } from 'html-to-image' // 引入html-to-image库
|
||||||
|
|
||||||
|
|
||||||
const mainStore = useMainStore()
|
const mainStore = useMainStore()
|
||||||
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
|
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
|
||||||
|
@ -167,9 +197,27 @@ const {
|
||||||
} = useCreateElement()
|
} = useCreateElement()
|
||||||
|
|
||||||
const insertImageElement = (files: FileList) => {
|
const insertImageElement = (files: FileList) => {
|
||||||
|
console.log('files', files)
|
||||||
const imageFile = files[0]
|
const imageFile = files[0]
|
||||||
if (!imageFile) return
|
if (!imageFile) return
|
||||||
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
|
// 上传图片转为线上地址
|
||||||
|
PPTApi.toRousrceUrl(imageFile).then(data=>{
|
||||||
|
createImageElement(data)
|
||||||
|
})
|
||||||
|
// getImageDataURL(imageFile).then(dataURL => {
|
||||||
|
// createImageElement(dataURL)
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
const onhtml2canvas = async (html: HTMLElement) => {
|
||||||
|
const base64Dta = await toPng(html);
|
||||||
|
// base64转图片File
|
||||||
|
const toFile = base64ToFile(base64Dta)
|
||||||
|
// 上传图片转为线上地址
|
||||||
|
PPTApi.toRousrceUrl(toFile).then(data=>{
|
||||||
|
createImageElement(data)
|
||||||
|
})
|
||||||
|
// createImageElement(ele);
|
||||||
}
|
}
|
||||||
|
|
||||||
const shapePoolVisible = ref(false)
|
const shapePoolVisible = ref(false)
|
||||||
|
@ -178,9 +226,11 @@ const chartPoolVisible = ref(false)
|
||||||
const tableGeneratorVisible = ref(false)
|
const tableGeneratorVisible = ref(false)
|
||||||
const mediaInputVisible = ref(false)
|
const mediaInputVisible = ref(false)
|
||||||
const latexEditorVisible = ref(false)
|
const latexEditorVisible = ref(false)
|
||||||
|
const classWorkTaskVisible = ref(false)
|
||||||
const textTypeSelectVisible = ref(false)
|
const textTypeSelectVisible = ref(false)
|
||||||
const shapeMenuVisible = ref(false)
|
const shapeMenuVisible = ref(false)
|
||||||
const moreVisible = ref(false)
|
const moreVisible = ref(false)
|
||||||
|
const materiaVisible = ref(false)
|
||||||
|
|
||||||
// 绘制文字范围
|
// 绘制文字范围
|
||||||
const drawText = (vertical = false) => {
|
const drawText = (vertical = false) => {
|
||||||
|
@ -227,6 +277,32 @@ const toggleSraechPanel = () => {
|
||||||
const toggleNotesPanel = () => {
|
const toggleNotesPanel = () => {
|
||||||
mainStore.setNotesPanelState(!showNotesPanel.value)
|
mainStore.setNotesPanelState(!showNotesPanel.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 插入素材
|
||||||
|
interface MaterialParams {
|
||||||
|
type: string,
|
||||||
|
data: string
|
||||||
|
}
|
||||||
|
const insertMaterial = async (item: MaterialParams) =>{
|
||||||
|
const { type, data } = item
|
||||||
|
if(type == 'video'){
|
||||||
|
createVideoElement(data)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
createImageElement(data)
|
||||||
|
}
|
||||||
|
materiaVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文生图
|
||||||
|
const imgVisible = ref(false)
|
||||||
|
const insertImg = async (file: any) =>{
|
||||||
|
PPTApi.toRousrceUrl(file).then(data=>{
|
||||||
|
createImageElement(data)
|
||||||
|
imgVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -343,6 +419,9 @@ const toggleNotesPanel = () => {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.class-work-task-modal{
|
||||||
|
height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (width <= 1200px) {
|
@media screen and (width <= 1200px) {
|
||||||
.right-handler .text {
|
.right-handler .text {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<Popover trigger="click" placement="bottom-start" v-model:value="mainMenuVisible">
|
<Popover trigger="click" placement="bottom-start" v-model:value="mainMenuVisible">
|
||||||
<template #content>
|
<template #content>
|
||||||
<FileInput accept=".pptist" @change="files => {
|
<!-- <FileInput accept=".pptist" @change="files => {
|
||||||
importSpecificFile(files)
|
importSpecificFile(files)
|
||||||
mainMenuVisible = false
|
mainMenuVisible = false
|
||||||
}">
|
}">
|
||||||
|
@ -15,8 +15,8 @@
|
||||||
}">
|
}">
|
||||||
<PopoverMenuItem>导入 pptx 文件</PopoverMenuItem>
|
<PopoverMenuItem>导入 pptx 文件</PopoverMenuItem>
|
||||||
</FileInput>
|
</FileInput>
|
||||||
<PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem>
|
<PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem> -->
|
||||||
<PopoverMenuItem @click="resetSlides(); mainMenuVisible = false">重置幻灯片</PopoverMenuItem>
|
<!-- <PopoverMenuItem @click="resetSlides(); mainMenuVisible = false">重置幻灯片</PopoverMenuItem> -->
|
||||||
<!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/issues')">意见反馈</PopoverMenuItem> -->
|
<!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/issues')">意见反馈</PopoverMenuItem> -->
|
||||||
<!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/blob/master/doc/Q&A.md')">常见问题</PopoverMenuItem> -->
|
<!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/blob/master/doc/Q&A.md')">常见问题</PopoverMenuItem> -->
|
||||||
<PopoverMenuItem @click="mainMenuVisible = false; hotkeyDrawerVisible = true">快捷操作</PopoverMenuItem>
|
<PopoverMenuItem @click="mainMenuVisible = false; hotkeyDrawerVisible = true">快捷操作</PopoverMenuItem>
|
||||||
|
@ -54,9 +54,9 @@
|
||||||
<div class="arrow-btn"><IconDown class="arrow" /></div>
|
<div class="arrow-btn"><IconDown class="arrow" /></div>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
|
<!-- <div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
|
||||||
<IconDownload class="icon" />
|
<IconDownload class="icon" />
|
||||||
</div>
|
</div> -->
|
||||||
<div class="menu-item" v-tooltip="`${userStore.user.parentDeptName}-${userStore.user.nickName}`">
|
<div class="menu-item" v-tooltip="`${userStore.user.parentDeptName}-${userStore.user.nickName}`">
|
||||||
<el-avatar size="small" :src="avatar" />
|
<el-avatar size="small" :src="avatar" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -162,6 +162,10 @@ const setDialogForExport = (type: DialogForExportTypes) => {
|
||||||
.icon {
|
.icon {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|
||||||
|
:deep(svg) {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
</Draggable>
|
</Draggable>
|
||||||
|
|
||||||
<div class="page-number">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
|
<div class="page-number">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ const mainStore = useMainStore()
|
||||||
const slidesStore = useSlidesStore()
|
const slidesStore = useSlidesStore()
|
||||||
const keyboardStore = useKeyboardStore()
|
const keyboardStore = useKeyboardStore()
|
||||||
const { selectedSlidesIndex: _selectedSlidesIndex, thumbnailsFocus } = storeToRefs(mainStore)
|
const { selectedSlidesIndex: _selectedSlidesIndex, thumbnailsFocus } = storeToRefs(mainStore)
|
||||||
const { slides, slideIndex, currentSlide } = storeToRefs(slidesStore)
|
const { slides, slideIndex, currentSlide, workList, workItem } = storeToRefs(slidesStore)
|
||||||
const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)
|
const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)
|
||||||
|
|
||||||
const { slidesLoadLimit } = useLoadSlides()
|
const { slidesLoadLimit } = useLoadSlides()
|
||||||
|
@ -123,6 +124,7 @@ const {
|
||||||
updateSectionTitle,
|
updateSectionTitle,
|
||||||
} = useSectionHandler()
|
} = useSectionHandler()
|
||||||
|
|
||||||
|
|
||||||
// 页面被切换时
|
// 页面被切换时
|
||||||
const thumbnailsRef = ref<InstanceType<typeof Draggable>>()
|
const thumbnailsRef = ref<InstanceType<typeof Draggable>>()
|
||||||
watch(() => slideIndex.value, () => {
|
watch(() => slideIndex.value, () => {
|
||||||
|
@ -145,6 +147,7 @@ watch(() => slideIndex.value, () => {
|
||||||
|
|
||||||
// 切换页面
|
// 切换页面
|
||||||
const changeSlideIndex = (index: number) => {
|
const changeSlideIndex = (index: number) => {
|
||||||
|
|
||||||
mainStore.setActiveElementIdList([])
|
mainStore.setActiveElementIdList([])
|
||||||
|
|
||||||
if (slideIndex.value === index) return
|
if (slideIndex.value === index) return
|
||||||
|
@ -393,12 +396,17 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => {
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
|
:deep(svg) {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.thumbnail-list {
|
.thumbnail-list {
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
border-bottom: 1px solid $borderColor;
|
||||||
}
|
}
|
||||||
.thumbnail-item {
|
.thumbnail-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -477,7 +485,6 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => {
|
||||||
.page-number {
|
.page-number {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border-top: 1px solid $borderColor;
|
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|
|
@ -95,8 +95,8 @@
|
||||||
style="width: 65%;"
|
style="width: 65%;"
|
||||||
:options="[
|
:options="[
|
||||||
{ label: '主动触发', value: 'click' },
|
{ label: '主动触发', value: 'click' },
|
||||||
{ label: '与上一动画同时', value: 'meantime' },
|
// { label: '与上一动画同时', value: 'meantime' },
|
||||||
{ label: '上一动画之后', value: 'auto' },
|
// { label: '上一动画之后', value: 'auto' },
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -245,7 +245,6 @@ const runAnimation = (elId: string, effect: string, duration: number) => {
|
||||||
const animationName = `${ANIMATION_CLASS_PREFIX}${effect}`
|
const animationName = `${ANIMATION_CLASS_PREFIX}${effect}`
|
||||||
document.documentElement.style.setProperty('--animate-duration', `${duration}ms`)
|
document.documentElement.style.setProperty('--animate-duration', `${duration}ms`)
|
||||||
elRef.classList.add(`${ANIMATION_CLASS_PREFIX}animated`, animationName)
|
elRef.classList.add(`${ANIMATION_CLASS_PREFIX}animated`, animationName)
|
||||||
|
|
||||||
const handleAnimationEnd = () => {
|
const handleAnimationEnd = () => {
|
||||||
document.documentElement.style.removeProperty('--animate-duration')
|
document.documentElement.style.removeProperty('--animate-duration')
|
||||||
elRef.classList.remove(`${ANIMATION_CLASS_PREFIX}animated`, animationName)
|
elRef.classList.remove(`${ANIMATION_CLASS_PREFIX}animated`, animationName)
|
||||||
|
|
|
@ -0,0 +1,346 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div style="display: flex;flex-wrap: wrap;">
|
||||||
|
<el-button size="small" title="活动引用" text style="height: 54px" @click="openList()">
|
||||||
|
<div class="buttonDiv">
|
||||||
|
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M133.36576 308.12842667v407.74314666c0 96.39253333 78.31552 174.76266667 174.76266667 174.76266667h407.74314666c96.39253333 0 174.76266667-78.31552 174.76266667-174.76266667V308.12842667c0-96.39253333-78.31552-174.76266667-174.76266667-174.76266667H308.12842667a174.87189333 174.87189333 0 0 0-174.76266667 174.76266667zM75.09333333 308.12842667A233.19893333 233.19893333 0 0 1 308.12842667 75.09333333h407.74314666A233.19893333 233.19893333 0 0 1 948.90666667 308.12842667v407.74314666A233.19893333 233.19893333 0 0 1 715.87157333 948.90666667H308.12842667A233.19893333 233.19893333 0 0 1 75.09333333 715.87157333V308.12842667z m706.9696 192.07509333c0 24.46677333-5.67978667 48.22357333-16.98474666 71.21578667-11.35957333 22.99221333-26.76053333 43.52682667-46.25749334 61.54922666-19.44234667 18.0224-42.05226667 32.44032-67.61130666 43.25376a207.20298667 207.20298667 0 0 1-81.21002667 16.16554667h-26.54208c-10.37653333 0-21.62688 0.10922667-33.64181333 0.27306667-12.01493333 0.21845333-23.86602667 0.32768-35.66250667 0.32768h-31.9488c-13.1072 0-23.53834667 1.25610667-31.23882667 3.71370666-7.70048 2.51221333-11.35957333 7.91893333-10.92266666 16.16554667 0 6.22592-0.10922667 13.38026667-0.32768 21.46304-0.21845333 8.08277333-0.32768 15.45557333-0.32768 22.06378667 0 10.81344-3.16757333 17.74933333-9.50272 20.86229333-6.33514667 3.11296-14.7456 0.92842667-25.12213334-6.5536-10.92266667-7.42741333-23.37450667-16.27477333-37.41013333-26.43285333a36528.56490667 36528.56490667 0 0 0-126.42986667-91.09504c-10.37653333-7.42741333-15.72864-14.47253333-15.94709333-21.13536-0.27306667-6.60821333 4.36906667-13.65333333 13.9264-21.13536 9.93962667-7.48202667 21.62688-16.27477333 34.95253333-26.43285334 13.38026667-10.15808 27.30666667-20.58922667 41.83381334-31.40266666l42.81685333-31.67573334c14.03562667-10.37653333 26.48746667-19.71541333 37.41013333-28.01664 11.74186667-9.12042667 21.62688-12.61568 29.4912-10.54037333 7.97354667 2.07530667 11.96032 8.73813333 11.96032 19.87925333 0 3.2768 0.10922667 7.3728 0.32768 12.12416a2794.40042667 2794.40042667 0 0 1 1.69301334 43.85450667c0 7.86432 2.62144 12.72490667 7.80970666 14.58176 5.24288 1.85685333 12.34261333 2.83989333 21.40842667 2.83989333 20.42538667-0.43690667 43.30837333-0.65536 68.64896-0.65536h70.66965333c10.43114667 0 20.86229333-2.18453333 31.29344-6.5536 10.37653333-4.36906667 19.93386667-10.10346667 28.56277334-17.36704 8.57429333-7.26357333 15.61941333-15.67402667 21.02613333-25.17674666 5.46133333-9.55733333 8.192-19.6608 8.192-30.47424v-43.52682667c0-16.60245333-0.10922667-32.65877333-0.32768-48.22357333a2840.00256 2840.00256 0 0 1-0.38229333-40.68693334V341.6064c0-9.12042667 3.05834667-16.65706667 9.17504-22.66453333 6.11669333-6.00746667 13.81717333-10.59498667 23.10144-13.70794667 9.28426667-3.11296 19.38773333-4.64213333 30.25578666-4.64213333 10.92266667 0 20.97152 1.41994667 30.25578667 4.36906666 9.28426667 2.83989333 16.98474667 7.09973333 23.10144 12.72490667 6.11669333 5.57056 9.17504 12.56106667 9.17504 20.80768v46.03904c0 10.75882667 0.10922667 22.28224 0.32768 34.51562667 0.27306667 12.23338667 0.38229333 23.92064 0.38229333 35.11637333V500.20352z" p-id="7882"></path></svg>
|
||||||
|
<div style="margin-top: 10px">活动引用</div>
|
||||||
|
</div>
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" title="习题训练" text style="height: 54px" @click="showDialog('习题训练')">
|
||||||
|
<div class="buttonDiv">
|
||||||
|
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M473.6 204.8c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4H243.2c-21.2 0-38.4-17.2-38.4-38.4s17.2-38.4 38.4-38.4h230.4z m-51.2 153.6c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4H243.2c-21.2 0-38.4-17.2-38.4-38.4s17.2-38.4 38.4-38.4h179.2zM371.2 512c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4h-128c-21.2 0-38.4-17.2-38.4-38.4S222 512 243.2 512h128z m192-384H153.6c-12.6 0-23 9.1-25.2 21l-0.4 4.6v512c0 12.6 9.1 23 21 25.2l4.6 0.4h257.1c-0.7-8.5-1.1-17-1.1-25.6 0-124 73.5-230.8 179.2-279.4V153.6c0-12.6-9.1-23-21-25.2l-4.6-0.4zM768 460.8H665.6v153.6H512v102.4h153.6v153.6H768V716.8h153.6V614.4H768V460.8zM563.2 51.2c56.6 0 102.4 45.8 102.4 102.4v209c16.6-2.8 33.7-4.2 51.2-4.2 169.7 0 307.2 137.5 307.2 307.2S886.5 972.8 716.8 972.8c-133.7 0-247.5-85.5-289.7-204.8H153.6C97 768 51.2 722.2 51.2 665.6v-512C51.2 97 97 51.2 153.6 51.2h409.6z" p-id="13225"></path></svg>
|
||||||
|
<div style="margin-top: 10px">习题训练</div>
|
||||||
|
</div>
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" title="课堂展示" text style="height: 54px" @click="showDialog('课堂展示')">
|
||||||
|
<div class="buttonDiv">
|
||||||
|
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M741.75298 604.605763l3.836774 0.697596 17.43988 3.767014c154.656857 35.089039 260.133252 103.732407 260.133252 194.070985 0 88.455072-101.360583 156.261326-251.134274 191.838682l-9.068737 2.092785-17.370121 3.836774-17.788678 3.487976c-23.927515 4.39485-48.831664 8.092104-74.642686 11.022004l-19.462907 2.023026-24.69487 2.092786-5.022685 0.348797-20.090742 1.325431-10.18489 0.488317-10.25465 0.418557-20.579058 0.697595c-10.324409 0.209279-20.788337 0.348798-31.322025 0.348798l-15.695892-0.139519-15.556373-0.279038-20.648818-0.627836-10.18489-0.418557-10.18489-0.488317-20.160501-1.325431-9.975612-0.697595-19.741944-1.743988a1100.665713 1100.665713 0 0 1-75.968118-9.905852l-18.137475-3.139178-17.788678-3.487976-17.37012-3.767014C105.406635 961.983786 0 893.410178 0 803.141358c0-88.594591 101.360583-156.261326 251.134273-191.9782l8.998979-2.092785 17.43988-3.767014 3.767014-0.697596v93.756796l-10.952245 2.581102-15.207575 3.906533-14.6495 4.046052-13.951904 4.185572c-75.340282 23.857756-121.660604 61.946454-121.660603 89.989781 0 27.206213 43.250903 61.527897 114.196335 85.036855l7.394509 2.371824 14.021663 4.255331 14.6495 4.046052 15.207575 3.906533c8.580421 2.092786 17.43988 4.115812 26.578377 5.999319l13.951904 2.790381 17.021323 3.139178 8.7897 1.39519 17.788678 2.790381 9.068737 1.255672 18.556033 2.302064 14.161182 1.604469 14.370462 1.39519 19.532665 1.53471a1139.731044 1139.731044 0 0 0 142.867498 1.255671l19.881463-1.255671 19.532666-1.53471c8.580421-0.767355 17.021323-1.674228 25.392466-2.581102l12.417194-1.53471 18.276995-2.441583 17.858437-2.790381 8.71994-1.39519 17.091082-3.139178c9.417535-1.813748 18.486273-3.697255 27.415492-5.720281l13.11479-3.069419 15.207575-3.906533 14.649499-4.046052 13.951904-4.185572c75.340282-23.857756 121.590844-59.365352 121.590845-87.408679 0-27.206213-43.250903-64.178759-114.196335-87.617957l-7.39451-2.371824-14.021663-4.255331-14.649499-4.115811-15.207576-3.836774-10.952245-2.581102V604.605763zM451.204578 588.072757l121.660604 69.82928-131.078139 86.153008 9.417535-155.982288z m280.921589-484.131072l121.660603 69.82928-257.552149 443.810069-121.590844-69.899039 257.48239-443.74031zM842.904285 6.278357l40.530281 23.22992a46.459841 46.459841 0 0 1 17.160842 63.690442l-23.439198 40.321003L755.565365 63.620683l23.36944-40.321003a46.948157 46.948157 0 0 1 63.96948-17.021323z" p-id="59383"></path></svg>
|
||||||
|
<div style="margin-top: 10px">课堂展示</div>
|
||||||
|
</div>
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" title="常规作业" text style="height: 54px;margin-left: 0" @click="showDialog('常规作业')">
|
||||||
|
<div class="buttonDiv">
|
||||||
|
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M901.705143 511.926857h-55.954286a8.045714 8.045714 0 0 1-8.045714-8.045714V183.881143H181.686857v656.091428H501.76c4.388571 0 8.045714 3.510857 8.045714 7.899429v56.027429c0 4.388571-3.657143 8.045714-8.045714 8.045714H141.750857a31.963429 31.963429 0 0 1-32.036571-32.036572V143.872c0-17.627429 14.336-31.963429 32.036571-31.963429H877.714286c17.700571 0 32.036571 14.336 32.036571 31.963429V503.954286c0 4.388571-3.657143 8.045714-8.045714 8.045714zM731.428571 911.945143a36.571429 36.571429 0 0 1-36.571428-36.571429v-109.714285H585.142857a36.571429 36.571429 0 0 1 0-73.142858h109.714286v-109.714285a36.571429 36.571429 0 0 1 73.142857 0v109.714285H877.714286a36.571429 36.571429 0 1 1 0 73.142858h-109.714286v109.714285a36.571429 36.571429 0 0 1-36.571429 36.571429z" p-id="22184"></path></svg>
|
||||||
|
<div style="margin-top: 10px">常规作业</div>
|
||||||
|
</div>
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" title="科学实验" text style="height: 54px;margin-left: 0" @click="showDialog('科学实验')">
|
||||||
|
<div class="buttonDiv">
|
||||||
|
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M901.705143 511.926857h-55.954286a8.045714 8.045714 0 0 1-8.045714-8.045714V183.881143H181.686857v656.091428H501.76c4.388571 0 8.045714 3.510857 8.045714 7.899429v56.027429c0 4.388571-3.657143 8.045714-8.045714 8.045714H141.750857a31.963429 31.963429 0 0 1-32.036571-32.036572V143.872c0-17.627429 14.336-31.963429 32.036571-31.963429H877.714286c17.700571 0 32.036571 14.336 32.036571 31.963429V503.954286c0 4.388571-3.657143 8.045714-8.045714 8.045714zM731.428571 911.945143a36.571429 36.571429 0 0 1-36.571428-36.571429v-109.714285H585.142857a36.571429 36.571429 0 0 1 0-73.142858h109.714286v-109.714285a36.571429 36.571429 0 0 1 73.142857 0v109.714285H877.714286a36.571429 36.571429 0 1 1 0 73.142858h-109.714286v109.714285a36.571429 36.571429 0 0 1-36.571429 36.571429z" p-id="22184"></path></svg>
|
||||||
|
<div style="margin-top: 10px">科学实验</div>
|
||||||
|
</div>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<!-- 作业列表 -->
|
||||||
|
<div class="c-apt-r" v-loading='loadingActive'>
|
||||||
|
<!-- 显示-作业内容 -->
|
||||||
|
<template v-for="(item, index) in slidesStore.workList" :key="index">
|
||||||
|
<div class="item">
|
||||||
|
<div class="item-title">
|
||||||
|
<el-tag :type="getTagType(item.worktype) || 'primary'">{{item.worktype}}</el-tag>
|
||||||
|
<el-tooltip :content="item.uniquekey" placement="top">
|
||||||
|
<div class="tt">{{item.uniquekey}}</div>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-button class="btn-del" type="danger" link @click="removeWork(item, index)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<!-- // 推送作业 -->
|
||||||
|
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="85%" height="500">
|
||||||
|
<el-scrollbar>
|
||||||
|
<div style="height: 75vh;">
|
||||||
|
<NewClassTsakAssign :currentCourse='currentCourse' @getData="getData" />
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 活动引用 -->
|
||||||
|
<el-dialog
|
||||||
|
v-loading="tasklist_loading"
|
||||||
|
v-model="activeVisible"
|
||||||
|
append-to-body
|
||||||
|
:show-close="false"
|
||||||
|
width="40%"
|
||||||
|
>
|
||||||
|
<el-table :data="taskList" style="width: 100%" height="500" @selection-change="handleSelectionChange">
|
||||||
|
<el-table-column type="selection" width="55" :selectable="selectable"/>
|
||||||
|
<el-table-column prop="uniquekey" label="活动名称" width="150" />
|
||||||
|
<el-table-column prop="worktype" label="活动类型" width="120" sortable>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getTagType(scope.row.worktype) || 'primary'">{{ scope.row.worktype }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="timestamp" label="创建时间" sortable/>
|
||||||
|
</el-table>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="activeVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="savePPtData">确 定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, nextTick, watch } from 'vue'
|
||||||
|
import Divider from '../../../../../components/Divider.vue'
|
||||||
|
import { ElMessageBox,ElMessage } from 'element-plus'
|
||||||
|
import NewClassTsakAssign from '@/views/classTask/newClassTaskAssign/index.vue'
|
||||||
|
import { sessionStore } from '@/utils/store'
|
||||||
|
import { useGetHomework } from '@/hooks/useGetHomework'
|
||||||
|
import { PPTApi } from '../../../../../api/index'
|
||||||
|
import {useSlidesStore} from '../../../../../store'
|
||||||
|
const slidesStore = useSlidesStore()
|
||||||
|
interface CourseNode {
|
||||||
|
rootid: number;
|
||||||
|
parentNode: { id: number };
|
||||||
|
id: number;
|
||||||
|
itemtitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CurrentCourse {
|
||||||
|
textbookId: number;
|
||||||
|
levelFirstId: number;
|
||||||
|
levelSecondId: number;
|
||||||
|
coursetitle: string;
|
||||||
|
node: CourseNode;
|
||||||
|
id: number;
|
||||||
|
worktype: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkType {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkItem {
|
||||||
|
status: string;
|
||||||
|
activityContent?: string;
|
||||||
|
worktype: string;
|
||||||
|
quizlist?: { id: number }[];
|
||||||
|
workcodes: string;
|
||||||
|
base64?: string;
|
||||||
|
prevData?: any;
|
||||||
|
id: number;
|
||||||
|
uniquekey?: string; // 添加 evaltitle 属性
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentCourse = reactive<CurrentCourse>({
|
||||||
|
textbookId: 0,
|
||||||
|
levelFirstId: 0,
|
||||||
|
levelSecondId: 0,
|
||||||
|
coursetitle: '',
|
||||||
|
node: {} as CourseNode,
|
||||||
|
id: 1,
|
||||||
|
worktype: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const tasklist_loading = ref<boolean>(false)
|
||||||
|
|
||||||
|
// 活动列表
|
||||||
|
const taskList = ref<WorkItem[]>([])
|
||||||
|
|
||||||
|
// 活动引用的弹窗
|
||||||
|
const activeVisible = ref<boolean>(false)
|
||||||
|
|
||||||
|
|
||||||
|
const type = ref<WorkType[]>([
|
||||||
|
{
|
||||||
|
label: '习题训练',
|
||||||
|
value: 'danger'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '课堂展示',
|
||||||
|
value: 'success'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '常规作业',
|
||||||
|
value: 'primary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '框架梳理',
|
||||||
|
value: 'primary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '科学实验',
|
||||||
|
value: 'primary'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 获取所选择的作业列表
|
||||||
|
const selectedWorkList = ref<WorkItem[]>([])
|
||||||
|
// 活动页面的loading框
|
||||||
|
const loadingActive = ref<boolean>(false)
|
||||||
|
|
||||||
|
// 选择活动列表
|
||||||
|
const selectable = (row: WorkItem, index: number): boolean => {
|
||||||
|
return row.status === '10';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除作业
|
||||||
|
const removeWork = (item: WorkItem, index: number) => {
|
||||||
|
ElMessageBox.confirm('是否确认删除?')
|
||||||
|
.then(async() => {
|
||||||
|
// 从workList中删除
|
||||||
|
// slidesStore.workList.splice(index, 1)
|
||||||
|
console.log('删除作业', item)
|
||||||
|
await upDateData('del', [item.id])
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
})
|
||||||
|
.catch(() => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取tag的样式
|
||||||
|
const getTagType = (worktype: string): string => {
|
||||||
|
return type.value.find(item => item.label === worktype)!.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取活动引用的列表数据
|
||||||
|
const initHomeWork = async () => {
|
||||||
|
tasklist_loading.value = true;
|
||||||
|
const { res, chapterId } = await useGetHomework(sessionStore.get('subject.curNode'));
|
||||||
|
taskList.value = res;
|
||||||
|
tasklist_loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多选活动引用
|
||||||
|
const handleSelectionChange = (val: WorkItem[]) => {
|
||||||
|
selectedWorkList.value = [...val]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开添加作业活动
|
||||||
|
const showDialog = (item: string) => {
|
||||||
|
currentCourse.worktype = item
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开活动引用列表
|
||||||
|
const openList = () => {
|
||||||
|
activeVisible.value = true
|
||||||
|
initHomeWork()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加活动引用列表作业
|
||||||
|
const savePPtData = async () => {
|
||||||
|
if (!selectedWorkList.value.length) {
|
||||||
|
ElMessage.warning('请选择活动')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const arr = selectedWorkList.value.map(item => item.id)
|
||||||
|
await upDateData('add', arr)
|
||||||
|
activeVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收习题训练的值
|
||||||
|
const getData = async (data: WorkItem) => {
|
||||||
|
await upDateData('add', [data.id])
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
// 更新数据-活动 type add/增加 | del/删除 ids 作业id
|
||||||
|
const upDateData = (type: string, ids: number[]): Promise<any> => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
loadingActive.value = true
|
||||||
|
// 更新本地数据
|
||||||
|
let workIds = slidesStore.workList.map((o:any) => o.id)
|
||||||
|
const id = slidesStore.currentSlide.id
|
||||||
|
if (type === 'del') { // 删除
|
||||||
|
workIds = workIds.filter(id => !ids.includes(id))
|
||||||
|
} else { // 增加
|
||||||
|
workIds = Array.from(new Set([...workIds, ...ids]))
|
||||||
|
}
|
||||||
|
// 保存到本地
|
||||||
|
const wItem:any = slidesStore.workItem.find((o:any) => o.id === id)
|
||||||
|
const workIdsStr = workIds.join(',') // 作业id->字符串
|
||||||
|
if (!!wItem) wItem.activityContent = workIdsStr
|
||||||
|
const data = { id, activityContent: workIdsStr }
|
||||||
|
// 修改数据库
|
||||||
|
await PPTApi.updateSlide(data, false)
|
||||||
|
// 刷新活动列表
|
||||||
|
await PPTApi.updateWorkList()
|
||||||
|
loadingActive.value = false
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const curNode = sessionStore.get('subject.curNode') as CourseNode
|
||||||
|
currentCourse.textbookId = curNode.rootid
|
||||||
|
currentCourse.levelFirstId = curNode.parentNode.id
|
||||||
|
currentCourse.levelSecondId = curNode.id
|
||||||
|
currentCourse.coursetitle = curNode.itemtitle
|
||||||
|
currentCourse.node = curNode
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.buttonDiv{
|
||||||
|
margin-top: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
// (apt)编辑区-右侧
|
||||||
|
.c-apt-r{
|
||||||
|
flex:1;
|
||||||
|
overflow: auto;
|
||||||
|
.item{
|
||||||
|
position: relative;
|
||||||
|
margin-top: 5px;
|
||||||
|
.item-title{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.tt{
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 10px;
|
||||||
|
cursor: default;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.btn-del{
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-body{
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid silver;
|
||||||
|
padding: 10px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 5px 10px;
|
||||||
|
cursor: default;
|
||||||
|
&:hover{
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
.el-text{
|
||||||
|
--el-text-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
.el-tag{
|
||||||
|
--el-tag-bg-color: var(--el-color-primary);
|
||||||
|
--el-tag-border-color: var(--el-color-primary);
|
||||||
|
--el-tag-text-color: var(--el-color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.c-icon-info{
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: -5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-divider{
|
||||||
|
margin: 5px 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
--el-border-color: var(--current-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.page .page-resource{
|
||||||
|
height: 500px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -88,7 +88,7 @@ import Button from '../../../../components/Button.vue'
|
||||||
import ButtonGroup from '../../../../components/ButtonGroup.vue'
|
import ButtonGroup from '../../../../components/ButtonGroup.vue'
|
||||||
import Popover from '../../../../components/Popover.vue'
|
import Popover from '../../../../components/Popover.vue'
|
||||||
import NumberInput from '../../../../components/NumberInput.vue'
|
import NumberInput from '../../../../components/NumberInput.vue'
|
||||||
|
import { PPTApi } from '../../../../api'
|
||||||
const shapeClipPathOptions = CLIPPATHS
|
const shapeClipPathOptions = CLIPPATHS
|
||||||
const ratioClipOptions = [
|
const ratioClipOptions = [
|
||||||
{
|
{
|
||||||
|
@ -221,10 +221,14 @@ const presetImageClip = (shape: string, ratio = 0) => {
|
||||||
const replaceImage = (files: FileList) => {
|
const replaceImage = (files: FileList) => {
|
||||||
const imageFile = files[0]
|
const imageFile = files[0]
|
||||||
if (!imageFile) return
|
if (!imageFile) return
|
||||||
getImageDataURL(imageFile).then(dataURL => {
|
PPTApi.toRousrceUrl(imageFile).then(data=>{
|
||||||
const props = { src: dataURL }
|
const props = { src: data }
|
||||||
updateImage(props)
|
updateImage(props)
|
||||||
})
|
})
|
||||||
|
// getImageDataURL(imageFile).then(dataURL => {
|
||||||
|
// const props = { src: dataURL }
|
||||||
|
// updateImage(props)
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置图片:清除全部样式
|
// 重置图片:清除全部样式
|
||||||
|
|
|
@ -25,6 +25,8 @@ import SlideDesignPanel from './SlideDesignPanel.vue'
|
||||||
import SlideAnimationPanel from './SlideAnimationPanel.vue'
|
import SlideAnimationPanel from './SlideAnimationPanel.vue'
|
||||||
import MultiPositionPanel from './MultiPositionPanel.vue'
|
import MultiPositionPanel from './MultiPositionPanel.vue'
|
||||||
import SymbolPanel from './SymbolPanel.vue'
|
import SymbolPanel from './SymbolPanel.vue'
|
||||||
|
// 新增的活动页面
|
||||||
|
import SymbolActivePanel from './ElementStylePanel/Active/index.vue'
|
||||||
import Tabs from '../../../components/Tabs.vue'
|
import Tabs from '../../../components/Tabs.vue'
|
||||||
|
|
||||||
interface ElementTabs {
|
interface ElementTabs {
|
||||||
|
@ -54,6 +56,7 @@ const slideTabs = [
|
||||||
{ label: '设计', key: ToolbarStates.SLIDE_DESIGN },
|
{ label: '设计', key: ToolbarStates.SLIDE_DESIGN },
|
||||||
{ label: '切换', key: ToolbarStates.SLIDE_ANIMATION },
|
{ label: '切换', key: ToolbarStates.SLIDE_ANIMATION },
|
||||||
{ label: '动画', key: ToolbarStates.EL_ANIMATION },
|
{ label: '动画', key: ToolbarStates.EL_ANIMATION },
|
||||||
|
{ label: '活动', key: ToolbarStates.EL_ACTIVE },
|
||||||
]
|
]
|
||||||
const multiSelectTabs = [
|
const multiSelectTabs = [
|
||||||
{ label: '样式', key: ToolbarStates.EL_STYLE },
|
{ label: '样式', key: ToolbarStates.EL_STYLE },
|
||||||
|
@ -86,6 +89,7 @@ const currentPanelComponent = computed(() => {
|
||||||
[ToolbarStates.SLIDE_ANIMATION]: SlideAnimationPanel,
|
[ToolbarStates.SLIDE_ANIMATION]: SlideAnimationPanel,
|
||||||
[ToolbarStates.MULTI_POSITION]: MultiPositionPanel,
|
[ToolbarStates.MULTI_POSITION]: MultiPositionPanel,
|
||||||
[ToolbarStates.SYMBOL]: SymbolPanel,
|
[ToolbarStates.SYMBOL]: SymbolPanel,
|
||||||
|
[ToolbarStates.EL_ACTIVE]: SymbolActivePanel,// 新增的活动页面
|
||||||
}
|
}
|
||||||
return panelMap[toolbarState.value] || null
|
return panelMap[toolbarState.value] || null
|
||||||
})
|
})
|
||||||
|
|
|
@ -30,15 +30,14 @@
|
||||||
@close="timerlVisible = false"
|
@close="timerlVisible = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="tools-left">
|
<div class="tools-left" v-if="!classcourse">
|
||||||
<IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" />
|
<IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" />
|
||||||
<IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" />
|
<IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="tools-right" :class="{ 'visible': rightToolsVisible }"
|
class="tools-right" :class="{ 'visible': rightToolsVisible }"
|
||||||
@mouseleave="rightToolsVisible = false"
|
@mouseleave="toolTrigger('leave')"
|
||||||
@mouseenter="rightToolsVisible = true"
|
@mouseenter="toolTrigger('enter')"
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="tool-btn page-number" @click="slideThumbnailModelVisible = true">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
|
<div class="tool-btn page-number" @click="slideThumbnailModelVisible = true">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
|
||||||
|
@ -48,16 +47,22 @@
|
||||||
<IconListView class="tool-btn" v-tooltip="'演讲者视图'" @click="changeViewMode('presenter')" />
|
<IconListView class="tool-btn" v-tooltip="'演讲者视图'" @click="changeViewMode('presenter')" />
|
||||||
<IconOffScreenOne class="tool-btn" v-tooltip="'退出全屏'" v-if="fullscreenState" @click="manualExitFullscreen()" />
|
<IconOffScreenOne class="tool-btn" v-tooltip="'退出全屏'" v-if="fullscreenState" @click="manualExitFullscreen()" />
|
||||||
<IconFullScreenOne class="tool-btn" v-tooltip="'进入全屏'" v-else @click="enterFullscreen()" />
|
<IconFullScreenOne class="tool-btn" v-tooltip="'进入全屏'" v-else @click="enterFullscreen()" />
|
||||||
<IconPower class="tool-btn" v-tooltip="'结束放映'" @click="exitScreening()" />
|
<IconPower class="tool-btn" v-if="!classcourse" v-tooltip="'结束放映'" @click="exitScreening()" />
|
||||||
|
<IconPower class="tool-btn" v-else v-tooltip="'结束课堂'" @click="exitCourse()" size="30" fill="#d0021b" strokeLinecap="butt" />
|
||||||
|
<Share class="tool-btn" v-if="classcourse" v-tooltip="'分享'" @click="ShareCode()" />
|
||||||
|
</div>
|
||||||
|
<div :class="['tools-icon',{opacity:iconHide}]" @click.stop="toolTrigger('icon')">
|
||||||
|
<circle-double-down v-if="rightToolsVisible" theme="outline" size="30" fill="#409EFF"/>
|
||||||
|
<circle-double-up v-else="!rightToolsVisible" theme="outline" size="30" fill="#E6A23C"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref , watchEffect, onMounted, onUnmounted} from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '../../store'
|
import { useSlidesStore ,useScreenStore, useClasscourseStore} from '../../store'
|
||||||
import type { ContextmenuItem } from '../../components/Contextmenu/types'
|
import type { ContextmenuItem } from '../../components/Contextmenu/types'
|
||||||
import { enterFullscreen } from '../../utils/fullscreen'
|
import { enterFullscreen } from '../../utils/fullscreen'
|
||||||
import useScreening from '../../hooks/useScreening'
|
import useScreening from '../../hooks/useScreening'
|
||||||
|
@ -69,12 +74,17 @@ import ScreenSlideList from './ScreenSlideList.vue'
|
||||||
import SlideThumbnails from './SlideThumbnails.vue'
|
import SlideThumbnails from './SlideThumbnails.vue'
|
||||||
import WritingBoardTool from './WritingBoardTool.vue'
|
import WritingBoardTool from './WritingBoardTool.vue'
|
||||||
import CountdownTimer from './CountdownTimer.vue'
|
import CountdownTimer from './CountdownTimer.vue'
|
||||||
|
import emitter from '@/utils/mitt';
|
||||||
|
import Chat from '../../api/chat' // 聊天
|
||||||
|
import { CircleDoubleDown, CircleDoubleUp, Share } from '@icon-park/vue-next' // icon-park 图标库
|
||||||
|
import { ShareCode } from '@/utils/ppt' // ppt相关
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
changeViewMode: (mode: 'base' | 'presenter') => void
|
changeViewMode: (mode: 'base' | 'presenter') => void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { slides, slideIndex } = storeToRefs(useSlidesStore())
|
const { slides, slideIndex } = storeToRefs(useSlidesStore())
|
||||||
|
const { classcourse, isEmit } = storeToRefs(useClasscourseStore()) // 课堂信息
|
||||||
|
|
||||||
const {
|
const {
|
||||||
autoPlayTimer,
|
autoPlayTimer,
|
||||||
|
@ -94,18 +104,22 @@ const {
|
||||||
execPrev,
|
execPrev,
|
||||||
execNext,
|
execNext,
|
||||||
animationIndex,
|
animationIndex,
|
||||||
|
turning,
|
||||||
} = useExecPlay()
|
} = useExecPlay()
|
||||||
|
|
||||||
const { slideWidth, slideHeight } = useSlideSize()
|
const { slideWidth, slideHeight } = useSlideSize()
|
||||||
const { exitScreening } = useScreening()
|
const { exitScreening } = useScreening()
|
||||||
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
||||||
|
const chat:any = Chat() // 聊天室
|
||||||
|
|
||||||
|
const screenStore =useScreenStore()
|
||||||
const rightToolsVisible = ref(false)
|
const rightToolsVisible = ref(false)
|
||||||
const writingBoardToolVisible = ref(false)
|
const writingBoardToolVisible = ref(false)
|
||||||
const timerlVisible = ref(false)
|
const timerlVisible = ref(false)
|
||||||
const slideThumbnailModelVisible = ref(false)
|
const slideThumbnailModelVisible = ref(false)
|
||||||
const laserPen = ref(false)
|
const laserPen = ref(false)
|
||||||
|
const timer = ref(0) // 记录操作时间
|
||||||
|
const iconHide = ref(false) // 工具栏图标是否显示
|
||||||
|
const timerId = ref(null) // 定时器id
|
||||||
const contextmenus = (): ContextmenuItem[] => {
|
const contextmenus = (): ContextmenuItem[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -187,6 +201,38 @@ const contextmenus = (): ContextmenuItem[] => {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
// 工具栏按钮触发
|
||||||
|
const toolTrigger = (type:string) => {
|
||||||
|
const curT = Date.now()
|
||||||
|
if (curT - timer.value < 200) return
|
||||||
|
iconHide.value = false // 显示图标按钮
|
||||||
|
if (timerId.value) clearTimeout(timerId.value) // 清除定时器
|
||||||
|
switch (type) {
|
||||||
|
case 'icon': // 点击图标
|
||||||
|
timer.value = curT
|
||||||
|
rightToolsVisible.value = !rightToolsVisible.value
|
||||||
|
break
|
||||||
|
case 'enter': // 移入
|
||||||
|
timer.value = curT
|
||||||
|
rightToolsVisible.value = true
|
||||||
|
break
|
||||||
|
case 'leave': // 移出
|
||||||
|
rightToolsVisible.value = false
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
timerId.value = setTimeout(() => { // 定时器
|
||||||
|
iconHide.value = true // 隐藏图标按钮
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
// 下课
|
||||||
|
const exitCourse = async () => {
|
||||||
|
// console.log('下课', chat)
|
||||||
|
await chat.exitCourse() // 下课消息
|
||||||
|
exitScreening() // 结束放映
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -242,6 +288,18 @@ const contextmenus = (): ContextmenuItem[] => {
|
||||||
top: -66px;
|
top: -66px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tools-icon{
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: -35px;
|
||||||
|
z-index: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity $transitionDelay;
|
||||||
|
&.opacity{
|
||||||
|
opacity: .35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -267,6 +325,9 @@ const contextmenus = (): ContextmenuItem[] => {
|
||||||
& + .tool-btn {
|
& + .tool-btn {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
&.close{
|
||||||
|
color: #d14424;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.page-number {
|
.page-number {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
<IconOffScreenOne class="tool-icon" v-else />
|
<IconOffScreenOne class="tool-icon" v-else />
|
||||||
<span>{{ fullscreenState ? '退出全屏' : '全屏' }}</span>
|
<span>{{ fullscreenState ? '退出全屏' : '全屏' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tool-btn" @click="ShareCode()"><Share class="tool-icon" /><span>分享</span></div>
|
||||||
<Divider class="divider" />
|
<Divider class="divider" />
|
||||||
<div class="tool-btn" @click="exitScreening()"><IconPower class="tool-icon" /><span>结束放映</span></div>
|
<div class="tool-btn" v-if="!classcourse" @click="exitScreening()"><IconPower class="tool-icon" /><span>结束放映</span></div>
|
||||||
|
<div class="tool-btn" v-else @click="exitCourse()" size="30" fill="#d0021b" strokeLinecap="butt"><IconPower class="tool-icon" /><span>结束课堂</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -55,7 +57,7 @@
|
||||||
:class="{ 'active': index === slideIndex }"
|
:class="{ 'active': index === slideIndex }"
|
||||||
v-for="(slide, index) in slides"
|
v-for="(slide, index) in slides"
|
||||||
:key="slide.id"
|
:key="slide.id"
|
||||||
@click="turnSlideToIndex(index)"
|
@click="turnSlideTo(index, $event)"
|
||||||
>
|
>
|
||||||
<ThumbnailSlide :slide="slide" :size="120 / viewportRatio" :visible="index < slidesLoadLimit" />
|
<ThumbnailSlide :slide="slide" :size="120 / viewportRatio" :visible="index < slidesLoadLimit" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -77,9 +79,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, nextTick, ref, watch } from 'vue'
|
import { Share } from '@icon-park/vue-next' // icon-park 图标库
|
||||||
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '../../store'
|
import { useSlidesStore, useClasscourseStore } from '../../store'
|
||||||
import type { ContextmenuItem } from '../../components/Contextmenu/types'
|
import type { ContextmenuItem } from '../../components/Contextmenu/types'
|
||||||
import { enterFullscreen } from '../../utils/fullscreen'
|
import { enterFullscreen } from '../../utils/fullscreen'
|
||||||
import { parseText2Paragraphs } from '../../utils/textParser'
|
import { parseText2Paragraphs } from '../../utils/textParser'
|
||||||
|
@ -94,12 +97,16 @@ import ScreenSlideList from './ScreenSlideList.vue'
|
||||||
import WritingBoardTool from './WritingBoardTool.vue'
|
import WritingBoardTool from './WritingBoardTool.vue'
|
||||||
import CountdownTimer from './CountdownTimer.vue'
|
import CountdownTimer from './CountdownTimer.vue'
|
||||||
import Divider from '../../components/Divider.vue'
|
import Divider from '../../components/Divider.vue'
|
||||||
|
import emitter from '@/utils/mitt';
|
||||||
|
import Chat from '../../api/chat' // 聊天
|
||||||
|
import { ShareCode } from '@/utils/ppt' // ppt相关
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
changeViewMode: (mode: 'base' | 'presenter') => void
|
changeViewMode: (mode: 'base' | 'presenter') => void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { slides, slideIndex, viewportRatio, currentSlide } = storeToRefs(useSlidesStore())
|
const { slides, slideIndex, viewportRatio, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
|
const { classcourse, isEmit } = storeToRefs(useClasscourseStore()) // 课堂信息
|
||||||
|
|
||||||
const slideListWrapRef = ref<HTMLElement>()
|
const slideListWrapRef = ref<HTMLElement>()
|
||||||
const thumbnailsRef = ref<HTMLElement>()
|
const thumbnailsRef = ref<HTMLElement>()
|
||||||
|
@ -117,17 +124,36 @@ const {
|
||||||
turnSlideToId,
|
turnSlideToId,
|
||||||
animationIndex,
|
animationIndex,
|
||||||
} = useExecPlay()
|
} = useExecPlay()
|
||||||
|
|
||||||
const { slideWidth, slideHeight } = useSlideSize(slideListWrapRef)
|
const { slideWidth, slideHeight } = useSlideSize(slideListWrapRef)
|
||||||
const { exitScreening } = useScreening()
|
const { exitScreening } = useScreening()
|
||||||
const { slidesLoadLimit } = useLoadSlides()
|
const { slidesLoadLimit } = useLoadSlides()
|
||||||
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
||||||
|
const chatApi:any = Chat() // 聊天室
|
||||||
|
|
||||||
const remarkFontSize = ref(16)
|
const remarkFontSize = ref(16)
|
||||||
const currentSlideRemark = computed(() => {
|
const currentSlideRemark = computed(() => {
|
||||||
return parseText2Paragraphs(currentSlide.value.remark || '无备注')
|
return parseText2Paragraphs(currentSlide.value.remark || '无备注')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 切换到指定的幻灯片
|
||||||
|
const turnSlideTo = (index: number, e: PointerEvent) => {
|
||||||
|
const preInd = slideIndex.value
|
||||||
|
turnSlideToIndex(index)
|
||||||
|
if (!!classcourse.value) {// 上课中
|
||||||
|
if (preInd == index) return
|
||||||
|
const animationSteps = 0
|
||||||
|
const animation = index > preInd?'Nextsteps':'Previoustep'
|
||||||
|
const msg = { current:index, animation, animationSteps}
|
||||||
|
chatApi.slideFlapping(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 下课
|
||||||
|
const exitCourse = async () => {
|
||||||
|
// console.log('下课', chat)
|
||||||
|
await chatApi.exitCourse() // 下课消息
|
||||||
|
exitScreening() // 结束放映
|
||||||
|
}
|
||||||
|
|
||||||
const handleMousewheelThumbnails = (e: WheelEvent) => {
|
const handleMousewheelThumbnails = (e: WheelEvent) => {
|
||||||
if (!thumbnailsRef.value) return
|
if (!thumbnailsRef.value) return
|
||||||
thumbnailsRef.value.scrollBy(e.deltaY, 0)
|
thumbnailsRef.value.scrollBy(e.deltaY, 0)
|
||||||
|
@ -192,6 +218,7 @@ const contextmenus = (): ContextmenuItem[] => {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -208,7 +235,7 @@ const contextmenus = (): ContextmenuItem[] => {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-right: solid 1px #eee;
|
border-right: solid 1px #eee;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin: 20px 0;
|
padding: 20px 0;
|
||||||
|
|
||||||
.tool-btn {
|
.tool-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -224,6 +251,9 @@ const contextmenus = (): ContextmenuItem[] => {
|
||||||
&:hover, &.active {
|
&:hover, &.active {
|
||||||
color: $themeColor;
|
color: $themeColor;
|
||||||
}
|
}
|
||||||
|
&.close{
|
||||||
|
color: #d14424;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { throttle } from 'lodash'
|
import { throttle } from 'lodash'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '../../../store'
|
import { useSlidesStore, useClasscourseStore } from '../../../store'
|
||||||
import { KEYS } from '../../../configs/hotkey'
|
import { KEYS } from '../../../configs/hotkey'
|
||||||
import { ANIMATION_CLASS_PREFIX } from '../../../configs/animation'
|
import { ANIMATION_CLASS_PREFIX } from '../../../configs/animation'
|
||||||
import message from '../../../utils/message'
|
import message from '../../../utils/message'
|
||||||
|
import emitter from '@/utils/mitt';
|
||||||
|
import Chat from '../../../api/chat' // 聊天封装
|
||||||
|
// import ChatWs from '@/plugins/socket' // 聊天socket
|
||||||
|
// import { MsgEnum } from '../../../api/types' // 消息枚举
|
||||||
|
|
||||||
export default () => {
|
export default (isLoader?: boolean = true) => {
|
||||||
|
// isLoader 是否执行 onMounted, onUnmounted
|
||||||
|
const chatApi = Chat()
|
||||||
const slidesStore = useSlidesStore()
|
const slidesStore = useSlidesStore()
|
||||||
const { slides, slideIndex, formatedAnimations } = storeToRefs(slidesStore)
|
const classcourseStore = useClasscourseStore() // 课堂信息-状态管理
|
||||||
|
const { slides, slideIndex, formatedAnimations, animationIndex } = storeToRefs(slidesStore)
|
||||||
|
|
||||||
// 当前页的元素动画执行到的位置
|
// 当前页的元素动画执行到的位置
|
||||||
const animationIndex = ref(0)
|
// const animationIndex = ref(0)
|
||||||
|
|
||||||
// 动画执行状态
|
// 动画执行状态
|
||||||
const inAnimation = ref(false)
|
const inAnimation = ref(false)
|
||||||
|
@ -19,10 +26,11 @@ export default () => {
|
||||||
// 最小已播放页面索引
|
// 最小已播放页面索引
|
||||||
const playedSlidesMinIndex = ref(slideIndex.value)
|
const playedSlidesMinIndex = ref(slideIndex.value)
|
||||||
|
|
||||||
// 执行元素动画
|
// 执行元素动画 isAsync 为 true 时,异步执行,否则同步执行
|
||||||
const runAnimation = () => {
|
const runAnimation = (isAsync: boolean) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
// 正在执行动画时,禁止其他新的动画开始
|
// 正在执行动画时,禁止其他新的动画开始
|
||||||
if (inAnimation.value) return
|
if (inAnimation.value && !isAsync) return resolve()
|
||||||
|
|
||||||
const { animations, autoNext } = formatedAnimations.value[animationIndex.value]
|
const { animations, autoNext } = formatedAnimations.value[animationIndex.value]
|
||||||
animationIndex.value += 1
|
animationIndex.value += 1
|
||||||
|
@ -53,7 +61,7 @@ export default () => {
|
||||||
elRef.classList.add(animationName, `${ANIMATION_CLASS_PREFIX}animated`)
|
elRef.classList.add(animationName, `${ANIMATION_CLASS_PREFIX}animated`)
|
||||||
|
|
||||||
// 执行动画结束,将“退场”以外的动画状态清除
|
// 执行动画结束,将“退场”以外的动画状态清除
|
||||||
const handleAnimationEnd = () => {
|
const handleAnimationEnd = async() => {
|
||||||
if (animation.type !== 'out') {
|
if (animation.type !== 'out') {
|
||||||
elRef.style.removeProperty('--animate-duration')
|
elRef.style.removeProperty('--animate-duration')
|
||||||
elRef.classList.remove(animationName, `${ANIMATION_CLASS_PREFIX}animated`)
|
elRef.classList.remove(animationName, `${ANIMATION_CLASS_PREFIX}animated`)
|
||||||
|
@ -63,13 +71,15 @@ export default () => {
|
||||||
endAnimationCount += 1
|
endAnimationCount += 1
|
||||||
if (endAnimationCount === animations.length) {
|
if (endAnimationCount === animations.length) {
|
||||||
inAnimation.value = false
|
inAnimation.value = false
|
||||||
if (autoNext) runAnimation()
|
if (autoNext) await runAnimation()
|
||||||
|
resolve() // 执行完成
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
|
elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
if (isLoader) { // 加载相关钩子
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const firstAnimations = formatedAnimations.value[0]
|
const firstAnimations = formatedAnimations.value[0]
|
||||||
if (firstAnimations && firstAnimations.animations.length) {
|
if (firstAnimations && firstAnimations.animations.length) {
|
||||||
|
@ -77,6 +87,7 @@ export default () => {
|
||||||
if (autoExecFirstAnimations) runAnimation()
|
if (autoExecFirstAnimations) runAnimation()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 撤销元素动画,除了将索引前移外,还需要清除动画状态
|
// 撤销元素动画,除了将索引前移外,还需要清除动画状态
|
||||||
const revokeAnimation = () => {
|
const revokeAnimation = () => {
|
||||||
|
@ -121,9 +132,9 @@ export default () => {
|
||||||
// 遇到元素动画时,优先执行动画播放,无动画则执行翻页
|
// 遇到元素动画时,优先执行动画播放,无动画则执行翻页
|
||||||
// 向上播放遇到动画时,仅撤销到动画执行前的状态,不需要反向播放动画
|
// 向上播放遇到动画时,仅撤销到动画执行前的状态,不需要反向播放动画
|
||||||
// 撤回到上一页时,若该页从未播放过(意味着不存在动画状态),需要将动画索引置为最小值(初始状态),否则置为最大值(最终状态)
|
// 撤回到上一页时,若该页从未播放过(意味着不存在动画状态),需要将动画索引置为最小值(初始状态),否则置为最大值(最终状态)
|
||||||
const execPrev = () => {
|
const execPrev = (isAsync: boolean) => {
|
||||||
if (formatedAnimations.value.length && animationIndex.value > 0) {
|
if (formatedAnimations.value.length && animationIndex.value > 0) {
|
||||||
revokeAnimation()
|
revokeAnimation(isAsync)
|
||||||
}
|
}
|
||||||
else if (slideIndex.value > 0) {
|
else if (slideIndex.value > 0) {
|
||||||
slidesStore.updateSlideIndex(slideIndex.value - 1)
|
slidesStore.updateSlideIndex(slideIndex.value - 1)
|
||||||
|
@ -139,9 +150,9 @@ export default () => {
|
||||||
}
|
}
|
||||||
inAnimation.value = false
|
inAnimation.value = false
|
||||||
}
|
}
|
||||||
const execNext = () => {
|
const execNext = async(isAsync: boolean) => {
|
||||||
if (formatedAnimations.value.length && animationIndex.value < formatedAnimations.value.length) {
|
if (formatedAnimations.value.length && animationIndex.value < formatedAnimations.value.length) {
|
||||||
runAnimation()
|
runAnimation(isAsync)
|
||||||
}
|
}
|
||||||
else if (slideIndex.value < slides.value.length - 1) {
|
else if (slideIndex.value < slides.value.length - 1) {
|
||||||
slidesStore.updateSlideIndex(slideIndex.value + 1)
|
slidesStore.updateSlideIndex(slideIndex.value + 1)
|
||||||
|
@ -173,50 +184,76 @@ export default () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 鼠标滚动翻页
|
// 鼠标滚动翻页
|
||||||
const mousewheelListener = throttle(function(e: WheelEvent) {
|
const mousewheelListener = (e: WheelEvent) => {
|
||||||
if (e.deltaY < 0) execPrev()
|
// console.log('mousewheel', e)
|
||||||
else if (e.deltaY > 0) execNext()
|
e.preventDefault() // 阻止默认事件
|
||||||
|
mousewheelListenerThrottle(e)
|
||||||
|
}
|
||||||
|
const mousewheelListenerThrottle = throttle(function(e: WheelEvent) {
|
||||||
|
if (e.deltaY < 0) turning(e, 'prev')
|
||||||
|
else if (e.deltaY > 0) turning(e, 'next')
|
||||||
}, 500, { leading: true, trailing: false })
|
}, 500, { leading: true, trailing: false })
|
||||||
|
|
||||||
// 触摸屏上下滑动翻页
|
// 触摸屏上下滑动翻页
|
||||||
const touchInfo = ref<{ x: number; y: number; } | null>(null)
|
const touchInfo = ref<{ x: number; y: number; } | null>(null)
|
||||||
|
|
||||||
const touchStartListener = (e: TouchEvent) => {
|
const touchStartListener = (e: TouchEvent) => {
|
||||||
|
e.preventDefault() // 阻止默认事件
|
||||||
touchInfo.value = {
|
touchInfo.value = {
|
||||||
x: e.changedTouches[0].pageX,
|
// x: e.changedTouches[0].pageX,
|
||||||
y: e.changedTouches[0].pageY,
|
// y: e.changedTouches[0].pageY,
|
||||||
|
x: e.changedTouches[0].clientX,
|
||||||
|
y: e.changedTouches[0].clientY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const touchEndListener = (e: TouchEvent) => {
|
const touchEndListener = (e: TouchEvent) => {
|
||||||
if (!touchInfo.value) return
|
if (!touchInfo.value) return
|
||||||
|
const offsetX = Math.abs(touchInfo.value.x - e.changedTouches[0].clientX)
|
||||||
const offsetX = Math.abs(touchInfo.value.x - e.changedTouches[0].pageX)
|
const offsetY = e.changedTouches[0].clientY - touchInfo.value.y
|
||||||
const offsetY = e.changedTouches[0].pageY - touchInfo.value.y
|
|
||||||
|
|
||||||
if ( Math.abs(offsetY) > offsetX && Math.abs(offsetY) > 50 ) {
|
if ( Math.abs(offsetY) > offsetX && Math.abs(offsetY) > 50 ) {
|
||||||
touchInfo.value = null
|
touchInfo.value = null
|
||||||
|
if (offsetY > 0) turning(e, 'prev')
|
||||||
if (offsetY > 0) execPrev()
|
else turning(e, 'next')
|
||||||
else execNext()
|
}
|
||||||
|
}
|
||||||
|
// 向上翻页/向下翻页
|
||||||
|
const turning = async (e, type) => {
|
||||||
|
e.preventDefault() // 阻止默认事件
|
||||||
|
window.scrollTo(0, 0) // 滚动到顶部
|
||||||
|
const isCourse = !!classcourseStore.classcourse
|
||||||
|
if (type === 'prev') {
|
||||||
|
if (!isCourse) execPrev() // 上一步
|
||||||
|
else { // 上课状态: 上一步 动作变成 上一页
|
||||||
|
const current = slideIndex.value
|
||||||
|
if (current <= 0) return throttleMassage('已经是第一页了')
|
||||||
|
turnSlideToIndex(current - 1) // 翻页: 上一页
|
||||||
|
}
|
||||||
|
} else if (type === 'next') execNext()
|
||||||
|
if (isCourse) { // 上课中
|
||||||
|
const current = slideIndex.value
|
||||||
|
const animationSteps = animationIndex.value
|
||||||
|
const animation = type == 'next'?'Nextsteps':'Previoustep'
|
||||||
|
const msg = { current, animation, animationSteps}
|
||||||
|
chatApi.slideFlapping(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 快捷键翻页
|
// 快捷键翻页
|
||||||
const keydownListener = (e: KeyboardEvent) => {
|
const keydownListener = (e: KeyboardEvent) => {
|
||||||
const key = e.key.toUpperCase()
|
const key = e.key.toUpperCase()
|
||||||
|
|
||||||
if (key === KEYS.UP || key === KEYS.LEFT || key === KEYS.PAGEUP) execPrev()
|
if (key === KEYS.UP || key === KEYS.LEFT || key === KEYS.PAGEUP) turning(e, 'prev')
|
||||||
else if (
|
else if (
|
||||||
key === KEYS.DOWN ||
|
key === KEYS.DOWN ||
|
||||||
key === KEYS.RIGHT ||
|
key === KEYS.RIGHT ||
|
||||||
key === KEYS.SPACE ||
|
key === KEYS.SPACE ||
|
||||||
key === KEYS.ENTER ||
|
key === KEYS.ENTER ||
|
||||||
key === KEYS.PAGEDOWN
|
key === KEYS.PAGEDOWN
|
||||||
) execNext()
|
) turning(e, 'next')
|
||||||
|
}
|
||||||
|
if (isLoader) { // 加载相关钩子
|
||||||
|
onMounted(() => {document.addEventListener('keydown', keydownListener)})
|
||||||
|
onUnmounted(() => {document.removeEventListener('keydown', keydownListener)})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => document.addEventListener('keydown', keydownListener))
|
|
||||||
onUnmounted(() => document.removeEventListener('keydown', keydownListener))
|
|
||||||
|
|
||||||
// 切换到上一张/上一张幻灯片(无视元素的入场动画)
|
// 切换到上一张/上一张幻灯片(无视元素的入场动画)
|
||||||
const turnPrevSlide = () => {
|
const turnPrevSlide = () => {
|
||||||
|
@ -259,5 +296,6 @@ export default () => {
|
||||||
execPrev,
|
execPrev,
|
||||||
execNext,
|
execNext,
|
||||||
animationIndex,
|
animationIndex,
|
||||||
|
turning,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,46 @@
|
||||||
<div class="pptist-screen">
|
<div class="pptist-screen">
|
||||||
<BaseView :changeViewMode="changeViewMode" v-if="viewMode === 'base'" />
|
<BaseView :changeViewMode="changeViewMode" v-if="viewMode === 'base'" />
|
||||||
<PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
|
<PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
|
||||||
|
<!-- 点赞组件 -->
|
||||||
|
<upvote-vue ref="upvoteRef" type="2"></upvote-vue>
|
||||||
|
<!-- <div style="z-index: 999;position: absolute;top:10px">
|
||||||
|
</div> -->
|
||||||
|
<!-- 推图上屏弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:fullscreen="true"
|
||||||
|
class="gridPicRefdiv"
|
||||||
|
style="overflow: hidden;"
|
||||||
|
:show-close="false"
|
||||||
|
>
|
||||||
|
<grid-pic ref="gridPicRef" style="height:100%;" @clear="clearchidrenPic"></grid-pic>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref , nextTick} from 'vue'
|
||||||
import { KEYS } from '../../configs/hotkey'
|
import { KEYS } from '../../configs/hotkey'
|
||||||
import useScreening from '../../hooks/useScreening'
|
import useScreening from '../../hooks/useScreening'
|
||||||
|
import hooksUpvote from '../../api/upvote' // 点赞-工具
|
||||||
|
|
||||||
import BaseView from './BaseView.vue'
|
import BaseView from './BaseView.vue'
|
||||||
import PresenterView from './PresenterView.vue'
|
import PresenterView from './PresenterView.vue'
|
||||||
|
import upvoteVue from '@/views/tool/components/upvote.vue' // 点赞-子组件
|
||||||
|
import gridPic from '@/components/grid-pic/index.vue' // 推图上屏弹子组件
|
||||||
|
|
||||||
|
import emitter from '@/utils/mitt' //mitt 事件总线
|
||||||
|
|
||||||
const viewMode = ref<'base' | 'presenter'>('base')
|
const viewMode = ref<'base' | 'presenter'>('base')
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const gridPicRef:any= ref(null)
|
||||||
const changeViewMode = (mode: 'base' | 'presenter') => {
|
const changeViewMode = (mode: 'base' | 'presenter') => {
|
||||||
viewMode.value = mode
|
viewMode.value = mode
|
||||||
}
|
}
|
||||||
|
|
||||||
const { exitScreening } = useScreening()
|
const { exitScreening } = useScreening()
|
||||||
|
const upvoteRef = ref(null)
|
||||||
|
hooksUpvote.init(upvoteRef) // 初始化点赞
|
||||||
|
|
||||||
// 快捷键退出放映
|
// 快捷键退出放映
|
||||||
const keydownListener = (e: KeyboardEvent) => {
|
const keydownListener = (e: KeyboardEvent) => {
|
||||||
|
@ -27,6 +49,23 @@ const keydownListener = (e: KeyboardEvent) => {
|
||||||
if (key === KEYS.ESC) exitScreening()
|
if (key === KEYS.ESC) exitScreening()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearchidrenPic= ()=> {
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开启推图上屏幕
|
||||||
|
emitter.on('opengridPic', async (data:object)=> {
|
||||||
|
if(gridPicRef.value) gridPicRef.value.clearPic()
|
||||||
|
dialogVisible.value = true
|
||||||
|
await nextTick();
|
||||||
|
gridPicRef.value.addPic(data.arr)
|
||||||
|
});
|
||||||
|
// 关闭推图
|
||||||
|
emitter.on('closegridPic', ()=> {
|
||||||
|
if(!gridPicRef.value) return
|
||||||
|
gridPicRef.value.clearPic()
|
||||||
|
dialogVisible.value = false
|
||||||
|
});
|
||||||
onMounted(() => document.addEventListener('keydown', keydownListener))
|
onMounted(() => document.addEventListener('keydown', keydownListener))
|
||||||
onUnmounted(() => document.removeEventListener('keydown', keydownListener))
|
onUnmounted(() => document.removeEventListener('keydown', keydownListener))
|
||||||
</script>
|
</script>
|
||||||
|
@ -38,4 +77,8 @@ onUnmounted(() => document.removeEventListener('keydown', keydownListener))
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.gridPicRefdiv .el-dialog__body){
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -228,6 +228,7 @@ const updateRange = () => {
|
||||||
width: parseInt(topImgPositionStyle.value.width),
|
width: parseInt(topImgPositionStyle.value.width),
|
||||||
height: parseInt(topImgPositionStyle.value.height),
|
height: parseInt(topImgPositionStyle.value.height),
|
||||||
}
|
}
|
||||||
|
console.log('retPosition', retPosition)
|
||||||
|
|
||||||
const widthScale = 100 / retPosition.width
|
const widthScale = 100 / retPosition.width
|
||||||
const heightScale = 100 / retPosition.height
|
const heightScale = 100 / retPosition.height
|
||||||
|
@ -475,7 +476,7 @@ const scaleClipRange = (e: MouseEvent, type: OperateResizeHandlers) => {
|
||||||
isMouseDown = false
|
isMouseDown = false
|
||||||
document.onmousemove = null
|
document.onmousemove = null
|
||||||
document.onmouseup = null
|
document.onmouseup = null
|
||||||
|
console.log('----------------------------------')
|
||||||
updateRange()
|
updateRange()
|
||||||
|
|
||||||
setTimeout(() => isSettingClipRange.value = false, 0)
|
setTimeout(() => isSettingClipRange.value = false, 0)
|
||||||
|
@ -537,6 +538,7 @@ const edgePoints = [
|
||||||
|
|
||||||
img {
|
img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
max-width: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,6 +183,7 @@ const handleClip = (data: ImageClipedEmitData | null) => {
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
max-width: none !important; // 防止图片被压缩
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.color-mask {
|
.color-mask {
|
||||||
|
|
|
@ -15,7 +15,7 @@ const size = ref('default')
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
/* text-align: center; */
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,10 @@ export const createChart = ({ headers, data }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 大模型对话
|
// 大模型对话
|
||||||
export const sendChart = ({ headers, data }) => {
|
export const sendChart = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/qf/sendTalk',
|
url: '/qf/sendTalk',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers,
|
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
|
@ -56,4 +56,5 @@ export class Other {
|
||||||
static baseUrl = "/common/upload"
|
static baseUrl = "/common/upload"
|
||||||
// 测试
|
// 测试
|
||||||
static uploadFile = data => ApiService.publicHttp(this.baseUrl, data, 'post', null, 'file')
|
static uploadFile = data => ApiService.publicHttp(this.baseUrl, data, 'post', null, 'file')
|
||||||
|
|
||||||
}
|
}
|
|
@ -71,6 +71,14 @@ export function updateClassworkeval(data) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateClassworkevalList(data) {
|
||||||
|
return request({
|
||||||
|
url: '/education/classworkeval/updateList',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 修改classworkdata
|
// 修改classworkdata
|
||||||
export function updateClassworkdata(data) {
|
export function updateClassworkdata(data) {
|
||||||
return request({
|
return request({
|
||||||
|
@ -80,6 +88,15 @@ export function updateClassworkdata(data) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批阅后, 待所有学生都批改完成后自动结束当前作业为[已完成]
|
||||||
|
export function updateClassWorkDataAutoFinish(data) {
|
||||||
|
return request({
|
||||||
|
url: '/education/classworkdata/updAutoFinish',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 修改classwork
|
// 修改classwork
|
||||||
export function updateClasswork(data) {
|
export function updateClasswork(data) {
|
||||||
return request({
|
return request({
|
||||||
|
|
|
@ -136,3 +136,17 @@ export function getJYPath(url,config) {
|
||||||
params: config.params
|
params: config.params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc: 百度识图转发
|
||||||
|
* @return: {*}
|
||||||
|
* @param {*} data
|
||||||
|
*/
|
||||||
|
export function getOcrContent(data) {
|
||||||
|
return request({
|
||||||
|
url: '/ocr/exam',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
|
@ -71,3 +71,11 @@ export const addFileToKj = (id) => {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getModelInfo = (params) => {
|
||||||
|
return request({
|
||||||
|
url: '/education/llmModel/getModelInfo',
|
||||||
|
method: 'post',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -95,3 +95,23 @@ export function getCourseTeachingMsg(id) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setPaging(data) {
|
||||||
|
return request({
|
||||||
|
url: '/education/classcourse/record/paging',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取分享码(邀请码)
|
||||||
|
* @param {*} id 课堂id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getShareCode(id) {
|
||||||
|
return request({
|
||||||
|
url: '/education/classcourse/refresh/code',
|
||||||
|
method: 'post',
|
||||||
|
data: { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4723712 */
|
font-family: "iconfont"; /* Project id 4723712 */
|
||||||
src: url('iconfont.woff2?t=1732240267757') format('woff2'),
|
src: url('iconfont.woff2?t=1734337029245') format('woff2'),
|
||||||
url('iconfont.woff?t=1732240267757') format('woff'),
|
url('iconfont.woff?t=1734337029245') format('woff'),
|
||||||
url('iconfont.ttf?t=1732240267757') format('truetype');
|
url('iconfont.ttf?t=1734337029245') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
|
@ -13,6 +13,30 @@
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-yinle:before {
|
||||||
|
content: "\e6c9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yuyin:before {
|
||||||
|
content: "\e648";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-dianying:before {
|
||||||
|
content: "\e693";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-jiqirenfushi:before {
|
||||||
|
content: "\e624";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-xiangmuicon_maobishufa:before {
|
||||||
|
content: "\e651";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-meishu-F:before {
|
||||||
|
content: "\e638";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-shangchuan:before {
|
.icon-shangchuan:before {
|
||||||
content: "\e61b";
|
content: "\e61b";
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,48 @@
|
||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "11819186",
|
||||||
|
"name": "音乐",
|
||||||
|
"font_class": "yinle",
|
||||||
|
"unicode": "e6c9",
|
||||||
|
"unicode_decimal": 59081
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6338162",
|
||||||
|
"name": "语音生成",
|
||||||
|
"font_class": "yuyin",
|
||||||
|
"unicode": "e648",
|
||||||
|
"unicode_decimal": 58952
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6880941",
|
||||||
|
"name": "视频生成",
|
||||||
|
"font_class": "dianying",
|
||||||
|
"unicode": "e693",
|
||||||
|
"unicode_decimal": 59027
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "11532042",
|
||||||
|
"name": "数字人生成",
|
||||||
|
"font_class": "jiqirenfushi",
|
||||||
|
"unicode": "e624",
|
||||||
|
"unicode_decimal": 58916
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "13522843",
|
||||||
|
"name": "文生图片",
|
||||||
|
"font_class": "xiangmuicon_maobishufa",
|
||||||
|
"unicode": "e651",
|
||||||
|
"unicode_decimal": 58961
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "37635062",
|
||||||
|
"name": "文生连环画",
|
||||||
|
"font_class": "meishu-F",
|
||||||
|
"unicode": "e638",
|
||||||
|
"unicode_decimal": 58936
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "4942656",
|
"icon_id": "4942656",
|
||||||
"name": "上传",
|
"name": "上传",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
After Width: | Height: | Size: 520 KiB |
|
@ -144,8 +144,14 @@ import { convertTextToPicture, getQueue, getPromptId, getPicture, chattoprompt,
|
||||||
import CryptoJS from 'crypto-js'
|
import CryptoJS from 'crypto-js'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
hasPPt: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
ratio: "512",
|
ratio: "512",
|
||||||
|
@ -384,7 +390,7 @@ export default {
|
||||||
urls.push(url0)
|
urls.push(url0)
|
||||||
buttonState.push({
|
buttonState.push({
|
||||||
disabled: false,
|
disabled: false,
|
||||||
text: "插入本课素材资源库",
|
text: this.hasPPt ? '插入' : "插入本课素材资源库",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.skeletonNumber = 0
|
this.skeletonNumber = 0
|
||||||
|
@ -476,6 +482,7 @@ export default {
|
||||||
|
|
||||||
//保存图片到素材库
|
//保存图片到素材库
|
||||||
async saveImage(resultIndex, index, url, resultItem) {
|
async saveImage(resultIndex, index, url, resultItem) {
|
||||||
|
|
||||||
this.buttonStates[resultIndex][index].disabled = true;
|
this.buttonStates[resultIndex][index].disabled = true;
|
||||||
this.buttonStates[resultIndex][index].text = "正在保存...";
|
this.buttonStates[resultIndex][index].text = "正在保存...";
|
||||||
const numberIndex = url.indexOf('filename=');
|
const numberIndex = url.indexOf('filename=');
|
||||||
|
@ -485,6 +492,7 @@ export default {
|
||||||
try {
|
try {
|
||||||
const blob = await this.getImageBlob(`https://ai.ysaix.com:7853/view?filename=${finalPath}&type=temp`);
|
const blob = await this.getImageBlob(`https://ai.ysaix.com:7853/view?filename=${finalPath}&type=temp`);
|
||||||
|
|
||||||
|
|
||||||
const hash = CryptoJS.MD5(blob).toString();
|
const hash = CryptoJS.MD5(blob).toString();
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
@ -492,7 +500,10 @@ export default {
|
||||||
let file = new File([blob], `${resultItem}.png`, {
|
let file = new File([blob], `${resultItem}.png`, {
|
||||||
type: 'image/png'
|
type: 'image/png'
|
||||||
})
|
})
|
||||||
|
if(this.hasPPt){
|
||||||
|
this.$emit('insertImg', file)
|
||||||
|
return
|
||||||
|
}
|
||||||
// 添加参数
|
// 添加参数
|
||||||
formData.append('md5', hash);
|
formData.append('md5', hash);
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
<template>
|
||||||
|
<div class="book-wrap">
|
||||||
|
<el-scrollbar height="100%">
|
||||||
|
<div class="book-name flex" @click="dialogVisible = true">
|
||||||
|
<span>{{ curBook.data.itemtitle }}</span>
|
||||||
|
<i class="iconfont icon-xiangyou"></i>
|
||||||
|
</div>
|
||||||
|
<div class="book-list" v-loading="treeLoading">
|
||||||
|
<el-tree :data="treeData" accordion :props="defaultProps" node-key="id"
|
||||||
|
:default-expanded-keys="defaultExpandedKeys" :current-node-key="curNode.data.id" highlight-current
|
||||||
|
@node-click="handleNodeClick">
|
||||||
|
<template #default="{ node }">
|
||||||
|
<span :title="node.label" class="tree-label">{{ node.label }}</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<!--弹窗 选择教材-->
|
||||||
|
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="550"
|
||||||
|
style="border-radius: 10px; padding: 10px 15px;">
|
||||||
|
<template #header>
|
||||||
|
<div class="choose-book-header flex">
|
||||||
|
<span>切换教材</span>
|
||||||
|
<i class="iconfont icon-guanbi" @click="dialogVisible = false"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="textbook-container">
|
||||||
|
<el-scrollbar height="450px">
|
||||||
|
<div class="textbook-item flex" v-for="item in subjectList" :class="curBook.data.id == item.id ? 'active-item' : ''"
|
||||||
|
:key="item.id" @click="changeBook(item)">
|
||||||
|
<img v-if="item.avartar" :src="item.avartar.indexOf('http') === 0 ? item.avartar : BaseUrl + item.avartar" class="textbook-img" alt="">
|
||||||
|
<div v-else class="textbook-img">
|
||||||
|
<i class="iconfont icon-jiaocaixuanze" style="font-size: 40px;"></i>
|
||||||
|
</div>
|
||||||
|
<span class="book-name">{{ item.itemtitle }}</span>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, nextTick, toRaw, reactive } from 'vue';
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { listEvaluation } from '@/api/subject'
|
||||||
|
import { sessionStore } from '@/utils/store'
|
||||||
|
|
||||||
|
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||||
|
// 定义要发送的emit事件
|
||||||
|
const emit = defineEmits(['nodeClick', 'changeBook'])
|
||||||
|
// 章节List
|
||||||
|
const unitList = ref([])
|
||||||
|
const subjectList = ref([])
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
// 当前教材下面单元内容数据
|
||||||
|
const treeData = ref([])
|
||||||
|
const defaultProps = {
|
||||||
|
children: 'children',
|
||||||
|
label: 'itemtitle',
|
||||||
|
class: 'textbook-tree'
|
||||||
|
}
|
||||||
|
//查当前学科
|
||||||
|
const subjectParams = reactive(
|
||||||
|
{
|
||||||
|
edusubject: '科学',
|
||||||
|
edustage:'小学',
|
||||||
|
itemkey: 'version',
|
||||||
|
orderby: 'orderidx asc',
|
||||||
|
pageSize: 10000
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 查所有的学科
|
||||||
|
const unitParams = reactive({
|
||||||
|
edusubject:'科学',
|
||||||
|
edustage:'小学',
|
||||||
|
itemgroup: 'textbook',
|
||||||
|
orderby: 'orderidx asc',
|
||||||
|
pageSize: 10000
|
||||||
|
})
|
||||||
|
// 当前选中的教材
|
||||||
|
const curBook = reactive({
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
// 当前节点
|
||||||
|
const curNode = reactive({
|
||||||
|
data:{}
|
||||||
|
})
|
||||||
|
const treeLoading = ref(false)
|
||||||
|
// 默认展开的节点
|
||||||
|
const defaultExpandedKeys = ref([])
|
||||||
|
|
||||||
|
//选择教材
|
||||||
|
const changeBook = (data) => {
|
||||||
|
curBook.data = data
|
||||||
|
treeData.value = getTreeData(data.id)
|
||||||
|
//切换教材后默认展开第一个并选中
|
||||||
|
nextTick(() =>{
|
||||||
|
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||||
|
curNode.data = getLastLevelData(treeData.value)[0]
|
||||||
|
handleNodeClick(curNode.data)
|
||||||
|
})
|
||||||
|
// 延迟关闭 视觉上选中
|
||||||
|
setTimeout(() => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLastLevelData = (tree) => {
|
||||||
|
let lastLevelData = [];
|
||||||
|
// 递归函数遍历树形结构
|
||||||
|
function traverseTree(nodes) {
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
// 如果当前节点有子节点,继续遍历
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
traverseTree(node.children);
|
||||||
|
} else {
|
||||||
|
// 如果没有子节点,说明是最后一层的节点
|
||||||
|
lastLevelData.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用递归函数开始遍历
|
||||||
|
traverseTree(tree);
|
||||||
|
|
||||||
|
// 返回最后一层的数据
|
||||||
|
return lastLevelData;
|
||||||
|
}
|
||||||
|
// 根据id 拿到父节点数据
|
||||||
|
const findParentByChildId = (treeData, targetNodeId) => {
|
||||||
|
// 递归查找函数
|
||||||
|
// 遍历树中的每个节点
|
||||||
|
for (let node of treeData) {
|
||||||
|
// 检查当前节点的子节点是否包含目标子节点 ID
|
||||||
|
if (node.children && node.children.some(child => child.id === targetNodeId)) {
|
||||||
|
// 如果当前节点的某个子节点的 ID 匹配目标子节点 ID,则当前节点即为父节点
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
// 如果当前节点没有匹配的子节点,则递归检查当前节点的子节点
|
||||||
|
if (node.children) {
|
||||||
|
let parentNode = findParentByChildId(node.children, targetNodeId);
|
||||||
|
if (parentNode) {
|
||||||
|
return parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果未找到匹配的父节点,则返回 null 或者适当的默认值
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const handleNodeClick = (data) => {
|
||||||
|
/**
|
||||||
|
* data : 当前节点数据
|
||||||
|
*/
|
||||||
|
let nodeData = cloneDeep(toRaw(data));
|
||||||
|
|
||||||
|
//增加一个label 之前取的label
|
||||||
|
nodeData.label = nodeData.itemtitle
|
||||||
|
// 父级节点 如果当前是一级节点 父级则为null
|
||||||
|
let parent = {
|
||||||
|
id: nodeData.parentid,
|
||||||
|
label: nodeData.parenttitle,
|
||||||
|
itemtitle: nodeData.parenttitle
|
||||||
|
}
|
||||||
|
const parentNode = nodeData.parentid ? parent : null
|
||||||
|
nodeData.parentNode = parentNode
|
||||||
|
let curData = {
|
||||||
|
textBook: {
|
||||||
|
curBookId: curBook.data.id,
|
||||||
|
curBookName: curBook.data.itemtitle,
|
||||||
|
curBookImg: BaseUrl + curBook.data.avartar,
|
||||||
|
curBookPath: curBook.data.fileurl
|
||||||
|
},
|
||||||
|
node: nodeData
|
||||||
|
}
|
||||||
|
// 本地存储:electron-store
|
||||||
|
emit('nodeClick', curData)
|
||||||
|
}
|
||||||
|
// 单元章节数据转为“树”结构
|
||||||
|
const getTreeData = (bookId) =>{
|
||||||
|
// 根据当前教材的id 查找出对应的章节
|
||||||
|
let data = unitList.value.filter(item => item.rootid == bookId && item.level == 1)
|
||||||
|
data.forEach( item => {
|
||||||
|
item.children = unitList.value.filter( item2 => item2.parentid == item.id && item2.level == 2)
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted( async () => {
|
||||||
|
treeLoading.value = true
|
||||||
|
try{
|
||||||
|
//获取学科列表
|
||||||
|
const { rows } = await listEvaluation(subjectParams)
|
||||||
|
// 获取所有的教材
|
||||||
|
subjectList.value = rows
|
||||||
|
|
||||||
|
const res = await listEvaluation(unitParams)
|
||||||
|
unitList.value = [...res.rows]
|
||||||
|
// 当前教材
|
||||||
|
curBook.data = rows[0]
|
||||||
|
|
||||||
|
// 章节"树"rows
|
||||||
|
treeData.value = getTreeData(rows[0].id)
|
||||||
|
|
||||||
|
nextTick(() =>{
|
||||||
|
// 默认展开 选中
|
||||||
|
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||||
|
curNode.data = getLastLevelData(treeData.value)[0]
|
||||||
|
handleNodeClick(curNode.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
} finally{
|
||||||
|
treeLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.book-wrap {
|
||||||
|
width: 300px;
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0px 0px 20px 0px rgba(99, 99, 99, 0.06);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.book-name {
|
||||||
|
background-color: #ffffff;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 45px;
|
||||||
|
padding: 0 15px;
|
||||||
|
z-index: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: #3b3b3b;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: solid #f4f5f7 1px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-list {
|
||||||
|
padding: 45px 10px 0 10px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.choose-dialog) {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-book-header {
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.icon-guanbi {
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.textbook-container {
|
||||||
|
.textbook-item {
|
||||||
|
padding: 10px 20px;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.book-name {
|
||||||
|
margin-left: 20px;
|
||||||
|
color: #3b3b3b;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f4f7f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-item {
|
||||||
|
background-color: #f4f7f9;
|
||||||
|
|
||||||
|
.book-name {
|
||||||
|
color: #368fff;
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.textbook-img {
|
||||||
|
width: 55px;
|
||||||
|
height: 70px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tree-node) {
|
||||||
|
.el-tree-node__content {
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #eaf3ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content) {
|
||||||
|
background-color: #eaf3ff !important;
|
||||||
|
color: #409EFF
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="book-wrap">
|
<div class="book-wrap">
|
||||||
<el-scrollbar height="100%">
|
<el-scrollbar height="100%">
|
||||||
<div class="book-name flex" @click="dialogVisible = true">
|
<div class="book-name flex" v-if="isStadium() === true"> {{userStore.user.deptName}}</div>
|
||||||
|
<div v-else class="book-name flex" @click="dialogVisible = true">
|
||||||
<span>{{ curBook.data.itemtitle }}</span>
|
<span>{{ curBook.data.itemtitle }}</span>
|
||||||
<i class="iconfont icon-xiangyou"></i>
|
<i class="iconfont icon-xiangyou"></i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,8 +10,17 @@
|
||||||
<el-tree :data="treeData" accordion :props="defaultProps" node-key="id"
|
<el-tree :data="treeData" accordion :props="defaultProps" node-key="id"
|
||||||
:default-expanded-keys="defaultExpandedKeys" :current-node-key="curNode.data.id" highlight-current
|
:default-expanded-keys="defaultExpandedKeys" :current-node-key="curNode.data.id" highlight-current
|
||||||
@node-click="handleNodeClick">
|
@node-click="handleNodeClick">
|
||||||
<template #default="{ node }">
|
<template #default="{ node, data }">
|
||||||
<span :title="node.label" class="tree-label">{{ node.label }}</span>
|
<div v-if="props.isClassTask && (data.bookId == '' || data.bookId == '0')">
|
||||||
|
<el-tooltip effect="light" placement="right" content="该单元章节无自主试题">
|
||||||
|
<span class="tree-label" style="color: #A5B3CA">
|
||||||
|
{{ node.label }}
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<span class="tree-label">{{ node.label }}</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,8 +56,21 @@ import { onMounted, ref, nextTick, toRaw, reactive } from 'vue';
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import { sessionStore } from '@/utils/store'
|
import { sessionStore } from '@/utils/store'
|
||||||
import { useGetSubject } from '@/hooks/useGetSubject'
|
import { useGetSubject } from '@/hooks/useGetSubject'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 是否为[教学大模型]中使用(作业设计中对应该章节是否存在第三方试题)
|
||||||
|
isClassTask: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const isStadium = () => {
|
||||||
|
let roles = userStore.user.roles
|
||||||
|
return roles.some(item => item.roleKey === 'stadium')
|
||||||
|
}
|
||||||
// 定义要发送的emit事件
|
// 定义要发送的emit事件
|
||||||
const emit = defineEmits(['nodeClick', 'changeBook'])
|
const emit = defineEmits(['nodeClick', 'changeBook'])
|
||||||
let useSubject = null
|
let useSubject = null
|
||||||
|
@ -142,13 +165,19 @@ const handleNodeClick = (data) => {
|
||||||
|
|
||||||
//增加一个label 之前取的label
|
//增加一个label 之前取的label
|
||||||
nodeData.label = nodeData.itemtitle
|
nodeData.label = nodeData.itemtitle
|
||||||
// 父级节点 如果当前是一级节点 父级则为null
|
let parentNode
|
||||||
let parent = {
|
// 存在children 则为一级节点
|
||||||
|
if(nodeData.children){
|
||||||
|
// 为一级节点
|
||||||
|
parentNode = null
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
parentNode = {
|
||||||
id: nodeData.parentid,
|
id: nodeData.parentid,
|
||||||
label: nodeData.parenttitle,
|
label: nodeData.parenttitle,
|
||||||
itemtitle: nodeData.parenttitle
|
itemtitle: nodeData.parenttitle
|
||||||
}
|
}
|
||||||
const parentNode = nodeData.parentid ? parent : null
|
}
|
||||||
nodeData.parentNode = parentNode
|
nodeData.parentNode = parentNode
|
||||||
let curData = {
|
let curData = {
|
||||||
textBook: {
|
textBook: {
|
||||||
|
@ -165,6 +194,7 @@ const handleNodeClick = (data) => {
|
||||||
sessionStore.set('subject.curNode', nodeData)
|
sessionStore.set('subject.curNode', nodeData)
|
||||||
emit('nodeClick', curData)
|
emit('nodeClick', curData)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted( async () => {
|
onMounted( async () => {
|
||||||
treeLoading.value = true
|
treeLoading.value = true
|
||||||
try{
|
try{
|
||||||
|
@ -175,7 +205,11 @@ onMounted( async () => {
|
||||||
curBook.data = sessionStore.get('subject.curBook')
|
curBook.data = sessionStore.get('subject.curBook')
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
if (subjectList.value) {
|
||||||
curBook.data = subjectList.value[0]
|
curBook.data = subjectList.value[0]
|
||||||
|
}else {
|
||||||
|
curBook.data = {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 章节"树"
|
// 章节"树"
|
||||||
|
|
|
@ -34,7 +34,14 @@ const getFileTypeIcon = () => {
|
||||||
gif: 'icon-gif',
|
gif: 'icon-gif',
|
||||||
txt: 'icon-txt',
|
txt: 'icon-txt',
|
||||||
rar: 'icon-rar',
|
rar: 'icon-rar',
|
||||||
apt: 'icon-A'
|
apt: 'icon-A',
|
||||||
|
aippt: 'icon-a-ziyuan91',
|
||||||
|
aiyuyin: 'icon-yuyin', // 语音生成
|
||||||
|
aivideo: 'icon-dianying', // 视频生成
|
||||||
|
airobot: 'icon-jiqirenfushi', // 数字人生成
|
||||||
|
aiimg: 'icon-xiangmuicon_maobishufa', // 文生图片
|
||||||
|
aidraw: 'icon-meishu-F', // 文生连环画
|
||||||
|
aiyinyue: 'icon-yinle' //文生音乐
|
||||||
}
|
}
|
||||||
if (iconObj[name]) {
|
if (iconObj[name]) {
|
||||||
return '#' + iconObj[name]
|
return '#' + iconObj[name]
|
||||||
|
|
|
@ -0,0 +1,269 @@
|
||||||
|
<!--
|
||||||
|
依赖: vuedraggable、v-viewer
|
||||||
|
属性: showToolbar //是否显示工具栏 默认:false
|
||||||
|
工具栏 添加图片:默认6个测试图片,不输入框添加则添加默认,输入图片链接,展示图片链接的图片
|
||||||
|
清空图片:清空图片
|
||||||
|
事件: clear 清空时触发
|
||||||
|
outIndex 超出九个图片时触发
|
||||||
|
方法: addPic //添加图片
|
||||||
|
参数: src 图片链接
|
||||||
|
clearPic //清空图片
|
||||||
|
参数: 无
|
||||||
|
使用方法: 加载组件后,通过ref调用addPic方法添加图片即可
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div style="position: relative;height: 100%;width: 100%;">
|
||||||
|
<draggable handle=".header-btn" :draggable="false" item-key="backgroundColor" v-model="gridPicList" class="grid-pic-wrap" :style="getGrid">
|
||||||
|
<template #item="{ element, index }">
|
||||||
|
<div class="grid-pic-item" :key="element.backgroundColor" :style="getWH(element,index)">
|
||||||
|
<div class="delete-btn" @click="()=>{gridPicList.splice(index,1);if(!gridPicList.length) emits('clear')} ">X</div>
|
||||||
|
<div class="header-btn"></div>
|
||||||
|
<ViewerItem :gridPicList="gridPicList" :index="index" :images="element"></ViewerItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
<div class="grid-pic-toolbar">
|
||||||
|
<el-input v-if="showToolbar" style="width: 500px" v-model="inputValue" type="text" />
|
||||||
|
<el-button v-if="showToolbar" class="add-btn" @click="pushPic">
|
||||||
|
添加
|
||||||
|
</el-button>
|
||||||
|
<el-button class="add-btn" @click="clearPic">
|
||||||
|
清空
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<!-- <el-button style="position:fixed;bottom: 20px;right: 80px;" @click="startPencil">
|
||||||
|
画笔
|
||||||
|
</el-button>-->
|
||||||
|
<!-- <div class="modal-mode">
|
||||||
|
<canvas id="canvas_pic_001" style="position: absolute;top: 0;left: 0;width: 100%;height: 100%;"></canvas>
|
||||||
|
</div>-->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, computed, onMounted} from 'vue'
|
||||||
|
import Draggable from 'vuedraggable'
|
||||||
|
import ViewerItem from "./viewer-item.vue";
|
||||||
|
// import Fabric from 'fabric';
|
||||||
|
const gridPicList = ref([])
|
||||||
|
const inputValue = ref('')
|
||||||
|
const isShow = ref(false)
|
||||||
|
const emits = defineEmits(['clear','outIndex']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
showToolbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 获取图片样式
|
||||||
|
const getWH = (item,index)=>{
|
||||||
|
return {
|
||||||
|
backgroundColor: item.backgroundColor,
|
||||||
|
'grid-area': 'a' + index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const picList = [
|
||||||
|
'https://prev.ysaix.com:7868/src/assets/images/homecard4.jpg',
|
||||||
|
'https://prev.ysaix.com:7868/src/assets/images/homecard3.jpg',
|
||||||
|
'https://prev.ysaix.com:7868/src/assets/images/homecard2.jpg',
|
||||||
|
'https://prev.ysaix.com:7868/src/assets/images/homecard1.jpg',
|
||||||
|
'https://prev.ysaix.com:7868/profile/avatar/2024/06/26/blob_20240626135106A001.png',
|
||||||
|
'https://prev.ysaix.com:7868/assets/app_download.b3fb227b.png'
|
||||||
|
]
|
||||||
|
// 获取grid样式
|
||||||
|
const getGrid = computed(() => {
|
||||||
|
switch (gridPicList.value.length) {
|
||||||
|
case 1:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0"`
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0 a1"`
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0 a1"
|
||||||
|
"a0 a2"`
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0 a2"
|
||||||
|
"a1 a3"`
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0 a2 a4"
|
||||||
|
"a1 a3 a4"`
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0 a2 a4"
|
||||||
|
"a1 a3 a5"`
|
||||||
|
}
|
||||||
|
case 7:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0 a2 a4"
|
||||||
|
"a0 a2 a4"
|
||||||
|
"a0 a2 a5"
|
||||||
|
"a1 a3 a5"
|
||||||
|
"a1 a3 a6"
|
||||||
|
"a1 a3 a6"`
|
||||||
|
}
|
||||||
|
case 8:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0 a3 a6"
|
||||||
|
"a0 a3 a6"
|
||||||
|
"a1 a4 a6"
|
||||||
|
"a1 a4 a7"
|
||||||
|
"a2 a5 a7"
|
||||||
|
"a2 a5 a7"`
|
||||||
|
}
|
||||||
|
case 9:
|
||||||
|
return {
|
||||||
|
'grid-template-areas':
|
||||||
|
`"a0 a3 a6"
|
||||||
|
"a1 a4 a7"
|
||||||
|
"a2 a5 a8"`
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const pushPic = () => {
|
||||||
|
let src = inputValue.value||picList[gridPicList.value.length]
|
||||||
|
addPic(src)
|
||||||
|
}
|
||||||
|
// 添加图片
|
||||||
|
const addPic = (data) => {
|
||||||
|
let list = Array.isArray(data)?data:[data]
|
||||||
|
if (gridPicList.value.length + list.length > 9) {
|
||||||
|
console.log("超出九个图片")
|
||||||
|
emits('outIndex')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let listArr = [];
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
let src = list[i]
|
||||||
|
if (!src) {
|
||||||
|
console.log("图片链接不能为空")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listArr.push({
|
||||||
|
src: src,
|
||||||
|
backgroundColor: getRandomColor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
gridPicList.value.push(...listArr)
|
||||||
|
inputValue.value = ''
|
||||||
|
}
|
||||||
|
// 清空图片
|
||||||
|
const clearPic = () => {
|
||||||
|
gridPicList.value = []
|
||||||
|
emits('clear')
|
||||||
|
}
|
||||||
|
//开始画笔
|
||||||
|
const startPencil = () => {
|
||||||
|
isShow.value = !isShow.value
|
||||||
|
}
|
||||||
|
// 生成随机颜色
|
||||||
|
function getRandomColor() {
|
||||||
|
let r = Math.floor(Math.random() * 256).toString(16);
|
||||||
|
let g = Math.floor(Math.random() * 256).toString(16);
|
||||||
|
let b = Math.floor(Math.random() * 256).toString(16);
|
||||||
|
// 如果生成的十六进制数字只有一位,前面补0
|
||||||
|
r = r.length === 1? '0' + r : r;
|
||||||
|
g = g.length === 1? '0' + g : g;
|
||||||
|
b = b.length === 1? '0' + b : b;
|
||||||
|
return `#${r}${g}${b}`;
|
||||||
|
}
|
||||||
|
/* //初始化画笔
|
||||||
|
const initPend = () => {
|
||||||
|
let canvas = new Fabric.fabric.Canvas('canvas_pic_001',{
|
||||||
|
interactive: false,
|
||||||
|
selection: true,
|
||||||
|
backgroundColor: "rgba(15,15,15,0)"
|
||||||
|
})
|
||||||
|
canvas.defaultCursor = 'default'
|
||||||
|
canvas.setHeight(300)
|
||||||
|
canvas.setWidth(400)
|
||||||
|
canvas.isDrawingMode = true;
|
||||||
|
canvas.freeDrawingBrush = new Fabric.fabric.PencilBrush(canvas)
|
||||||
|
canvas.freeDrawingBrush.width = 1//设置画笔粗细
|
||||||
|
canvas.freeDrawingBrush.color = "red"//设置画笔颜色
|
||||||
|
}*/
|
||||||
|
/*onMounted(() => {
|
||||||
|
initPend()
|
||||||
|
})*/
|
||||||
|
defineExpose({addPic,clearPic})
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.modal-mode{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1001;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.grid-pic-wrap{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
.grid-pic-item{
|
||||||
|
//animation: fadeIn 0.5s ease-in-out forwards;
|
||||||
|
background-color: #0a84ff;
|
||||||
|
position: relative;
|
||||||
|
.delete-btn{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 999;
|
||||||
|
&:hover{
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.header-btn{
|
||||||
|
position: absolute;
|
||||||
|
z-index: 998;
|
||||||
|
height: 30px;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px dotted #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.grid-pic-toolbar{
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
.add-btn{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,147 @@
|
||||||
|
<template>
|
||||||
|
<div class="viewer-item-wrap" :id="'viewer_id'+index">
|
||||||
|
<Viewer @move="move" @moved="moved" @inited="inited" @zoomed="zoomed" :ref="collectRef('viewerRef'+index)" :options="optins" :images="[images.src]" class="images clearfix">
|
||||||
|
<template #default="scope">
|
||||||
|
<div class="viewer-img-box">
|
||||||
|
<img v-for="src in scope.images" :key="index" :src="src" style="display: none">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Viewer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import {ref, watch, nextTick, onMounted} from "vue";
|
||||||
|
import { component as Viewer } from 'v-viewer'
|
||||||
|
// import Fabric from 'fabric';
|
||||||
|
import 'viewerjs/dist/viewer.css'
|
||||||
|
const props = defineProps({
|
||||||
|
images: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
gridPicList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let $viewer = null;
|
||||||
|
const refs = ref([]);
|
||||||
|
//初始化时
|
||||||
|
const inited = (viewer) => {
|
||||||
|
$viewer = viewer
|
||||||
|
}
|
||||||
|
//缩放时
|
||||||
|
const zoomed = (e) => {
|
||||||
|
// setImgStyle()
|
||||||
|
}
|
||||||
|
//移动时
|
||||||
|
const moved = (e) => {
|
||||||
|
// setImgStyle()
|
||||||
|
}
|
||||||
|
const move = (e) => {
|
||||||
|
}
|
||||||
|
const appendCanvasToShow = () => {
|
||||||
|
initImgStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
const setImgStyle = () => {
|
||||||
|
let item = window.document.getElementById('viewer_id'+props.index)
|
||||||
|
let canvas = item.querySelectorAll('.viewer-canvas')[0]
|
||||||
|
let img = canvas.querySelectorAll('img')[0]
|
||||||
|
let imgStyle = img.getAttribute('style')
|
||||||
|
imgStyle = imgStyle.replace('relative', 'absolute') + 'z-index: 1002';
|
||||||
|
img.style = imgStyle;
|
||||||
|
let canvasNew = canvas.querySelectorAll('canvas')[0]
|
||||||
|
canvasNew.style = imgStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initImgStyle = () => {
|
||||||
|
let item = window.document.getElementById('viewer_id'+props.index)
|
||||||
|
let canvas = item.querySelectorAll('.viewer-canvas')[0]
|
||||||
|
let img = canvas.querySelectorAll('img')[0];
|
||||||
|
let imgStyle = img.getAttribute('style')
|
||||||
|
imgStyle = imgStyle.replace('relative', 'absolute') + 'z-index: 1002';
|
||||||
|
img.style = imgStyle;
|
||||||
|
const canvasNew = document.createElement('canvas');
|
||||||
|
canvasNew.style = imgStyle;
|
||||||
|
canvasNew.id = 'canvas_pic_'+props.index
|
||||||
|
canvas.appendChild(canvasNew);
|
||||||
|
|
||||||
|
initPend()
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectRef = (key) => {
|
||||||
|
return (el) => {
|
||||||
|
refs.value[key] = el;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
//viewer配置
|
||||||
|
const optins = ref({
|
||||||
|
"inline": true,
|
||||||
|
"button": false,
|
||||||
|
"navbar": false,
|
||||||
|
"title": false,
|
||||||
|
"toolbar": false,
|
||||||
|
"tooltip": true,
|
||||||
|
"zoomable": true,
|
||||||
|
"rotatable": true,
|
||||||
|
"movable": true,
|
||||||
|
"scalable": true,
|
||||||
|
"transition": true,
|
||||||
|
"fullscreen": true,
|
||||||
|
"keyboard": true
|
||||||
|
})
|
||||||
|
const initViewers = () => {
|
||||||
|
refs.value['viewerRef'+props.index]?.rebuildViewer()
|
||||||
|
/*setTimeout(()=>{
|
||||||
|
initImgStyle()
|
||||||
|
},300)*/
|
||||||
|
}
|
||||||
|
//初始化画笔
|
||||||
|
const initPend = () => {
|
||||||
|
let canvas = new Fabric.fabric.Canvas('canvas_pic_'+props.index,{
|
||||||
|
interactive: false,
|
||||||
|
selection: true,
|
||||||
|
backgroundColor: "rgba(15,15,15,0)"
|
||||||
|
})
|
||||||
|
canvas.defaultCursor = 'default'
|
||||||
|
canvas.setHeight(300)
|
||||||
|
canvas.setWidth(400)
|
||||||
|
canvas.isDrawingMode = true;
|
||||||
|
canvas.freeDrawingBrush = new Fabric.fabric.PencilBrush(canvas)
|
||||||
|
canvas.freeDrawingBrush.width = 1//设置画笔粗细
|
||||||
|
canvas.freeDrawingBrush.color = "red"//设置画笔颜色
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(props.gridPicList, (newValue, oldValue) => {
|
||||||
|
nextTick(()=>{
|
||||||
|
initViewers()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
watch(props.images, (newValue, oldValue) => {
|
||||||
|
// optins.value.movable = newValue.dragable
|
||||||
|
initPend()
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*onMounted(()=>{
|
||||||
|
setTimeout(()=>{
|
||||||
|
appendCanvasToShow()
|
||||||
|
}, 300)
|
||||||
|
})*/
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.viewer-item-wrap{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
:deep(.viewer-canvas img) {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -204,7 +204,11 @@ onMounted(async () => {
|
||||||
curBook.data = sessionStore.get('subject.curBook')
|
curBook.data = sessionStore.get('subject.curBook')
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
if (subjectList.value) {
|
||||||
curBook.data = subjectList.value[0]
|
curBook.data = subjectList.value[0]
|
||||||
|
}else {
|
||||||
|
curBook.data = {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 章节"树"
|
// 章节"树"
|
||||||
|
|
|
@ -29,6 +29,20 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
|
<div class="file-list">
|
||||||
|
<el-dropdown @command="changeFile" v-if="type == 3">
|
||||||
|
<span class="el-dropdown-link">
|
||||||
|
{{ curFile.fileName }}
|
||||||
|
<i class="iconfont icon-xiangxia"></i>
|
||||||
|
</span>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="item in fileList" :command="item" :key="item.id">{{ item.fileName
|
||||||
|
}}</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
<div class="input-box flex">
|
<div class="input-box flex">
|
||||||
<el-input v-model="textarea" @keyup.enter="send" :disabled="loaded"/>
|
<el-input v-model="textarea" @keyup.enter="send" :disabled="loaded"/>
|
||||||
<div class="ipt-icon" @click="send">
|
<div class="ipt-icon" @click="send">
|
||||||
|
@ -40,13 +54,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, watch } from 'vue'
|
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||||
import { completion } from '@/api/mode/index'
|
import { completion, docList } from '@/api/mode/index'
|
||||||
import { sessionStore } from '@/utils/store'
|
import { sessionStore } from '@/utils/store'
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { dataSetJson } from '@/utils/comm.js'
|
import { dataSetJson } from '@/utils/comm.js'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
import { sendChart } from '@/api/ai/index'
|
||||||
import emitter from '@/utils/mitt';
|
import emitter from '@/utils/mitt';
|
||||||
|
|
||||||
|
const userInfo = useUserStore().user
|
||||||
|
|
||||||
const textarea = ref('')
|
const textarea = ref('')
|
||||||
|
|
||||||
const isDialog = defineModel()
|
const isDialog = defineModel()
|
||||||
|
@ -55,12 +72,20 @@ const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
return { name: '11' }
|
return { name: '' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1
|
default: 1
|
||||||
|
},
|
||||||
|
curMode:{
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
conversation_id: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -84,7 +109,8 @@ const curNode = reactive({})
|
||||||
const params = reactive(
|
const params = reactive(
|
||||||
{
|
{
|
||||||
prompt: '',
|
prompt: '',
|
||||||
dataset_id: ''
|
dataset_id: '',
|
||||||
|
template: ''
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,7 +118,24 @@ const params = reactive(
|
||||||
const getCompletion = async (val) => {
|
const getCompletion = async (val) => {
|
||||||
try {
|
try {
|
||||||
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value} 对${curNode.itemtitle}进行教学分析`
|
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value} 对${curNode.itemtitle}进行教学分析`
|
||||||
const { data } = await completion(params)
|
params.template = props.item.prompt
|
||||||
|
|
||||||
|
let data = null;
|
||||||
|
// 教学大模型
|
||||||
|
if(props.curMode == 1){
|
||||||
|
const res = await sendChart({
|
||||||
|
content: params.prompt,
|
||||||
|
conversationId: props.conversation_id,
|
||||||
|
stream: false
|
||||||
|
})
|
||||||
|
data = res.data
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// 知识库模型
|
||||||
|
const res = await completion(params)
|
||||||
|
data = res.data
|
||||||
|
}
|
||||||
|
|
||||||
let answer = data.answer
|
let answer = data.answer
|
||||||
msgList.value.push({
|
msgList.value.push({
|
||||||
type: 'robot',
|
type: 'robot',
|
||||||
|
@ -106,31 +149,48 @@ const getCompletion = async (val) => {
|
||||||
|
|
||||||
const saveAdjust = (item) =>{
|
const saveAdjust = (item) =>{
|
||||||
isDialog.value = false
|
isDialog.value = false
|
||||||
ElMessage.success('操作成功')
|
|
||||||
emitter.emit('onSaveAdjust', item.msg)
|
emitter.emit('onSaveAdjust', item.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const modeType = ref('课标')
|
|
||||||
watch(() => props.type, (newVal) => {
|
const curFile = reactive({})
|
||||||
if (newVal == 1){
|
const dataset_id = ref('')
|
||||||
modeType.value = '课标'
|
const fileList = ref([])
|
||||||
|
const getList = () =>{
|
||||||
|
docList({
|
||||||
|
userId: userInfo.userId,
|
||||||
|
dataset_id: dataset_id.value
|
||||||
|
}).then( res =>{
|
||||||
|
fileList.value = res.rows
|
||||||
|
Object.assign(curFile, fileList.value[0])
|
||||||
|
params.document_ids = fileList.value[0].docId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (newVal == 2){
|
emitter.on('changeCurFile', (item) =>{
|
||||||
modeType.value = '教材'
|
changeFile(item)
|
||||||
}
|
})
|
||||||
if (newVal == 2){
|
const changeFile = (val) =>{
|
||||||
modeType.value = '考试'
|
Object.assign(curFile, val);
|
||||||
|
params.document_ids = val.docId
|
||||||
}
|
}
|
||||||
|
|
||||||
}, { immediate: false })
|
const modeType = ref('')
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let data = sessionStore.get('subject.curNode')
|
let data = sessionStore.get('subject.curNode')
|
||||||
|
|
||||||
Object.assign(curNode, data);
|
Object.assign(curNode, data);
|
||||||
|
|
||||||
|
modeType.value = props.type == 1 ? '课标' : props.type == 2 ? '教材' : '考试'
|
||||||
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
|
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
|
||||||
params.dataset_id = dataSetJson[jsonKey]
|
params.dataset_id = dataSetJson[jsonKey]
|
||||||
|
if(props.type == 3){
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// 解绑
|
||||||
|
onUnmounted(() => {
|
||||||
|
emitter.off('changeCurFile');
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -267,4 +327,9 @@ onMounted(() => {
|
||||||
transform: scale(0.01);
|
transform: scale(0.01);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-list{
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<el-dialog v-model="mode" :show-close="false" width="600" append-to-body destroy-on-close>
|
<el-dialog v-model="mode" :show-close="false" width="600" append-to-body destroy-on-close>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="custom-header flex">
|
<div class="custom-header flex">
|
||||||
<span>{{ item.ex3 == '1' ? '请输入新的模板名称' : isAdd ? '添加提示词' : '编辑提示词' }}</span>
|
<span>{{ item.ex3 == '1' ? '请输入新的模板名称' : item.isAdd ? '添加提示词' : '编辑提示词' }}</span>
|
||||||
<i class="iconfont icon-guanbi" @click="mode = false"></i>
|
<i class="iconfont icon-guanbi" @click="mode = false"></i>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -40,11 +40,7 @@ const props = defineProps({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1
|
default: 1
|
||||||
},
|
},
|
||||||
isAdd: {
|
item: { // 当前操作的模板
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
item: { // 子模板
|
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
return { ex3: '' }
|
return { ex3: '' }
|
||||||
|
@ -58,37 +54,45 @@ const form = reactive({
|
||||||
prompt: '',
|
prompt: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.isAdd, (newVal) => {
|
|
||||||
if (!newVal) {
|
watch(() => mode.value, (newVal) => {
|
||||||
|
if(newVal){
|
||||||
|
if (props.item.isAdd) {
|
||||||
|
form.name = ''
|
||||||
|
form.prompt = ''
|
||||||
|
}
|
||||||
|
else{
|
||||||
form.name = props.item?.name
|
form.name = props.item?.name
|
||||||
form.prompt = props.item?.prompt
|
form.prompt = props.item?.prompt
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, { immediate: false })
|
},{ deep: true})
|
||||||
|
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const saveAdd = async () => {
|
const saveAdd = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
if (props.item.ex3 == '1') {
|
if (props.item.ex3 == '1') {
|
||||||
|
let id; // id 为主模板id
|
||||||
if (props.isAdd) {
|
if (props.item.isAdd) {
|
||||||
|
id = props.item.id
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// 编辑状态下 item 为子模板 主模板则是item.parentId
|
||||||
|
id = props.item.parentId
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// 系统预设模板 copy一份
|
// 系统预设模板 copy一份
|
||||||
const { msg } = await addKeyWords({ name: form.name, id: props.item.id })
|
const { msg } = await addKeyWords({ name: form.name, id })
|
||||||
emitter.emit('onGetMain')
|
emitter.emit('onGetMain')
|
||||||
ElMessage.success(msg)
|
ElMessage.success(msg)
|
||||||
mode.value = false
|
mode.value = false
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else{
|
|
||||||
onAddChildTemp(props.item.parentId)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (props.isAdd) {
|
} else {
|
||||||
|
if (props.item.isAdd) {
|
||||||
onAddChildTemp(props.item.id)
|
onAddChildTemp(props.item.id)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="isDialog" :show-close="false" width="900" destroy-on-close>
|
<el-dialog v-model="isDialog" :show-close="false" width="900" append-to-body destroy-on-close>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="custom-header flex">
|
<div class="custom-header flex">
|
||||||
<span>选择{{ title }}</span>
|
<span>选择{{ title }}</span>
|
||||||
|
@ -7,37 +7,79 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div class="flex">
|
|
||||||
<el-radio-group v-model="radio" @change="changeRadio">
|
|
||||||
<el-radio :value="item.value" v-for="item in radioList">{{ item.label }}</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</div>
|
|
||||||
<div class="content-list">
|
<div class="content-list">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(item, index) in list" :class="activeIndex == index ? 'li-active' : ''" @click="clickItem(index)">
|
<li v-for="(item, index) in fileList" :class="activeIndex == index ? 'li-active' : ''"
|
||||||
<el-image class="img" :src="item.url" />
|
@click="clickItem(index, item)">
|
||||||
<span>{{ item.name }}</span>
|
<el-image class="img" :src="url" />
|
||||||
|
<el-button type="primary" class="prev-btn" @click.stop="onPrevItem(item)">预览</el-button>
|
||||||
|
<el-text truncated>{{ item.fileName }}</el-text>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
|
<el-upload class="upload-demo" :action="uploadFileUrl" :limit="1" :show-file-list="false" :headers="headers"
|
||||||
|
:on-success="onSuccess">
|
||||||
|
<el-button type="primary">上传</el-button>
|
||||||
|
</el-upload>
|
||||||
|
<div>
|
||||||
<el-button @click="isDialog = false">取消</el-button>
|
<el-button @click="isDialog = false">取消</el-button>
|
||||||
<el-button type="primary" @click="isDialog = false">
|
<el-button type="primary" @click="isDialog = false">
|
||||||
确定
|
确定
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog v-model="prevVisible" fullscreen :show-close="false" class="prev-dialog">
|
||||||
|
<template #header>
|
||||||
|
<div class="custom-header flex">
|
||||||
|
<span>预览</span>
|
||||||
|
<i class="iconfont icon-guanbi" @click="prevVisible = false"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div style="height: calc(100vh - 120px);">
|
||||||
|
<template v-if="getFileSuffix(prevItem.fileUrl) == 'pdf'">
|
||||||
|
<iframe :src="prevItem.fileUrl"
|
||||||
|
frameborder="0" width="100%" height="100%"></iframe>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-image :src="prevItem.fileUrl" style="height:100%"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<div></div>
|
||||||
|
<el-button type="primary" @click="prevVisible = false">
|
||||||
|
关闭
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, onMounted, reactive } from 'vue'
|
||||||
|
import { completion, addDoc, docList } from '@/api/mode/index.js'
|
||||||
|
import { getToken } from "@/utils/auth";
|
||||||
|
import { sessionStore } from '@/utils/store'
|
||||||
|
import { dataSetJson } from '@/utils/comm.js'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
import { getFileSuffix } from '@/utils/ruoyi.js'
|
||||||
|
import emitter from '@/utils/mitt';
|
||||||
|
|
||||||
|
const userInfo = useUserStore().user
|
||||||
|
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload");
|
||||||
|
const headers = ref({ Authorization: "Bearer " + getToken() });
|
||||||
|
|
||||||
const url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F11044b08-04c1-41a0-a453-1fd20b58a614%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1732953359&t=7ab1d1b3a903db85b1149914407aea35'
|
const url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F11044b08-04c1-41a0-a453-1fd20b58a614%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1732953359&t=7ab1d1b3a903db85b1149914407aea35'
|
||||||
|
|
||||||
const isDialog = defineModel()
|
const isDialog = defineModel()
|
||||||
|
const prevVisible = ref(false)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modeType: {
|
modeType: {
|
||||||
|
@ -52,14 +94,13 @@ const title = computed(() => {
|
||||||
if (props.modeType == 3) return '考试';
|
if (props.modeType == 3) return '考试';
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const radio = ref(1)
|
const radio = ref(1)
|
||||||
const radioList = ref([
|
const radioList = ref([
|
||||||
{ label: '浏览研读', value: 1 },
|
{ label: '浏览研读', value: 1 },
|
||||||
// { label: '跨学科研读', value: 2 },
|
{ label: '跨学科研读', value: 2 },
|
||||||
// { label: '跨学段研读', value: 3 },
|
{ label: '跨学段研读', value: 3 },
|
||||||
// { label: '课标修订研读', value: 4 },
|
{ label: '课标修订研读', value: 4 },
|
||||||
// { label: '自由研读', value: 5 },
|
{ label: '自由研读', value: 5 },
|
||||||
])
|
])
|
||||||
const list = ref([
|
const list = ref([
|
||||||
{
|
{
|
||||||
|
@ -76,15 +117,70 @@ const changeRadio = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const activeIndex = ref(0)
|
||||||
|
|
||||||
const activeIndex = ref(-1)
|
const dataset_id = ref('')
|
||||||
|
|
||||||
const clickItem = (index) => {
|
// 上传成功
|
||||||
activeIndex.value = index
|
const onSuccess = async (response) => {
|
||||||
|
let data = {
|
||||||
|
url: response.url,
|
||||||
|
dataset_id: dataset_id.value
|
||||||
|
}
|
||||||
|
const res = await completion(data)
|
||||||
|
|
||||||
|
if (res.data.code != 200) return
|
||||||
|
let docData = {
|
||||||
|
fileUrl: response.url,
|
||||||
|
fileId: response.file.id,
|
||||||
|
fileName: response.file.fileName,
|
||||||
|
filesize: response.file.fileSize,
|
||||||
|
datasetId: dataset_id.value,
|
||||||
|
docId: res.data.document_id,
|
||||||
|
edustage: curNode.edustage,
|
||||||
|
edusubject: curNode.edusubject
|
||||||
|
}
|
||||||
|
const { msg } = await addDoc(docData)
|
||||||
|
ElMessage.success(msg)
|
||||||
|
getList()
|
||||||
|
|
||||||
|
}
|
||||||
|
const curNode = reactive({})
|
||||||
|
|
||||||
|
const fileList = ref([])
|
||||||
|
const curFile = reactive({})
|
||||||
|
const getList = () => {
|
||||||
|
docList({
|
||||||
|
userId: userInfo.userId,
|
||||||
|
dataset_id: dataset_id.value
|
||||||
|
}).then(res => {
|
||||||
|
fileList.value = [...res.rows]
|
||||||
|
Object.assign(curFile, fileList.value[0])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
const clickItem = (index, item) => {
|
||||||
|
activeIndex.value = index
|
||||||
|
Object.assign(curFile, item)
|
||||||
|
emitter.emit('changeCurFile', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevItem = reactive({})
|
||||||
|
const onPrevItem = (item) => {
|
||||||
|
Object.assign(prevItem, item)
|
||||||
|
prevVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let data = sessionStore.get('subject.curNode')
|
||||||
|
Object.assign(curNode, data);
|
||||||
|
// 暂时写死"考试-" 目前只有考试分析才会弹出来
|
||||||
|
let jsonKey = `考试-${curNode.edustage}-${curNode.edusubject}`
|
||||||
|
dataset_id.value = dataSetJson[jsonKey]
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.custom-header {
|
.custom-header {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -98,13 +194,16 @@ const clickItem = (index) => {
|
||||||
|
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
|
||||||
.content-list {
|
.content-list {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
width: 130px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
@ -114,9 +213,11 @@ const clickItem = (index) => {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.img {
|
.img {
|
||||||
width: 100px;
|
width: 100%;
|
||||||
height: 130px;
|
height: 130px;
|
||||||
border: solid #ccc 1px;
|
border: solid #ccc 1px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -125,6 +226,10 @@ const clickItem = (index) => {
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #E0EAFF;
|
background: #E0EAFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover .prev-btn {
|
||||||
|
transform: translate(-50%, -40px)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.li-active {
|
.li-active {
|
||||||
|
@ -134,4 +239,20 @@ const clickItem = (index) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) translateY(-110px);
|
||||||
|
/* 按钮初始位置在容器外 */
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
/* 设置过渡效果 */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -2,12 +2,13 @@
|
||||||
<div class="container-left-page flex">
|
<div class="container-left-page flex">
|
||||||
<div class="container-left-header flex">
|
<div class="container-left-header flex">
|
||||||
<el-button link @click="onClick">
|
<el-button link @click="onClick">
|
||||||
{{ curNode.edustage }}{{ curNode.edusubject }}{{ type == 1 ? '课标研读' : '教材分析' }}<i
|
{{ curNode.edustage }}{{ curNode.edusubject }}{{ type == 1 ? '课标研读' : type == 2 ? '教材分析' : '考试分析' }}<i
|
||||||
class="iconfont icon-xiangxia"></i>
|
class="iconfont icon-xiangxia"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="container-left-pdf">
|
<div class="container-left-pdf">
|
||||||
<PDF :url="pdfUrl" :showCatalog="false" v-if="pdfUrl" />
|
<PDF :url="pdfUrl" :showCatalog="false" v-if="pdfUrl" />
|
||||||
|
<el-empty v-else description="暂无数据" />
|
||||||
</div>
|
</div>
|
||||||
<!--弹窗-->
|
<!--弹窗-->
|
||||||
<LeftDialog v-model="showDialog" :modeType="type" />
|
<LeftDialog v-model="showDialog" :modeType="type" />
|
||||||
|
@ -15,29 +16,36 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, nextTick } from 'vue'
|
import { ref, onMounted, nextTick, reactive } from 'vue'
|
||||||
import { sessionStore } from '@/utils/store'
|
import { sessionStore } from '@/utils/store'
|
||||||
import PDF from '@/components/PdfJs/index.vue'
|
import PDF from '@/components/PdfJs/index.vue'
|
||||||
import LeftDialog from './left-dialog.vue'
|
import LeftDialog from './left-dialog.vue'
|
||||||
|
|
||||||
const props = defineProps(['curNode', 'type'])
|
const props = defineProps(['type'])
|
||||||
|
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (props.type == 1) return
|
if (props.type != 3) return
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载PDF
|
// 加载PDF
|
||||||
const pdfUrl = ref('')
|
const pdfUrl = ref('')
|
||||||
|
const curNode = reactive({})
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
// 当前节点
|
||||||
|
let nodeData = sessionStore.get('subject.curNode')
|
||||||
|
Object.assign(curNode, nodeData);
|
||||||
|
|
||||||
let data = sessionStore.get('subject.curBook')
|
let data = sessionStore.get('subject.curBook')
|
||||||
let fileurl = data.fileurl
|
let fileurl = data.fileurl
|
||||||
if(props.type == 1){
|
if(props.type == 1){
|
||||||
fileurl = `${data.edustage}-${data.edusubject}-课标.txt`
|
fileurl = `${data.edustage}-${data.edusubject}-课标.txt`
|
||||||
}
|
}
|
||||||
|
if(fileurl == '') return
|
||||||
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf')
|
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf')
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,14 @@
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
<div>
|
<div class="flex">
|
||||||
<el-button type="danger" link @click="removeItem(curTemplate, false)">
|
<el-select v-model="curMode" placeholder="Select" class="mr-4 w-30">
|
||||||
|
<el-option v-for="item in modeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-button type="danger" link :disabled="!(templateList.length)" @click="removeItem(curTemplate, false)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" link @click="onAdd">
|
<el-button type="primary" link :disabled="!(templateList.length)" @click="onAdd">
|
||||||
<i class="iconfont icon-jiahao"></i>
|
<i class="iconfont icon-jiahao"></i>
|
||||||
添加提示词
|
添加提示词
|
||||||
</el-button>
|
</el-button>
|
||||||
|
@ -26,7 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--List-->
|
<!--List-->
|
||||||
<div class="container-right-list">
|
<div class="container-right-list" ref="listRef">
|
||||||
<template v-for="(item, index) in childTempList">
|
<template v-for="(item, index) in childTempList">
|
||||||
<div class="template-item" v-loading="item.loading">
|
<div class="template-item" v-loading="item.loading">
|
||||||
<div class="item-header">
|
<div class="item-header">
|
||||||
|
@ -39,7 +42,7 @@
|
||||||
<i class="iconfont icon-shenglvehao"></i></el-button>
|
<i class="iconfont icon-shenglvehao"></i></el-button>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-button type="primary" link @click="editKeyWord(item)">编辑</el-button>
|
<el-button type="primary" link @click="editKeyWord(item, false)">编辑</el-button>
|
||||||
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
|
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
@ -52,7 +55,8 @@
|
||||||
<i class="iconfont icon-ai"></i>
|
<i class="iconfont icon-ai"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-answer">
|
<div class="item-answer">
|
||||||
<TypingEffect :text="item.oldAnswer" :delay="10" :aiShow="item.aiShow" @complete="onSaveTemp(item)" />
|
<TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow"
|
||||||
|
@complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ai-btn" v-if="item.answer">
|
<div class="ai-btn" v-if="item.answer">
|
||||||
|
@ -77,24 +81,40 @@
|
||||||
<!--编辑结果-->
|
<!--编辑结果-->
|
||||||
<EditDialog v-model="isEdit" :item="editItem" />
|
<EditDialog v-model="isEdit" :item="editItem" />
|
||||||
<!--AI 对话调整-->
|
<!--AI 对话调整-->
|
||||||
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" />
|
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" :curMode="curMode" :conversation_id="conversation_id"/>
|
||||||
<!--添加、编辑提示词-->
|
<!--添加、编辑提示词-->
|
||||||
<keywordDialog v-model="isWordDialog" :isAdd="isAdd" :item="editItem" />
|
<keywordDialog v-model="isWordDialog" :item="editItem" :modeType="type" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, watch, onUnmounted } from 'vue'
|
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
|
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
|
||||||
|
import { createChart, sendChart } from '@/api/ai/index'
|
||||||
import { sessionStore } from '@/utils/store'
|
import { sessionStore } from '@/utils/store'
|
||||||
import keywordDialog from './keyword-dialog.vue';
|
import keywordDialog from './keyword-dialog.vue';
|
||||||
import AdjustDialog from './adjust-dialog.vue'
|
import AdjustDialog from './adjust-dialog.vue'
|
||||||
import EditDialog from './edit-dialog.vue'
|
import EditDialog from './edit-dialog.vue'
|
||||||
import TypingEffect from '@/components/typing-effect/index.vue'
|
import TypingEffect from '@/components/typing-effect/index.vue'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
import emitter from '@/utils/mitt';
|
import emitter from '@/utils/mitt';
|
||||||
import { dataSetJson } from '@/utils/comm.js'
|
import { dataSetJson } from '@/utils/comm.js'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
|
||||||
const props = defineProps(['curNode', 'type'])
|
const props = defineProps(['type'])
|
||||||
|
const { user } = useUserStore()
|
||||||
|
|
||||||
|
const curMode = ref(2)
|
||||||
|
const modeOptions = ref([
|
||||||
|
{
|
||||||
|
label: '教学大模型',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '知识库模型',
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
/*****************提示词相关****************/
|
/*****************提示词相关****************/
|
||||||
|
|
||||||
|
@ -105,21 +125,22 @@ const props = defineProps(['curNode', 'type'])
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const isWordDialog = ref(false)
|
const isWordDialog = ref(false)
|
||||||
const isAdd = ref(false)
|
|
||||||
const editItem = reactive({})
|
const editItem = reactive({})
|
||||||
const onAdd = () => {
|
const onAdd = () => {
|
||||||
isAdd.value = true
|
|
||||||
Object.assign(editItem, curTemplate)
|
Object.assign(editItem, curTemplate)
|
||||||
|
editItem.isAdd = true
|
||||||
isWordDialog.value = true
|
isWordDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const editKeyWord = (item, val) => {
|
const editKeyWord = (item, val) => {
|
||||||
/**
|
/**
|
||||||
* isAdd: 字模板中的移除 为编辑 头部删除 添加提示词为新增
|
* isAdd: 子模板中的移除 为编辑false 头部删除 添加提示词为新增 true
|
||||||
*/
|
*/
|
||||||
isAdd.value = val
|
|
||||||
Object.assign(editItem, item)
|
Object.assign(editItem, item)
|
||||||
|
editItem.isAdd = val
|
||||||
isWordDialog.value = true
|
isWordDialog.value = true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************模板相关**********************/
|
/*******************模板相关**********************/
|
||||||
|
@ -136,39 +157,69 @@ const curTemplate = reactive({ name: '', id: '' })
|
||||||
const templateList = ref([])
|
const templateList = ref([])
|
||||||
const childTempList = ref([])
|
const childTempList = ref([])
|
||||||
const getTemplateList = () => {
|
const getTemplateList = () => {
|
||||||
modelList({ model: props.type, type: 1, pageNum: 1, pageSize: 10000 }).then(res => {
|
modelList({ createUser: user.userId, model: props.type, type: 1, pageNum: 1, pageSize: 10000, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
|
||||||
templateList.value = res.rows
|
templateList.value = res.rows
|
||||||
|
if (res.rows.length > 0) {
|
||||||
Object.assign(curTemplate, res.rows[0]);
|
Object.assign(curTemplate, res.rows[0]);
|
||||||
getChildTemplate()
|
getChildTemplate()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const getChildTemplate = () => {
|
const getChildTemplate = () => {
|
||||||
tempLoading.value = true
|
tempLoading.value = true
|
||||||
modelList({ model: props.type, type: 2, parentId: curTemplate.id }).then(res => {
|
modelList({ model: props.type, type: 2, parentId: curTemplate.id, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
|
||||||
childTempList.value = res.rows
|
childTempList.value = res.rows
|
||||||
|
if (childTempList.value.length) {
|
||||||
|
childTempList.value.forEach(item => item.answer = '')
|
||||||
|
}
|
||||||
getTempResult()
|
getTempResult()
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
tempLoading.value = false
|
tempLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const isStarted = ref([]);
|
||||||
|
const listRef = ref()
|
||||||
// 查询模板结果
|
// 查询模板结果
|
||||||
const getTempResult = () => {
|
const getTempResult = () => {
|
||||||
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000 }).then(res => {
|
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(res => {
|
||||||
let rows = res.rows
|
let rows = res.rows
|
||||||
childTempList.value.forEach(item => {
|
childTempList.value.forEach(item => {
|
||||||
rows.forEach(el => {
|
rows.forEach(el => {
|
||||||
if (item.id == el.modelId) {
|
if (item.id == el.modelId) {
|
||||||
item.answer = el.content
|
item.answer = getResult(el.content)
|
||||||
item.resultId = el.id
|
item.resultId = el.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
if (rows.length > 0) {
|
||||||
|
isStarted.value = new Array(rows.length).fill(true)
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollToBottom = (height, index) => {
|
||||||
|
|
||||||
|
if (listRef.value) {
|
||||||
|
let sum = 0
|
||||||
|
let listDom = listRef.value.children
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
// 220 去掉头部
|
||||||
|
let screenHeight = window.innerHeight - 220
|
||||||
|
if (height > screenHeight) {
|
||||||
|
listRef.value.scrollTop = (height - screenHeight + 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
sum += listDom[i].clientHeight
|
||||||
|
}
|
||||||
|
listRef.value.scrollTop = sum + height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 模板切换
|
// 模板切换
|
||||||
const changeTemplate = (val) => {
|
const changeTemplate = (val) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
|
@ -219,7 +270,6 @@ const removeItem = async (item, isChild) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Ai对话调整
|
// Ai对话调整
|
||||||
const curIndex = ref(-1)
|
const curIndex = ref(-1)
|
||||||
const isAdjust = ref(false)
|
const isAdjust = ref(false)
|
||||||
|
@ -237,22 +287,6 @@ const onEdit = (index, item) => {
|
||||||
isEdit.value = true
|
isEdit.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const modeType = ref('课标')
|
|
||||||
watch(() => props.type, (newVal) => {
|
|
||||||
if (newVal == 1){
|
|
||||||
modeType.value = '课标'
|
|
||||||
}
|
|
||||||
if (newVal == 2){
|
|
||||||
modeType.value = '教材'
|
|
||||||
}
|
|
||||||
if (newVal == 2){
|
|
||||||
modeType.value = '考试'
|
|
||||||
}
|
|
||||||
|
|
||||||
}, { immediate: false })
|
|
||||||
|
|
||||||
|
|
||||||
// 重新研读
|
// 重新研读
|
||||||
const params = reactive(
|
const params = reactive(
|
||||||
{
|
{
|
||||||
|
@ -260,76 +294,147 @@ const params = reactive(
|
||||||
dataset_id: ''
|
dataset_id: ''
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
const prompt = ref('')
|
||||||
|
|
||||||
// 重新研读
|
// 重新研读
|
||||||
|
const isAgain = ref(false)
|
||||||
const againResult = async (index, item) => {
|
const againResult = async (index, item) => {
|
||||||
|
isAgain.value = true
|
||||||
|
isStarted.value[index] = false
|
||||||
|
childTempList.value[index].answer = ''
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
listRef.value.scrollTop = 0
|
||||||
|
|
||||||
|
} else {
|
||||||
|
scrollToBottom(50, index)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await nextTick()
|
||||||
childTempList.value[index].loading = true
|
childTempList.value[index].loading = true
|
||||||
item.aiShow = true
|
item.aiShow = true
|
||||||
childTempList.value[index].oldAnswer = ''
|
|
||||||
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value} 对${curNode.itemtitle}进行教学分析`
|
let str = cloneDeep(prompt.value)
|
||||||
const { data } = await completion(params)
|
str = str.replace('{模板标题}',item.name)
|
||||||
let answer = data.answer
|
str = str.replace('{模板内容}',item.prompt)
|
||||||
childTempList.value[index].oldAnswer = answer
|
params.prompt = str
|
||||||
childTempList.value[index].answer = getResult(answer);
|
params.template = item.prompt
|
||||||
// onEditSave(item)
|
|
||||||
|
let data = null;
|
||||||
|
// 教学大模型
|
||||||
|
if (curMode.value == 1) {
|
||||||
|
const res = await sendChart({
|
||||||
|
content: params.prompt,
|
||||||
|
conversationId: conversation_id.value,
|
||||||
|
stream: false
|
||||||
|
})
|
||||||
|
data = res.data
|
||||||
|
} else {
|
||||||
|
// 知识库模型
|
||||||
|
const res = await completion(params)
|
||||||
|
data = res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
childTempList.value[index].answer = getResult(data.answer);
|
||||||
|
isStarted.value[index] = true
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
childTempList.value[index].loading = false
|
childTempList.value[index].loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 一键研读
|
// 一键研读
|
||||||
const getCompletion = async () => {
|
const getCompletion = async () => {
|
||||||
|
isStarted.value = new Array(childTempList.length).fill(false)
|
||||||
|
isStarted.value[0] = true
|
||||||
|
|
||||||
|
childTempList.value.forEach(item => {
|
||||||
|
if (item.answer) {
|
||||||
|
item.answer = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
for (let item of childTempList.value) {
|
for (let item of childTempList.value) {
|
||||||
try {
|
try {
|
||||||
item.loading = true
|
item.loading = true
|
||||||
item.aiShow = true
|
item.aiShow = true
|
||||||
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value} 对${curNode.itemtitle}进行教学分析`
|
let str = cloneDeep(prompt.value)
|
||||||
const { data } = await completion(params)
|
str = str.replace('{模板标题}',item.name)
|
||||||
let answer = data.answer
|
str = str.replace('{模板内容}',item.prompt)
|
||||||
item.oldAnswer = answer
|
params.prompt = str
|
||||||
item.answer = getResult(answer);
|
params.template = item.prompt
|
||||||
|
// 教学大模型
|
||||||
|
let data = null
|
||||||
|
if (curMode.value == 1) {
|
||||||
|
const res = await sendChart({
|
||||||
|
content: params.prompt,
|
||||||
|
conversationId: conversation_id.value,
|
||||||
|
stream: false
|
||||||
|
})
|
||||||
|
data = res.data
|
||||||
|
}
|
||||||
|
// 知识库模型
|
||||||
|
else {
|
||||||
|
const res = await completion(params)
|
||||||
|
data = res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
item.answer = getResult(data.answer)
|
||||||
|
onSaveTemp(item)
|
||||||
} finally {
|
} finally {
|
||||||
item.loading = false
|
item.loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCompleteText = async (answer, index) => {
|
||||||
|
if (index < childTempList.value.length - 1) {
|
||||||
|
isStarted.value[index + 1] = true; // 开始显示下一个文本
|
||||||
|
}
|
||||||
|
if (isAgain.value) {
|
||||||
|
try {
|
||||||
|
await editTempResult({ id: childTempList.value[index].resultId, content: answer })
|
||||||
|
} finally {
|
||||||
|
isAgain.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 替换分析结果
|
// 替换分析结果
|
||||||
emitter.on('onSaveAdjust', (item) => {
|
emitter.on('onSaveAdjust', (item) => {
|
||||||
childTempList.value[curIndex.value].oldAnswer = item
|
childTempList.value[curIndex.value].answer = item
|
||||||
let answer = getResult(item);
|
|
||||||
childTempList.value[curIndex.value].oldAnswer = item
|
|
||||||
childTempList.value[curIndex.value].answer = answer
|
|
||||||
onEditSave(childTempList.value[curIndex.value])
|
onEditSave(childTempList.value[curIndex.value])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// 保存 重新研读后的结果
|
// 保存 重新研读后的结果
|
||||||
const onEditSave = async (item) => {
|
const onEditSave = async (item) => {
|
||||||
const { res } = await editTempResult({id: item.resultId, content: item.oldAnswer})
|
const { msg } = await editTempResult({ id: item.resultId, content: item.answer })
|
||||||
ElMessage.success(res)
|
ElMessage.success(msg)
|
||||||
getChildTemplate()
|
getChildTemplate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存模板
|
// 保存模板
|
||||||
const onSaveTemp = (item) => {
|
const onSaveTemp = async (item) => {
|
||||||
if(item.oldAnswer == '') return
|
if (item.answer == '') return
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
mainModelId: curTemplate.id,
|
mainModelId: curTemplate.id,
|
||||||
modelId: item.id,
|
modelId: item.id,
|
||||||
examDocld: '',
|
examDocld: '',
|
||||||
content: item.oldAnswer
|
content: item.answer,
|
||||||
|
ex1: curNode.id
|
||||||
|
}
|
||||||
|
const res = await tempSave(data)
|
||||||
|
if(!item.resultId){
|
||||||
|
item.resultId = res.data
|
||||||
}
|
}
|
||||||
tempSave(data).then(res => {})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分析获取课标对话结果
|
// 去掉字符串中的 ### **
|
||||||
let getResult = (text) => {
|
let getResult = (str) => {
|
||||||
text = text.replace(/^\n\n(.*?)\n\n$/s, '<div>$1</div>');
|
let newStr = str.replace(/#+|(\*\*)/g, '');
|
||||||
text = text.replace(/^\n(.*?)\n$/s, '<p>$1</p>');
|
return newStr
|
||||||
text = text.replace(/\*\*(.*?)\*\*/g, "<div class='text-tit'>$1</div>");
|
|
||||||
text = text.replace(/(\d+\..*?)\n/g, "<div class='text-num'>$1</div>\n");
|
|
||||||
return text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 操作之后获取字模板
|
// 操作之后获取字模板
|
||||||
|
@ -342,13 +447,49 @@ emitter.on('onGetMain', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 创建对话
|
||||||
|
const conversation_id = ref('')
|
||||||
|
const getChartId = () => {
|
||||||
|
createChart({ app_id: '712ff0df-ed6b-470f-bf87-8cfbaf757be5' }).then(res => {
|
||||||
|
localStorage.setItem("conversation_id", res.data.conversation_id);
|
||||||
|
conversation_id.value = res.data.conversation_id;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询prompt 替换
|
||||||
|
const getPrompt = async () => {
|
||||||
|
const { rows } = await modelList({ model: 5 })
|
||||||
|
let str = rows.find(item => item.name.indexOf(modeType.value) != -1).prompt
|
||||||
|
str = str.replace('{学段}', curNode.edustage)
|
||||||
|
str = str.replace('{学科}', curNode.edusubject)
|
||||||
|
let bookV = curNode.roottitle.split('-')[1] + '版本'
|
||||||
|
str = str.replace('{教材版本}', bookV)
|
||||||
|
str = str.replace('{课程名称}', `《${curNode.itemtitle}》`)
|
||||||
|
if(modeType.value == '课标'){
|
||||||
|
str = str.replace('{课标名称}', `${curNode.edustage}${curNode.edusubject}课标`)
|
||||||
|
}
|
||||||
|
prompt.value = str
|
||||||
|
}
|
||||||
|
|
||||||
const curNode = reactive({})
|
const curNode = reactive({})
|
||||||
|
const modeType = ref('')
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getTemplateList()
|
|
||||||
let data = sessionStore.get('subject.curNode')
|
let data = sessionStore.get('subject.curNode')
|
||||||
Object.assign(curNode, data);
|
Object.assign(curNode, data);
|
||||||
|
modeType.value = props.type == 1 ? '课标' : props.type == 2 ? '教材' : '考试'
|
||||||
|
|
||||||
|
getTemplateList()
|
||||||
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
|
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
|
||||||
params.dataset_id = dataSetJson[jsonKey]
|
params.dataset_id = dataSetJson[jsonKey]
|
||||||
|
// 获取百度千帆会话ID
|
||||||
|
conversation_id.value = localStorage.getItem('conversation_id')
|
||||||
|
if (!conversation_id.value) {
|
||||||
|
getChartId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取prompt
|
||||||
|
getPrompt()
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 解绑
|
// 解绑
|
||||||
|
@ -381,8 +522,6 @@ onUnmounted(() => {
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.template-item {
|
.template-item {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
|
@ -2,18 +2,16 @@
|
||||||
<div class="page-template flex">
|
<div class="page-template flex">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<Left :curNode="curNode" :type="type" />
|
<Left :type="type" />
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<Right :curNode="curNode" :type="type" />
|
<Right :type="type" />
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, onMounted } from 'vue'
|
|
||||||
import { sessionStore } from '@/utils/store'
|
|
||||||
import Left from './container/left.vue'
|
import Left from './container/left.vue'
|
||||||
import Right from './container/right.vue'
|
import Right from './container/right.vue'
|
||||||
|
|
||||||
|
@ -23,13 +21,6 @@ const props = defineProps({
|
||||||
default: 1
|
default: 1
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const curNode = reactive({})
|
|
||||||
onMounted(() =>{
|
|
||||||
let data = sessionStore.get('subject.curNode')
|
|
||||||
Object.assign(curNode, data);
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="typing-effect">
|
<div class="typing-effect" ref="typingEffectRef">
|
||||||
<!-- <span v-html="displayedText"></span> -->
|
<!-- <span v-html="displayedText"></span> -->
|
||||||
<el-input
|
<el-input
|
||||||
v-model="displayedText"
|
v-model="displayedText"
|
||||||
|
@ -8,17 +8,17 @@
|
||||||
readonly
|
readonly
|
||||||
resize="none"
|
resize="none"
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:15px"
|
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:14px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, watch, nextTick } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
text: {
|
text: {
|
||||||
type: String,
|
type: [String, Object],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
delay: {
|
delay: {
|
||||||
|
@ -26,41 +26,51 @@ const props = defineProps({
|
||||||
default: 100 // 默认每个字符出现的延迟时间,单位是毫秒
|
default: 100 // 默认每个字符出现的延迟时间,单位是毫秒
|
||||||
},
|
},
|
||||||
aiShow: {
|
aiShow: {
|
||||||
type: [Boolean]
|
type: [Boolean] // 为true 只展示
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['complete']);
|
const typingEffectRef = ref(null);
|
||||||
|
const emit = defineEmits(['complete', 'updateScroll']);
|
||||||
const displayedText = ref('');
|
const displayedText = ref('');
|
||||||
const index = ref(0);
|
const index = ref(0);
|
||||||
|
|
||||||
const type = () => {
|
const type = async () => {
|
||||||
if(!props.aiShow) return
|
await nextTick()
|
||||||
|
if(!props.aiShow) {
|
||||||
|
displayedText.value = props.text
|
||||||
|
return
|
||||||
|
}
|
||||||
if (index.value <= props.text.length) {
|
if (index.value <= props.text.length) {
|
||||||
displayedText.value += props.text.charAt(index.value);
|
displayedText.value += props.text.charAt(index.value);
|
||||||
index.value++;
|
index.value++;
|
||||||
setTimeout(() => type(), props.delay);
|
setTimeout(() => {
|
||||||
|
type();
|
||||||
|
emit('updateScroll', typingEffectRef.value.clientHeight); // 每次添加新字符后滚动到底部
|
||||||
|
}, props.delay);
|
||||||
} else {
|
} else {
|
||||||
// 当所有字符都显示完毕时,触发 complete 事件
|
// 当所有字符都显示完毕时,触发 complete 事件
|
||||||
emit('complete');
|
emit('complete',displayedText.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
type();
|
resetAndType();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听 props 的变化,以便当传入的 text 或 delay 发生改变时重新开始打字机效果
|
const resetAndType = () =>{
|
||||||
watch([() => props.text, () => props.delay], () => {
|
|
||||||
displayedText.value = '';
|
displayedText.value = '';
|
||||||
index.value = 0;
|
index.value = 0;
|
||||||
type();
|
type();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// 监听 props 的变化,以便当传入的 text 或 delay 发生改变时重新开始打字机效果
|
||||||
|
watch([() => props.text, () => props.delay], resetAndType);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.typing-effect {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
:deep(.el-textarea__inner:hover){
|
:deep(.el-textarea__inner:hover){
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="whiteboart-container" :style="{ height: height + 'px' }">
|
<div class="whiteboart-container" :style="{ height: height + 'px' }">
|
||||||
<div class="canvasBox" ref="box"></div>
|
<div class="canvasBox" ref="box" @mouseenter.capture="handleMouseEnter" @mouseleave.capture="handleMouseLeave"></div>
|
||||||
|
|
||||||
<div class="footerLeft" @click.stop
|
<div class="footerLeft" @click.stop
|
||||||
:style="type == 'design' ? ['top: 10px', 'justify-content: space-between'] : ['bottom: 10px', 'justify-content: center']">
|
:style="type == 'design' ? ['top: 10px', 'justify-content: space-between'] : ['bottom: 10px', 'justify-content: center']">
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="blockBox">
|
<div class="blockBox">
|
||||||
<el-button @click="currentType = 'selection'"><el-image src="../../../src/assets/images/mouse-pointer.png"
|
<el-button @click="currentType = 'selection'"><el-image :src="pointerImg"
|
||||||
style="width: 14px; height: 14px; color: silver" /></el-button>
|
style="width: 14px; height: 14px; color: silver" /></el-button>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="type == 'design'">
|
<template v-if="type == 'design'">
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
<!-- 边框粗细 -->
|
<!-- 边框粗细 -->
|
||||||
<div class="blockBox">
|
<div class="blockBox">
|
||||||
<el-dropdown @command="updateStyle('lineWidth', $event)" placement="top">
|
<el-dropdown @command="updateStyle('lineWidth', $event)" placement="top">
|
||||||
<el-button><el-image src="../../../src/assets/images/borderwidth.png"
|
<el-button><el-image :src="borderImg"
|
||||||
style="width: 14px; height: 14px"></el-image></el-button>
|
style="width: 14px; height: 14px"></el-image></el-button>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
|
@ -278,7 +278,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref, watch, toRaw, nextTick, computed, reactive, defineProps, defineEmits } from 'vue'
|
import { onMounted, onBeforeUnmount, ref, getCurrentInstance, watch, toRaw, nextTick, computed, reactive, defineProps, defineEmits } from 'vue'
|
||||||
import TinyWhiteboard from 'whiteboard_lyc'
|
import TinyWhiteboard from 'whiteboard_lyc'
|
||||||
import ColorPicker from './components/ColorPicker.vue'
|
import ColorPicker from './components/ColorPicker.vue'
|
||||||
import {
|
import {
|
||||||
|
@ -303,6 +303,10 @@ import {
|
||||||
import Contextmenu from './components/Contextmenu.vue'
|
import Contextmenu from './components/Contextmenu.vue'
|
||||||
import { fontFamilyList, fontSizeList } from './constants'
|
import { fontFamilyList, fontSizeList } from './constants'
|
||||||
|
|
||||||
|
const borderImg = new URL('../../../src/assets/images/borderwidth.png', import.meta.url).href
|
||||||
|
const pointerImg = new URL('../../../src/assets/images/mouse-pointer.png', import.meta.url).href
|
||||||
|
const { proxy } = getCurrentInstance()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -504,6 +508,7 @@ const backToCenter = () => {
|
||||||
app.scrollToCenter()
|
app.scrollToCenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 显示全部
|
// 显示全部
|
||||||
const showFit = () => {
|
const showFit = () => {
|
||||||
let elementList = app.elements.elementList
|
let elementList = app.elements.elementList
|
||||||
|
@ -697,8 +702,6 @@ const getCanvasBlob = async () =>{
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
watch(() => props.data, (newVal) => {
|
watch(() => props.data, (newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
setCanvasData(newVal)
|
setCanvasData(newVal)
|
||||||
|
@ -710,6 +713,13 @@ watch(() => props.data, (newVal) => {
|
||||||
|
|
||||||
// dom元素挂载完成
|
// dom元素挂载完成
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
init();
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化画布内容
|
||||||
|
*/
|
||||||
|
const init = () => {
|
||||||
// 创建实例
|
// 创建实例
|
||||||
app = new TinyWhiteboard({
|
app = new TinyWhiteboard({
|
||||||
container: box.value,
|
container: box.value,
|
||||||
|
@ -731,6 +741,7 @@ onMounted(() => {
|
||||||
})
|
})
|
||||||
// 监听元素激活事件
|
// 监听元素激活事件
|
||||||
app.on('activeElementChange', element => {
|
app.on('activeElementChange', element => {
|
||||||
|
console.log('点击元素 监听 activeElementChange-----------')
|
||||||
if (activeElement.value) {
|
if (activeElement.value) {
|
||||||
activeElement.value.off('elementRotateChange', onElementRotateChange)
|
activeElement.value.off('elementRotateChange', onElementRotateChange)
|
||||||
}
|
}
|
||||||
|
@ -778,7 +789,39 @@ onMounted(() => {
|
||||||
app.resize()
|
app.resize()
|
||||||
}, 300)
|
}, 300)
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const isMyCanvas = ref(false); // 鼠标 是否进入画布判断
|
||||||
|
const handleKeyDown=(event)=> {
|
||||||
|
// console.log('键盘按键被按下:', event.key);
|
||||||
|
// console.log(isMyCanvas.value,'??????????')
|
||||||
|
if(isMyCanvas.value == false){
|
||||||
|
event.stopPropagation();
|
||||||
|
// console.log('键盘事件被捕获,已阻止冒泡:', event.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 鼠标进入事件
|
||||||
|
*/
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
console.log('进入白板')
|
||||||
|
isMyCanvas.value = true;
|
||||||
|
document.addEventListener('keydown', handleKeyDown, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 课堂展示-鼠标离开白板监听事件:该事件是避免,选中状态,在其他地方点击、后退、删除等事件,会删除白板内选中的元素
|
||||||
|
*/
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
console.log('离开白板')
|
||||||
|
// 清除激活项--点击事件的激活项
|
||||||
|
app.cancelActiveElement()
|
||||||
|
// 阻止 点击拖动范围的取消激活项……
|
||||||
|
isMyCanvas.value = false;
|
||||||
|
// 确保事件处理函数在组件挂载后绑定
|
||||||
|
document.addEventListener('keydown', handleKeyDown, true);
|
||||||
|
};
|
||||||
|
|
||||||
// 暴露方法
|
// 暴露方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
@ -788,7 +831,7 @@ defineExpose({
|
||||||
getCanvasJson,
|
getCanvasJson,
|
||||||
getCanvasBase64,
|
getCanvasBase64,
|
||||||
setCanvasData,
|
setCanvasData,
|
||||||
getCanvasBlob
|
getCanvasBlob,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ const closeWindow = () => {
|
||||||
ElMessageBox.confirm('确认退出系统吗?', '提示', {
|
ElMessageBox.confirm('确认退出系统吗?', '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
|
customClass: 'login-close-tool',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
userStore.logOut().then(() => {
|
userStore.logOut().then(() => {
|
||||||
|
@ -54,7 +55,11 @@ onMounted(() =>{
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.login-close-tool {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.header-tool {
|
.header-tool {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* 无限滚动
|
||||||
|
*/
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
const mountedHook = async (el, binding) => {
|
||||||
|
console.log(el, binding)
|
||||||
|
const value = binding.value
|
||||||
|
if (typeof value !== 'function') return console.error('v-scroll must be a function')
|
||||||
|
await nextTick()
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
// Hooks for Vue3
|
||||||
|
mounted(el, binding) {
|
||||||
|
mountedHook(el, binding)
|
||||||
|
},
|
||||||
|
// Hooks for Vue2
|
||||||
|
inserted(el, binding) {
|
||||||
|
mountedHook(el, binding)
|
||||||
|
},
|
||||||
|
|
||||||
|
update(el, binding){
|
||||||
|
},
|
||||||
|
updated(el, binding){
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import { listEntpcoursework,getEvaluationclue } from '@/api/classTask';
|
import { listEntpcoursework,getEvaluationclue } from '@/api/classTask';
|
||||||
import { processList } from '@/hooks/useProcessList';
|
import { processList } from '@/hooks/useProcessList';
|
||||||
|
import useClassTaskStore from '@/store/modules/classTask'
|
||||||
|
const useClassTaskStores = useClassTaskStore();
|
||||||
|
|
||||||
const isJson = (str) => {
|
const isJson = (str) => {
|
||||||
if (typeof str == 'string') {
|
if (typeof str == 'string') {
|
||||||
|
@ -32,7 +34,7 @@ export const editListItem = (row, courseObj) => {
|
||||||
worktype: '', // 设计中的作业类型
|
worktype: '', // 设计中的作业类型
|
||||||
quizlist: [], // 设计中的试题列表
|
quizlist: [], // 设计中的试题列表
|
||||||
chooseWorkLists: [],// 设计中的框架梳理list
|
chooseWorkLists: [],// 设计中的框架梳理list
|
||||||
fileHomeworkList: [],// 设计中的常规作业list
|
fileHomeworkList: [],//TODO 暂时共用这个字段(新增了 科学实验) 设计中的常规作业list
|
||||||
whiteboardObj: '',// 设计中的课堂展示对象
|
whiteboardObj: '',// 设计中的课堂展示对象
|
||||||
question: '', // 设计中的[课堂展示]的问题
|
question: '', // 设计中的[课堂展示]的问题
|
||||||
};
|
};
|
||||||
|
@ -112,6 +114,24 @@ export const editListItem = (row, courseObj) => {
|
||||||
return resolve(classtaskObj);
|
return resolve(classtaskObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (row.worktype == '科学实验') {
|
||||||
|
if(isJson(row.workcodes)){
|
||||||
|
// 同步更新实验内部的科目信息
|
||||||
|
if (row.worktag && row.worktag.indexOf('-') > -1){
|
||||||
|
const eduInfo = row.worktag.split('-');
|
||||||
|
useClassTaskStores.experimentObj.edustage = eduInfo[0];
|
||||||
|
useClassTaskStores.experimentObj.edusubject = eduInfo[1];
|
||||||
|
useClassTaskStores.experimentObj.updateEduInfo = row.worktag;
|
||||||
|
}
|
||||||
|
// 更新科学实验内容
|
||||||
|
classtaskObj.fileHomeworkList = JSON.parse(row.workcodes);
|
||||||
|
//
|
||||||
|
// console.log('科学实验', classtaskObj);
|
||||||
|
// 更新默认的科学实验( 学段 学科 以及实验科目)
|
||||||
|
console.log('科学实验', classtaskObj);
|
||||||
|
return resolve(classtaskObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,13 +87,15 @@ const getHomeWorkList = async () => {
|
||||||
// } else
|
// } else
|
||||||
// 课标研读 目标设定 教材研读 框架梳理 学科定位 TODO 后续接入在添加
|
// 课标研读 目标设定 教材研读 框架梳理 学科定位 TODO 后续接入在添加
|
||||||
if (res.rows[i].worktype == '课堂展示') {
|
if (res.rows[i].worktype == '课堂展示') {
|
||||||
res.rows[i].workclass = 'primary';
|
res.rows[i].workclass = 'success';
|
||||||
} else if (res.rows[i].worktype == '框架梳理') {
|
} else if (res.rows[i].worktype == '框架梳理') {
|
||||||
res.rows[i].workclass = 'warning';
|
res.rows[i].workclass = 'warning';
|
||||||
} else if (res.rows[i].worktype == '常规作业') {
|
} else if (res.rows[i].worktype == '常规作业') {
|
||||||
res.rows[i].workclass = 'info';
|
res.rows[i].workclass = 'info';
|
||||||
} else if (res.rows[i].worktype == '习题训练') {
|
} else if (res.rows[i].worktype == '习题训练') {
|
||||||
res.rows[i].workclass = 'danger';
|
res.rows[i].workclass = 'danger';
|
||||||
|
} else if (res.rows[i].worktype == '科学实验') {
|
||||||
|
res.rows[i].workclass = 'primary';
|
||||||
} else {
|
} else {
|
||||||
res.rows[i].workclass = 'primary';
|
res.rows[i].workclass = 'primary';
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,17 @@ export const useGetSubject = async () =>{
|
||||||
|
|
||||||
// 根据学科 + 学段 获取所有单元章节
|
// 根据学科 + 学段 获取所有单元章节
|
||||||
const getSubjectUnit = async () =>{
|
const getSubjectUnit = async () =>{
|
||||||
|
if(isStadium(userStore.user)) {
|
||||||
|
//如果是基地人员,直接拿treeData
|
||||||
|
const unitParams = {
|
||||||
|
itemgroup: '基地课程',
|
||||||
|
orderby: 'orderidx asc',
|
||||||
|
entpid: userStore.user.deptId,
|
||||||
|
pageSize: 10000
|
||||||
|
}
|
||||||
|
const { rows } = await listEvaluation(unitParams)
|
||||||
|
unitList.value = rows
|
||||||
|
}else{
|
||||||
if(sessionStore.get('subject.unitList')){
|
if(sessionStore.get('subject.unitList')){
|
||||||
unitList.value = sessionStore.get('subject.unitList')
|
unitList.value = sessionStore.get('subject.unitList')
|
||||||
}
|
}
|
||||||
|
@ -34,6 +45,11 @@ export const useGetSubject = async () =>{
|
||||||
}
|
}
|
||||||
await getSubject()
|
await getSubject()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
const isStadium = (user) => {
|
||||||
|
let roles = user.roles
|
||||||
|
return roles.some(item => item.roleKey === 'stadium')
|
||||||
|
}
|
||||||
|
|
||||||
// 根据学科 + 学段 获取教材
|
// 根据学科 + 学段 获取教材
|
||||||
const getSubject = async () =>{
|
const getSubject = async () =>{
|
||||||
|
@ -68,6 +84,9 @@ export const useGetSubject = async () =>{
|
||||||
|
|
||||||
// 单元章节数据转为“树”结构
|
// 单元章节数据转为“树”结构
|
||||||
const getTreeData = (bookId) =>{
|
const getTreeData = (bookId) =>{
|
||||||
|
if (!bookId) {
|
||||||
|
return unitList.value
|
||||||
|
}
|
||||||
// 根据当前教材的id 查找出对应的章节
|
// 根据当前教材的id 查找出对应的章节
|
||||||
let data = unitList.value.filter(item => item.rootid == bookId && item.level == 1)
|
let data = unitList.value.filter(item => item.rootid == bookId && item.level == 1)
|
||||||
data.forEach( item => {
|
data.forEach( item => {
|
||||||
|
|
|
@ -36,13 +36,13 @@ export const processList = (row, aloneOption=false) => {
|
||||||
row[i].method = jjj.method
|
row[i].method = jjj.method
|
||||||
row[i].discuss = jjj.discuss
|
row[i].discuss = jjj.discuss
|
||||||
//row[i].discusscollapse = false;
|
//row[i].discusscollapse = false;
|
||||||
if (row[i].examdate !== null && row[i].examdate !== undefined) {
|
if (row[i].examdate && row[i].examdate != "") {
|
||||||
row[i].examdate = row[i].examdate.substring(0, 10)
|
row[i].examdate = row[i].examdate.substring(0, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 具体题型数据结构处理
|
// 具体题型数据结构处理
|
||||||
if (row[i].worktype == '复合题') {
|
if (row[i].worktype == '复合题') {
|
||||||
// 旧类型
|
// 复合题 - 旧格式
|
||||||
if (row[i].title.indexOf('!@#$%') !== -1) {
|
if (row[i].title.indexOf('!@#$%') !== -1) {
|
||||||
// 1.选项解析替换
|
// 1.选项解析替换
|
||||||
const options = JSON.parse(row[i].workdesc)
|
const options = JSON.parse(row[i].workdesc)
|
||||||
|
@ -129,7 +129,9 @@ export const processList = (row, aloneOption=false) => {
|
||||||
|
|
||||||
row[i].workanswerFormat = answer
|
row[i].workanswerFormat = answer
|
||||||
} else {
|
} else {
|
||||||
// 处理[题干显示] - 不再需要处理
|
// 复合题 - 现格式
|
||||||
|
|
||||||
|
// 处理[题干显示] - 不再需要处理(头部已处理)
|
||||||
// row[i].titleFormat = row[i].title; // 仅占位提示
|
// row[i].titleFormat = row[i].title; // 仅占位提示
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -222,6 +224,7 @@ export const processList = (row, aloneOption=false) => {
|
||||||
row[i].workanswerFormat = workAnswerHtml
|
row[i].workanswerFormat = workAnswerHtml
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
|
/** 主观题/非基础题(其中主要为第三方的各类解答题) */
|
||||||
row[i].worktype == '主观题' ||
|
row[i].worktype == '主观题' ||
|
||||||
(row[i].worktype !== '单选题' &&
|
(row[i].worktype !== '单选题' &&
|
||||||
row[i].worktype !== '多选题' &&
|
row[i].worktype !== '多选题' &&
|
||||||
|
@ -236,7 +239,7 @@ export const processList = (row, aloneOption=false) => {
|
||||||
row[i].workanswerFormat = JSON.parse(row[i].workanswer)
|
row[i].workanswerFormat = JSON.parse(row[i].workanswer)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
// 基础题: 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||||
// 通用选项结构 ['ABC123','ABC123'] | ['ABC123','ABC123'] | [](填空题无选项) | [](判断题无选项)
|
// 通用选项结构 ['ABC123','ABC123'] | ['ABC123','ABC123'] | [](填空题无选项) | [](判断题无选项)
|
||||||
let workDescArr = []
|
let workDescArr = []
|
||||||
if (
|
if (
|
||||||
|
@ -296,21 +299,28 @@ export const processList = (row, aloneOption=false) => {
|
||||||
if(!aloneOption && j%2== 0){
|
if(!aloneOption && j%2== 0){
|
||||||
tmp += '</div>';
|
tmp += '</div>';
|
||||||
}
|
}
|
||||||
row[i].workdescFormat = tmp
|
row[i].workdescFormat = tmp;
|
||||||
|
|
||||||
// 处理[答案显示] - 转换ABCD
|
// 处理[答案显示] - 转换ABCD
|
||||||
let arr2Char = workAnswerArr
|
let arr2Char = workAnswerArr
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
return String.fromCharCode(65 + Number(item))
|
return String.fromCharCode(65 + Number(item))
|
||||||
})
|
})
|
||||||
.join('')
|
.join('');
|
||||||
row[i].workanswerFormat = arr2Char
|
row[i].workanswerFormat = arr2Char;
|
||||||
} else if (row[i].worktype == '填空题') {
|
} else if (row[i].worktype == '填空题') {
|
||||||
// 处理[选项显示] - 填空题中无选项, 故置空
|
// 处理[选项显示] - 填空题中无选项, 故置空
|
||||||
row[i].workdescFormat = ''
|
row[i].workdescFormat = '';
|
||||||
|
|
||||||
// 处理[答案显示] - 逗号连接
|
// 处理[答案显示] - 逗号连接
|
||||||
row[i].workanswerFormat = workAnswerArr.join('、')
|
|
||||||
|
// 当[答案显示]为 [<div] 开头时,不再需逗号连接(一般为自主上传, 当前答案每个自带div标签)
|
||||||
|
let linkChar = '、';
|
||||||
|
if (workAnswerArr.length != 0 && workAnswerArr[0].indexOf('<div') == 0) {
|
||||||
|
linkChar = '';
|
||||||
|
}
|
||||||
|
row[i].workanswerFormat = workAnswerArr.join(linkChar);
|
||||||
|
|
||||||
} else if (row[i].worktype == '判断题') {
|
} else if (row[i].worktype == '判断题') {
|
||||||
// 处理[选项显示] - 判断题中无选项, 故置空
|
// 处理[选项显示] - 判断题中无选项, 故置空
|
||||||
row[i].workdescFormat = ''
|
row[i].workdescFormat = ''
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
<el-popover ref="popoverRef" placement="right" trigger="hover" popper-class="popoverStyle" :tabindex="999" >
|
<el-popover ref="popoverRef" placement="right" trigger="hover" popper-class="popoverStyle" :tabindex="999" >
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<el-image class="user-img" :src="dev_api + userStore.user.avatar" />
|
<el-image class="user-img" :src="userStore.user.avatar ==='/img/avatar-default.jpg' || userStore.user.avatar ==='/images/img-avatar.png' ? defaultUserImg : dev_api + userStore.user.avatar" />
|
||||||
<span>{{ userStore.user.nickName }}</span>
|
<span>{{ userStore.user.nickName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="head-aside">
|
<div class="head-aside">
|
||||||
<ul >
|
<ul >
|
||||||
<li :class="computedregistertype==1 || computedregistertype==3?'auth-li':'auth-li pointer-events'" @click="onUserTo('/schoolCertification')" >
|
<li v-if="isStadium() !== true" :class="computedregistertype==1 || computedregistertype==3?'auth-li':'auth-li pointer-events'" @click="onUserTo('/schoolCertification')" >
|
||||||
<i class="iconfont icon-renzheng-" :style="computedregistertype==4?'color:green;':''"></i>
|
<i class="iconfont icon-renzheng-" :style="computedregistertype==4?'color:green;':''"></i>
|
||||||
<span class="mlr-5" v-if="computedregistertype!=4">学校认证</span>
|
<span class="mlr-5" v-if="computedregistertype!=4">学校认证</span>
|
||||||
<span class="mlr-5" v-else>{{ userStore.DeptInfo.register.schoolName }}</span>
|
<span class="mlr-5" v-else>{{ userStore.DeptInfo.register.schoolName }}</span>
|
||||||
|
@ -19,8 +19,8 @@
|
||||||
</li>
|
</li>
|
||||||
<li v-if="computedregistertype!=4" :class="computedregistertype==1 || computedregistertype==2 ? '':'pointer-events'" @click="onUserTo('/joinSchool')">加入学校</li>
|
<li v-if="computedregistertype!=4" :class="computedregistertype==1 || computedregistertype==2 ? '':'pointer-events'" @click="onUserTo('/joinSchool')">加入学校</li>
|
||||||
<li @click="onUserTo('/profile')">个人中心</li>
|
<li @click="onUserTo('/profile')">个人中心</li>
|
||||||
<li @click="onUserTo('/schoolManagement')">学校管理</li>
|
<li v-if="isStadium() !== true" @click="onUserTo('/schoolManagement')">学校管理</li>
|
||||||
<li @click="onUserTo('/class')">班级中心</li>
|
<li v-if="isStadium() !== true" @click="onUserTo('/class')">班级中心</li>
|
||||||
<li @click="logout">退出登录</li>
|
<li @click="logout">退出登录</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,12 +53,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch , reactive, onMounted,computed} from 'vue'
|
import { ref, watch , reactive, onMounted, onBeforeMount, computed} from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||||
import useUserStore from '@/store/modules/user'
|
import useUserStore from '@/store/modules/user'
|
||||||
import { sessionStore } from '@/utils/store'
|
|
||||||
import pkc from "../../../../../package.json"
|
import pkc from "../../../../../package.json"
|
||||||
|
import defaultUserImg from '@/assets/images/img-avatar.png'
|
||||||
|
import { sessionStore } from '@/utils/store'
|
||||||
|
|
||||||
|
|
||||||
const { ipcRenderer } = window.electron || {}
|
const { ipcRenderer } = window.electron || {}
|
||||||
|
@ -74,18 +75,42 @@ const version = ref(pkc.version)
|
||||||
|
|
||||||
const popoverRef = ref('')
|
const popoverRef = ref('')
|
||||||
|
|
||||||
const headerMenus = [
|
//是否是基地人员
|
||||||
|
const isStadium = () => {
|
||||||
|
let user = userStore.user
|
||||||
|
let roles = user.roles
|
||||||
|
return roles.some(item => item.roleKey === 'stadium')
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerMenus = isStadium() ?[{
|
||||||
|
name: '教学实践',
|
||||||
|
id: 6,
|
||||||
|
icon: 'icon-jiaoxueshijian',
|
||||||
|
path: '/prepare'
|
||||||
|
},]:[
|
||||||
{
|
{
|
||||||
name: '教学大模型',
|
name: '教学大模型',
|
||||||
id: 1,
|
id: 1,
|
||||||
icon: 'icon-shouye',
|
icon: 'icon-shouye',
|
||||||
path: '/model/index'
|
path: '/model/index'
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// name: '教学工作台',
|
||||||
|
// id: 2,
|
||||||
|
// icon: 'icon-gongzuotai',
|
||||||
|
// path: '/desktop'
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
name: '教学工作台',
|
name: '教学实践',
|
||||||
id: 2,
|
id: 4,
|
||||||
icon: 'icon-gongzuotai',
|
icon: 'icon-jiaoxueshijian',
|
||||||
path: '/desktop'
|
path: '/prepare'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '教学活动',
|
||||||
|
id: 5,
|
||||||
|
icon: 'icon-zuoyepigai',
|
||||||
|
path: '/classTask'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '资源中心',
|
name: '资源中心',
|
||||||
|
@ -157,9 +182,9 @@ watch(
|
||||||
|
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
const hasClass = sessionStore.has('activeClass.id')
|
|
||||||
const hasTool = sessionStore.get('isToolWin')
|
if(!!sessionStore.get('curr.classcourse'))return ElMessage.warning('当前正在上课,请先结束上课')
|
||||||
if (hasClass || hasTool) return ElMessage.warning('当前正在上课,请先结束上课')
|
|
||||||
ElMessageBox.confirm('确认退出系统吗?', '提示', {
|
ElMessageBox.confirm('确认退出系统吗?', '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
|
|
|
@ -44,9 +44,8 @@ const breadList = ref([])
|
||||||
watch(
|
watch(
|
||||||
() => router.currentRoute.value,
|
() => router.currentRoute.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
let path = newValue.path
|
const { showBread } = newValue.meta
|
||||||
|
if (showBread) {
|
||||||
if (path.includes('/model') && path !== ('/model/index')) {
|
|
||||||
isShowBack.value = true
|
isShowBack.value = true
|
||||||
breadList.value = newValue.matched.map(item => item.meta)
|
breadList.value = newValue.matched.map(item => item.meta)
|
||||||
let data = sessionStore.get('subject.curNode')
|
let data = sessionStore.get('subject.curNode')
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
<el-header>
|
<el-header>
|
||||||
<Header/>
|
<Header/>
|
||||||
</el-header>
|
</el-header>
|
||||||
<el-main :style="{ 'padding-top' : isShowBread ? 0 : '20px'}">
|
<el-main style="padding-top : 20px">
|
||||||
<template v-if="isShowBread">
|
<!-- <template v-if="isShowBread">
|
||||||
<div class="bread-row">
|
<div class="bread-row">
|
||||||
<div class="back" @click="onBack">
|
<div class="back" @click="onBack">
|
||||||
<i class="iconfont icon-fanhui"></i>
|
<i class="iconfont icon-fanhui"></i>
|
||||||
|
@ -19,8 +19,8 @@
|
||||||
<el-breadcrumb-item v-for="item in breadList"> {{ item.title }} </el-breadcrumb-item>
|
<el-breadcrumb-item v-for="item in breadList"> {{ item.title }} </el-breadcrumb-item>
|
||||||
</el-breadcrumb>
|
</el-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template> -->
|
||||||
<AppMain :style="{ height: isShowBread ? 'calc(100% - 45px)' : '100%' }" />
|
<AppMain />
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
<Uploader v-if="uploaderStore.uploadList && uploaderStore.uploadList.length > 0" />
|
<Uploader v-if="uploaderStore.uploadList && uploaderStore.uploadList.length > 0" />
|
||||||
|
|
|
@ -16,6 +16,7 @@ import router from './router'
|
||||||
import log from 'electron-log/renderer' // 渲染进程日志-文件记录
|
import log from 'electron-log/renderer' // 渲染进程日志-文件记录
|
||||||
import customComponent from '@/components/common' // 自定义组件
|
import customComponent from '@/components/common' // 自定义组件
|
||||||
import plugins from './plugins' // plugins插件
|
import plugins from './plugins' // plugins插件
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
|
||||||
if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件
|
if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件
|
||||||
Object.assign(console, log.functions) // 渲染进程日志-控制台替换
|
Object.assign(console, log.functions) // 渲染进程日志-控制台替换
|
||||||
|
@ -23,7 +24,6 @@ if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
|
||||||
//专为菁优网配置的请求转发
|
//专为菁优网配置的请求转发
|
||||||
app.config.globalProperties.$requestGetJYW = (url,config)=>{
|
app.config.globalProperties.$requestGetJYW = (url,config)=>{
|
||||||
config.params = config.params?config.params:{}
|
config.params = config.params?config.params:{}
|
||||||
|
@ -47,3 +47,16 @@ app.use(router)
|
||||||
.use(Icon)
|
.use(Icon)
|
||||||
.use(Directive)
|
.use(Directive)
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
|
|
||||||
|
const isStadium = (user) => {
|
||||||
|
let roles = user.roles
|
||||||
|
return roles.some(item => item.roleKey === 'stadium')
|
||||||
|
}
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (to.path === '/model/index') {
|
||||||
|
isStadium(useUserStore().user) === true ? next('/prepare') :next()
|
||||||
|
}else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -56,6 +56,8 @@ export class MsgEnum {
|
||||||
*/
|
*/
|
||||||
static HEADS = {
|
static HEADS = {
|
||||||
// === 旧定义-消息头(兼容以前) ===
|
// === 旧定义-消息头(兼容以前) ===
|
||||||
|
/** @desc: 开课 */
|
||||||
|
MSG_open : 'open',
|
||||||
/** @desc: 结束课程(下课) */
|
/** @desc: 结束课程(下课) */
|
||||||
MSG_closed : 'closed',
|
MSG_closed : 'closed',
|
||||||
/** @desc: 在线状态 */
|
/** @desc: 在线状态 */
|
||||||
|
@ -78,6 +80,8 @@ export class MsgEnum {
|
||||||
MSG_anmationclick : 'anmationclick',
|
MSG_anmationclick : 'anmationclick',
|
||||||
/** @desc: 群组创建成功 */
|
/** @desc: 群组创建成功 */
|
||||||
MSG_classcourseopen : 'classcourseopen',
|
MSG_classcourseopen : 'classcourseopen',
|
||||||
|
/** @desc: 学生提交作业 */
|
||||||
|
MSG_finishHomework : 'finishHomework',
|
||||||
/** @desc: 学生的测练结果反馈 */
|
/** @desc: 学生的测练结果反馈 */
|
||||||
MSG_classquizfeedback : 'classquizfeedback',
|
MSG_classquizfeedback : 'classquizfeedback',
|
||||||
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
|
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
|
||||||
|
@ -92,7 +96,19 @@ export class MsgEnum {
|
||||||
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
|
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
|
||||||
/** @desc: 课堂讲授活动,选择不同的内容 */
|
/** @desc: 课堂讲授活动,选择不同的内容 */
|
||||||
MSG_classlecturePagesrc : 'classlecturePagesrc',
|
MSG_classlecturePagesrc : 'classlecturePagesrc',
|
||||||
|
/** @desc: 课堂作业|活动 */
|
||||||
|
MSG_homework : 'HOMEWORK',
|
||||||
|
/** @desc: 公屏 - 课堂作业|活动 */
|
||||||
|
MSG_pushSreen_work : 'pushSreen_work',
|
||||||
|
/** @desc: 公屏 - 实验 */
|
||||||
|
MSG_pushSreen_experiment : 'pushSreen_experiment',
|
||||||
|
/** @desc: 点赞 */
|
||||||
|
MSG_dz : 'dz',
|
||||||
|
/** @desc: 疑惑 */
|
||||||
|
MSG_yh : 'yh',
|
||||||
// === 新定义-消息头 ===
|
// === 新定义-消息头 ===
|
||||||
|
/** @desc: 课程创建-待开课 */
|
||||||
|
MSG_0000: 0x0000,
|
||||||
/** @desc: 点赞 */
|
/** @desc: 点赞 */
|
||||||
MSG_0001: 0x0001,
|
MSG_0001: 0x0001,
|
||||||
/** @desc: 疑惑 */
|
/** @desc: 疑惑 */
|
||||||
|
@ -134,4 +150,4 @@ export class MsgEnum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { MsgEnum as default }
|
export default MsgEnum;
|
|
@ -5,51 +5,51 @@ let loadingInstance;
|
||||||
export default {
|
export default {
|
||||||
// 消息提示
|
// 消息提示
|
||||||
msg(content) {
|
msg(content) {
|
||||||
ElMessage.info(content)
|
return ElMessage.info(content)
|
||||||
},
|
},
|
||||||
// 错误消息
|
// 错误消息
|
||||||
msgError(content) {
|
msgError(content) {
|
||||||
ElMessage.error(content)
|
return ElMessage.error(content)
|
||||||
},
|
},
|
||||||
// 成功消息
|
// 成功消息
|
||||||
msgSuccess(content) {
|
msgSuccess(content) {
|
||||||
ElMessage.success(content)
|
return ElMessage.success(content)
|
||||||
},
|
},
|
||||||
// 警告消息
|
// 警告消息
|
||||||
msgWarning(content) {
|
msgWarning(content) {
|
||||||
ElMessage.warning(content)
|
return ElMessage.warning(content)
|
||||||
},
|
},
|
||||||
// 弹出提示
|
// 弹出提示
|
||||||
alert(content) {
|
alert(content, option = {}) {
|
||||||
ElMessageBox.alert(content, "系统提示")
|
return ElMessageBox.alert(content, "系统提示", option)
|
||||||
},
|
},
|
||||||
// 错误提示
|
// 错误提示
|
||||||
alertError(content) {
|
alertError(content, option = {}) {
|
||||||
ElMessageBox.alert(content, "系统提示", { type: 'error' })
|
return ElMessageBox.alert(content, "系统提示", { type: 'error', ...option })
|
||||||
},
|
},
|
||||||
// 成功提示
|
// 成功提示
|
||||||
alertSuccess(content) {
|
alertSuccess(content, option = {}) {
|
||||||
ElMessageBox.alert(content, "系统提示", { type: 'success' })
|
return ElMessageBox.alert(content, "系统提示", { type: 'success', ...option })
|
||||||
},
|
},
|
||||||
// 警告提示
|
// 警告提示
|
||||||
alertWarning(content) {
|
alertWarning(content, option = {}) {
|
||||||
ElMessageBox.alert(content, "系统提示", { type: 'warning' })
|
return ElMessageBox.alert(content, "系统提示", { type: 'warning', ...option })
|
||||||
},
|
},
|
||||||
// 通知提示
|
// 通知提示
|
||||||
notify(content) {
|
notify(content) {
|
||||||
ElNotification.info(content)
|
return ElNotification.info(content)
|
||||||
},
|
},
|
||||||
// 错误通知
|
// 错误通知
|
||||||
notifyError(content) {
|
notifyError(content) {
|
||||||
ElNotification.error(content);
|
return ElNotification.error(content);
|
||||||
},
|
},
|
||||||
// 成功通知
|
// 成功通知
|
||||||
notifySuccess(content) {
|
notifySuccess(content) {
|
||||||
ElNotification.success(content)
|
return ElNotification.success(content)
|
||||||
},
|
},
|
||||||
// 警告通知
|
// 警告通知
|
||||||
notifyWarning(content) {
|
notifyWarning(content) {
|
||||||
ElNotification.warning(content)
|
return ElNotification.warning(content)
|
||||||
},
|
},
|
||||||
// 确认窗体
|
// 确认窗体
|
||||||
confirm(content) {
|
confirm(content) {
|
||||||
|
@ -78,5 +78,8 @@ export default {
|
||||||
// 关闭遮罩层
|
// 关闭遮罩层
|
||||||
closeLoading() {
|
closeLoading() {
|
||||||
loadingInstance.close();
|
loadingInstance.close();
|
||||||
}
|
},
|
||||||
|
// messageBox: opt => ElMessageBox(opt),
|
||||||
|
// 其他实例放出去,方便调用
|
||||||
|
ElMessage, ElMessageBox, ElNotification, ElLoading
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
/**
|
||||||
|
* websocket 工具类(im 自己实现)
|
||||||
|
* 单例模式: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
|
||||||
|
* 实现的方法为先判断实例存在与否,如果存在则直接返回,不存在就创建了再返回,这就确保了一个类只有一个实例对象。
|
||||||
|
*/
|
||||||
|
import useUserStore from '@/store/modules/user' // 用户信息
|
||||||
|
|
||||||
|
export class ChatWs {
|
||||||
|
instance = null; // 实例
|
||||||
|
id = null; // 群聊id || 单聊id-用户id(userId)
|
||||||
|
url = null; // ws地址
|
||||||
|
closed = false; // 关闭状态
|
||||||
|
onmessage = null; // 自定义处理
|
||||||
|
errCount = 5; // 重连次数 (ms) 暂时不使用
|
||||||
|
errTime = null; // 重连时间 (ms) 1秒内zhi间内不重连
|
||||||
|
// 类型定义
|
||||||
|
TYPES = {
|
||||||
|
group: 'group', // 群发
|
||||||
|
single: 'single', // 单发
|
||||||
|
beat: 'heart_beat', // 心跳
|
||||||
|
}
|
||||||
|
static base = 'wss://file.ysaix.com:7868'
|
||||||
|
|
||||||
|
// 构造函数 是否自动连接
|
||||||
|
constructor(bool = true) {
|
||||||
|
if (!ChatWs.instance) {
|
||||||
|
if (bool) { // 是否自动连接
|
||||||
|
const userStore = useUserStore() // 用户信息
|
||||||
|
const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址
|
||||||
|
this.url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`;
|
||||||
|
this.closed = false; // 关闭状态 防止重连失败
|
||||||
|
// this.init(url);
|
||||||
|
}
|
||||||
|
ChatWs.instance = this;
|
||||||
|
}
|
||||||
|
return ChatWs.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
init(url) {
|
||||||
|
!!url && (this.url = url);
|
||||||
|
this.closed = false; // 关闭状态 防止重连失败
|
||||||
|
this.ws = null;
|
||||||
|
const _this = this
|
||||||
|
this.heartCheck = {
|
||||||
|
timeout: 1000 * 60, // 60s
|
||||||
|
timeoutObj: null,
|
||||||
|
serverTimeoutObj: null,
|
||||||
|
reset() {
|
||||||
|
clearTimeout(this.timeoutObj);
|
||||||
|
clearTimeout(this.serverTimeoutObj);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
start() {
|
||||||
|
const self = this;
|
||||||
|
this.timeoutObj = setTimeout(function () {
|
||||||
|
// 这里发送一个心跳,后端收到后,返回一个心跳消息,
|
||||||
|
// onmessage拿到返回的心跳就说明连接正常
|
||||||
|
console.log("websocket-发送心跳")
|
||||||
|
_this.sendMsgBeat();
|
||||||
|
self.serverTimeoutObj = setTimeout(function () {
|
||||||
|
console.log("websocket-心跳响应超时")
|
||||||
|
// 如果超过一定时间还没重置,说明后端主动断开了
|
||||||
|
_this.ws.close(); // 如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
|
||||||
|
}, self.timeout);
|
||||||
|
}, this.timeout);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return this.reconnect();
|
||||||
|
}
|
||||||
|
// 重连
|
||||||
|
reconnect() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const self = this;
|
||||||
|
if (!!this.ws) { // 关闭之前的链接
|
||||||
|
this.ws.close()
|
||||||
|
this.ws = null
|
||||||
|
}
|
||||||
|
this.ws = new WebSocket(this.url);
|
||||||
|
this.ws.onopen = function () {
|
||||||
|
console.log("websocket-连接成功")
|
||||||
|
self.heartCheck.reset().start();
|
||||||
|
resolve()
|
||||||
|
};
|
||||||
|
this.ws.onmessage = function (e) {
|
||||||
|
// console.log("websocket-收到消息", e)
|
||||||
|
// 拿到任何消息都说明当前连接是正常的
|
||||||
|
const isBeat = e.data == 'pong'
|
||||||
|
isBeat && self.heartCheck.reset().start();
|
||||||
|
const isEmpty = !e.data
|
||||||
|
const isExts = e.data.includes('sessionId') || e.data == ('pong')
|
||||||
|
if (isEmpty || isExts) return;
|
||||||
|
// 自定义处理
|
||||||
|
self.onmessage && self.onmessage(e.data, e);
|
||||||
|
};
|
||||||
|
this.ws.onerror = function (e) {
|
||||||
|
console.log("websocket-连接异常", e)
|
||||||
|
self.connectSocket() // 重连
|
||||||
|
};
|
||||||
|
this.ws.onclose = function (e) {
|
||||||
|
console.log("websocket-连接断开", e)
|
||||||
|
self.connectSocket() // 重连
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
connectSocket() {
|
||||||
|
this.heartCheck.reset() // 重置心跳
|
||||||
|
if (this.closed) return this.ws = null; // 关闭状态不重连
|
||||||
|
// if(self.errCount <= 0) return; // 超过重连次数
|
||||||
|
// self.errCount--; // 重连次数减1
|
||||||
|
if (this.errTime) {
|
||||||
|
const nowTime = Date.now();
|
||||||
|
const bool = nowTime - this.errTime < 1000 // 1s内zhi间内不重连
|
||||||
|
if (bool) return; // 1s内不重连
|
||||||
|
}
|
||||||
|
this.errTime = Date.now();
|
||||||
|
// 延时5s 后重连
|
||||||
|
console.log('重连中...')
|
||||||
|
this.sleep(5000).then(_ => {this.reconnect()})
|
||||||
|
}
|
||||||
|
// 发送消息
|
||||||
|
send(msg) {
|
||||||
|
if (!msg) throw new Error("msg is not null")
|
||||||
|
if (!this.ws) throw new Error("ws is not null")
|
||||||
|
if (typeof msg === "object") msg = JSON.stringify(msg)
|
||||||
|
if (!msg.includes('"msg":')) throw new Error("msg 格式错误请重试")
|
||||||
|
this.ws.send(msg)
|
||||||
|
}
|
||||||
|
// 发送消息-带消息头(key)
|
||||||
|
sendMsg(head, content, option = {}, ...arg) {
|
||||||
|
if (!head && head!==0) throw new Error("head is not null")
|
||||||
|
if (!content && content!==0) throw new Error("content is not null")
|
||||||
|
let msg = { head, content, ...option }
|
||||||
|
// 发送消息
|
||||||
|
this.send(this.getMsgObj(msg, ...arg))
|
||||||
|
}
|
||||||
|
// 发送心跳
|
||||||
|
sendMsgBeat() {
|
||||||
|
// this.send(this.getMsgObj('ping', this.TYPES.beat))
|
||||||
|
this.ws.send('ping')
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description 获取消息对象
|
||||||
|
* @param {*} msg 消息内容
|
||||||
|
* @param {*} chatType 群发 group| 单发 single| 心态 heart_beat
|
||||||
|
* @param {*} id 群聊id || 单聊id-用户id(userId)
|
||||||
|
*/
|
||||||
|
getMsgObj(msg, chatType = 'group', id) {
|
||||||
|
// if (typeof msg === "object") msg = JSON.stringify(msg)
|
||||||
|
const res = {msg, chatType}
|
||||||
|
// if (!id) throw new Error(`${type=='group'?'群ID':'用户ID'} is not null`)
|
||||||
|
if (chatType == 'group') res.groupId = id || this.id || ''
|
||||||
|
else if (chatType == 'single') res.to = id || this.id || ''
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
// 监听
|
||||||
|
watch(callback) {
|
||||||
|
callback && (this.onmessage = callback);
|
||||||
|
}
|
||||||
|
// 关闭链接
|
||||||
|
close() {
|
||||||
|
this.closed = true;
|
||||||
|
this.ws.close();
|
||||||
|
}
|
||||||
|
// 下课
|
||||||
|
closedCourse(id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendMsg('closed', '下课', null, 'group', id)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 延时 ms 毫秒
|
||||||
|
sleep(ms){
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 连接socket
|
||||||
|
export const connect = () => new ChatWs()
|
||||||
|
export const getInstance = () => new ChatWs(false)
|
||||||
|
// 默认实例
|
||||||
|
export default new ChatWs()
|
|
@ -31,6 +31,11 @@ export const constantRoutes = [
|
||||||
component: () => import('@/AixPPTist/src/App.vue'),
|
component: () => import('@/AixPPTist/src/App.vue'),
|
||||||
hidden: true
|
hidden: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/gridPic',
|
||||||
|
component: () => import('@/components/grid-pic/index.vue'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/model',
|
path: '/model',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
@ -47,19 +52,19 @@ export const constantRoutes = [
|
||||||
path: 'curriculum',
|
path: 'curriculum',
|
||||||
component: () => import('@/views/curriculum-standards/index.vue'),
|
component: () => import('@/views/curriculum-standards/index.vue'),
|
||||||
name: 'curriculum-standard',
|
name: 'curriculum-standard',
|
||||||
meta: { title: '课标研读' }
|
meta: { title: '课标研读', showBread: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'teaching',
|
path: 'teaching',
|
||||||
component: () => import('@/views/teaching-material/index.vue'),
|
component: () => import('@/views/teaching-material/index.vue'),
|
||||||
name: 'teaching-material',
|
name: 'teaching-material',
|
||||||
meta: { title: '教材研读' }
|
meta: { title: '教材研读', showBread: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'examination',
|
path: 'examination',
|
||||||
component: () => import('@/views/examination-analysis/index.vue'),
|
component: () => import('@/views/examination-analysis/index.vue'),
|
||||||
name: 'examination-analysis',
|
name: 'examination-analysis',
|
||||||
meta: { title: '考试分析' }
|
meta: { title: '考试分析', showBread: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'management',
|
path: 'management',
|
||||||
|
@ -71,25 +76,25 @@ export const constantRoutes = [
|
||||||
path: 'design',
|
path: 'design',
|
||||||
component: () => import('@/views/teachingDesign/index.vue'),
|
component: () => import('@/views/teachingDesign/index.vue'),
|
||||||
name: 'teaching-design',
|
name: 'teaching-design',
|
||||||
meta: { title: '教学框架设计' },
|
meta: { title: '教学框架设计' , showBread: true},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'newClassTaskAssign',
|
path: 'newClassTaskAssign',
|
||||||
component: () => import('@/views/classTask/newClassTaskAssign/index.vue'),
|
component: () => import('@/views/classTask/newClassTaskAssign/index.vue'),
|
||||||
name: 'newClassTaskAssign',
|
name: 'newClassTaskAssign',
|
||||||
meta: { title: '作业管理' }
|
meta: { title: '作业设计', showBread: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'questionUpload',
|
path: 'questionUpload',
|
||||||
component: () => import('@/views/classTask/newClassTaskAssign/questionUpload/index.vue'),
|
component: () => import('@/views/classTask/newClassTaskAssign/questionUpload/index.vue'),
|
||||||
name: 'questionUpload',
|
name: 'questionUpload',
|
||||||
meta: { title: '习题上传' }
|
meta: { title: '习题上传', showBread: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'aiKolors',
|
path: 'aiKolors',
|
||||||
component: () => import('@/components/ai-kolors/index.vue'),
|
component: () => import('@/components/ai-kolors/index.vue'),
|
||||||
name: 'aiKolors',
|
name: 'aiKolors',
|
||||||
meta: { title: '文生图片' }
|
meta: { title: '文生图片', showBread: true }
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -101,7 +106,7 @@ const dynamicRoutes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/desktop',
|
redirect: '/model/index',
|
||||||
meta: { title: '教学工作台' },
|
meta: { title: '教学工作台' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -132,7 +137,7 @@ const dynamicRoutes = [
|
||||||
path: 'prepare',
|
path: 'prepare',
|
||||||
component: () => import('@/views/prepare/index.vue'),
|
component: () => import('@/views/prepare/index.vue'),
|
||||||
name: 'prepare',
|
name: 'prepare',
|
||||||
meta: { title: '教学实践', showBread: true }
|
meta: { title: '教学实践' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'newClassTask',
|
path: 'newClassTask',
|
||||||
|
@ -152,13 +157,6 @@ const dynamicRoutes = [
|
||||||
name: 'classCorrect',
|
name: 'classCorrect',
|
||||||
meta: { title: '作业批改', showBread: true }
|
meta: { title: '作业批改', showBread: true }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/teach',
|
|
||||||
component: () => import('@/views/teach/index.vue'),
|
|
||||||
name: 'teach',
|
|
||||||
meta: { title: '授课' }
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
component: () => import('@/views/profile/index.vue'),
|
component: () => import('@/views/profile/index.vue'),
|
||||||
|
|
|
@ -5,6 +5,13 @@ import { JYApiListCT, JYApiListOriginYear, JYApiListSO} from "@/utils/examQuesti
|
||||||
|
|
||||||
const useClassTaskStore = defineStore('classTask',{
|
const useClassTaskStore = defineStore('classTask',{
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
experimentObj:{
|
||||||
|
edustage: '小学', // 教育阶段
|
||||||
|
edusubject: '数学', // 学科
|
||||||
|
experimentList: [], // 实验科目列表
|
||||||
|
updateEduInfo: '小学-数学', //实际需上传的学段+学科信息(用于上传及回显实验内的学段学科)
|
||||||
|
},
|
||||||
|
isOpenQuestUploadView: false, // 是否打开习题上传的页面
|
||||||
classListIds: [],
|
classListIds: [],
|
||||||
entpCourseWorkTypeList: [
|
entpCourseWorkTypeList: [
|
||||||
{value: 0, label: "不限"},
|
{value: 0, label: "不限"},
|
||||||
|
|
|
@ -396,5 +396,18 @@ export const dataSetJson = {
|
||||||
"课标-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
|
"课标-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
|
||||||
"课标-高中-地理": "270516829fd111efb13c0242ac140006",
|
"课标-高中-地理": "270516829fd111efb13c0242ac140006",
|
||||||
"课标-高中-政治": "a2f0b247b85d11ef84290242ac140005",
|
"课标-高中-政治": "a2f0b247b85d11ef84290242ac140005",
|
||||||
|
"教材-高中-语文": "cee3062a9fcf11efa6910242ac140006",
|
||||||
|
"教材-高中-生物": "fb5d01d59fd011ef9bb90242ac140006",
|
||||||
|
"教材-高中-历史": "f2f6c1fb9fd011ef98740242ac140006",
|
||||||
|
"教材-高中-英语": "e889fcac9fd011efb22a0242ac140006",
|
||||||
|
"教材-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
|
||||||
|
"教材-高中-地理": "270516829fd111efb13c0242ac140006",
|
||||||
|
"教材-高中-政治": "a2f0b247b85d11ef84290242ac140005",
|
||||||
|
"课标-小学-科学": "935cfec8bf6a11ef98950242ac140006",
|
||||||
|
"课标-小学-数学": "3c4e298fbf7911ef8e8b0242ac140002",
|
||||||
|
"课标-小学-语文": "f76f1aa5bf7111ef90c80242ac140002",
|
||||||
|
"教材-小学-科学": "935cfec8bf6a11ef98950242ac140006",
|
||||||
|
"教材-小学-数学": "3c4e298fbf7911ef8e8b0242ac140002",
|
||||||
|
"教材-小学-语文": "f76f1aa5bf7111ef90c80242ac140002",
|
||||||
"鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm"
|
"鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm"
|
||||||
}
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* 弹窗-函数
|
||||||
|
*/
|
||||||
|
import { h, render } from 'vue'
|
||||||
|
import { ElDialog } from 'element-plus'
|
||||||
|
|
||||||
|
// 打开弹窗-函数
|
||||||
|
export const openDialog = (option, content) => {
|
||||||
|
let vNode
|
||||||
|
const body = document.body
|
||||||
|
const dOpts = {
|
||||||
|
modelValue: true,
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
title: '添加-超连接',
|
||||||
|
draggable: true,
|
||||||
|
'onUpdate:modelValue': val => {
|
||||||
|
if (vNode && !val) render(null, body)
|
||||||
|
},
|
||||||
|
...option
|
||||||
|
}
|
||||||
|
vNode = h(ElDialog, dOpts, {
|
||||||
|
default: typeof content == 'function' ? content(h) : content
|
||||||
|
})
|
||||||
|
render(vNode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开链接
|
||||||
|
export const openLink = (option, title) => {
|
||||||
|
// https://phet.colorado.edu/sims/html/number-play/latest/number-play_zh_CN.html
|
||||||
|
const isStr = typeof option == 'string'
|
||||||
|
const opt = isStr ? {} : option
|
||||||
|
const url = isStr ? option : option?.url || option?.src || option?.href
|
||||||
|
const titleNew = isStr? title||'实验室' : option?.title || '添加-超连接'
|
||||||
|
openDialog({
|
||||||
|
title: titleNew,
|
||||||
|
...opt
|
||||||
|
}, (h) => {
|
||||||
|
return h('iframe', {
|
||||||
|
src: url,
|
||||||
|
width: '100%',
|
||||||
|
style: {
|
||||||
|
height: 'calc(80vh - 75px)',
|
||||||
|
},
|
||||||
|
scrolling: 'no',
|
||||||
|
frameborder: '0',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,734 @@
|
||||||
|
{
|
||||||
|
"title": "实验",
|
||||||
|
"data": {
|
||||||
|
"primary":[
|
||||||
|
{
|
||||||
|
"label": "数量比较",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/number-compare/latest/number-compare_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "数字游戏",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/number-play/latest/number-play_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "数轴:距离",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/number-line-distance/latest/number-line-distance_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "比率和比例",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/ratio-and-proportion/latest/ratio-and-proportion_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "数轴:运算",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/number-line-operations/latest/number-line-operations_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "数轴:整数",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/number-line-integers/latest/number-line-integers_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "向量的和:等式",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/vector-addition-equations/latest/vector-addition-equations_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "向量相加",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/vector-addition/latest/vector-addition_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "曲线拟合",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/curve-fitting/latest/curve-fitting_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分数:带分数",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/fractions-mixed-numbers/latest/fractions-mixed-numbers_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分数:入门",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/fractions-intro/latest/fractions-intro_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "构建一个分数",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/build-a-fraction/latest/build-a-fraction_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分数:等式",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/fractions-equality/latest/fractions-equality_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "单位价格",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/unit-rates/latest/unit-rates_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "获得一个10",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/make-a-ten/latest/make-a-ten_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "木棒的计算问题",
|
||||||
|
"fileurl": "https://www.netpad.net.cn/resource_web/course/?pack_id=6dc2ab05-cb06-4716-92ca-e00fb89ad1e6#/20808",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "几何光学",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/geometric-optics/latest/geometric-optics_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "密度",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/density/latest/density_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "能量滑板竞技场: 基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/energy-skate-park-basics/latest/energy-skate-park-basics_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "法拉第定律",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/faradays-law/latest/faradays-law_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "绳波",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/wave-on-a-string/latest/wave-on-a-string_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "光的混合",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/color-vision/latest/color-vision_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "平衡探究实验",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/balancing-act/latest/balancing-act_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "受到压力",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/under-pressure/latest/under-pressure_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "摩擦力",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/friction/latest/friction_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "力和运动:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/forces-and-motion-basics/latest/forces-and-motion-basics_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "静电电压",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/john-travoltage/latest/john-travoltage_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "万有引力实验",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gravity-force-lab/latest/gravity-force-lab_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "气球和静电(摩擦起电)",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/balloons-and-static-electricity/latest/balloons-and-static-electricity_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "密度",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/density/latest/density_zh_CN.html",
|
||||||
|
"subject": "biology"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "基因表达基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gene-expression-essentials/latest/gene-expression-essentials_zh_CN.html",
|
||||||
|
"subject": "biology"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "密度",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/density/latest/density_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "PH值",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/ph-scale/latest/ph-scale_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "密度",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/density/latest/density_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "创造一个分子",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/build-a-molecule/latest/build-a-molecule_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "扩散",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/diffusion/latest/diffusion_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"junior": [
|
||||||
|
{
|
||||||
|
"label": "二项分布弹珠台几率",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/plinko-probability/latest/plinko-probability_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "建立方程",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/function-builder/latest/function-builder_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "三角函数之旅",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/trig-tour/latest/trig-tour_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "四则运算",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/arithmetic/latest/arithmetic_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "二次函数图像",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/graphing-quadratics/latest/graphing-quadratics_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "质量和弹簧",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/masses-and-springs/latest/masses-and-springs_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "等式探索:两个变量",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/equality-explorer-two-variables/latest/equality-explorer-two-variables_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "等式探索:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/equality-explorer-basics/latest/equality-explorer-basics_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "等式探索",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/equality-explorer/latest/equality-explorer_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "面积模型代数",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/area-model-algebra/latest/area-model-algebra_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "面积模型:小数",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/area-model-decimals/latest/area-model-decimals_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "面积模型乘法",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/area-model-multiplication/latest/area-model-multiplication_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "面积模型入门",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/area-model-introduction/latest/area-model-introduction_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "钟摆实验",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/pendulum-lab/latest/pendulum-lab_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "斜抛运动",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/projectile-motion/latest/projectile-motion_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "表达式变换",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/expression-exchange/latest/expression-exchange_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "电路建设工具包:交流",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/circuit-construction-kit-ac/latest/circuit-construction-kit-ac_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "交流虚拟实验室",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/circuit-construction-kit-ac-virtual-lab/latest/circuit-construction-kit-ac-virtual-lab_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "碰撞实验室",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/collision-lab/latest/collision-lab_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "能量滑板竞技场",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/energy-skate-park/latest/energy-skate-park_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "向量相加",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/vector-addition/latest/vector-addition_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "曲线拟合",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/curve-fitting/latest/curve-fitting_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "引力实验室:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gravity-force-lab-basics/latest/gravity-force-lab-basics_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "波动入门",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/waves-intro/latest/waves-intro_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "扩散",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/diffusion/latest/diffusion_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "气体基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gases-intro/latest/gases-intro_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "气体性质",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gas-properties/latest/gas-properties_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "质量与弹簧:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/masses-and-springs-basics/latest/masses-and-springs-basics_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "黑体辐射",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/blackbody-spectrum/latest/blackbody-spectrum_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "能量的形式和转换",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/energy-forms-and-changes/latest/energy-forms-and-changes_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "波的干涉",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/wave-interference/latest/wave-interference_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "库仑定律",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/coulombs-law/latest/coulombs-law_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "质量和弹簧",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/masses-and-springs/latest/masses-and-springs_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "电容器实验:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/capacitor-lab-basics/latest/capacitor-lab-basics_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "电路组建实验:直流虚拟实验室",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/circuit-construction-kit-dc-virtual-lab/latest/circuit-construction-kit-dc-virtual-lab_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "电路组建实验:直流",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/circuit-construction-kit-dc/latest/circuit-construction-kit-dc_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "钟摆实验",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/pendulum-lab/latest/pendulum-lab_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "斜抛运动",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/projectile-motion/latest/projectile-motion_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "物质状态:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/states-of-matter-basics/latest/states-of-matter-basics_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "物质状态",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/states-of-matter/latest/states-of-matter_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "重力和轨道",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gravity-and-orbits/latest/gravity-and-orbits_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "分子与光",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/molecules-and-light/latest/molecules-and-light_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "PH值",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/ph-scale/latest/ph-scale_zh_CN.html",
|
||||||
|
"subject": "biology"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "光的混合",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/color-vision/latest/color-vision_zh_CN.html",
|
||||||
|
"subject": "biology"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "自然选择",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/natural-selection/latest/natural-selection_zh_CN.html",
|
||||||
|
"subject": "biology"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "受到压力",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/under-pressure/latest/under-pressure_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "万有引力实验",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gravity-force-lab/latest/gravity-force-lab_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "气球和静电(摩擦起电)",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/balloons-and-static-electricity/latest/balloons-and-static-electricity_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "气体基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gases-intro/latest/gases-intro_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "气体性质",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gas-properties/latest/gas-properties_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "黑体辐射",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/blackbody-spectrum/latest/blackbody-spectrum_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "能量的形式和转换",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/energy-forms-and-changes/latest/energy-forms-and-changes_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "库仑定律",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/coulombs-law/latest/coulombs-law_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分子极性",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/molecule-polarity/latest/molecule-polarity_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "物质状态:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/states-of-matter-basics/latest/states-of-matter-basics_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "物质状态",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/states-of-matter/latest/states-of-matter_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "原子的相互作用",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/atomic-interactions/latest/atomic-interactions_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "卢瑟福散射",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/rutherford-scattering/latest/rutherford-scattering_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "原子的相互作用",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/atomic-interactions/latest/atomic-interactions_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"senior": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "一次线性函数的拟合",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/least-squares-regression/latest/least-squares-regression_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "区域建造者",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/area-builder/latest/area-builder_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "绳波",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/wave-on-a-string/latest/wave-on-a-string_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "直线图形",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/graphing-lines/latest/graphing-lines_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分数配对",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/fraction-matcher/latest/fraction-matcher_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "平衡探究实验",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/balancing-act/latest/balancing-act_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "绘图:斜率与截距",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/graphing-slope-intercept/latest/graphing-slope-intercept_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "函数构造器:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/function-builder-basics/latest/function-builder-basics_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "比例游乐场",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/proportion-playground/latest/proportion-playground_zh_CN.html",
|
||||||
|
"subject": "math"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "二项分布弹珠台几率",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/plinko-probability/latest/plinko-probability_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "原子的相互作用",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/atomic-interactions/latest/atomic-interactions_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "电荷与电场",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/charges-and-fields/latest/charges-and-fields_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "卢瑟福散射",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/rutherford-scattering/latest/rutherford-scattering_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "光的折射",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/bending-light/latest/bending-light_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "胡克定律",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/hookes-law/latest/hookes-law_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "部分电路欧姆定律",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/ohms-law/latest/ohms-law_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "电线的电阻",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/resistance-in-a-wire/latest/resistance-in-a-wire_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "原子模型",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/build-an-atom/latest/build-an-atom_zh_CN.html",
|
||||||
|
"subject": "physics"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "分子极性",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/molecule-polarity/latest/molecule-polarity_zh_CN.html",
|
||||||
|
"subject": "biology"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "神经元",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/neuron/latest/neuron_zh_CN.html",
|
||||||
|
"subject": "biology"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "引力实验室:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gravity-force-lab-basics/latest/gravity-force-lab-basics_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "波动入门",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/waves-intro/latest/waves-intro_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "扩散",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/diffusion/latest/diffusion_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "气体基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gases-intro/latest/gases-intro_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "气体性质",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gas-properties/latest/gas-properties_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分子与光",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/molecules-and-light/latest/molecules-and-light_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "绳波",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/wave-on-a-string/latest/wave-on-a-string_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "黑体辐射",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/blackbody-spectrum/latest/blackbody-spectrum_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "波的干涉",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/wave-interference/latest/wave-interference_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "重力和轨道",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/gravity-and-orbits/latest/gravity-and-orbits_zh_CN.html",
|
||||||
|
"subject": "sciences"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"label": "同位素和原子的质量",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/isotopes-and-atomic-mass/latest/isotopes-and-atomic-mass_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分子与光",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/molecules-and-light/latest/molecules-and-light_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分子形状",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/molecule-shapes/latest/molecule-shapes_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "分子形状:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/molecule-shapes-basics/latest/molecule-shapes-basics_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "反应物,生成物及未反应物",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/reactants-products-and-leftovers/latest/reactants-products-and-leftovers_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "pH值:基础",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/ph-scale-basics/latest/ph-scale-basics_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "绳波",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/wave-on-a-string/latest/wave-on-a-string_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "PH值",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/ph-scale/latest/ph-scale_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "配平化学方程式",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/balancing-chemical-equations/latest/balancing-chemical-equations_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "酸碱溶度",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/acid-base-solutions/latest/acid-base-solutions_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "浓度",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/concentration/latest/concentration_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "气球和静电(摩擦起电)",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/balloons-and-static-electricity/latest/balloons-and-static-electricity_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "比尔定律实验",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/beers-law-lab/latest/beers-law-lab_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},{
|
||||||
|
"label": "摩尔浓度",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/molarity/latest/molarity_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "原子模型",
|
||||||
|
"fileurl": "https://phet.colorado.edu/sims/html/build-an-atom/latest/build-an-atom_zh_CN.html",
|
||||||
|
"subject": "chemistry"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* ppt 转换为图片
|
||||||
|
*/
|
||||||
|
import { h, render, getCurrentInstance } from 'vue'
|
||||||
|
import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
|
||||||
|
import { PPTXFileToJson } from "@/AixPPTist/src/hooks/useImport"
|
||||||
|
import ThumbnailSlide from '@/AixPPTist/src/views/components/ThumbnailSlide/index.vue'
|
||||||
|
import { useSlidesStore } from '@/AixPPTist/src/store'
|
||||||
|
import * as ElementPlus from 'element-plus'
|
||||||
|
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||||
|
import * as Http_Classcourse from '@/api/teaching/classcourse' // api接口
|
||||||
|
|
||||||
|
// 延时
|
||||||
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 渲染组件
|
||||||
|
* @param {*} node 节点或属性
|
||||||
|
* @param {*} props 属性
|
||||||
|
* @param {*} children 子元素
|
||||||
|
* @param {*} container 容器
|
||||||
|
*/
|
||||||
|
const renderComponent = (node, props = {}, children, container) => {
|
||||||
|
let vNode, body
|
||||||
|
if (!node) throw new Error('vNode is required')
|
||||||
|
if (typeof container == 'string') {
|
||||||
|
if (node == 'slide') {
|
||||||
|
vNode = h(ThumbnailSlide, props, children)
|
||||||
|
} else throw new Error('vNode has no corresponding component')
|
||||||
|
} else {
|
||||||
|
vNode = h(node, props, children)
|
||||||
|
}
|
||||||
|
if (!container) body = document.body // 默认为body
|
||||||
|
else { // 判断是否为字符串
|
||||||
|
if (typeof container == 'string') {
|
||||||
|
body = document.querySelector(container)
|
||||||
|
} else {
|
||||||
|
body = container
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return render(vNode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 幻灯片转换为图片
|
||||||
|
* 提示:icon组件找不到是应为 h() 渲染是底层创建的虚拟节点,找不到全局组件
|
||||||
|
* @param {*} slides 幻灯片数据
|
||||||
|
* @param {*} options 配置 number 为幻灯片宽度 | object 为配置项
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const slidesToImg = (slides = [], options) => {
|
||||||
|
let width, option, ispng = true
|
||||||
|
if (typeof options =='number'){width = options; option = {}}
|
||||||
|
else {const { width: w, isPng, ...opt } = options;width = w; ispng=isPng; option = opt}
|
||||||
|
const slidesStore = useSlidesStore()
|
||||||
|
!!width && slidesStore.setViewportSize(width) // 设置幻灯片宽度
|
||||||
|
return new Promise(async(resolve) => {
|
||||||
|
const instance = getCurrentInstance()
|
||||||
|
console.log('instance', instance)
|
||||||
|
const slidesDom = []
|
||||||
|
for(const slide of slides) {
|
||||||
|
const props = { class: 'c-thumbnail', slide, size: 120, ...option }
|
||||||
|
const node = h(ThumbnailSlide, props)
|
||||||
|
slidesDom.push(node)
|
||||||
|
}
|
||||||
|
// 渲染组件到body
|
||||||
|
const props = { class: 'c-thumbnails', style:{position:'absolute',top:0,left:'-200vw'}}
|
||||||
|
renderComponent('div', props, slidesDom)
|
||||||
|
let imgs = []
|
||||||
|
const toImag = ispng? toPng : toJpeg
|
||||||
|
for(const slide of slidesDom) {
|
||||||
|
const img = await toImag(slide.el)
|
||||||
|
imgs.push(img)
|
||||||
|
}
|
||||||
|
// console.log('ppt生成图片: ', imgs)
|
||||||
|
// console.log('图片已生成,正在卸载组件')
|
||||||
|
!!width && slidesStore.setViewportSize(1000) // 设置幻灯片宽度-恢复
|
||||||
|
render(null, document.body) // 卸载组件
|
||||||
|
resolve(imgs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* description: ppt 文件转换为图片
|
||||||
|
* @param {*} file file 为文件对象| arrayBuffer 为数组
|
||||||
|
* @param {*} options 配置 number 为幻灯片宽度 | object 为配置项
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pptToImg = async(file, options) => {
|
||||||
|
const { slides } = await PPTXFileToJson(file)
|
||||||
|
return slidesToImg(slides, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课堂-分享码
|
||||||
|
*/
|
||||||
|
export const ShareCode = async(code, cb) => {
|
||||||
|
let shareCode
|
||||||
|
if (typeof code =='string') shareCode = code
|
||||||
|
else { // 自动获取邀请码
|
||||||
|
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
|
||||||
|
if (!classcourse) return ElementPlus.ElMessage.warning('没有课堂信息')
|
||||||
|
const isRefresh = typeof code == 'boolean' && code // 是否刷新邀请码
|
||||||
|
shareCode = classcourse.shareCode
|
||||||
|
if (!shareCode || isRefresh) { // 获取邀请码
|
||||||
|
const res = await Http_Classcourse.getShareCode(classcourse.id)
|
||||||
|
shareCode = res.msg
|
||||||
|
// 更新邀请码
|
||||||
|
sessionStore.set('curr.classcourse.shareCode', shareCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const msg = h('div', [
|
||||||
|
h('h1', [`我的邀请码:`, h('b',{style:{color:'#F56C6C',fontSize:'1.5em'}}, shareCode)]),
|
||||||
|
h('div', {style:{color:'#E6A23C',fontSize:'13px'}}, `该邀请码1小时内有效,请在学生端填写邀请码后即可进入课堂。`)
|
||||||
|
])
|
||||||
|
return ElementPlus.ElMessageBox.alert(msg, '分享课程', {
|
||||||
|
confirmButtonText: '更新',
|
||||||
|
cancelButtonText: '关闭',
|
||||||
|
showCancelButton: true,
|
||||||
|
|
||||||
|
beforeClose: (action, instance, done) => {
|
||||||
|
if (action =='confirm') { // 更新
|
||||||
|
if (!!cb) { // 回调
|
||||||
|
cb({ h, instance, action, done }, done) && done()
|
||||||
|
} else { // 默认更新
|
||||||
|
ShareCode(true)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
} else done()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
|
@ -57,24 +57,31 @@ export const resourceFormat = [
|
||||||
// 资源类型
|
// 资源类型
|
||||||
export const resourceType = [
|
export const resourceType = [
|
||||||
{
|
{
|
||||||
|
id:1,
|
||||||
label: '课例库',
|
label: '课例库',
|
||||||
value: "'apt','课件','教案'"
|
value: "'apt','课件','教案'"
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: '作业库',
|
// label: '作业库',
|
||||||
value: '作业',
|
// value: '作业',
|
||||||
disabled: true
|
// disabled: true
|
||||||
},
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
|
id:2,
|
||||||
label: '素材库',
|
label: '素材库',
|
||||||
value: "'素材'"
|
value: "'素材'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '习题库',
|
id:3,
|
||||||
value: '习题',
|
label: '实验室',
|
||||||
disabled: true
|
value: "'素材'"
|
||||||
}
|
},
|
||||||
|
// {
|
||||||
|
// label: '习题库',
|
||||||
|
// value: '习题',
|
||||||
|
// disabled: true
|
||||||
|
// }
|
||||||
]
|
]
|
||||||
// 年级划分
|
// 年级划分
|
||||||
export const gradeList = [
|
export const gradeList = [
|
||||||
|
|
|
@ -258,16 +258,45 @@ export const getFileName = (filename) => {
|
||||||
return filename.replace(/\.[^/.]+$/, "");
|
return filename.replace(/\.[^/.]+$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除当前选中的教材 章节 相关信息
|
|
||||||
export const clearBookInfo = () =>{
|
/**
|
||||||
//当前选中的教材
|
* 根据图片的url转换对应的base64值
|
||||||
localStorage.removeItem('curBook')
|
* @param { String } url 如:http://xxxx/xxx.png
|
||||||
// 当前选中的节点
|
* @returns base64取值
|
||||||
localStorage.removeItem('curNode')
|
*/
|
||||||
// 所有章节单元数据
|
export const urlToBase64 = (url) => {
|
||||||
localStorage.removeItem('unitList')
|
|
||||||
// 所有教材数据
|
return new Promise((resolve, reject) => {
|
||||||
localStorage.removeItem('subjectList')
|
if (!url) {
|
||||||
// 展开的节点
|
reject('请传入url内容')
|
||||||
localStorage.removeItem('defaultExpandedKeys')
|
}
|
||||||
|
if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(url)) {
|
||||||
|
// 图片地址
|
||||||
|
let image = new Image()
|
||||||
|
// 设置跨域问题
|
||||||
|
image.setAttribute('crossOrigin', 'anonymous')
|
||||||
|
// 图片地址
|
||||||
|
image.src = url +"?v=" + Math.random(); // 处理缓存,fix缓存bug,有缓存,浏览器会报错;
|
||||||
|
image.onload = () => {
|
||||||
|
let canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
canvas.width = image.width
|
||||||
|
canvas.height = image.height
|
||||||
|
ctx.drawImage(image, 0, 0, image.width, image.height)
|
||||||
|
|
||||||
|
// 获取图片后缀
|
||||||
|
const ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase()
|
||||||
|
// 转base64
|
||||||
|
const dataUrl = canvas.toDataURL(`image/${ext}`)
|
||||||
|
resolve(dataUrl || '')
|
||||||
|
canvas = null // 清除canvas元素
|
||||||
|
image = null // 清除img元素
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 非图片地址
|
||||||
|
reject('非(png/jpe?g/gif/svg等)图片地址');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
const { ipcRenderer } = window.electron || {}
|
const { ipcRenderer } = window.electron || {}
|
||||||
|
|
||||||
export const asyncLocalFile = (item) => {
|
export const asyncLocalFile = (item) => {
|
||||||
|
@ -9,12 +11,15 @@ export const asyncLocalFile = (item) => {
|
||||||
if (isAsync === true) {
|
if (isAsync === true) {
|
||||||
item.async = 'on'
|
item.async = 'on'
|
||||||
if (type === 'down') {
|
if (type === 'down') {
|
||||||
console.log(item)
|
// console.log(item)
|
||||||
ipcRenderer.send('download-file-default', {
|
ipcRenderer.send('download-file-default', {
|
||||||
url: item.fileFullPath,
|
url: item.fileFullPath,
|
||||||
fileName: item.fileNewName
|
fileName: item.fileNewName
|
||||||
})
|
})
|
||||||
ipcRenderer.once('download-file-default' + item.fileNewName, (e, isSuccess) => {
|
ipcRenderer.once('download-file-default' + item.fileNewName, (e, isSuccess) => {
|
||||||
|
if (isSuccess == false) {
|
||||||
|
ElMessage.error(`${item.fileShowName}下载失败!`)
|
||||||
|
}
|
||||||
item.async = isSuccess
|
item.async = isSuccess
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
|
@ -112,3 +117,24 @@ export const creatAIPPT = (name, url, uploadData) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const removeLocalFiles = async (list) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ipcRenderer.send('remove-local-file-list', JSON.parse(JSON.stringify(list)))
|
||||||
|
ipcRenderer.removeListener('remove-local-file-list-error', removeLocalFileListError)
|
||||||
|
ipcRenderer.removeListener('remove-local-file-list-not', removeLocalFileListNot)
|
||||||
|
ipcRenderer.on('remove-local-file-list-error', removeLocalFileListError)
|
||||||
|
ipcRenderer.on('remove-local-file-list-not', removeLocalFileListNot)
|
||||||
|
ipcRenderer.once('remove-local-file-list-reply', (e, res) => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeLocalFileListError(e, item) {
|
||||||
|
ElMessage.error(`${item.fileShowName}删除失败`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeLocalFileListNot(e, item) {
|
||||||
|
ElMessage.error(`${item.fileShowName}删除失败,并没有该文件!`)
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue