Compare commits
529 Commits
Author | SHA1 | Date |
---|---|---|
朱浩 | 27c7033522 | |
朱浩 | 4c66cc90c0 | |
lyc | c2b54714ac | |
lyc | cafea2d719 | |
yangws | d5afede36c | |
朱浩 | 03e61c8440 | |
朱浩 | 79b9a0726a | |
朱浩 | 0ccde18f00 | |
朱浩 | 274c80ad0a | |
yangws | 923006eab1 | |
yangws | a64ba24742 | |
lyc | df1f8737bd | |
lyc | 19a3b948c5 | |
朱浩 | 622cdfe0fe | |
朱浩 | 78b795bf4f | |
lyc | 92667a7d25 | |
lyc | 71f7e4e5a1 | |
朱浩 | 3cced30959 | |
朱浩 | ef9fe838a3 | |
zhangxuelin | 9ff824e082 | |
zhangxuelin | 8975784ce0 | |
lyc | e586755b75 | |
lyc | ef771f4e0b | |
朱浩 | 049768c8b6 | |
朱浩 | ac599a2c39 | |
lyc | 00871797bf | |
lyc | fa7a44472b | |
lyc | 85529ad81f | |
lyc | f9a87143aa | |
zhengdegang | 17ef183741 | |
zdg | d5833f6f03 | |
zdg | c93eeeb308 | |
zhengdegang | 0d48111e50 | |
zdg | ca57e72e9c | |
zhengdegang | 928a1401db | |
zdg | e0d3a6da6a | |
zdg | 1a9b426ec3 | |
lyc | d4cfedf883 | |
lyc | b5fd1f2a9d | |
lyc | c6477d5ca9 | |
lyc | 27ec047981 | |
zhengdegang | 7da4defed4 | |
zdg | 51cb1989a1 | |
zdg | 4ab4d5dff2 | |
lyc | 80d6e1c59c | |
lyc | 75c6777933 | |
朱浩 | a75f151b19 | |
朱浩 | 2531c25d7c | |
朱浩 | 9e5527d822 | |
朱浩 | e002ef5f7d | |
朱浩 | 2ca637072d | |
baigl | bd7f6f765f | |
白了个白 | ef6c3d2a2b | |
白了个白 | 9ea05568e1 | |
zhengdegang | 93a061ca78 | |
zdg | c5075af2fc | |
zdg | 7da60f0815 | |
zdg | 02a25db5e2 | |
yangws | 59de30b6b2 | |
yangws | 746cb150c4 | |
lyc | 893be27671 | |
lyc | b0b429c7aa | |
lyc | dbfa1a4037 | |
lyc | ce8b2e6ab0 | |
baigl | a082b437b7 | |
白了个白 | 9eb6d991c1 | |
白了个白 | 0cbb513a60 | |
lyc | be765b21b6 | |
lyc | 34c1c66b31 | |
lyc | 5f06657b09 | |
白了个白 | 3ec25f07b4 | |
zhengdegang | aa35621b59 | |
zdg | 49de9ee9c2 | |
zdg | 0ef5ef424f | |
baigl | 89ae43a458 | |
白了个白 | 9d7aad1946 | |
lyc | db2be260a7 | |
baigl | b06ad0f411 | |
白了个白 | 129f875469 | |
白了个白 | 2c6da76cf7 | |
ekooo | 499e428138 | |
朱浩 | 81f873bcc3 | |
lyc | c58322ca0b | |
lyc | 08dad41e4d | |
zhengdegang | 675613bbee | |
zdg | 9188df0d94 | |
zdg | 4e8560b033 | |
“zouyf” | 4e835cac82 | |
朱浩 | 7303128334 | |
yangws | d047e85d03 | |
yangws | acd66c11fa | |
朱浩 | f90e2548c6 | |
yangws | c32bd6eef4 | |
yangws | 3b9877e788 | |
朱浩 | fb48984e7f | |
lyc | aaa2aef3ca | |
lyc | 5cfd5747a1 | |
lyc | 04263cd519 | |
lyc | aee945f90f | |
白了个白 | bae24601ef | |
白了个白 | 980e4d66e8 | |
lyc | 7439c9759a | |
lyc | ddd53943b8 | |
ekooo | 70ba70df45 | |
baigl | 7ae8547d41 | |
zhengdegang | c5c28b4bf5 | |
zdg | 67d7395c47 | |
白了个白 | 46adf4c058 | |
zdg | f615efbd88 | |
zdg | 741e7336f3 | |
朱浩 | a67f801356 | |
朱浩 | 1c89217fbf | |
朱浩 | 3a20f453c6 | |
朱浩 | dfa5c7d99b | |
lyc | e144fbe36a | |
lyc | b279d36c89 | |
lyc | 8013b1c2cc | |
baigl | 610aac6985 | |
白了个白 | 6c85654608 | |
白了个白 | a5b6cd3516 | |
lyc | 01718f573b | |
lyc | ec9c673b5a | |
白了个白 | 578fc9feba | |
白了个白 | f49cba5e36 | |
lyc | a92176174a | |
lyc | 8063783284 | |
lyc | eadc6f1440 | |
lyc | 8753951796 | |
白了个白 | 490add5467 | |
白了个白 | 634ae460f3 | |
白了个白 | 1cc7007852 | |
朱浩 | 502cf13168 | |
朱浩 | a4fa5be295 | |
yangws | e907a1c07a | |
yangws | 1afaed082a | |
lyc | 69362a6153 | |
lyc | 1da3ca0ee0 | |
lyc | d4a6f74cfc | |
zouyf | f57c67aab2 | |
“zouyf” | 63ad74c77f | |
“zouyf” | d171117f03 | |
lyc | 0426b85d3f | |
lyc | 17b8ce1dda | |
zhangxuelin | 0fa3c84f5e | |
zhangxuelin | 490536654a | |
zhangxuelin | 2efb870a16 | |
yangws | 5778a8955d | |
朱浩 | e4e23e7508 | |
yangws | 6a33ea8023 | |
朱浩 | bc0579fa32 | |
yangws | 3357cf8a78 | |
yangws | 3fdf500366 | |
zhengdegang | e985417675 | |
zdg | 1978d1bac1 | |
zdg | b9014b950a | |
lyc | 03ecbb1d50 | |
lyc | ec3b978185 | |
zdg | 3632beebfa | |
zdg | f0e87338fb | |
lyc | 54266f8a37 | |
lyc | 82bed1be03 | |
lyc | 8a74f17266 | |
lyc | 40c6e1303b | |
yangws | ee196b6eb8 | |
yangws | 6c60cd4442 | |
baigl | 471d73a224 | |
白了个白 | d7761883f2 | |
baigl | d4b1e547de | |
白了个白 | b34182eb39 | |
白了个白 | 300093cf74 | |
yangws | aaf03eb60d | |
yangws | 08075746c0 | |
yangws | 8ec93353bc | |
yangws | 68df866db5 | |
朱浩 | add564c4ed | |
朱浩 | 77e76fcf7d | |
朱浩 | 0268f1af36 | |
朱浩 | 278dfd4d93 | |
lyc | db6ebbdbaf | |
lyc | e2017c3553 | |
lyc | 0be3d131e2 | |
yangws | 8ec738de46 | |
yangws | 076a510109 | |
zhangxuelin | ee9e5d1326 | |
zhangxuelin | 7ed7b2efd2 | |
zhangxuelin | eaf2d82ead | |
朱浩 | 786c6816ec | |
朱浩 | 1b504a1653 | |
朱浩 | 85c61ca802 | |
zhangxuelin | 7da1d97939 | |
baigl | 3603c4e0f2 | |
白了个白 | cb1c3c23fe | |
白了个白 | de1dee7380 | |
zhangxuelin | 151cdbb1a1 | |
朱浩 | a2c962e94d | |
朱浩 | 9cad353d36 | |
朱浩 | 53f43019e9 | |
baigl | 4996af57c7 | |
白了个白 | 7a4cc9eb64 | |
白了个白 | fd6aa2a56a | |
zdg | ad2e6b92db | |
白了个白 | 6be4b3526e | |
zhangxuelin | bbe4037a71 | |
白了个白 | 0b6b0c318d | |
zouyf | d896209948 | |
“zouyf” | c618ec04c7 | |
zhangxuelin | c79e7966ee | |
lyc | 3a1cf6224a | |
白了个白 | 2d89ef8de3 | |
白了个白 | 44002ae78d | |
白了个白 | 0e88eb8226 | |
白了个白 | 5bda6cfad2 | |
朱浩 | 280313e638 | |
朱浩 | ed7a8a124e | |
zouyf | 4b71008bee | |
“zouyf” | aab477999e | |
“zouyf” | d1b7101ba5 | |
白了个白 | 7cb84ffe37 | |
lyc | 47f3202444 | |
lyc | 3cd8c51aae | |
lyc | be187e1a2d | |
白了个白 | 0c506a09f2 | |
白了个白 | 04ac5dc8b5 | |
zhangxuelin | aca464fddd | |
zhangxuelin | 25d026bd13 | |
lyc | 119e07bcea | |
“zouyf” | 555960b8bd | |
“zouyf” | d709753534 | |
朱浩 | 78ac6e5ca6 | |
朱浩 | acc29b02e4 | |
zhengdegang | 12da4ac2c0 | |
zdg | 5ecba3fb62 | |
zdg | f5f33eaa63 | |
zhengdegang | 090bc5997c | |
zdg | 1c69d10263 | |
zdg | b794d55cd7 | |
zdg | 28b5a131fd | |
zdg | e740cb0c7e | |
zdg | 3a83a31970 | |
zhangxuelin | 30f7b9d5b3 | |
zhangxuelin | 37810586b7 | |
zhangxuelin | e31858a3fe | |
zdg | 3a6b78bc76 | |
zhengdegang | 10a1342b95 | |
zdg | 70e99e69ba | |
lyc | 3171c0b3f9 | |
lyc | 33e84ca006 | |
lyc | 28a815169d | |
zhengdegang | eace8b56d8 | |
zdg | e03e385dbc | |
zdg | 2aaa15230a | |
zdg | 7894214859 | |
朱浩 | fe41a63b2d | |
朱浩 | cdf12cf213 | |
yangws | 7bb8d2afeb | |
yangws | cfac2086e6 | |
朱浩 | 59ca00d6d1 | |
zdg | b6504e114f | |
lyc | 931ed531ab | |
zhengdegang | 5433e47d0b | |
zdg | eb3778dde2 | |
lyc | a6bda302af | |
zhengdegang | 411ef757e2 | |
zdg | 0201385e75 | |
zdg | ea9af1440e | |
zdg | b4c2751e8c | |
yangws | c5257b760a | |
yangws | 22239090ea | |
zdg | 1d808950b5 | |
zhengdegang | cb0d60b93e | |
zdg | d65af70a34 | |
zdg | 3f4e9c35b0 | |
zhengdegang | a133c322f3 | |
zdg | b5d41050ae | |
朱浩 | 0e5ca5eff2 | |
lyc | eb481e265b | |
zdg | fa77c6cc6b | |
lyc | ff97a2bef6 | |
lyc | 05c567d1cd | |
lyc | 1a6abfaa50 | |
zhengdegang | 2ae200cc9e | |
zdg | 177df96d69 | |
zdg | 571bfc98f7 | |
yangws | b249df2dff | |
yangws | 1871e7e565 | |
lyc | bb56c3fdab | |
lyc | df8f4fcf92 | |
lyc | d6b4ae011d | |
yangws | bf7f740aaf | |
yangws | e6a0859f87 | |
zhengdegang | 3d8dc4320d | |
zdg | 1518d9c3ae | |
lyc | 23878f5843 | |
lyc | 144da0ace9 | |
zhengdegang | 46098aa733 | |
zdg | 13e755bb9a | |
zdg | b5ce949172 | |
zdg | d033c217d6 | |
yangws | edfdf8b9ad | |
yangws | 59be0c07d1 | |
朱浩 | e985c2e7af | |
朱浩 | 0ea628d174 | |
朱浩 | d4982d47c7 | |
yangws | 0ad99e9ac2 | |
yangws | be222a2ba6 | |
lyc | b922219602 | |
lyc | 386c4e09c6 | |
zhengdegang | b738ade9a4 | |
zdg | 6f6d56b37b | |
zdg | c1f81a8e5c | |
lyc | d003ecf8ce | |
lyc | 146784bf26 | |
yangws | 88e63f376c | |
yangws | 99ee438fd7 | |
lyc | 2279bbb904 | |
lyc | b0d969546a | |
lyc | 8ec96996c6 | |
zhengdegang | 968b5d3e43 | |
zdg | c0fd0ae9d3 | |
zdg | 86cd50b8a3 | |
zhengdegang | 9240c055f4 | |
zdg | 6d77d54bc1 | |
zdg | c57f21461a | |
lyc | d669b6ee42 | |
lyc | 13598615c7 | |
zdg | a80a3b006c | |
zdg | a2e72b855b | |
zhangxuelin | 1e0ff00503 | |
zhangxuelin | d30b4a230a | |
zhangxuelin | b8ea091bb5 | |
zdg | 04a03b0c43 | |
zdg | f7ad8d4c71 | |
朱浩 | f38a14bc0b | |
朱浩 | f98fa5a3f3 | |
zdg | 6fb7369ce5 | |
zdg | 49c4c7245a | |
朱浩 | 5f6839058a | |
朱浩 | 0ff0cd42bc | |
朱浩 | 6dfb2d4a8a | |
zhengdegang | 4fd47d2f2f | |
zhangxuelin | 44bec9fab4 | |
zhangxuelin | 56483a9a49 | |
zhengdegang | 4f6c7f157c | |
zdg | 631fcf38f7 | |
zdg | cf477398c9 | |
zdg | e3e704d95d | |
yangws | f8eece2ff6 | |
yangws | 7fc7135d73 | |
朱浩 | 88177d610e | |
zhengdegang | 6bc2579c2e | |
朱浩 | 57b7d3d601 | |
zdg | 75caf13d8b | |
zdg | 7fb98309fb | |
zdg | a3b7248977 | |
zdg | 6ae7c2c4b7 | |
朱浩 | c19631eb89 | |
朱浩 | b8896bf53d | |
朱浩 | 6f5eee4289 | |
朱浩 | aec8e9a21f | |
朱浩 | de909adccb | |
yangws | f7b00b1ccd | |
yangws | c94563fd43 | |
lyc | c1a34c61c9 | |
lyc | a68b2a2805 | |
lyc | 0d1a96b202 | |
yangws | 71fe8a0a8f | |
yangws | b8f95eb20f | |
lyc | 5328607ce1 | |
lyc | a17be49850 | |
yangws | 2a4dac58ac | |
yangws | 0fe264054c | |
lyc | f632bab6b3 | |
lyc | ae52510c7c | |
lyc | 86d5f33119 | |
zhangxuelin | 2ed62802ce | |
zhangxuelin | 2ee5e24e6f | |
lyc | cf3129ed66 | |
lyc | 97e035398c | |
lyc | 6e8c96ce2e | |
zhangxuelin | 3448a029c6 | |
lyc | decfb09eb4 | |
zhangxuelin | 1dce3f6f70 | |
lyc | 2a84455f05 | |
zhangxuelin | d9ae544022 | |
zhangxuelin | b520858f3f | |
zdg | 758a4b09c9 | |
lyc | ba9b381b69 | |
zdg | 716c16928e | |
zdg | 0da2b25586 | |
朱浩 | 6616ac640e | |
朱浩 | f3619817f9 | |
zdg | bb1d5da104 | |
zdg | 69f54a217b | |
lyc | 4754ed641c | |
yangws | c64fbe49e9 | |
yangws | 3c2dbb722a | |
zdg | 1c67ce1b8f | |
yangws | acbd36ca88 | |
yangws | 1e52abc170 | |
yangws | 1a1f20d175 | |
朱浩 | 1aded1d50a | |
lyc | 94ae78bffc | |
lyc | c72d103082 | |
lyc | 22f35a6638 | |
lyc | bf783cf940 | |
lyc | b255e48be5 | |
lyc | 41b0660559 | |
朱浩 | 0964cc0965 | |
朱浩 | 219e4abdc0 | |
朱浩 | c3709bf0c7 | |
朱浩 | e6a06a67b8 | |
朱浩 | ea4afb409f | |
朱浩 | 79055dc1c2 | |
yangws | e3a04c0add | |
yangws | f6bf811ee5 | |
朱浩 | 3b872f371f | |
朱浩 | 7fc04faad1 | |
lyc | d18c47994a | |
lyc | 7b804f9e62 | |
yangws | 62367c13d3 | |
朱浩 | 8f50174595 | |
lyc | bc9c6a3c89 | |
lyc | e2d9b61fbe | |
lyc | bc5a6e961d | |
lyc | 2b5acf272d | |
lyc | 9dda6b934c | |
zhangxuelin | 799dbed676 | |
yangws | 3b52f9acc4 | |
lyc | a1e00ba716 | |
lyc | acc4d7c81e | |
zhangxuelin | f80e7dee2a | |
yangws | 1f9788498a | |
白了个白 | 9ab62f180e | |
yangws | f60fdbafa0 | |
yangws | 776be8eaac | |
朱浩 | 67320f897e | |
朱浩 | 2cdc33d706 | |
朱浩 | f334adfda9 | |
朱浩 | fe26d348fc | |
yangws | 444daa3cec | |
yangws | c7d2eba5bb | |
zhangxuelin | 2bf82dc294 | |
zhangxuelin | cc5c823071 | |
zdg | 8df3871f7d | |
zdg | 0ffa45313b | |
lyc | c4e82017e5 | |
lyc | d4fc68c902 | |
朱浩 | 732e4c9a64 | |
yangws | 6c717ea345 | |
yangws | c4c4bca54a | |
朱浩 | e89ffd4416 | |
lyc | cc1faa7f88 | |
lyc | 0b67ca30f8 | |
zhangxuelin | 7d3007bbf2 | |
zhangxuelin | bf99c4dd00 | |
朱浩 | d690a4ae70 | |
朱浩 | 974aaf3522 | |
lyc | 524eedc451 | |
lyc | 9d38f715a1 | |
朱浩 | b4c9db619e | |
朱浩 | 7cf3487bfb | |
朱浩 | 74275ac327 | |
朱浩 | c29a3d7a77 | |
朱浩 | b8325d336b | |
朱浩 | c2876ff983 | |
zhengdegang | 6035803db1 | |
zdg | f9de8514ed | |
zdg | cc8ea2b043 | |
lyc | 9e0e2a41f2 | |
lyc | 3fcd0cdb21 | |
lyc | 815b2b15cb | |
lyc | eb17167feb | |
朱浩 | ae500c94ce | |
lyc | 258a99bc36 | |
朱浩 | e5845a6714 | |
朱浩 | 1c1ec2be71 | |
朱浩 | 572bdb5fec | |
朱浩 | 7974aeabe4 | |
zhangxuelin | c11b9765e6 | |
lyc | 0e34b6a1e9 | |
lyc | b10be40ddc | |
zhangxuelin | 964e99c186 | |
zhangxuelin | e1e9f171e0 | |
zhangxuelin | a8f9c828d7 | |
朱浩 | c56039270c | |
朱浩 | 32c64d073b | |
lyc | 1498b9bc9f | |
lyc | 9a8d0e61d5 | |
lyc | d4b24f8d71 | |
lyc | ccf4211f44 | |
朱浩 | aaa75df73b | |
lyc | 6ddd172bb9 | |
lyc | 00d23f4b31 | |
朱浩 | 54d9f6d782 | |
朱浩 | 5db876ebfa | |
zhengdegang | 6b1ec46360 | |
zdg | 8fc1339419 | |
zdg | 0c8af75e2a | |
zhengdegang | b8f2eb24e2 | |
zdg | 618618f736 | |
zdg | 43afd575de | |
zhengdegang | 8c2362ff4f | |
zhangxuelin | 4687d62b90 | |
zhangxuelin | 2a78cb40dc | |
zdg | d12b5bb29f | |
zdg | 5430cb8c50 | |
zdg | 6f890843ab | |
朱浩 | bd2024eed2 | |
朱浩 | 9f510187c0 | |
zhangxuelin | dca4eaae91 | |
zhangxuelin | d64122775a | |
zhangxuelin | 29ad68e5ab | |
zdg | 4e2512ed77 | |
zdg | c9d377aa74 | |
zdg | 7221a203e9 | |
朱浩 | 3bcb0a2ef3 | |
朱浩 | 9018abb673 | |
朱浩 | a55a662ce9 | |
朱浩 | 528e7876a9 | |
朱浩 | 82456a7f96 | |
zdg | 7dfa51f5dc | |
lyc | 74b0cf5fdb | |
lyc | df7658a17d | |
zhangxuelin | db496ee154 | |
zhangxuelin | a6fff59601 | |
zhangxuelin | 2a670c1447 | |
zhangxuelin | 1d4cad94c1 | |
zhangxuelin | da78c7e1e8 | |
baigl | 954f43d8b3 |
|
@ -9,7 +9,8 @@ VITE_APP_BASE_API = '/dev-api'
|
|||
|
||||
VITE_APP_DOMAIN = 'file.ysaix.com'
|
||||
|
||||
VITE_APP_UPLOAD_API = 'http://192.168.2.52:7863'
|
||||
VITE_APP_UPLOAD_API = 'https://file.ysaix.com:7868/prod-api'
|
||||
#VITE_APP_UPLOAD_API = 'http://192.168.2.52:7863'
|
||||
|
||||
VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# 页面标题
|
||||
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/'
|
|
@ -1,19 +1,19 @@
|
|||
# 页面标题
|
||||
VITE_APP_TITLE = AIx数字平台
|
||||
VITE_APP_TITLE = AIX智慧课堂
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
|
||||
# AIx融合数字管理系统/生产环境
|
||||
VITE_APP_BASE_API = 'https://file.ysaix.com:7868/prod-api'
|
||||
VITE_APP_BASE_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||
|
||||
VITE_APP_DOMAIN = 'file.ysaix.com'
|
||||
VITE_APP_DOMAIN = 'prev.ysaix.com'
|
||||
|
||||
VITE_APP_UPLOAD_API = 'https://file.ysaix.com:7868/prod-api'
|
||||
VITE_APP_UPLOAD_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS = gzip
|
||||
|
||||
VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# 页面标题
|
||||
VITE_APP_TITLE = AIx数字平台(测试版)
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
|
||||
# AIx融合数字管理系统/生产环境
|
||||
VITE_APP_BASE_API = 'https://file.ysaix.com:7868/prod-api'
|
||||
|
||||
VITE_APP_DOMAIN = 'file.ysaix.com'
|
||||
|
||||
VITE_APP_UPLOAD_API = 'https://file.ysaix.com:7868/prod-api'
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS = gzip
|
||||
|
||||
VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'
|
|
@ -10,6 +10,7 @@ module.exports = {
|
|||
],
|
||||
rules: {
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/multi-word-component-names': 'off'
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'prettier/prettier': 'off'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
appId: com.electron.app
|
||||
productName: 文枢课堂
|
||||
directories:
|
||||
output: dist
|
||||
buildResources: build
|
||||
win:
|
||||
executableName: 文枢课堂
|
||||
icon: resources/logo2.ico
|
||||
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}-${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/smarttalk/
|
||||
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
|
||||
productName: AIx
|
||||
directories:
|
||||
output: dist
|
||||
buildResources: build
|
||||
win:
|
||||
executableName: AIx
|
||||
icon: resources/logo2.ico
|
||||
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}-${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/smarttalk/
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
# 额外依赖打包到输出目录
|
||||
extraFiles:
|
||||
- from: ./node_modules/im_electron_sdk/lib/
|
||||
to: ./resources
|
||||
filter:
|
||||
- '**/*'
|
|
@ -45,3 +45,9 @@ publish:
|
|||
url: http://localhost:3000
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
# 额外依赖打包到输出目录
|
||||
extraFiles:
|
||||
- from: ./node_modules/im_electron_sdk/lib/
|
||||
to: ./resources
|
||||
filter:
|
||||
- '**/*'
|
||||
|
|
|
@ -45,3 +45,9 @@ publish:
|
|||
url: https://file.ysaix.com:7868/src/assets/smarttalk/
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
# 额外依赖打包到输出目录
|
||||
extraFiles:
|
||||
- from: ./node_modules/im_electron_sdk/lib/
|
||||
to: ./resources
|
||||
filter:
|
||||
- '**/*'
|
||||
|
|
|
@ -3,7 +3,14 @@ import path from 'path'
|
|||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import WindiCSS from "vite-plugin-windicss"
|
||||
|
||||
/*import electron from 'vite-plugin-electron'
|
||||
plugins: [electron({
|
||||
main: {
|
||||
builderOptions: {
|
||||
asar: false
|
||||
}
|
||||
}
|
||||
})],*/
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
|
@ -25,6 +32,7 @@ export default defineConfig({
|
|||
proxy: {
|
||||
'/dev-api': {
|
||||
target: 'http://27.128.240.72:7865',
|
||||
// target: 'http://36.134.181.164:7863',
|
||||
// target: 'http://192.168.2.52:7863',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||
|
|
20
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "aix-win",
|
||||
"version": "1.0.2",
|
||||
"description": "An Electron application with Vue",
|
||||
"version": "2.0.6",
|
||||
"description": "",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "example.com",
|
||||
"homepage": "https://electron-vite.org",
|
||||
|
@ -14,7 +14,9 @@
|
|||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
"build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
|
||||
"build:test": "npm run build && 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:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml",
|
||||
"build:mac": "npm run build && electron-builder --mac",
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
},
|
||||
|
@ -24,23 +26,35 @@
|
|||
"@electron/remote": "^2.1.2",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
||||
"@vue-office/docx": "^1.6.2",
|
||||
"@vue-office/excel": "^1.7.11",
|
||||
"@vue-office/pdf": "^2.0.2",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"circular-json": "^0.5.9",
|
||||
"cropperjs": "^1.6.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.5.1",
|
||||
"electron-dl-manager": "^3.0.0",
|
||||
"electron-log": "^5.1.7",
|
||||
"electron-store": "8.0.0",
|
||||
"electron-updater": "^6.1.7",
|
||||
"element-china-area-data": "^6.1.0",
|
||||
"element-plus": "^2.7.6",
|
||||
"fabric": "^5.3.0",
|
||||
"im_electron_sdk": "^8.0.5904",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jsondiffpatch": "0.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-addon-api": "^8.1.0",
|
||||
"pdfjs-dist": "4.4.168",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"spark-md5": "^3.0.2",
|
||||
"vite-plugin-electron": "^0.28.8",
|
||||
"vue-qr": "^4.0.9",
|
||||
"vue-router": "^4.4.0",
|
||||
"xgplayer": "^3.0.19",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* @description 腾讯云-即时通讯-sdkID
|
||||
*/
|
||||
// import { ipcMain } from 'electron'
|
||||
// const TimMain = require('im_electron_sdk/dist/main')
|
||||
import TimMain from 'im_electron_sdk/dist/main'
|
||||
// import {TIMErrCode} from 'im_electron_sdk/dist/enumbers'
|
||||
const sdkappidDef = 1600034736 // 可以去腾讯云即时通信IM控制台申请
|
||||
|
||||
// 初始化
|
||||
function init(sdkappid = sdkappidDef) {
|
||||
return new TimMain({sdkappid})
|
||||
}
|
||||
export function initialize(){
|
||||
// ipcMain.handle('im-chat:init', (event, sdkappid) => {
|
||||
// return init(sdkappid)
|
||||
// })
|
||||
return init()
|
||||
}
|
||||
export default { initialize, init }
|
|
@ -42,19 +42,23 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
let filePath = appRootFilePath + fileNewName
|
||||
let uploadId = null
|
||||
let isOn = false
|
||||
let lastMTime = fs.statSync(filePath).mtime.getTime()
|
||||
console.log(lastMTime)
|
||||
setInterval(() => {
|
||||
getFileMD5(filePath).then((md5New) => {
|
||||
if (md5New !== md5) {
|
||||
md5 = md5New
|
||||
getFileMsg(filePath).then((msg) => {
|
||||
if (msg !== lastMTime) {
|
||||
lastMTime = msg
|
||||
if (uploadId) {
|
||||
clearTimeout(uploadId)
|
||||
}
|
||||
if (isOn === false) {
|
||||
console.log(fileNewName)
|
||||
e.reply('listen-file-change-on' + fileNewName)
|
||||
isOn = true
|
||||
}
|
||||
//倒数十秒提交更改,十秒之内有继续修改则重置倒数
|
||||
uploadId = setTimeout(() => {
|
||||
console.log(223)
|
||||
//执行更新,上传文件
|
||||
let formData = new FormData()
|
||||
formData.append('id', id)
|
||||
|
@ -83,6 +87,13 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
}, 1000)
|
||||
})
|
||||
|
||||
function getFileMsg(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stats = fs.statSync(path)
|
||||
return resolve(stats.mtime.getTime())
|
||||
})
|
||||
}
|
||||
|
||||
function getFileMD5(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, (err, dataFile) => {
|
||||
|
@ -121,13 +132,14 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
e.reply('is-async-local-file-reply' + fileNewName, { isAsync: true, type: 'down' })
|
||||
return
|
||||
}
|
||||
getFileMD5(filePath).then((localMd5) => {
|
||||
if (localMd5 === md5) {
|
||||
getFileMsg(filePath).then((msg) => {
|
||||
let time = new Date(lastModifyTime).getTime();
|
||||
msg = parseInt(msg/1000)*1000;
|
||||
if (msg == time) {
|
||||
e.reply('is-async-local-file-reply' + fileNewName, { isAsync: false, type: '' })
|
||||
} else {
|
||||
const stats = fs.statSync(filePath)
|
||||
//如果线上时间大于线下时间,就需要从线上下载,否则则需要上传
|
||||
let time = new Date(lastModifyTime)
|
||||
if (time > stats.mtime.getTime()) {
|
||||
e.reply('is-async-local-file-reply' + fileNewName, { isAsync: true, type: 'down' })
|
||||
} else if (time < stats.mtime.getTime()) {
|
||||
|
@ -146,7 +158,7 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
//使用默认应用打开本地文件
|
||||
ipcMain.on('open-path-app', (e, destination) => {
|
||||
let path = appRootFilePath + destination
|
||||
shell.openExternal(path).catch((error) => {
|
||||
shell.openPath(path).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
})
|
||||
|
@ -201,7 +213,8 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
/*创建新的ppt文件*/
|
||||
ipcMain.on('creat-file-default', (e, { name, uploadData, cookie }) => {
|
||||
createFolder('tempFile').then(() => {
|
||||
let path = appTempFilePath + name
|
||||
let path = appTempFilePath + name.replace(/[\\/:*?"<>|]/, '')
|
||||
console.log(path)
|
||||
fs.writeFileSync(path, '', 'utf-8')
|
||||
let fileType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||
let formData = new FormData()
|
||||
|
@ -211,7 +224,7 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
formData.append(key, uploadData[key])
|
||||
}
|
||||
}
|
||||
formData.append('fileFlag', '教案')
|
||||
formData.append('fileFlag', '课件')
|
||||
uploadFileByFS({
|
||||
url: uploadUrl,
|
||||
path,
|
||||
|
@ -237,37 +250,43 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
|
||||
//下载文件
|
||||
ipcMain.on('download-file-default', (e, { url, fileName }) => {
|
||||
createFolder('selfFile').then(async () => {
|
||||
const browserWindow = BrowserWindow.getFocusedWindow()
|
||||
const id = await manager.download({
|
||||
window: browserWindow,
|
||||
url: url,
|
||||
saveAsFilename: fileName,
|
||||
directory: appRootFilePath,
|
||||
callbacks: {
|
||||
onDownloadStarted: async ({ id, item, webContents }) => {
|
||||
// Do something with the download id
|
||||
},
|
||||
onDownloadProgress: async ({ id, item, percentCompleted }) => {},
|
||||
onDownloadCompleted: async ({ id, item }) => {
|
||||
console.log('完成')
|
||||
e.reply('download-file-default' + fileName, true)
|
||||
},
|
||||
onDownloadCancelled: async () => {
|
||||
console.log('取消')
|
||||
e.reply('download-file-default' + fileName, false)
|
||||
},
|
||||
onDownloadInterrupted: async () => {
|
||||
console.log('中断')
|
||||
e.reply('download-file-default' + fileName, false)
|
||||
},
|
||||
onError: (err, data) => {
|
||||
console.log(err.toString())
|
||||
e.reply('download-file-default' + fileName, false)
|
||||
createFolder('selfFile')
|
||||
.then(async () => {
|
||||
const browserWindow = BrowserWindow.getFocusedWindow()
|
||||
const id = await manager.download({
|
||||
window: browserWindow,
|
||||
url: url,
|
||||
saveAsFilename: fileName,
|
||||
directory: appRootFilePath,
|
||||
callbacks: {
|
||||
onDownloadStarted: async ({ id, item, webContents }) => {
|
||||
// Do something with the download id
|
||||
},
|
||||
onDownloadProgress: async ({ id, item, percentCompleted }) => {
|
||||
e.reply('download-file-default-prog' + fileName, percentCompleted)
|
||||
},
|
||||
onDownloadCompleted: async ({ id, item }) => {
|
||||
console.log('完成')
|
||||
e.reply('download-file-default' + fileName, true)
|
||||
},
|
||||
onDownloadCancelled: async () => {
|
||||
console.log('取消')
|
||||
e.reply('download-file-default' + fileName, false)
|
||||
},
|
||||
onDownloadInterrupted: async () => {
|
||||
console.log('中断')
|
||||
e.reply('download-file-default' + fileName, false)
|
||||
},
|
||||
onError: (err, data) => {
|
||||
console.log(err.toString())
|
||||
e.reply('download-file-default' + fileName, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
e.reply('download-file-default' + fileName, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/**另存为...
|
||||
|
|
|
@ -3,17 +3,45 @@ import { join } from 'path'
|
|||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
import File from './file'
|
||||
import Logger from './logger' // 日志封装
|
||||
import chat from './chat' // chat封装
|
||||
import Store from './store' // Store封装
|
||||
import updateInit from './update'
|
||||
// 代理 electron/remote
|
||||
// 第一步:引入remote
|
||||
import remote from '@electron/remote/main'
|
||||
// 第二步: 初始化remote
|
||||
remote.initialize()
|
||||
import updateInit from './update'
|
||||
// 日志配置-初始化(日志直接绑定到console上)
|
||||
if(!is.dev) Logger.initialize()
|
||||
// 持久化数据-初始化
|
||||
Store.initialize()
|
||||
|
||||
File({ app, shell, BrowserWindow, ipcMain })
|
||||
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||
let mainWindow, loginWindow
|
||||
|
||||
const additionalData = {myKey:'ys_axi_smarttalk'}
|
||||
const gotTheLock = app.requestSingleInstanceLock(additionalData)
|
||||
|
||||
if(!gotTheLock){
|
||||
app.quit()
|
||||
}else{
|
||||
app.on('second-instance',(event,commandLine,workingDirectory,additionalData)=>{
|
||||
//输入从第二个实例中接收到的数据
|
||||
console.log(additionalData)
|
||||
//有人试图运行第二个实例,我们应该关注我们的窗口
|
||||
if(mainWindow){
|
||||
if(mainWindow.isMinimized()) mainWindow.restore()
|
||||
mainWindow.focus()
|
||||
}
|
||||
if(loginWindow){
|
||||
if(loginWindow.isMinimized()) loginWindow.restore()
|
||||
loginWindow.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//登录窗口
|
||||
function createLoginWindow() {
|
||||
if (loginWindow) return
|
||||
|
@ -28,6 +56,7 @@ function createLoginWindow() {
|
|||
icon: join(__dirname, '../../resources/logo2.ico'),
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
defaultEncoding: 'utf-8',
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
nodeIntegration: true,
|
||||
|
@ -60,13 +89,16 @@ function createLoginWindow() {
|
|||
function createMainWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
minWidth: 1350,
|
||||
height: 700,
|
||||
show: false,
|
||||
frame: false, // 无边框
|
||||
autoHideMenuBar: true,
|
||||
maximizable: false,
|
||||
icon: join(__dirname, '../../resources/logo2.ico'),
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
defaultEncoding: 'utf-8',
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
// nodeIntegration: true,
|
||||
|
@ -90,7 +122,7 @@ function createMainWindow() {
|
|||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
mainWindow.webContents.openDevTools()
|
||||
// mainWindow.webContents.openDevTools()
|
||||
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
|
@ -115,24 +147,26 @@ async function createLinkWin(data) {
|
|||
autoHideMenuBar: true,
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
defaultEncoding: 'utf-8',
|
||||
sandbox: false,
|
||||
nodeIntegration: true,
|
||||
worldSafeExecuteJavaScript: true,
|
||||
contextIsolation: true
|
||||
}
|
||||
})
|
||||
linkWin[data.key].type = 'link' // 唯一标识
|
||||
linkWin[data.key].type = 'link'+data.key // 唯一标识
|
||||
|
||||
let cookieDetails = { ...data.cookieData }
|
||||
await linkWin[data.key].webContents.session.cookies
|
||||
.set(cookieDetails)
|
||||
.then(() => {
|
||||
console.log('Cookie is successful')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Cookie is error', error)
|
||||
})
|
||||
.then(() => {})
|
||||
.catch((error) => {})
|
||||
data.fullPath = data.fullPath.replaceAll('//', '/')
|
||||
if (data.fullPath.indexOf('?') !== -1) {
|
||||
data.fullPath += '&urlSource=smarttalk&t' + Date.now()
|
||||
}else {
|
||||
data.fullPath += '?urlSource=smarttalk&t' + Date.now()
|
||||
}
|
||||
linkWin[data.key].loadURL(data.fullPath)
|
||||
|
||||
linkWin[data.key].once('ready-to-show', () => {
|
||||
|
@ -147,6 +181,8 @@ async function createLinkWin(data) {
|
|||
|
||||
// 初始化完成
|
||||
app.on('ready', () => {
|
||||
appWatchError() // 监听app错误
|
||||
process.env.LANG = 'en_US.UTF-8'
|
||||
// 设置应用程序用户模型标识符
|
||||
electronApp.setAppUserModelId('com.electron')
|
||||
|
||||
|
@ -226,12 +262,14 @@ app.on('window-all-closed', () => {
|
|||
|
||||
// 监听全局事件
|
||||
function handleAll() {
|
||||
const chatInstance = chat.initialize() // im-chat 实例
|
||||
// 新窗口创建-监听
|
||||
ipcMain.on('new-window', (e, data) => {
|
||||
const { id, type } = data
|
||||
const win = BrowserWindow.fromId(id)
|
||||
win.type = type // 绑定独立标识
|
||||
remote.enable(win.webContents) // 开启远程服务
|
||||
remote.enable(win.webContents) // 开启远程服务
|
||||
chatInstance.enable(win.webContents) // 开启im-chat
|
||||
})
|
||||
// 用于监听-状态管理变化-同步所有窗口
|
||||
ipcMain.handle('pinia-state-change', (e, storeName, jsonStr) => {
|
||||
|
@ -243,4 +281,41 @@ function handleAll() {
|
|||
}
|
||||
}
|
||||
})
|
||||
// 用于监听-状态管理变化-初始同步
|
||||
ipcMain.handle('pinia-state-init', (e, wid, storeName, jsonStr) => {
|
||||
// console.log('pinia-state-init', jsonStr)
|
||||
const win = BrowserWindow.fromId(wid)
|
||||
win.webContents.send('pinia-state-set', storeName, jsonStr)
|
||||
})
|
||||
}
|
||||
|
||||
// app 崩溃监听器
|
||||
function appWatchError() {
|
||||
// 渲染进程崩溃
|
||||
app.on('renderer-process-crashed', (event, webContents, killed) => {
|
||||
console.error(
|
||||
`APP-ERROR:renderer-process-crashed; event: ${JSON.stringify(event)}; webContents:${JSON.stringify(
|
||||
webContents
|
||||
)}; killed:${JSON.stringify(killed)}`
|
||||
)
|
||||
})
|
||||
|
||||
// GPU进程崩溃
|
||||
app.on('gpu-process-crashed', (event, killed) => {
|
||||
console.error(`APP-ERROR:gpu-process-crashed; event: ${JSON.stringify(event)}; killed: ${JSON.stringify(killed)}`)
|
||||
})
|
||||
|
||||
// 渲染进程结束
|
||||
app.on('render-process-gone', async (event, webContents, details) => {
|
||||
console.error(
|
||||
`APP-ERROR:render-process-gone; event: ${JSON.stringify(event)}; webContents:${JSON.stringify(
|
||||
webContents
|
||||
)}; details:${JSON.stringify(details)}`
|
||||
)
|
||||
})
|
||||
|
||||
// 子进程结束
|
||||
app.on('child-process-gone', async (event, details) => {
|
||||
console.error(`APP-ERROR:child-process-gone; event: ${JSON.stringify(event)}; details:${JSON.stringify(details)}`)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @description 日志配置
|
||||
* @author zdg
|
||||
* @date 2021-07-05 14:07:01
|
||||
*/
|
||||
// import log from 'electron-log'
|
||||
import log from 'electron-log/main'
|
||||
import { app } from 'electron'
|
||||
import path from 'path'
|
||||
|
||||
// 关闭控制台打印
|
||||
// 日志控制台等级,默认值:false
|
||||
log.transports.console.level = false
|
||||
// log.transports.console.level = 'info'
|
||||
// 日志文件等级,默认值:false
|
||||
log.transports.file.level = 'info'
|
||||
// 日志文件名,默认:main.log
|
||||
// log.transports.file.fileName = 'main.log';
|
||||
// 日志大小,默认:1048576(1M),达到最大上限后,备份文件并重命名为:main.old.log,有且仅有一个备份文件
|
||||
log.transports.file.maxSize = 10 * 1024 * 1024; // 文件最大不超过 10M
|
||||
// 自定义日志文件滚动策略
|
||||
log.transports.file.rollSize = 10 * 1024 * 1024; // 10MB
|
||||
// 日志格式,默认:[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}
|
||||
log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}'
|
||||
let date = new Date()
|
||||
let dateStr = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
|
||||
// 文件位置及命名方式
|
||||
// 默认位置为:C:\Users\[user]\AppData\Roaming\[appname]\electron_log\
|
||||
// 文件名为:年-月-日.log
|
||||
// 自定义文件保存位置为安装目录下 \log\年-月-日.log
|
||||
// log.transports.file.resolvePathFn = () => 'logs\\' + dateStr+ '.log';
|
||||
log.transports.file.resolvePathFn = () => path.join(app.getPath('userData'), `logs/${dateStr}.log`)
|
||||
|
||||
// 有六个日志级别error, warn, info, verbose, debug, silly。默认是silly
|
||||
export const logger = {
|
||||
error: (...args) => log.error(...args),
|
||||
warn: (...args) => log.warn(...args),
|
||||
info: (...args) => log.info(...args),
|
||||
verbose: (...args) => log.verbose(...args),
|
||||
debug: (...args) => log.debug(...args),
|
||||
silly: (...args) => log.silly(...args)
|
||||
}
|
||||
export function initialize(bool = true, type = 'all') {
|
||||
log.initialize() // 为渲染器进行初始化
|
||||
if (bool) { // 是否替换默认的console
|
||||
if (type == 'all') Object.assign(console, log.functions)
|
||||
else { // 替换指定类型
|
||||
console[type] = log[type]
|
||||
}
|
||||
}
|
||||
}
|
||||
export default { initialize }
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* @description 解决 主进程|渲染进程 数据共享
|
||||
*/
|
||||
import Store from 'electron-store' // 持久化存储
|
||||
|
||||
// 设置ipc与渲染器通信
|
||||
Store.initRenderer()
|
||||
|
||||
// 默认共享数据
|
||||
const defaultData = {
|
||||
session: { // 缓存(临时sessionStorage)
|
||||
model: 'select', // 悬浮球-当前模式
|
||||
showBoardAll: false, // 全屏画板-是否显示
|
||||
isPdfWin: false, // pdf窗口是否打开
|
||||
isToolWin: false, // 工具窗口是否打开
|
||||
curSubjectNode: {
|
||||
data: {}, // 当前教材节点 (包含当前教材 单元)
|
||||
querySearch: {} // 查询资源所需参数
|
||||
}
|
||||
},
|
||||
local: { // 本地(永久localStorage)
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化
|
||||
export function initialize(){
|
||||
// 缓存数据-sessionStore
|
||||
const sessionStore = new Store({
|
||||
name: 'session-store', // 存储文件名
|
||||
fileExtension: 'ini', // 文件后缀名
|
||||
encryptionKey: 'BvPLmgCC4DSIG0KkTec5', // 数据加密-防止用户直接改配置
|
||||
beforeEachMigration: (store, context) => { // 版本迁移回调
|
||||
console.log(`[session-store] 迁移从 ${context.fromVersion} → ${context.toVersion}`);
|
||||
},
|
||||
migrations: { // 版本变化
|
||||
'0.0.0': store => {
|
||||
// store.set('debugPhase', true);
|
||||
}
|
||||
}
|
||||
})
|
||||
sessionStore.clear() // 先清除-所有缓存数据
|
||||
sessionStore.set(defaultData.session) // 初始化-默认数据
|
||||
|
||||
// 缓存数据-localStore
|
||||
const localStore = new Store({
|
||||
name: 'local-store', // 存储文件名
|
||||
fileExtension: 'ini', // 文件后缀名
|
||||
encryptionKey: '6CyoHQmUaPmLzvVsh', // 数据加密-防止用户直接改配置
|
||||
beforeEachMigration: (store, context) => { // 版本迁移回调
|
||||
console.log(`[local-store] 迁移从 ${context.fromVersion} → ${context.toVersion}`);
|
||||
},
|
||||
migrations: { // 版本变化
|
||||
'0.0.0': store => {
|
||||
// store.set('debugPhase', true);
|
||||
}
|
||||
}
|
||||
})
|
||||
localStore.set(defaultData.local) // 初始化-默认数据
|
||||
return {sessionStore, localStore}
|
||||
}
|
||||
export default { initialize }
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* @description: electron 封装的工具函数
|
||||
* 消息整理
|
||||
* tool-sphere:create 创建-悬浮球-窗口
|
||||
*/
|
||||
import { app, shell, BrowserWindow, ipcMain } from 'electron'
|
||||
import { is } from '@electron-toolkit/utils'
|
||||
|
||||
// const baseUrl = 'http://localhost:5173/#' // 开发环境使用
|
||||
const baseUrl = process.env['ELECTRON_RENDERER_URL']+'/#' // 开发环境使用
|
||||
// 所有窗口
|
||||
let allWindow = {}
|
||||
// 其他已有窗口 wins
|
||||
export function init() {
|
||||
// 创建工具-悬浮球
|
||||
ipcMain.on('tool-sphere:create', async(e, data) => {
|
||||
// console.log('测试xxxx', data)
|
||||
await createTools(data) // 执行逻辑
|
||||
e.reply('tool-sphere:create-reply', {code: 200, msg: 'success'}) // 返回结果
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description: 创建工具
|
||||
* @param {*} url 路由地址
|
||||
* @param {number} [width=800] 窗口宽度
|
||||
* @param {number} [height=600] 窗口高度
|
||||
* @param {{}} [option={}] 自定义选项
|
||||
* @author: zdg
|
||||
* @date 2021-07-05 14:07:01
|
||||
*/
|
||||
export function createTools({url, width = 800, height = 600, option={}}) {
|
||||
const { mainWindow } = allWindow||{} // 获取主窗口
|
||||
const devUrl = `${baseUrl}${url}`
|
||||
const buildUrl = `file://${__dirname}/index.html${url}`
|
||||
const urlAll = is.dev ? devUrl : buildUrl
|
||||
return new Promise((resolve) => {
|
||||
let win = new BrowserWindow({
|
||||
width, height,
|
||||
type: 'toolbar', // 创建的窗口类型为工具栏窗口
|
||||
frame: false, // 要创建无边框窗口
|
||||
resizable: false, // 禁止窗口大小缩放
|
||||
transparent: true, // 设置透明
|
||||
alwaysOnTop: true, // 窗口是否总是显示在其他窗口之前
|
||||
|
||||
parent: mainWindow, // 父窗口
|
||||
autoClose: true, // 关闭窗口后自动关闭
|
||||
webPreferences: {
|
||||
nodeIntegration: true, // nodeApi调用
|
||||
contextIsolation: false, // 沙箱取消
|
||||
webSecurity: false // 跨域关闭
|
||||
},
|
||||
...option
|
||||
})
|
||||
// console.log(urlAll)
|
||||
// url = 'https://www.baidu.com'
|
||||
console.log(urlAll)
|
||||
win.loadURL(urlAll)
|
||||
win.setFullScreen(true) // 设置窗口为全屏
|
||||
win.setIgnoreMouseEvents(true) // 忽略鼠标事件|使窗口不可选中
|
||||
win.once('ready-to-show', () => {
|
||||
win.show()
|
||||
resolve(win)
|
||||
})
|
||||
win.on('closed', () => {
|
||||
win = null
|
||||
})
|
||||
})
|
||||
}
|
||||
// 保存窗口
|
||||
export function setWin(win = {}) {
|
||||
if (win && Object.keys(win).length){
|
||||
Object.keys(win).forEach(key => {
|
||||
if (!allWindow[key]) { // 不存在就保存
|
||||
allWindow[key] = win[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -42,6 +42,11 @@ const updateInit = (win) => {
|
|||
logger.error('检查更新失败')
|
||||
})
|
||||
|
||||
// 监听下载进度
|
||||
autoUpdater.on('download-progress', (progressObj) => {
|
||||
win.webContents.send('update-app-progress', progressObj.percent);
|
||||
});
|
||||
|
||||
// 跟新下载完毕
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
dialog
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { contextBridge } from 'electron'
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
|
||||
import TimRender from 'im_electron_sdk/dist/renderer' // im渲染部分实例
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
preloadPath: __dirname, // 当前preload地址
|
||||
getTimRender: () => new TimRender(), // im渲染部分实例
|
||||
}
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
// renderer only if context isolation is enabled, otherwise
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>AIx智慧教育</title>
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<!-- <meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
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 *; default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src * 'self' data: blob:" />
|
||||
<meta http-equiv="Content-Security-Policy" content="connect-src *; default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *;img-src * 'self' data: blob:" />
|
||||
|
||||
</head>
|
||||
|
||||
|
|
After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,20 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 创建对话
|
||||
export const createChart = ({ headers, data }) => {
|
||||
return request({
|
||||
url: '/qf/createChart',
|
||||
method: 'post',
|
||||
headers,
|
||||
data,
|
||||
})
|
||||
}
|
||||
// 大模型对话
|
||||
export const sendChart = ({ headers, data }) => {
|
||||
return request({
|
||||
url: '/qf/sendTalk',
|
||||
method: 'post',
|
||||
headers,
|
||||
data,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* @description: 后端接口api
|
||||
* @author zdg
|
||||
* @date 2023-07-03
|
||||
*/
|
||||
import request from '@/utils/request'
|
||||
// /system/user/txCloudSign
|
||||
export class ApiService {
|
||||
// zdg: 公共请求-处理(可进行特殊处理)
|
||||
static publicHttp(url, data, method, option = {}, type) {
|
||||
method = method || 'get' // 默认GET
|
||||
const config = { url, method }
|
||||
if (!!data) config[method=='get'?'params':'data'] = data
|
||||
if (!!option) Object.assign(config, option)
|
||||
// 特殊格式处理
|
||||
let headers
|
||||
if (type == 'file') headers = { 'Content-Type': 'multipart/form-data' }
|
||||
else if (type == 'json') headers = { 'Content-Type': 'application/json' }
|
||||
else if (type == 'form') headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
headers && (config.headers = { ...config.headers, ...headers })
|
||||
return request(config)
|
||||
}
|
||||
}
|
||||
// zdg: 腾讯云-即时通讯
|
||||
export class imChat {
|
||||
// 获取腾讯im-chat appid 签名
|
||||
static getTxCloudSign = data => ApiService.publicHttp('/system/user/txCloudSign', data)
|
||||
}
|
|
@ -50,7 +50,7 @@ export function getClassmain(id) {
|
|||
// 获取小组列表
|
||||
export function listClassgroup(query) {
|
||||
return request({
|
||||
url: '/education/classgroup/list',
|
||||
url: '/education/classgroup/new/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
|
@ -130,3 +130,71 @@ export function addStudentmainByNameArray(data) {
|
|||
data: data
|
||||
})
|
||||
}
|
||||
//新增课程预约
|
||||
export function addSmartClassReserv(data) {
|
||||
return request({
|
||||
url: '/smarttalk/classReserv/addSmartClassReserv',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
//修改课程预约
|
||||
export function updateSmartClassReserv(data) {
|
||||
return request({
|
||||
url: '/smarttalk/classReserv/updateSmartClassReserv',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
//查询课程预约
|
||||
export function getSelfReserv() {
|
||||
return request({
|
||||
url: '/smarttalk/classReserv/getSelfReserv',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
export function deleteSmartReserv(id) {
|
||||
return request({
|
||||
url: '/smarttalk/classReserv/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
export function startClass(id, ex3) {
|
||||
const params = {id}
|
||||
!!ex3 && (params.ex3 = ex3)
|
||||
return request({
|
||||
url: '/smarttalk/classReserv/startClass',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
export function endClass(id) {
|
||||
return request({
|
||||
url: '/smarttalk/classReserv/endClass',
|
||||
method: 'get',
|
||||
params: {id}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 获取课堂信息
|
||||
* @param {*} id
|
||||
* @returns
|
||||
*/
|
||||
export function getClassInfo(id) {
|
||||
return request({
|
||||
url: '/smarttalk/classReserv/selectById',
|
||||
method: 'get',
|
||||
params: {id}
|
||||
})
|
||||
}
|
||||
//加入班级
|
||||
export function addClasses(data) {
|
||||
return request({
|
||||
url: '/smarttalk/audit/applyAddClass',
|
||||
method: 'post',
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
// 查询evaluation列表
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询作业列表
|
||||
export function listByDeadDate(query) {
|
||||
return request({
|
||||
url: '/education/classwork/listByDeadDate',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
//多个班级学生作业数据
|
||||
export function listClassworkdataByDeadDate(query) {
|
||||
return request({
|
||||
url: '/education/classworkdata/listByDeadDate',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询classworkdata列表 班级作业列表
|
||||
export function listClassworkdata(query) {
|
||||
return request({
|
||||
url: '/education/classworkdata/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询entpcoursework列表 课程作业列表
|
||||
export function listEntpcoursework(query) {
|
||||
return request({
|
||||
url: '/education/entpcoursework/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询classworkeval列表 课堂作业列表
|
||||
export function listClassworkeval(query) {
|
||||
return request({
|
||||
url: '/education/classworkeval/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 修改classworkeval
|
||||
export function updateClassworkeval(data) {
|
||||
return request({
|
||||
url: '/education/classworkeval',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改classworkdata
|
||||
export function updateClassworkdata(data) {
|
||||
return request({
|
||||
url: '/education/classworkdata',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 新增pdf圈点勾画
|
||||
export const addsmartBookMark = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/bookMark/addSmartBookMark',
|
||||
method: 'post',
|
||||
data:params
|
||||
})
|
||||
}
|
||||
|
||||
// 修改pdf圈点勾画
|
||||
export const updateSmartBookMarkContent = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/bookMark/updateSmartBookMarkContent',
|
||||
method: 'post',
|
||||
data:params
|
||||
})
|
||||
}
|
||||
|
||||
// 根据书id获取pdf圈点勾画
|
||||
export const getBookMarkById = (bookId) => {
|
||||
return request({
|
||||
url: '/smarttalk/bookMark/' + bookId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
//根据id删除对应页数
|
||||
export function deleteBookMark(ids) {
|
||||
return request({
|
||||
url: '/smarttalk/bookMark/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询entpcoursework列表
|
||||
export function listEntpcoursework(query) {
|
||||
return request({
|
||||
url: '/education/entpcoursework/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询entpcoursework详细
|
||||
export function getEntpcoursework(id) {
|
||||
return request({
|
||||
url: '/education/entpcoursework/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增entpcoursework
|
||||
export function addEntpcoursework(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursework',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改entpcoursework
|
||||
export function updateEntpcoursework(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursework',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除entpcoursework
|
||||
export function delEntpcoursework(id) {
|
||||
return request({
|
||||
url: '/education/entpcoursework/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// xuekubaoapi
|
||||
export function xuekubaoAPI(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursework/xuekubaoapi',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// PPT文件上传
|
||||
export function uploadEntpcourseworkFile(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursework/uploadWord',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 查询entpcoursework列表
|
||||
export function listEntpcourseworkNew(query) {
|
||||
return request({
|
||||
url: '/education/entpcoursework/new/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @desc: 学科网接口api
|
||||
* @return: {*}
|
||||
* @param {*} path 请求路径 /xopqbm/questions(无需全拼, 后端学科网sdk自动处理)
|
||||
* @param {*} method 请求方式 post/get
|
||||
* @param {*} params 请求参数 {key: value,}
|
||||
*/
|
||||
export function xkwAPI(path, method, isPostBody, params) {
|
||||
return request({
|
||||
url: '/xkw/post',
|
||||
method: 'post',
|
||||
data: {
|
||||
path: path,
|
||||
method: method,
|
||||
isPostBody: isPostBody,
|
||||
params: params,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc: 图文识别接口 python_OCR_api
|
||||
* @return: {*}
|
||||
* @param {*} path 请求路径 /ocrApi/data
|
||||
* @param {*} method 请求方式 post
|
||||
* @param {*} params 请求参数 {key: value,}
|
||||
*/
|
||||
export function pyOCRAPI(path) {
|
||||
return request({
|
||||
url: '/ocrApi/data',
|
||||
method: 'post',
|
||||
data: {
|
||||
imageBas64: path,
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询entpcoursefile列表
|
||||
export function listEntpcoursefile(query) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
// zdg:查询entpcoursefile列表-新
|
||||
export function listEntpcoursefileNew(query) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/new/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
// 查询entpcoursefile详细
|
||||
export function getEntpcoursefile(id) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增entpcoursefile
|
||||
export function addEntpcoursefile(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 新增entpcoursefile
|
||||
export function addEntpcoursefileReturnId(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/addReturnId',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// addFromId
|
||||
export function addFromId(fromid, toid, entpid, entpcourseid, edituserid) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/addFromId/'+fromid+'/'+toid+'/'+entpid+'/'+entpcourseid+'/'+edituserid,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 修改entpcoursefile
|
||||
export function updateEntpcoursefile(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
// 新增 修改接口
|
||||
export function updateEntpcoursefileNew(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/newUpdateFile',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// updateFileByIds
|
||||
export function updateFileByIds(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/updateFileByIds',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// updateFileByArray
|
||||
export function updateFileByArray(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/updateFileByArray',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改entpcoursefile
|
||||
export function updateFile2Redis(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/updateFile2Redis',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除entpcoursefile
|
||||
export function delEntpcoursefile(id) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 保存base64图片,返回url
|
||||
export function saveEntpCourseBase64File(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/saveBase64File',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 文件上传
|
||||
export function saveEntpCourseBase64File2(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/saveBase64File2',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 保存PPT页面预览base64图片,返回url
|
||||
export function savePPTPreviewBase64File(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/savePreviewBase64',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// PPT文件上传
|
||||
export function saveEntpCoursePPT(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/importPPT',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// PPT文件解析
|
||||
export function parsePPT(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/parsePPT',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 修改ppt.slide.index
|
||||
export function updateSlideIndex(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/saveSlideOrder',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
|
@ -9,6 +9,14 @@ export const getSmarttalkPage = (params) => {
|
|||
})
|
||||
}
|
||||
|
||||
export const creatAPT = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/file/createApt',
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export const getPrepareById = (id) => {
|
||||
return request({
|
||||
url: '/smarttalk/file/' + id,
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
//查询第三方课件的接口
|
||||
import request from '@/utils/request'
|
||||
//获取学科
|
||||
export const getSubjects = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/cnjy/getSubjects',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
//获取教材版本
|
||||
export const getTextbookVersion = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/cnjy/getVersions',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
//获得书籍
|
||||
export const getTextbook = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/cnjy/getBooks',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
//获取书籍章节
|
||||
export const getBook = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/cnjy/getChapters',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
//获取知识点信息
|
||||
export const getKnowledge = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/cnjy/getKnowledgePoints',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
//查询列表资源
|
||||
export const getBookList = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/cnjy/getDocuments',
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
||||
//获取图片路径
|
||||
export const getImgPath = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/cnjy/getPreview',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -57,3 +57,76 @@ export function getCodeImg() {
|
|||
timeout: 20000
|
||||
})
|
||||
}
|
||||
|
||||
// 注册模块-生成人机验证
|
||||
export function captchaImg(data) {
|
||||
return request({
|
||||
url: '/captchaImg',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'get',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
//注册模块-发送验证码
|
||||
export function sendCode(data) {
|
||||
return request({
|
||||
url: '/smarttalk/register/authSendCode',
|
||||
method: 'post',
|
||||
data:data
|
||||
})
|
||||
}
|
||||
|
||||
//注册模块-申请注册
|
||||
export function signIn(data) {
|
||||
return request({
|
||||
url: '/smarttalk/register/authSignIn',
|
||||
method: 'post',
|
||||
data:data
|
||||
})
|
||||
}
|
||||
|
||||
//登录模块-找回密码
|
||||
export function retrievePwd(data) {
|
||||
return request({
|
||||
url: '/smarttalk/register/authRetrievePwd',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//注册模块-获取学校
|
||||
export function deptTree(data) {
|
||||
return request({
|
||||
url: '/smarttalk/register/authDeptTree',
|
||||
method: 'get',
|
||||
params:data
|
||||
})
|
||||
}
|
||||
// 查询部门详细
|
||||
export function getDept(query) {
|
||||
return request({
|
||||
url: '/system/dept/detail',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询classmain列表
|
||||
export function listClassmain(query) {
|
||||
return request({
|
||||
url: '/education/classmain/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
// 查询evaluation列表
|
||||
export function listEvaluation(query) {
|
||||
return request({
|
||||
url: '/smarttalk/register/authEvaluationList',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
|
@ -50,3 +50,9 @@ export function updateUserInfo(data) {
|
|||
data: data
|
||||
})
|
||||
}
|
||||
export function getUserInfo(userId) {
|
||||
return request({
|
||||
url: '/system/user/' + userId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询classcourse列表
|
||||
export function listClasscourse(query) {
|
||||
return request({
|
||||
url: '/education/classcourse/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询classcourse详细
|
||||
export function getClasscourse(id) {
|
||||
return request({
|
||||
url: '/education/classcourse/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增classcourse
|
||||
export function addClasscourse(data) {
|
||||
return request({
|
||||
url: '/education/classcourse',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 新增classcourse
|
||||
export function addClasscourseReturnId(data) {
|
||||
return request({
|
||||
url: '/education/classcourse/saveReturnId',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改classcourse
|
||||
export function updateClasscourse(data) {
|
||||
return request({
|
||||
url: '/education/classcourse',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除classcourse
|
||||
export function delClasscourse(id) {
|
||||
return request({
|
||||
url: '/education/classcourse/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 删除classcourse
|
||||
export function delClasscourseWithData(id) {
|
||||
return request({
|
||||
url: '/education/classcourse/removeData/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// classcourse开始上课
|
||||
export function startCourseTeaching(id) {
|
||||
return request({
|
||||
url: '/education/classcourse/startCourseTeaching/'+id,
|
||||
method: 'post',
|
||||
})
|
||||
}
|
||||
|
||||
// 老师学生发送新的消息
|
||||
export function sendCourseTeachingMsg(data) {
|
||||
return request({
|
||||
url: '/education/classcourse/sendCourseTeachingMsg',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 老师学生获取新的交互消息
|
||||
export function getCourseTeachingMsg(id) {
|
||||
return request({
|
||||
url: '/education/classcourse/getCourseTeachingMsg/'+id,
|
||||
method: 'post',
|
||||
})
|
||||
}
|
||||
|
|
@ -43,3 +43,12 @@ export function delClasswork(id) {
|
|||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增classwork
|
||||
export function addClassworkReturnId(data) {
|
||||
return request({
|
||||
url: '/education/classwork/saveAndReturnId',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
|
@ -1,539 +0,0 @@
|
|||
/* Logo 字体 */
|
||||
@font-face {
|
||||
font-family: "iconfont logo";
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: "iconfont logo";
|
||||
font-size: 160px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
.nav-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-more {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#tabs {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#tabs li {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid transparent;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: -1px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
#tabs .active {
|
||||
border-bottom-color: #f00;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.tab-container .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 页面布局 */
|
||||
.main {
|
||||
padding: 30px 100px;
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.main .logo {
|
||||
color: #333;
|
||||
text-align: left;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1;
|
||||
height: 110px;
|
||||
margin-top: -50px;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.main .logo a {
|
||||
font-size: 160px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.helps {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.helps pre {
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
border: solid 1px #e7e1cd;
|
||||
background-color: #fffdef;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon_lists {
|
||||
width: 100% !important;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.icon_lists li {
|
||||
width: 100px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
list-style: none !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.icon_lists li .code-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.icon_lists .icon {
|
||||
display: block;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
font-size: 42px;
|
||||
margin: 10px auto;
|
||||
color: #333;
|
||||
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
transition: font-size 0.25s linear, width 0.25s linear;
|
||||
}
|
||||
|
||||
.icon_lists .icon:hover {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
.icon_lists .svg-icon {
|
||||
/* 通过设置 font-size 来改变图标大小 */
|
||||
width: 1em;
|
||||
/* 图标和文字相邻时,垂直对齐 */
|
||||
vertical-align: -0.15em;
|
||||
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||
fill: currentColor;
|
||||
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||
normalize.css 中也包含这行 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon_lists li .name,
|
||||
.icon_lists li .code-name {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* markdown 样式 */
|
||||
.markdown {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown img {
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
color: #404040;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
color: #404040;
|
||||
margin: 1.6em 0 0.6em 0;
|
||||
font-weight: 500;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.markdown h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown hr {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: #e9e9e9;
|
||||
margin: 16px 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown>p,
|
||||
.markdown>blockquote,
|
||||
.markdown>.highlight,
|
||||
.markdown>ol,
|
||||
.markdown>ul {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.markdown ul>li {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.markdown>ul li,
|
||||
.markdown blockquote ul>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown>ul li p,
|
||||
.markdown>ol li p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.markdown ol>li {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.markdown>ol li,
|
||||
.markdown blockquote ol>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
margin: 0 3px;
|
||||
padding: 0 5px;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown strong,
|
||||
.markdown b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
empty-cells: show;
|
||||
border: 1px solid #e9e9e9;
|
||||
width: 95%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
white-space: nowrap;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table th,
|
||||
.markdown>table td {
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
font-size: 90%;
|
||||
color: #999;
|
||||
border-left: 4px solid #e9e9e9;
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown .anchor {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.markdown .waiting {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.markdown h1:hover .anchor,
|
||||
.markdown h2:hover .anchor,
|
||||
.markdown h3:hover .anchor,
|
||||
.markdown h4:hover .anchor,
|
||||
.markdown h5:hover .anchor,
|
||||
.markdown h6:hover .anchor {
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.markdown>br,
|
||||
.markdown>p>br {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 代码高亮 */
|
||||
/* PrismJS 1.15.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre)>code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre)>code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #9a6e3a;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2794390 */
|
||||
src: url('iconfont.woff2?t=1722393125520') format('woff2'),
|
||||
url('iconfont.woff?t=1722393125520') format('woff'),
|
||||
url('iconfont.ttf?t=1722393125520') format('truetype'),
|
||||
url('iconfont.svg?t=1722393125520#iconfont') format('svg');
|
||||
src: url('iconfont.woff2?t=1725847033097') format('woff2'),
|
||||
url('iconfont.woff?t=1725847033097') format('woff'),
|
||||
url('iconfont.ttf?t=1725847033097') format('truetype'),
|
||||
url('iconfont.svg?t=1725847033097#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
@ -14,6 +14,154 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-aijiqiren:before {
|
||||
content: "\e73c";
|
||||
}
|
||||
|
||||
.icon-saoyisao:before {
|
||||
content: "\e691";
|
||||
}
|
||||
|
||||
.icon-jiaoxuezhiliangfenxi:before {
|
||||
content: "\e690";
|
||||
}
|
||||
|
||||
.icon-jiaoxuejihua:before {
|
||||
content: "\e7e9";
|
||||
}
|
||||
|
||||
.icon-tongji:before {
|
||||
content: "\e68f";
|
||||
}
|
||||
|
||||
.icon-pigai:before {
|
||||
content: "\e68d";
|
||||
}
|
||||
|
||||
.icon-jiaoxuefansi:before {
|
||||
content: "\e6b2";
|
||||
}
|
||||
|
||||
.icon-kaoshi:before {
|
||||
content: "\e68a";
|
||||
}
|
||||
|
||||
.icon-yiwen:before {
|
||||
content: "\e687";
|
||||
}
|
||||
|
||||
.icon-yiwen-01:before {
|
||||
content: "\e688";
|
||||
}
|
||||
|
||||
.icon-yihuo:before {
|
||||
content: "\e689";
|
||||
}
|
||||
|
||||
.icon-a-yiwen:before {
|
||||
content: "\e6b1";
|
||||
}
|
||||
|
||||
.icon-zan:before {
|
||||
content: "\e658";
|
||||
}
|
||||
|
||||
.icon-zan1:before {
|
||||
content: "\e659";
|
||||
}
|
||||
|
||||
.icon-zan2:before {
|
||||
content: "\e65a";
|
||||
}
|
||||
|
||||
.icon-zan3:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.icon-zan4:before {
|
||||
content: "\e67c";
|
||||
}
|
||||
|
||||
.icon-yizan:before {
|
||||
content: "\e67e";
|
||||
}
|
||||
|
||||
.icon-zan5:before {
|
||||
content: "\e67f";
|
||||
}
|
||||
|
||||
.icon-zan-yizan:before {
|
||||
content: "\e680";
|
||||
}
|
||||
|
||||
.icon-zan6:before {
|
||||
content: "\e681";
|
||||
}
|
||||
|
||||
.icon-MBEfenggeduosetubiao-xihuan:before {
|
||||
content: "\e682";
|
||||
}
|
||||
|
||||
.icon-zan7:before {
|
||||
content: "\e683";
|
||||
}
|
||||
|
||||
.icon-zan11:before {
|
||||
content: "\e6ff";
|
||||
}
|
||||
|
||||
.icon-zan8:before {
|
||||
content: "\e684";
|
||||
}
|
||||
|
||||
.icon-dianzan-red:before {
|
||||
content: "\e685";
|
||||
}
|
||||
|
||||
.icon-zan9:before {
|
||||
content: "\e69e";
|
||||
}
|
||||
|
||||
.icon-zanping:before {
|
||||
content: "\100ae";
|
||||
}
|
||||
|
||||
.icon-zan10:before {
|
||||
content: "\e686";
|
||||
}
|
||||
|
||||
.icon-arrangement:before {
|
||||
content: "\e656";
|
||||
}
|
||||
|
||||
.icon-zanwushuju:before {
|
||||
content: "\e655";
|
||||
}
|
||||
|
||||
.icon-xiangzuo:before {
|
||||
content: "\e64d";
|
||||
}
|
||||
|
||||
.icon-kechengziyuan1:before {
|
||||
content: "\e647";
|
||||
}
|
||||
|
||||
.icon-tubiaozhizuomobanyihuifu-:before {
|
||||
content: "\e69d";
|
||||
}
|
||||
|
||||
.icon-window-01:before {
|
||||
content: "\e70b";
|
||||
}
|
||||
|
||||
.icon-chuangkou-chuangkouhua:before {
|
||||
content: "\e64b";
|
||||
}
|
||||
|
||||
.icon-a-lujing13357:before {
|
||||
content: "\e64c";
|
||||
}
|
||||
|
||||
.icon-icon:before {
|
||||
content: "\e640";
|
||||
}
|
||||
|
@ -466,6 +614,10 @@
|
|||
content: "\e604";
|
||||
}
|
||||
|
||||
.icon-zuoye2:before {
|
||||
content: "\f48d";
|
||||
}
|
||||
|
||||
.icon-zuoye1:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
|
|
@ -5,6 +5,265 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "34666608",
|
||||
"name": "ai机器人",
|
||||
"font_class": "aijiqiren",
|
||||
"unicode": "e73c",
|
||||
"unicode_decimal": 59196
|
||||
},
|
||||
{
|
||||
"icon_id": "12657402",
|
||||
"name": "资源库",
|
||||
"font_class": "saoyisao",
|
||||
"unicode": "e691",
|
||||
"unicode_decimal": 59025
|
||||
},
|
||||
{
|
||||
"icon_id": "6513175",
|
||||
"name": "教学质量分析",
|
||||
"font_class": "jiaoxuezhiliangfenxi",
|
||||
"unicode": "e690",
|
||||
"unicode_decimal": 59024
|
||||
},
|
||||
{
|
||||
"icon_id": "38447338",
|
||||
"name": "教学计划",
|
||||
"font_class": "jiaoxuejihua",
|
||||
"unicode": "e7e9",
|
||||
"unicode_decimal": 59369
|
||||
},
|
||||
{
|
||||
"icon_id": "8455509",
|
||||
"name": "统计",
|
||||
"font_class": "tongji",
|
||||
"unicode": "e68f",
|
||||
"unicode_decimal": 59023
|
||||
},
|
||||
{
|
||||
"icon_id": "5969226",
|
||||
"name": "批改",
|
||||
"font_class": "pigai",
|
||||
"unicode": "e68d",
|
||||
"unicode_decimal": 59021
|
||||
},
|
||||
{
|
||||
"icon_id": "36295514",
|
||||
"name": "教学反思",
|
||||
"font_class": "jiaoxuefansi",
|
||||
"unicode": "e6b2",
|
||||
"unicode_decimal": 59058
|
||||
},
|
||||
{
|
||||
"icon_id": "21088705",
|
||||
"name": "考试",
|
||||
"font_class": "kaoshi",
|
||||
"unicode": "e68a",
|
||||
"unicode_decimal": 59018
|
||||
},
|
||||
{
|
||||
"icon_id": "20574719",
|
||||
"name": "疑问",
|
||||
"font_class": "yiwen",
|
||||
"unicode": "e687",
|
||||
"unicode_decimal": 59015
|
||||
},
|
||||
{
|
||||
"icon_id": "21052326",
|
||||
"name": "yiwen-01",
|
||||
"font_class": "yiwen-01",
|
||||
"unicode": "e688",
|
||||
"unicode_decimal": 59016
|
||||
},
|
||||
{
|
||||
"icon_id": "30456317",
|
||||
"name": "疑惑",
|
||||
"font_class": "yihuo",
|
||||
"unicode": "e689",
|
||||
"unicode_decimal": 59017
|
||||
},
|
||||
{
|
||||
"icon_id": "33439935",
|
||||
"name": "[疑问]",
|
||||
"font_class": "a-yiwen",
|
||||
"unicode": "e6b1",
|
||||
"unicode_decimal": 59057
|
||||
},
|
||||
{
|
||||
"icon_id": "1242129",
|
||||
"name": "赞",
|
||||
"font_class": "zan",
|
||||
"unicode": "e658",
|
||||
"unicode_decimal": 58968
|
||||
},
|
||||
{
|
||||
"icon_id": "1741390",
|
||||
"name": "赞",
|
||||
"font_class": "zan1",
|
||||
"unicode": "e659",
|
||||
"unicode_decimal": 58969
|
||||
},
|
||||
{
|
||||
"icon_id": "3159200",
|
||||
"name": "赞",
|
||||
"font_class": "zan2",
|
||||
"unicode": "e65a",
|
||||
"unicode_decimal": 58970
|
||||
},
|
||||
{
|
||||
"icon_id": "3402139",
|
||||
"name": "赞",
|
||||
"font_class": "zan3",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "4931286",
|
||||
"name": "赞 (1)",
|
||||
"font_class": "zan4",
|
||||
"unicode": "e67c",
|
||||
"unicode_decimal": 59004
|
||||
},
|
||||
{
|
||||
"icon_id": "4942300",
|
||||
"name": "已赞",
|
||||
"font_class": "yizan",
|
||||
"unicode": "e67e",
|
||||
"unicode_decimal": 59006
|
||||
},
|
||||
{
|
||||
"icon_id": "5806181",
|
||||
"name": "赞",
|
||||
"font_class": "zan5",
|
||||
"unicode": "e67f",
|
||||
"unicode_decimal": 59007
|
||||
},
|
||||
{
|
||||
"icon_id": "7172310",
|
||||
"name": "赞-已赞",
|
||||
"font_class": "zan-yizan",
|
||||
"unicode": "e680",
|
||||
"unicode_decimal": 59008
|
||||
},
|
||||
{
|
||||
"icon_id": "7293361",
|
||||
"name": "赞2",
|
||||
"font_class": "zan6",
|
||||
"unicode": "e681",
|
||||
"unicode_decimal": 59009
|
||||
},
|
||||
{
|
||||
"icon_id": "8705087",
|
||||
"name": "MBE风格多色图标-喜欢",
|
||||
"font_class": "MBEfenggeduosetubiao-xihuan",
|
||||
"unicode": "e682",
|
||||
"unicode_decimal": 59010
|
||||
},
|
||||
{
|
||||
"icon_id": "10024138",
|
||||
"name": "赞",
|
||||
"font_class": "zan7",
|
||||
"unicode": "e683",
|
||||
"unicode_decimal": 59011
|
||||
},
|
||||
{
|
||||
"icon_id": "11055391",
|
||||
"name": "赞",
|
||||
"font_class": "zan11",
|
||||
"unicode": "e6ff",
|
||||
"unicode_decimal": 59135
|
||||
},
|
||||
{
|
||||
"icon_id": "11086734",
|
||||
"name": "赞",
|
||||
"font_class": "zan8",
|
||||
"unicode": "e684",
|
||||
"unicode_decimal": 59012
|
||||
},
|
||||
{
|
||||
"icon_id": "23592614",
|
||||
"name": "点赞",
|
||||
"font_class": "dianzan-red",
|
||||
"unicode": "e685",
|
||||
"unicode_decimal": 59013
|
||||
},
|
||||
{
|
||||
"icon_id": "26327261",
|
||||
"name": "赞",
|
||||
"font_class": "zan9",
|
||||
"unicode": "e69e",
|
||||
"unicode_decimal": 59038
|
||||
},
|
||||
{
|
||||
"icon_id": "27804883",
|
||||
"name": "赞评",
|
||||
"font_class": "zanping",
|
||||
"unicode": "100ae",
|
||||
"unicode_decimal": 65710
|
||||
},
|
||||
{
|
||||
"icon_id": "29252894",
|
||||
"name": "赞",
|
||||
"font_class": "zan10",
|
||||
"unicode": "e686",
|
||||
"unicode_decimal": 59014
|
||||
},
|
||||
{
|
||||
"icon_id": "4978988",
|
||||
"name": "作业-布置作业",
|
||||
"font_class": "arrangement",
|
||||
"unicode": "e656",
|
||||
"unicode_decimal": 58966
|
||||
},
|
||||
{
|
||||
"icon_id": "9689424",
|
||||
"name": "暂无数据",
|
||||
"font_class": "zanwushuju",
|
||||
"unicode": "e655",
|
||||
"unicode_decimal": 58965
|
||||
},
|
||||
{
|
||||
"icon_id": "6176588",
|
||||
"name": "向左",
|
||||
"font_class": "xiangzuo",
|
||||
"unicode": "e64d",
|
||||
"unicode_decimal": 58957
|
||||
},
|
||||
{
|
||||
"icon_id": "3053509",
|
||||
"name": "课程资源",
|
||||
"font_class": "kechengziyuan1",
|
||||
"unicode": "e647",
|
||||
"unicode_decimal": 58951
|
||||
},
|
||||
{
|
||||
"icon_id": "4427402",
|
||||
"name": "窗口",
|
||||
"font_class": "tubiaozhizuomobanyihuifu-",
|
||||
"unicode": "e69d",
|
||||
"unicode_decimal": 59037
|
||||
},
|
||||
{
|
||||
"icon_id": "18418603",
|
||||
"name": "窗口",
|
||||
"font_class": "window-01",
|
||||
"unicode": "e70b",
|
||||
"unicode_decimal": 59147
|
||||
},
|
||||
{
|
||||
"icon_id": "30970631",
|
||||
"name": "窗口-窗口化",
|
||||
"font_class": "chuangkou-chuangkouhua",
|
||||
"unicode": "e64b",
|
||||
"unicode_decimal": 58955
|
||||
},
|
||||
{
|
||||
"icon_id": "41196567",
|
||||
"name": "下课",
|
||||
"font_class": "a-lujing13357",
|
||||
"unicode": "e64c",
|
||||
"unicode_decimal": 58956
|
||||
},
|
||||
{
|
||||
"icon_id": "680440",
|
||||
"name": "教材",
|
||||
|
@ -595,7 +854,7 @@
|
|||
},
|
||||
{
|
||||
"icon_id": "22779610",
|
||||
"name": "数译_教学管理",
|
||||
"name": "教学管理",
|
||||
"font_class": "shuyi_jiaoxueguanli",
|
||||
"unicode": "e678",
|
||||
"unicode_decimal": 59000
|
||||
|
@ -796,6 +1055,13 @@
|
|||
"unicode": "e604",
|
||||
"unicode_decimal": 58884
|
||||
},
|
||||
{
|
||||
"icon_id": "41372607",
|
||||
"name": "作业-2",
|
||||
"font_class": "zuoye2",
|
||||
"unicode": "f48d",
|
||||
"unicode_decimal": 62605
|
||||
},
|
||||
{
|
||||
"icon_id": "8023423",
|
||||
"name": "作业",
|
||||
|
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 354 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,99 @@
|
|||
body{font-family: "微软雅黑", Arial,"宋体"; color: #333;}
|
||||
a{ text-decoration: none; color: #2489f6;}
|
||||
dl, ul, ol, ul { list-style: none; padding: 0; margin: 0; }
|
||||
.wrapper{ width: 1200px; margin: 0 auto; }
|
||||
.ques-detail{}
|
||||
.ques-detail ul li{margin-bottom: 20px;border: 1px solid #dadada;background: #fff;border-radius: 10px;}
|
||||
.ques-detail ul li:last-child{ margin-bottom: 0; }
|
||||
|
||||
table.edittable{ border-collapse: collapse; text-align: center; margin: 2px; }
|
||||
table.edittable th, table.edittable td{ line-height: 30px; padding: 5px; white-space: normal; word-break: break-all; border: 1px solid #000; vertical-align: middle; }
|
||||
table.composition{ border-collapse: collapse; text-align: left; margin: 2px; width: 98%; }
|
||||
table.composition th, table.composition td{ line-height: 30px; white-space: normal; word-break: break-all; border-width: 0px; vertical-align: middle; }
|
||||
table.composition2{ border-collapse: collapse;width:auto }
|
||||
table.composition2 th, table.composition2 td{text-align:left;line-height:30px; white-space:normal;word-break:break-all;border:none;border-width: 0px;vertical-align: middle; }
|
||||
.MathJye{ border: 0 none; direction: ltr; line-height: normal; display: inline-block; float: none; font-family: 'Times New Roman','宋体'; font-size: 15px; font-style: normal; font-weight: normal; letter-spacing: 1px; line-height: normal; margin: 0; padding: 0; text-align: left; text-indent: 0; text-transform: none; white-space: nowrap; word-spacing: normal; word-wrap: normal; -webkit-text-size-adjust: none; }
|
||||
.MathJye div, .MathJye span{ border: 0 none; margin: 0; padding: 0; line-height: normal; text-align: left; height: auto; _height: auto; white-space: normal; }
|
||||
.MathJye table{ border-collapse: collapse; margin: 0; padding: 0; text-align: center; vertical-align: middle; line-height: normal; font-size: inherit; *font-size: 100%; _font-size: 100%; font-style: normal; font-weight: normal; border: 0; float: none; display: inline-block; *display: inline; zoom: 0; }
|
||||
.MathJye table td{ padding: 0; font-size: inherit; line-height: normal; white-space: nowrap; border: 0 none; width: auto; _height: auto; }
|
||||
.MathJye_mi{ font-style: italic; }
|
||||
.flipv{-ms-transform: scaleX(-1);-moz-transform: scaleX(-1);-webkit-transform: scaleX(-1);-o-transform: scaleX(-1);transform: scaleX(-1);filter: FlipH;}
|
||||
.fliph{-ms-transform: scaleY(-1);-moz-transform: scaleY(-1);-webkit-transform: scaleY(-1);-o-transform: scaleY(-1);transform: scaleY(-1);filter: FlipV;}
|
||||
.mathjye-bold{font-weight:800}
|
||||
.mathjye-del{text-decoration:line-through}
|
||||
.mathjye-underline{border-bottom:1px solid #000;padding-bottom:2px;}
|
||||
@-moz-document url-prefix() {.mathjye-underline{padding-bottom:0px;}}
|
||||
.mathjye-underpline{border-bottom:2px dotted #000; padding-bottom:3px;}
|
||||
@-moz-document url-prefix() {.mathjye-underpline {padding-bottom:1px;}}
|
||||
.mathjye-underpoint{background: url(http://img.jyeoo.net/images/formula/point.png) no-repeat center bottom; padding-bottom:4px;}
|
||||
.mathjye-underpoint2{border-bottom:2px dotted #000; padding-bottom:3px;}
|
||||
@-moz-document url-prefix() {.mathjye-underpoint{padding-bottom:1px;}}
|
||||
.mathjye-underwave{background: url(http://img.jyeoo.net/images/formula/wave.png) bottom repeat-x; padding-bottom:4px;}
|
||||
@-moz-document url-prefix() {.mathjye-underwave {padding-bottom:1px;}}
|
||||
.mathjye-alignleft{display:block;text-align:left;}
|
||||
.mathjye-aligncenter{display:block;text-align:center;}
|
||||
.mathjye-alignright{display:block;text-align:right;}
|
||||
|
||||
|
||||
/*试题*/
|
||||
.artpreview fieldset { padding-top: 10px; font-size: 14px; clear: both; overflow: hidden; zoom: 1; line-height: 24px; font-family: 'Times New Roman',宋体,sans-serif; position: relative; }
|
||||
.artpreview fieldset legend { padding: 5px 0; display: block; margin: 5px; background: #f1f1f1; color: #000; overflow: hidden; zoom: 1; }
|
||||
.queserror { border: 1px dotted #f00; padding: 2px; }
|
||||
fieldset.quesborder {display: block;padding: 0;line-height: 25px;letter-spacing: 1px;word-break: break-all;margin: 0;}
|
||||
fieldset.queserror { border: 1px solid #f00; font-size: 12px; padding: 2px; margin-bottom: 1px; }
|
||||
fieldset.quesborder td, fieldset.queserror td { line-height: 16px; }
|
||||
fieldset.quesborder em, fieldset.queserror em { font-style: normal; font-weight: bold; position: absolute; left: 20px; }
|
||||
fieldset.thiserror1 { border: 1px solid #f00; }
|
||||
fieldset.thiserror1 legend { border: 4px solid #f00; }
|
||||
fieldset.thiserror2 { border: 1px solid #ADCD3C; }
|
||||
fieldset.thiserror2 legend { border: 4px solid #ADCD3C; }
|
||||
fieldset.thisques { border: 1px solid blue; }
|
||||
fieldset.thison { border: 1px solid #A9C9E2; }
|
||||
fieldset.thison div.border { border: 1px solid #ADCD3C; background-color: #F2FDDB; }
|
||||
fieldset, img { border: 0 none; }
|
||||
table.thison { border: 1px solid #00F; }
|
||||
table.thiserr { border: 1px solid #F00; }
|
||||
fieldset.thisvip1 { border: 1px solid #00F; }
|
||||
fieldset.thisvip1 legend { border: 4px solid #00F; }
|
||||
fieldset.status17 { border: 1px solid #ff00ff; }
|
||||
fieldset.status17 legend { border: 4px solid #ff00ff; }
|
||||
.selectoption { vertical-align: middle; font-size: 14px; padding: 2px; }
|
||||
.selectoption:hover { color: #EA8511; }
|
||||
.selectoption label { padding: 4px; line-height: 24px; }
|
||||
fieldset.quesbordere { border: 2px dotted #f00; }
|
||||
.answer { border: 1px dotted #ffffff; }
|
||||
ol.answer li, ul.answer li { padding: 1px; font-size: 14px; }
|
||||
ol.answer li:hover { background: #f2f2f2; }
|
||||
.collapseContainerPanel { border: 0; }
|
||||
.collapsePanelHeader { height: 30px; font-weight: bold; padding: 6px 0 0 0; }
|
||||
.collapseHeaderContent { float: left; padding-left: 5px; }
|
||||
.collapseContent { margin: 0; padding: 0; border: 1px solid #ccc; border-top: 0; }
|
||||
.pt0 { padding: 2px 0 5px 0; font-size: 14px; font-family: "黑体",sans-serif; font-weight: 700; }
|
||||
.pt1 {overflow: hidden;zoom: 1;clear: both;line-height: 25px;font-size: 14px;padding: 20px;position: relative;word-break: break-word;}
|
||||
fieldset.quesborder .pt1 em { position: static; }
|
||||
.pt1 img { position: relative; }
|
||||
.pt2 {padding: 20px;padding-top: 0;}
|
||||
.pt3, .pt4, .pt5, .pt6, .pt7 { clear: both; zoom: 1; position: relative; padding: 0px 20px 20px 80px; }
|
||||
.pt8 a:link, .pt8 a:visited { margin-right: 10px; padding: 2px 5px; }
|
||||
.pt8 a:hover { background: #fc0; }
|
||||
.pt9 { padding: 20px; border: 0 none; color: #999999; font-size: 12px; }
|
||||
.fieldtip {height: 36px;line-height: 36px;background-color: #f4f4f4;border-top: 1px solid #dadada;padding: 0 20px;color: #666666;position: relative;font-size: 12px;border-radius: 0 0 10px 10px;}
|
||||
.newFieldtip .pt1, .newFieldtip .pt2, .newFieldtip .pt3, .newFieldtip .pt4, .newFieldtip .pt5, .newFieldtip .pt6, .newFieldtip .pt7, .newFieldtip .pt8, .newFieldtip.pt9, .newFieldtip + .fieldtip { padding: 0; }
|
||||
fieldset img { max-width: 100%; }
|
||||
|
||||
.fieldtip-left {float: left;}
|
||||
.fieldtip-left >* {margin-right: 20px;}
|
||||
.fieldtip-right { float: right; }
|
||||
.fieldtip-right>* { margin-left: 20px; display: inline-block; color: #666666; }
|
||||
.fieldtip .btn {display: inline-block;margin-bottom: 0;font-weight: normal;text-align: center;vertical-align: middle;-ms-touch-action: manipulation;touch-action: manipulation;cursor: pointer;background-image: none;border: 1px solid transparent;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;font-size: 14px;border-radius: 4px;color: #ffffff;background-color: #ff8a00;line-height: 18px;min-width: 28px;padding: 0 5px;}
|
||||
.fieldtip .btn:hover, .fieldtip .btn:active, .fieldtip .btn:active:hover, .fieldtip .btn:hover { color: #ffffff; background-color: #faad4a; }
|
||||
|
||||
/*填空题*/
|
||||
div.quizPutTag { display: inline-block; *display: inline; padding: 3px 10px 1px 10px; margin: 0 3px; font-size: 14px; min-width: 1em; min-height: 16px; line-height: 18px; height: auto; border-bottom: 1px solid #0033FF; text-decoration: none; zoom: 1; color: #127176; word-break: break-all; }
|
||||
div.quizPutTag:hover { color: #f60; }
|
||||
div.quizPutTag img { cursor: pointer; width: 200px; margin-left: 10px; }
|
||||
.sanwser { padding: 4px 10px; margin: 0px; border: 1px solid #ADCD3C; background-color: #F2FDDB; color: #000; display: none; }
|
||||
/*答案*/
|
||||
.selectoption label.s, div.s { border: 1px solid #91cbed; background-color: #deeeff; display: inline-block; }
|
||||
.selectoption label.s.sh, div.s.sh { margin: 1px; border: none; background: none; }
|
||||
del { text-decoration: none; color: #f00; font-style: normal; font-weight: normal; }
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div>
|
||||
{{ displayedText }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 50, // 默认每字符间隔100毫秒
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['loaded', 'onSuccess'])
|
||||
|
||||
const displayedText = ref('')
|
||||
|
||||
const startTyping = async () =>{
|
||||
if(!props.text || props.text.length == 0) return
|
||||
for (let i = 0; i < props.text.length; i++) {
|
||||
displayedText.value += props.text.charAt(i);
|
||||
if(displayedText.value.length == props.text.length -1){
|
||||
emit('onSuccess')
|
||||
}
|
||||
emit('loaded')
|
||||
await wait(props.speed);
|
||||
nextTick();
|
||||
}
|
||||
}
|
||||
|
||||
const wait = (ms) =>{
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
onMounted(() =>{
|
||||
startTyping()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,453 @@
|
|||
<template>
|
||||
<div class="page-ai-chart">
|
||||
<svg class="icon ai-icon" aria-hidden="true" @click="isOpen = true; isMax = false" v-if="!isOpen">
|
||||
<use xlink:href="#icon-aijiqiren"></use>
|
||||
</svg>
|
||||
<div v-else v-drag shadow="always" class="chart-card" :class="[isMax ? 'card-max' : '']">
|
||||
<div class="flex chart-header">
|
||||
<div class="header-name flex">
|
||||
<svg class="icon header-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-aijiqiren"></use>
|
||||
</svg>
|
||||
<span>教学助手</span>
|
||||
</div>
|
||||
<div class="header-tool">
|
||||
<i class="iconfont icon-window-max_line icon-tool" @click="isMax = !isMax"></i>
|
||||
<i class="iconfont icon-close icon-tool" @click="closeChart"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-body">
|
||||
<el-scrollbar ref="chatref">
|
||||
<div class="default-chart flex">
|
||||
<div>你好,{{ userStore.nickName }}</div>
|
||||
<div>我是AIx教学助手,我可以帮助你:</div>
|
||||
<div class="outer-ai flex">
|
||||
<ul class="ai-ul">
|
||||
<li class="flex ai-li" v-for="item in outerAi" :key="item.id" @click="onClick(item)"
|
||||
:class="item.disabled ? 'li-disabled' : ''">
|
||||
<el-image class="ai-img" :src="item.img" />
|
||||
<div class="ai-name flex">
|
||||
<span class="title">{{ item.title }}</span>
|
||||
<span>{{ item.secondTit }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!--对话-->
|
||||
<div class="chart-content" ref="innerRef">
|
||||
<div v-for="item in msgList" :key="item.timestamp">
|
||||
<div class="author-con" v-if="item.name == 'user'">
|
||||
<div class="author-msg">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="robot-msg">
|
||||
<Answer :text="item.content ? item.content : ''" @loaded="chartLoaded" @onSuccess="isFinally = true"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loaded" class="chart-loading">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="chart-input">
|
||||
<el-input v-model="msgVal" size="large" class="chart-ipt" @keyup.enter="sendMsg" @focus="isFocus = true" @blur="isFocus = false" />
|
||||
<i class="iconfont icon-tujing" :class="[isFocus ? 'icon-focus' : '']" @click="sendMsg"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import outLink from '@/utils/linkConfig'
|
||||
import Answer from './container/text.vue'
|
||||
import { createChart, sendChart } from '@/api/ai/index'
|
||||
|
||||
import vDrag from '@/views/tool/directive/drag'
|
||||
|
||||
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
const userStore = useUserStore().user
|
||||
|
||||
|
||||
/**
|
||||
* 大模型相关
|
||||
* conversation_id : 会话ID 用于对话
|
||||
* headers : 请求头
|
||||
* app_id :大模型 应用id
|
||||
* loaded : 回答状态
|
||||
* msgList : 消息列表
|
||||
* curAnswer : 当前回答的文字答案
|
||||
*/
|
||||
|
||||
let conversation_id = null
|
||||
const app_id = '712ff0df-ed6b-470f-bf87-8cfbaf757be5'
|
||||
const headers = {
|
||||
isToken: true,
|
||||
Authorization: 'Bearer bce-v3/ALTAK-VpMGiUjWehcHSPZGjUKwB/c97384c2c71a0d3b1d1060d7f9e2a4eac3343732',
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
}
|
||||
const loaded = ref(false)
|
||||
const msgList = ref([])
|
||||
const chatref = ref(null)
|
||||
|
||||
const innerRef = ref(null)
|
||||
const isFinally = ref(true)
|
||||
// 是否最大化、打开对话框
|
||||
const isMax = ref(false)
|
||||
const isOpen = ref(false)
|
||||
// 对话框框输入的值
|
||||
const msgVal = ref('')
|
||||
// 对话框是否聚焦
|
||||
const isFocus = ref(false)
|
||||
|
||||
const outerAi = [
|
||||
{
|
||||
id: 1,
|
||||
title: '生成图片',
|
||||
secondTit: '文生图大模型',
|
||||
img: new URL('../../../src/assets/images/ai-01.png', import.meta.url).href,
|
||||
path: '/ais/aisd3'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '教学大模型',
|
||||
secondTit: '中小学基础教学大模型',
|
||||
img: new URL('../../../src/assets/images/ai-02.png', import.meta.url).href,
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '育人大模型',
|
||||
secondTit: '全场域育人大模型',
|
||||
img: new URL('../../../src/assets/images/ai-03.png', import.meta.url).href,
|
||||
path: '/ais/aimodel'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'ChatTTS',
|
||||
secondTit: '文字转语音大模型',
|
||||
img: new URL('../../../src/assets/images/ai-04.png', import.meta.url).href,
|
||||
path: '/ais/aiChatTTS'
|
||||
}
|
||||
]
|
||||
|
||||
// 打开web端相关内容
|
||||
const onClick = ({ path, disabled }) => {
|
||||
if (disabled) return
|
||||
let configObj = outLink().getBaseData()
|
||||
let fullPath = configObj.fullPath + path
|
||||
fullPath = fullPath.replaceAll('//', '/')
|
||||
// 通知主进程
|
||||
ipcRenderer.send('openWindow', {
|
||||
key: path,
|
||||
fullPath: fullPath,
|
||||
cookieData: { ...configObj.data }
|
||||
})
|
||||
}
|
||||
|
||||
// 创建对话
|
||||
const getChartId = () =>{
|
||||
const data = { app_id }
|
||||
createChart({ data, headers }).then( res =>{
|
||||
if( res.code == 200){
|
||||
localStorage.setItem("conversation_id", res.data.conversation_id);
|
||||
conversation_id = res.data.conversation_id;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 大模型对话
|
||||
const sendMsg = () =>{
|
||||
//发送的内容不能为空
|
||||
if(msgVal.value == '') return
|
||||
|
||||
if(!isFinally.value) return
|
||||
isFinally.value = false
|
||||
let msg = msgVal.value
|
||||
msgVal.value = ''
|
||||
msgList.value.push({ name: 'user', timestamp: + new Date(), content: msg})
|
||||
chatref.value.setScrollTop(innerRef.value.clientHeight)
|
||||
loaded.value = true
|
||||
|
||||
const data = {
|
||||
appId: app_id,
|
||||
content: msg,
|
||||
stream: false,
|
||||
conversationId: conversation_id,
|
||||
}
|
||||
sendChart({ data, headers }).then(res =>{
|
||||
loaded.value = false
|
||||
msgList.value.push({name: 'robot', timestamp: + new Date(), content: res.data.answer})
|
||||
})
|
||||
}
|
||||
|
||||
const chartLoaded = () =>{
|
||||
chatref.value.setScrollTop(innerRef.value.clientHeight)
|
||||
}
|
||||
|
||||
const closeChart = () =>{
|
||||
loaded.value = false
|
||||
msgList.value = []
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
watch(isOpen, (newVal)=>{
|
||||
if(newVal){
|
||||
if (!conversation_id) {
|
||||
getChartId();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
conversation_id = localStorage.getItem("conversation_id");
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin flex-direction-c {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-ai-chart {
|
||||
|
||||
.ai-icon {
|
||||
position: fixed;
|
||||
right: 30px;
|
||||
bottom: 30px;
|
||||
font-size: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
background-color: #fff;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 300px;
|
||||
height: calc(100% - 110px);
|
||||
border-radius: 10px;
|
||||
z-index: 10;
|
||||
box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
|
||||
|
||||
.chart-body {
|
||||
padding: 10px 15px;
|
||||
height: calc(100% - 110px);
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
}
|
||||
|
||||
.card-max {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
-webkit-app-region: no-drag;
|
||||
.header-name {
|
||||
align-items: center;
|
||||
.header-icon {
|
||||
font-size: 28px;
|
||||
align-items: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-tool {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #8a8a8a;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.default-chart {
|
||||
background-color: #F2F2F2;
|
||||
font-size: 13px;
|
||||
border-radius: 5px;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.outer-ai {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
margin-top: 5px;
|
||||
|
||||
.ai-ul {
|
||||
@include flex-direction-c;
|
||||
|
||||
.ai-li {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 2px;
|
||||
|
||||
.ai-img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 10px
|
||||
}
|
||||
|
||||
.ai-name {
|
||||
@include flex-direction-c;
|
||||
align-items: flex-start;
|
||||
font-size: 13px;
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.li-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
font-size: 14px;
|
||||
margin-top: 20px;
|
||||
|
||||
.author-con {
|
||||
@include flex-direction-c;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.author-msg {
|
||||
display: inline-block;
|
||||
background-color: #E0DFFF;
|
||||
padding: 5px 8px;
|
||||
border-radius: 7px;
|
||||
|
||||
}
|
||||
|
||||
.robot-msg {
|
||||
background-color: #fff;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-input {
|
||||
position: absolute;
|
||||
width: 90%;
|
||||
left: 5%;
|
||||
bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 15px;
|
||||
|
||||
.icon-tujing {
|
||||
font-size: 26px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
color: #c0c0c2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-focus {
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.chart-ipt {
|
||||
:deep(.el-input__wrapper) {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.chart-loading,
|
||||
.chart-loading>div {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chart-loading {
|
||||
display: block;
|
||||
font-size: 0;
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.chart-loading.la-dark {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.chart-loading>div {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
background-color: currentColor;
|
||||
border: 0 solid currentColor;
|
||||
}
|
||||
|
||||
.chart-loading {
|
||||
width: 54px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-loading>div:nth-child(1) {
|
||||
animation-delay: -200ms;
|
||||
}
|
||||
|
||||
.chart-loading>div:nth-child(2) {
|
||||
animation-delay: -100ms;
|
||||
}
|
||||
|
||||
.chart-loading>div:nth-child(3) {
|
||||
animation-delay: 0ms;
|
||||
}
|
||||
|
||||
.chart-loading>div {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 100%;
|
||||
margin-right: 4px;
|
||||
animation: ball-pulse 1s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes ball-pulse {
|
||||
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
30% {
|
||||
opacity: 0.1;
|
||||
transform: scale(0.01);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -2,12 +2,12 @@
|
|||
<div class="book-wrap">
|
||||
<el-scrollbar height="100%">
|
||||
<div class="book-name flex" @click="dialogVisible = true">
|
||||
<span>{{ curBookName }}</span>
|
||||
<span>{{ curBook.data.itemtitle }}</span>
|
||||
<i class="iconfont icon-xiangyou"></i>
|
||||
</div>
|
||||
<div class="book-list" v-loading="treeLoading">
|
||||
<el-tree ref="refTree" :data="treeData" :props="defaultProps" node-key="id"
|
||||
:default-expanded-keys="defaultExpandedKeys" :current-node-key="currentNodeId" highlight-current
|
||||
<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>
|
||||
|
@ -28,9 +28,9 @@
|
|||
|
||||
<div class="textbook-container">
|
||||
<el-scrollbar height="450px">
|
||||
<div class="textbook-item flex" v-for="item in subjectList" :class="curBookId == item.id ? 'active-item' : ''"
|
||||
<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="BaseUrl + item.avartar" class="textbook-img" alt="">
|
||||
<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>
|
||||
|
@ -43,143 +43,86 @@
|
|||
|
||||
<script setup>
|
||||
import { onMounted, ref, nextTick, toRaw, reactive } from 'vue';
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { listEvaluation } from '@/api/subject'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { useGetSubject } from '@/hooks/useGetSubject'
|
||||
|
||||
const BaseUrl = 'https://prev.ysaix.com:7868/'
|
||||
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
// 定义要发送的emit事件
|
||||
const emit = defineEmits(['nodeClick', 'changeBook'])
|
||||
// store
|
||||
const userStore = useUserStore()
|
||||
const { edustage, edusubject, userId } = userStore.user
|
||||
//
|
||||
let useSubject = null
|
||||
const subjectList = ref([])
|
||||
const evaluationList = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
// 当前教材下面单元内容数据
|
||||
const treeData = ref([])
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
label: 'itemtitle',
|
||||
class: 'textbook-tree'
|
||||
}
|
||||
|
||||
const treeLoading = ref(false)
|
||||
//当前教材ID
|
||||
const curBookId = ref(-1)
|
||||
//当前教材名称
|
||||
const curBookName = ref('')
|
||||
//当前教材封面图
|
||||
const curBookImg = ref('')
|
||||
// 上册
|
||||
const volumeOne = ref([])
|
||||
// 下册
|
||||
const volumeTwo = ref([])
|
||||
// 当前选中的教材
|
||||
const curBook = reactive({
|
||||
data: {}
|
||||
})
|
||||
// 当前节点
|
||||
const currentNode = reactive({
|
||||
const curNode = reactive({
|
||||
data:{}
|
||||
})
|
||||
// 当前选中的节点ID
|
||||
const currentNodeId = ref(0)
|
||||
// 当前选中的节点名称
|
||||
const currentNodeName = ref('')
|
||||
const treeLoading = ref(false)
|
||||
// 默认展开的节点
|
||||
const defaultExpandedKeys = ref([])
|
||||
// tree
|
||||
const refTree = ref(null)
|
||||
|
||||
|
||||
//获取教材下面的单元内容
|
||||
const getSubjectContent = async () => {
|
||||
treeLoading.value = true
|
||||
const params = {
|
||||
edusubject,
|
||||
edustage,
|
||||
entpcourseedituserid: userId,
|
||||
pageSize: 500
|
||||
}
|
||||
|
||||
let data;
|
||||
if (localStorage.getItem('evaluationList')) {
|
||||
evaluationList.value = JSON.parse(localStorage.getItem('evaluationList'))
|
||||
data = evaluationList.value
|
||||
treeLoading.value = false
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const { rows } = await listEvaluation(params)
|
||||
localStorage.setItem('evaluationList', JSON.stringify(rows))
|
||||
evaluationList.value = rows
|
||||
data = rows
|
||||
} finally {
|
||||
treeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
//获取教材版本
|
||||
await getSubject()
|
||||
//上册
|
||||
volumeOne.value = data.filter(item => item.level == 1 && item.semester == '上册')
|
||||
//下册
|
||||
volumeTwo.value = data.filter(item => item.level == 1 && item.semester == '下册')
|
||||
getTreeData()
|
||||
}
|
||||
|
||||
//选择教材
|
||||
const changeBook = ({ id, itemtitle, avartar }) => {
|
||||
curBookId.value = id
|
||||
curBookName.value = itemtitle
|
||||
curBookImg.value = BaseUrl + avartar
|
||||
getTreeData()
|
||||
const changeBook = (data) => {
|
||||
curBook.data = data
|
||||
|
||||
localStorage.setItem('curBook', JSON.stringify(data))
|
||||
treeData.value = useSubject.getTreeData(data.id)
|
||||
|
||||
//切换教材后默认展开第一个并选中
|
||||
nextTick(() =>{
|
||||
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||
curNode.data = getLastLevelData(treeData.value)[0]
|
||||
|
||||
localStorage.setItem('defaultExpandedKeys', JSON.stringify(defaultExpandedKeys.value))
|
||||
localStorage.setItem('curNode',JSON.stringify(curNode.data))
|
||||
emitChangeBook()
|
||||
})
|
||||
// 延迟关闭 视觉上选中
|
||||
setTimeout(() => {
|
||||
dialogVisible.value = false
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const getTreeData = () => {
|
||||
//数据过滤
|
||||
let upData = transData(volumeOne.value)
|
||||
let downData = transData(volumeTwo.value)
|
||||
if(upData.length && downData.length){
|
||||
treeData.value = [...upData,...downData]
|
||||
}
|
||||
else if(upData.length || downData.length){
|
||||
treeData.value = upData.length ? upData : downData
|
||||
}
|
||||
else{
|
||||
treeData.value = []
|
||||
return
|
||||
}
|
||||
nextTick(() => {
|
||||
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||
currentNode.data = getLastLevelData(treeData.value)[0]
|
||||
currentNodeId.value = getLastLevelData(treeData.value)[0].id
|
||||
currentNodeName.value = getLastLevelData(treeData.value)[0].label
|
||||
emitChangeBook()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const emitChangeBook = () => {
|
||||
let curNode = {
|
||||
id: currentNodeId.value,
|
||||
label: currentNodeName.value,
|
||||
itemtitle: currentNode.data.itemtitle,
|
||||
edudegree: currentNode.data.edudegree,
|
||||
edustage: currentNode.data.edustage,
|
||||
edusubject: currentNode.data.edusubject,
|
||||
}
|
||||
let parentNode = findParentByChildId(treeData.value, currentNodeId.value)
|
||||
curNode.parentNode = toRaw(parentNode)
|
||||
|
||||
const emitChangeBook = async () => {
|
||||
let curData = cloneDeep(toRaw(curNode.data))
|
||||
let parentNode = findParentByChildId(treeData.value, curData.id)
|
||||
curData.parentNode = toRaw(parentNode)
|
||||
//怎加一个label 之前取的label
|
||||
curData.label = curData.itemtitle
|
||||
const data = {
|
||||
textBook: {
|
||||
curBookId: curBookId.value,
|
||||
curBookName: curBookName.value,
|
||||
curBookImg: curBookImg.value
|
||||
curBookId: curBook.data.id,
|
||||
curBookName: curBook.data.itemtitle,
|
||||
curBookImg: BaseUrl + curBook.data.avartar,
|
||||
curBookPath: curBook.data.fileurl
|
||||
},
|
||||
node: curNode
|
||||
node: curData
|
||||
}
|
||||
/**
|
||||
* 临时用 后续删除 unitId
|
||||
*/
|
||||
let levelFirstId = null
|
||||
let levelSecondId = null
|
||||
let bookeId = curBook.data.id
|
||||
if (curData.parentNode) {
|
||||
levelFirstId = curData.parentNode.id
|
||||
levelSecondId = curData.id
|
||||
} else {
|
||||
levelFirstId = curData.id
|
||||
levelSecondId = ''
|
||||
}
|
||||
localStorage.setItem('unitId', JSON.stringify({ levelFirstId, levelSecondId, bookeId}))
|
||||
emit('changeBook', data)
|
||||
}
|
||||
|
||||
|
@ -227,95 +170,95 @@ const findParentByChildId = (treeData, targetNodeId) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const transData = (data) => {
|
||||
let ary = []
|
||||
data.forEach(item => {
|
||||
let obj = {}
|
||||
if (item.rootid == curBookId.value) {
|
||||
obj.label = item.itemtitle
|
||||
obj.id = item.id
|
||||
obj.itemtitle = item.itemtitle
|
||||
obj.edudegree = item.edudegree
|
||||
obj.edustage = item.edustage
|
||||
obj.edusubject = item.edusubject
|
||||
let ary2 = []
|
||||
evaluationList.value.forEach(el => {
|
||||
let obj2 = {}
|
||||
if (item.id == el.parentid) {
|
||||
obj2 = {
|
||||
label: el.itemtitle,
|
||||
id: el.id,
|
||||
itemtitle : el.itemtitle,
|
||||
edudegree : el.edudegree,
|
||||
edustage : el.edustage,
|
||||
edusubject : el.edusubject,
|
||||
}
|
||||
ary2.push(obj2)
|
||||
}
|
||||
obj.children = ary2
|
||||
})
|
||||
ary.push(obj)
|
||||
}
|
||||
})
|
||||
return ary
|
||||
}
|
||||
|
||||
//获取教材
|
||||
const getSubject = async () => {
|
||||
if (localStorage.getItem('subjectList')) {
|
||||
subjectList.value = JSON.parse(localStorage.getItem('subjectList'))
|
||||
}
|
||||
else {
|
||||
const { rows } = await listEvaluation({ itemkey: "version", pageSize: 500 })
|
||||
subjectList.value = rows.filter(item => item.edustage == edustage && item.edusubject == edusubject && isHaveUnit(item.id))
|
||||
localStorage.setItem('subjectList', JSON.stringify(subjectList.value))
|
||||
}
|
||||
|
||||
// 默认第一个
|
||||
if(!subjectList.value.length) return
|
||||
curBookName.value = subjectList.value[0].itemtitle
|
||||
curBookId.value = subjectList.value[0].id
|
||||
curBookImg.value = BaseUrl + subjectList.value[0].avartar
|
||||
}
|
||||
|
||||
|
||||
const isHaveUnit = (id) => {
|
||||
return evaluationList.value.some(item => {
|
||||
return item.rootid == id
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const handleNodeClick = (data, node) => {
|
||||
/**
|
||||
* data : 当前节点数据
|
||||
* node : 当前节点对象 包含当前节点所有数据 parent属性 指向父节点Node对象
|
||||
*/
|
||||
|
||||
const nodeData = data;
|
||||
let nodeData = cloneDeep(toRaw(data));
|
||||
//怎加一个label 之前取的label
|
||||
nodeData.label = nodeData.itemtitle
|
||||
const parentNode = node.parent.data;
|
||||
|
||||
// parentNode 为数组 则点击的是一级节点
|
||||
if (Array.isArray(parentNode)) {
|
||||
//
|
||||
nodeData.parentNode = null
|
||||
}
|
||||
else {
|
||||
nodeData.parentNode = parentNode
|
||||
// 否则 点击的为二级节点 parentNode 为它的父级节点
|
||||
nodeData.parentNode = toRaw(parentNode)
|
||||
}
|
||||
|
||||
let curData = {
|
||||
textBook: {
|
||||
curBookId: curBookId.value,
|
||||
curBookName: curBookName.value,
|
||||
curBookImg: curBookImg.value
|
||||
curBookId: curBook.data.id,
|
||||
curBookName: curBook.data.itemtitle,
|
||||
curBookImg: BaseUrl + curBook.data.avartar,
|
||||
curBookPath: curBook.data.fileurl
|
||||
},
|
||||
node: toRaw(nodeData)
|
||||
}
|
||||
currentNode.data = curData
|
||||
emit('nodeClick', curData)
|
||||
}
|
||||
localStorage.setItem('defaultExpandedKeys', parentNode ? JSON.stringify([parentNode.id]) : JSON.stringify([data.id]))
|
||||
localStorage.setItem('curNode', JSON.stringify(nodeData))
|
||||
|
||||
/**
|
||||
* 临时用 后续删除 unitId
|
||||
*/
|
||||
let levelFirstId = null
|
||||
let levelSecondId = null
|
||||
let bookeId = curBook.data.id
|
||||
if (nodeData.parentNode) {
|
||||
levelFirstId = nodeData.parentNode.id
|
||||
levelSecondId = nodeData.id
|
||||
} else {
|
||||
levelFirstId = nodeData.id
|
||||
levelSecondId = ''
|
||||
}
|
||||
localStorage.setItem('unitId', JSON.stringify({ levelFirstId, levelSecondId, bookeId}))
|
||||
|
||||
emit('nodeClick', curData)
|
||||
|
||||
}
|
||||
onMounted( async () => {
|
||||
treeLoading.value = true
|
||||
|
||||
try{
|
||||
useSubject = await useGetSubject()
|
||||
subjectList.value = useSubject.subjectList
|
||||
|
||||
let book = localStorage.getItem('curBook')
|
||||
if(book){
|
||||
book = JSON.parse(book)
|
||||
curBook.data = book
|
||||
treeData.value = useSubject.getTreeData(book.id)
|
||||
}
|
||||
else{
|
||||
curBook.data = useSubject.subjectList[0]
|
||||
treeData.value = useSubject.treeData
|
||||
}
|
||||
// 设置展开并选中
|
||||
nextTick(() =>{
|
||||
// 取缓存
|
||||
let node = localStorage.getItem('curNode')
|
||||
if(node){
|
||||
curNode.data = JSON.parse(node)
|
||||
defaultExpandedKeys.value = JSON.parse(localStorage.getItem('defaultExpandedKeys'))
|
||||
}
|
||||
else{
|
||||
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||
curNode.data = getLastLevelData(treeData.value)[0]
|
||||
//缓存记录当前展开以及选中节点
|
||||
localStorage.setItem('defaultExpandedKeys', JSON.stringify(defaultExpandedKeys.value))
|
||||
localStorage.setItem('curNode', JSON.stringify(curNode.data))
|
||||
}
|
||||
emitChangeBook()
|
||||
})
|
||||
|
||||
} finally{
|
||||
treeLoading.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSubjectContent()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,293 @@
|
|||
<template>
|
||||
<div class="book-wrap">
|
||||
<el-scrollbar height="100%">
|
||||
<div class="book-name">
|
||||
<el-dropdown style="width: 100%;height: 100%">
|
||||
<div class="el-dropdown-link flex" style="width: 100%;height: 100%;justify-content: space-between;align-items: center">
|
||||
<span>{{titleName}}</span>
|
||||
<!-- <i class="iconfont icon-xiangyou"></i>-->
|
||||
</div>
|
||||
<!-- 学科学段选择-->
|
||||
<template #dropdown>
|
||||
<div style="width: 300px;padding: 20px">
|
||||
<ThirdIndex @getVertion="getVertion"></ThirdIndex>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div class="book-list" v-loading="treeLoading">
|
||||
<el-tree ref="refTree" :data="treeData" :props="defaultProps" node-key="id"
|
||||
:default-expanded-keys="defaultExpandedKeys" :current-node-key="node.currentNodeId" highlight-current
|
||||
@node-click="handleNodeClick">
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ThirdIndex from './third/index.vue'
|
||||
import {nextTick, reactive, ref,onMounted,watch} from 'vue'
|
||||
import {getBook, getTextbook} from '@/api/file/third'
|
||||
import useThirdStore from '@/store/modules/thirdTextbook'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const useThird = useThirdStore()
|
||||
const useStore = useUserStore()
|
||||
const emit = defineEmits(['nodeClick'])
|
||||
const titleName = ref('')
|
||||
//树结构的数据
|
||||
const treeData = ref([])
|
||||
// 默认展开的节点
|
||||
const defaultExpandedKeys = ref([])
|
||||
const treeLoading = ref(false)
|
||||
const node = reactive({
|
||||
// 当前节点
|
||||
currentNode:{
|
||||
data:{}
|
||||
},
|
||||
// 当前选中的节点ID
|
||||
currentNodeId:0,
|
||||
// 当前选中的节点名称
|
||||
currentNodeName:''
|
||||
})
|
||||
//获取bookid
|
||||
const bookId = ref(0)
|
||||
//获取第一步所有章节
|
||||
const getVertion = (data) => {
|
||||
const arr = [...data]
|
||||
treeData.value = arr.map(item => {
|
||||
return {
|
||||
level:0,
|
||||
id: item.versionId,
|
||||
name: item.versionName,
|
||||
childs: []
|
||||
}
|
||||
})
|
||||
//判断是否存在该教材,有则展示第一项
|
||||
if(treeData.value.length === 0) return
|
||||
nextTick(() => {
|
||||
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||
node.currentNode.data = treeData.value[0]
|
||||
node.currentNodeId = treeData.value[0].id
|
||||
node.currentNodeName = treeData.value[0].name
|
||||
treeLoading.value = false
|
||||
})
|
||||
}
|
||||
//初始化树形结构
|
||||
const getCurrent = (data) => {
|
||||
if(!data || data.length == 0) return
|
||||
nextTick(() => {
|
||||
defaultExpandedKeys.value = [data[0].id]
|
||||
node.currentNode.data = data[0]
|
||||
node.currentNodeId = data[0].id
|
||||
node.currentNodeName = data[0].name
|
||||
})
|
||||
}
|
||||
//获取教材
|
||||
const textbook = async (item) => {
|
||||
treeLoading.value = true
|
||||
const res = await getTextbook({versionId:item.id})
|
||||
if(res.code === 200){
|
||||
item.childs = res.data.map(items => {
|
||||
return {
|
||||
level:1,
|
||||
id: items.bookId,
|
||||
name: items.bookName,
|
||||
childs: []
|
||||
}
|
||||
})
|
||||
getCurrent(item.childs)
|
||||
treeLoading.value = false
|
||||
}
|
||||
}
|
||||
//获取年级
|
||||
const grade = async (item) => {
|
||||
treeLoading.value = true
|
||||
bookId.value = item.id
|
||||
const res = await getBook({bookId:item.id})
|
||||
if(res.code === 200){
|
||||
item.childs = res.data.map(items => {
|
||||
return {
|
||||
...items,
|
||||
}
|
||||
})
|
||||
getLastLevelData(item.childs)
|
||||
getCurrent(item.childs)
|
||||
treeLoading.value = false
|
||||
}
|
||||
}
|
||||
const getLastLevelData = (tree) => {
|
||||
let lastLevelData = [];
|
||||
// 递归函数遍历树形结构
|
||||
function traverseTree(nodes) {
|
||||
nodes.forEach((node) => {
|
||||
// 如果当前节点有子节点,继续遍历
|
||||
if (node.childs && node.childs.length > 0) {
|
||||
traverseTree(node.childs);
|
||||
} else {
|
||||
// 如果没有子节点,说明是最后一层的节点
|
||||
lastLevelData.push(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 调用递归函数开始遍历
|
||||
traverseTree(tree);
|
||||
|
||||
// 返回最后一层的数据
|
||||
return lastLevelData;
|
||||
}
|
||||
|
||||
//点击节点
|
||||
const handleNodeClick = (data,node) => {
|
||||
/**
|
||||
* data : 当前节点数据
|
||||
* node : 当前节点对象 包含当前节点所有数据 parent属性 指向父节点Node对象
|
||||
*/
|
||||
switch(node.data.level){
|
||||
case 0: textbook(data); break;
|
||||
case 1: grade(data); break;
|
||||
default: {
|
||||
getCurrent(data.childs);
|
||||
//获取资源列表
|
||||
emit('nodeClick',{
|
||||
chapterId:data.id,
|
||||
bookId:bookId.value,
|
||||
stage:data.stage,
|
||||
subjectId:data.subjectId,
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//缓存
|
||||
onMounted(() => {
|
||||
titleName.value = `${useStore.user.edustage}-${useStore.user.edusubject}`
|
||||
treeLoading.value = true
|
||||
//存在查看无课程的情况,两秒后关闭loading框
|
||||
setTimeout(() => {
|
||||
treeLoading.value = false
|
||||
},2000)
|
||||
})
|
||||
//监听数据变化
|
||||
watch(() => useThird,() => {
|
||||
titleName.value = `${useStore.user.edustage}-${useStore.user.edusubject}`
|
||||
},{deep:true})
|
||||
|
||||
const defaultProps = {
|
||||
children: 'childs',
|
||||
label: 'name',
|
||||
class: 'textbook-tree'
|
||||
}
|
||||
</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>
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<el-tabs v-model="active" class="demo-tabs" @tab-change="handleClick">
|
||||
<template v-for="(item,index) in gradeList" :key="index">
|
||||
<el-tab-pane :label="item.label" :name="item.value" disabled>
|
||||
<SelectSubject ref="selectSubject" :subjectList="subjectList" @clickTag="getTagId"></SelectSubject>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import { gradeList } from '@/utils/resourceDict'
|
||||
import SelectSubject from './selectSubject.vue'
|
||||
import {getSubjects,getTextbookVersion} from '@/api/file/third'
|
||||
import useThirdStore from '@/store/modules/thirdTextbook'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
const useThird = useThirdStore()
|
||||
const useStore = useUserStore()
|
||||
|
||||
const emit = defineEmits(['getVertion'])
|
||||
const active = ref(1)
|
||||
//用来获取所有学科
|
||||
const subjectList = ref([])
|
||||
//用来获取学科的id
|
||||
const textbookVersionId = ref(0)
|
||||
//不需要切换年级教材了
|
||||
const handleClick = (tab) => {
|
||||
console.log(tab,'tab')
|
||||
getSubject(tab)
|
||||
}
|
||||
//获取当前学段所有学科
|
||||
const getSubject = (value) => {
|
||||
const currentIndex = gradeList.findIndex(item => item.value === value)
|
||||
getSubjects({stage:value}).then(res => {
|
||||
if(res.code === 200){
|
||||
if(res.data.length === 0) return
|
||||
subjectList.value = res.data.map(item => {
|
||||
return {
|
||||
...item,
|
||||
gradeName:gradeList[currentIndex].label
|
||||
}
|
||||
})
|
||||
//判断是否有名称或者包括名称就对应哪个学科
|
||||
const nameIndex = subjectList.value.findIndex(item => item.subjectName === useStore.user.edusubject || item.subjectName.includes(useStore.user.edusubject))
|
||||
if(nameIndex === -1) return;
|
||||
getTagId({subjectId:subjectList.value[nameIndex].subjectId,subjectName:subjectList.value[nameIndex].subjectName})
|
||||
if(textbookVersionId.value === 0){
|
||||
getTagId(subjectList.value[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
//获取教材列表
|
||||
const getTagId = (item) => {
|
||||
textbookVersionId.value = item.subjectId
|
||||
const currentIndex = gradeList.findIndex(item => item.value === active.value)
|
||||
getTextbookVersion({stage:active.value,subjectId:textbookVersionId.value}).then(res => {
|
||||
if(res.code === 200){
|
||||
emit('getVertion',res.data)
|
||||
useThird.getSelectBookInfo({...item,activeGrade:active.value,gradeName:gradeList[currentIndex].label})
|
||||
}
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
const currentIndex = gradeList.findIndex(item => item.label === useStore.user.edustage)
|
||||
//首次渲染获取
|
||||
getSubject(gradeList[currentIndex].value)
|
||||
active.value = gradeList[currentIndex].value
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<template v-for="item in subjectList" :key="item.subjectId">
|
||||
<el-tag
|
||||
:type="useStore.user.edusubject === item.subjectName && useStore.user.edustage === item.gradeName ? 'primary' : 'info'"
|
||||
effect="dark"
|
||||
round
|
||||
class="tagItem"
|
||||
:style="useStore.user.edusubject === item.subjectName && useStore.user.edustage === item.gradeName ? {'cursor': 'pointer'} : {'cursor': 'no-Drop'}"
|
||||
>
|
||||
{{ item.subjectName }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup name="selectSubject">
|
||||
import useUserStore from "@/store/modules/user";
|
||||
const useStore = useUserStore()
|
||||
const props = defineProps({
|
||||
subjectList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['clickTag'])
|
||||
// const clickTag = (item) => {
|
||||
// emit('clickTag',item)
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tagItem{
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
|
@ -5,7 +5,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, defineProps, onMounted, defineEmits } from 'vue';
|
||||
import { ref, watch, onMounted, defineEmits } from 'vue';
|
||||
const placeholder = ref('')
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
<template>
|
||||
<!-- 表单-组件(自定义) -->
|
||||
<slot>
|
||||
<el-row v-bind="rows||{}">
|
||||
<!-- 其他内容-start -->
|
||||
<slot name="start"></slot>
|
||||
<el-form :model="form" ref="form" @submit.native.prevent
|
||||
v-bind="option||{}"
|
||||
:status-icon="option.isIcon" :inline-message="option.inlineMsg"
|
||||
:label-width="option.labelW" :label-position="option.labelP" :label-suffix="option.labelS">
|
||||
<!-- 内容区 -->
|
||||
<template v-for="(item, index) in itemFields">
|
||||
<slot :name="'cols_'+item.prop" :row="item" :index="index" :prop="item.prop">
|
||||
<el-col :span="colsFn(item,'span')" :offset="colsFn(item,'offset')" :push="colsFn(item,'push')" :pull="colsFn(item,'pull')"
|
||||
:xs="colsFn(item,'xs')" :sm="colsFn(item,'sm')" :md="colsFn(item,'md')" :lg="colsFn(item,'lg')" :xl="colsFn(item,'xl')"
|
||||
:tag="colsFn(item,'tag')" :key="index" :style="item.style">
|
||||
<!-- slot可自定义-元素 默认input -->
|
||||
<slot :name="item.prop" :row="item" :index="index" :prop="item.prop" :form="form">
|
||||
<el-form-item :label="item.label" :prop="item.prop" :label-width="item.labelW"
|
||||
:required="item.required" :rules="item.rules" :error="item.error"
|
||||
:show-message="item.showMsg" :inline-message="item.inlineMsg" :size="item.size">
|
||||
<!-- slot可自定义-item内部 单个 -->
|
||||
<slot :name="'item_'+item.prop" :row="item" :index="index" :value="form[item.prop]" :prop="item.prop" :form="form">
|
||||
<!-- slot可自定义-item内部 所有 -->
|
||||
<slot name="input" :row="item" :index="index" :value="form[item.prop]" :prop="item.prop" :form="form">
|
||||
<el-input v-model="form[item.prop]" v-bind="item.inputOpt||{}"
|
||||
@keyup.enter.native="handle(item, $event)" v-on="item.on||{}">
|
||||
<!-- slot自定义-输入框头部内容 单个 -->
|
||||
<template #prefix v-if="slotKeys.includes(`prefix_${item.prop}`)"><slot :name="'prefix_'+item.prop"></slot></template>
|
||||
<!-- slot自定义-输入框尾部内容 单个 -->
|
||||
<template #suffix v-if="slotKeys.includes(`suffix_${item.prop}`)"><slot :name="'suffix_'+item.prop"></slot></template>
|
||||
<!-- slot自定义-输入框前置内容 单个 -->
|
||||
<template #prepend v-if="slotKeys.includes(`prepend_${item.prop}`)"><slot :name="'prepend_'+item.prop"></slot></template>
|
||||
<!-- slot自定义-输入框后置内容 单个 -->
|
||||
<template #append v-if="slotKeys.includes(`append_${item.prop}`)"><slot :name="'append_'+item.prop"></slot></template>
|
||||
|
||||
<!-- slot自定义-输入框头部内容 所有 -->
|
||||
<template #prefix v-if="slotKeys.includes(`prefix`)"><slot :name="'prefix_'+item.prop"></slot></template>
|
||||
<!-- slot自定义-输入框尾部内容 所有 -->
|
||||
<template #suffix v-if="slotKeys.includes(`suffix`)"><slot :name="'suffix_'+item.prop"></slot></template>
|
||||
<!-- slot自定义-输入框前置内容 所有 -->
|
||||
<template #prepend v-if="slotKeys.includes(`prepend`)"><slot :name="'prepend_'+item.prop"></slot></template>
|
||||
<!-- slot自定义-输入框后置内容 所有 -->
|
||||
<template #append v-if="slotKeys.includes(`append`)"><slot :name="'append_'+item.prop"></slot></template>
|
||||
</el-input>
|
||||
</slot>
|
||||
</slot>
|
||||
<!-- slot自定义-标签文本的内容 单个 -->
|
||||
<template #label v-if="slotKeys.includes(`label_${item.prop}`)">
|
||||
<slot :name="'label_'+item.prop" :row="item" :index="index" :prop="item.prop"></slot>
|
||||
</template>
|
||||
<!-- slot自定义-标签文本的内容 所有 -->
|
||||
<template #label v-if="slotKeys.includes(`label`)">
|
||||
<slot name="label" :row="item" :index="index" :prop="item.prop"></slot>
|
||||
</template>
|
||||
|
||||
<!-- slot自定义-自定义表单校验信息的显示方式 单个 -->
|
||||
<template #error="{error}" v-if="slotKeys.includes(`error_${item.prop}`)">
|
||||
<slot :name="'error_'+item.prop" :row="item" :index="index" :prop="item.prop" :error="error"></slot>
|
||||
</template>
|
||||
<!-- slot自定义-自定义表单校验信息的显示方式 所有 -->
|
||||
<template #error="{error}" v-if="slotKeys.includes(`error`)">
|
||||
<slot name="error" :row="item" :index="index" :prop="item.prop" :error="error"></slot>
|
||||
</template>
|
||||
|
||||
</el-form-item>
|
||||
</slot>
|
||||
</el-col>
|
||||
</slot>
|
||||
</template>
|
||||
<!-- 其他内容 -->
|
||||
<slot name="add"></slot>
|
||||
<!-- 提交 -->
|
||||
<slot name="submit" :formRef="$refs.form">
|
||||
<div class="subBtn" v-if="btnDef" :style="btnStyle">
|
||||
<el-button type="primary" size="small" @click="confirm" :loading="btnLoad">{{btnText}}</el-button>
|
||||
</div>
|
||||
</slot>
|
||||
</el-form>
|
||||
<!-- 其他内容-end -->
|
||||
<slot name="end"></slot>
|
||||
</el-row>
|
||||
</slot>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'cForm',
|
||||
props: {
|
||||
form: { // 数据 form
|
||||
type: Object,
|
||||
default: _ => { return {} }
|
||||
},
|
||||
option: { // 配置
|
||||
type: Object,
|
||||
default: _ => {
|
||||
return { labelW: '80px' }
|
||||
}
|
||||
},
|
||||
itemOption: { // item配置
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
rows: { // 行 layout布局
|
||||
type: Object,
|
||||
default: _ => { return {} }
|
||||
},
|
||||
cols: { // 列 layout布局
|
||||
type: Object,
|
||||
default: _ => { return {} }
|
||||
},
|
||||
// onEnter: Function, // 默认回车
|
||||
onEnter: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
btnText: { // 文本
|
||||
type: String,
|
||||
default: '确 定'
|
||||
},
|
||||
btnDef: Boolean, // 默认提交-按钮
|
||||
btnStyle: String, // 按钮样式css
|
||||
btnLoad: Boolean, // 加载load
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
slotKeys: [] // 外部自定义插槽
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
itemFields() {
|
||||
return this.itemOption
|
||||
.filter(o => o.show !== false)
|
||||
.map(o => {
|
||||
for (const key in o) {
|
||||
const inputOpt = {placeholder:'请输入'}
|
||||
if (key.startsWith('i')) {
|
||||
const newKey = key.substring(1)
|
||||
inputOpt[newKey] = o[key]
|
||||
delete o[key]
|
||||
}
|
||||
o.inputOpt = inputOpt
|
||||
return o
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.test = this
|
||||
this.slotKeys = Object.keys(this.$slots)
|
||||
},
|
||||
methods: {
|
||||
// 字段--cols属性
|
||||
colsFn(row, attr) {
|
||||
const cols = row.cols || this.cols
|
||||
return cols[attr]
|
||||
},
|
||||
// input -- 回车事件
|
||||
handle(row, e) {
|
||||
const onEnter = row.onEnter || this.onEnter
|
||||
if (row.prop) onEnter(row, e)
|
||||
},
|
||||
// 确定--提交
|
||||
confirm() {
|
||||
const formRefs = this.$refs.form
|
||||
this.$emit('confirm', formRefs)
|
||||
},
|
||||
// 重置
|
||||
resetFields() {
|
||||
this.$refs.form.resetFields()
|
||||
},
|
||||
// 清除
|
||||
clearValidate(props) {
|
||||
this.$refs.form.clearValidate(props)
|
||||
},
|
||||
// 检验-字段
|
||||
validateField(props, callback) {
|
||||
this.$refs.form.validateField(props, callback)
|
||||
},
|
||||
// 整个表单验证
|
||||
validate(callback) {
|
||||
return this.$refs.form.validate(callback)
|
||||
}
|
||||
},
|
||||
// filters: {
|
||||
// // 是否清除input内容-按钮
|
||||
// clearable_f(val) {
|
||||
// return val == null ? true : val
|
||||
// }
|
||||
// }
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.subBtn{
|
||||
float: left;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.el-form {
|
||||
width: 100%;
|
||||
}
|
||||
.el-form--inline > .el-col {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
float: unset;
|
||||
}
|
||||
.el-form-item .el-input{
|
||||
vertical-align: bottom;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,64 @@
|
|||
|
||||
/**
|
||||
* 使用-批量导入--方式
|
||||
*/
|
||||
|
||||
// 导入 import.meta.glob 用于获取所有符合要求的 .vue 文件
|
||||
const files = import.meta.glob('./!(index).vue', { eager: true });
|
||||
|
||||
export default {
|
||||
install(Vue, options) {
|
||||
Object.entries(files).forEach(([path, file]) => {
|
||||
const fileName = path.split('/').pop().replace(/\.\w+$/, '')
|
||||
// fileName == 'cDialog' && initDialog(Vue, file) // 弹窗--组件化
|
||||
Vue.component(fileName, file.default)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗--函数化
|
||||
function initDialog(Vue, dialog) {
|
||||
// 全局绑定
|
||||
Vue.prototype.$cDialog = (props) => {
|
||||
// props.dialog = Object.assign({ isOpen: true }, props.dialog) // 默认配置 propsData: option
|
||||
props.dialog.isOpen == null && (props.dialog.isOpen = true) // 默认打开
|
||||
props.isRemove == null && (props.isRemove = true) // 默认关闭后移除
|
||||
const Constructor = Vue.extend(dialog)
|
||||
const Instance = new Constructor({ propsData: props })
|
||||
props.slots && (Instance.$slots = props.slots) // 插槽内容
|
||||
props.scopedSlots && (Instance.$scopedSlots = props.scopedSlots) // 作用域插槽内容
|
||||
props.content && (Instance.$slots.default = props.content) // 插槽内容
|
||||
document.body.appendChild(Instance.$mount().$el)
|
||||
return Instance.showBox().then(v => {
|
||||
props.callback && props.callback(v)
|
||||
return Promise.resolve(v)
|
||||
}).catch(v => {
|
||||
props.callback && props.callback(v)
|
||||
// 移除弹窗
|
||||
props.isRemove && document.body.removeChild(Instance.$mount().$el)
|
||||
return Promise.reject(v)
|
||||
})
|
||||
}
|
||||
// 全局绑定2
|
||||
Vue.prototype.$cDialog2 = (props) => {
|
||||
// props.dialog = Object.assign({ isOpen: true }, props.dialog) // 默认配置 propsData: option
|
||||
props.dialog.isOpen == null && (props.dialog.isOpen = true) // 默认打开
|
||||
props.isRemove == null && (props.isRemove = true) // 默认关闭后移除
|
||||
const Constructor = Vue.extend(dialog)
|
||||
const Instance = new Constructor({ propsData: props })
|
||||
props.slots && (Instance.$slots = props.slots) // 插槽内容
|
||||
props.scopedSlots && (Instance.$scopedSlots = props.scopedSlots) // 作用域插槽内容
|
||||
props.content && (Instance.$slots.default = props.content) // 插槽内容
|
||||
document.body.appendChild(Instance.$mount().$el)
|
||||
Instance.showBox().then(v => {
|
||||
props.callback && props.callback(v)
|
||||
return Promise.resolve(v)
|
||||
}).catch(v => {
|
||||
props.callback && props.callback(v)
|
||||
// 移除弹窗
|
||||
props.isRemove && document.body.removeChild(Instance.$mount().$el)
|
||||
return Promise.reject(v)
|
||||
})
|
||||
return Instance
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ const getFileTypeIcon = () => {
|
|||
gif: 'icon-gif',
|
||||
txt: 'icon-txt',
|
||||
rar: 'icon-rar',
|
||||
|
||||
apt: 'icon-lunwen'
|
||||
}
|
||||
if (iconObj[name]) {
|
||||
return '#' + iconObj[name]
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
<template>
|
||||
<el-drawer v-model="model" class="preview-drawer" title="title" :modal="true" :destroy-on-close="true" :with-header="false" :append-to-body="true"
|
||||
size="50%">
|
||||
<div class="flex drawer-header">
|
||||
<div>
|
||||
<div class="flex file-name">
|
||||
<FileImage :size="30" :file-name="'row.fileName'" />
|
||||
<span class="name">{{ row.fileShowName }}</span>
|
||||
</div>
|
||||
<div class="flex file-tag">
|
||||
<el-tag type="info" class="tag">{{ row.fileSuffix }}</el-tag>
|
||||
<el-tag type="info" class="tag">{{ row.fileFlag }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-close" @click="onClose"><i class="iconfont icon-guanbi"></i></div>
|
||||
</div>
|
||||
<div class="drawer-content">
|
||||
<!-- <iframe src="./aaa.pdf" width="600px" height="600px"></iframe> -->
|
||||
<!--<div ref="playerRef" class="video-box"></div> -->
|
||||
<video v-if="showPrev(row) === 'video'" :src="row.fileFullPath" controls autoplay></video>
|
||||
<el-image v-if="showPrev(row) === 'img'" style="width: 100%;" :src="row.fileFullPath" />
|
||||
<template v-if="showPrev(row) === 'office'">
|
||||
<template v-for="item in row.prevImgList">
|
||||
<el-image :key="item.targetFileId" v-if="item.targetFileType === '预览图'" style="width: 100%;" :src="item.targetFilePath" />
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import { watch, ref, nextTick, onMounted } from 'vue'
|
||||
import Player from 'xgplayer'
|
||||
import 'xgplayer/dist/index.min.css'
|
||||
import FileImage from '@/components/file-image/index.vue'
|
||||
|
||||
const model = defineModel()
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
default(){
|
||||
return {}
|
||||
}
|
||||
},
|
||||
})
|
||||
const playerRef = ref();
|
||||
const handleClose = () => {
|
||||
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
})
|
||||
// 关闭
|
||||
const onClose = () => {
|
||||
model.value = false
|
||||
}
|
||||
|
||||
const showPrev = (row)=>{
|
||||
if(row.fileSuffix === 'mp4' || row.fileSuffix === 'mp3'){
|
||||
return 'video'
|
||||
}else if(row.fileType.indexOf('image') !== -1){
|
||||
return 'img'
|
||||
}else if(row.fileSuffix === 'doc' || row.fileSuffix === 'docx'|| row.fileSuffix === 'ppt'|| row.fileSuffix === 'pptx'|| row.fileSuffix === 'xls'|| row.fileSuffix === 'xlsx'){
|
||||
return 'office'
|
||||
}else {
|
||||
return 'other'
|
||||
}
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
nextTick(() => {
|
||||
// 播放器 基础用法
|
||||
let player = new Player({
|
||||
el: playerRef.value,
|
||||
url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',
|
||||
width: '100%',
|
||||
autoplay: false,// 是否自动播放(默认false)
|
||||
videoAttributes: {// video扩展属性
|
||||
crossOrigin: 'anonymous',// 元素获取数据的 CORS 请求的配置
|
||||
},
|
||||
cssFullscreen: false,
|
||||
loop:true,// 是否开启循环播放(默认false)
|
||||
commonStyle:{
|
||||
progressColor: '#cccccce6',//进度条底色
|
||||
},
|
||||
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
watch(model, (newVal) => {
|
||||
if (newVal) {
|
||||
// init()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-drawer__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.preview-drawer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.drawer-header {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
|
||||
.file-name {
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.name {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-tag {
|
||||
.tag {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-close {
|
||||
cursor: pointer;
|
||||
|
||||
.icon-guanbi {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.video-box {
|
||||
width: 100%;
|
||||
height: 300px
|
||||
}
|
||||
</style>
|
|
@ -88,8 +88,6 @@ const curBookId = ref(-1)
|
|||
const curBookName = ref('')
|
||||
// 上册
|
||||
const volumeOne = ref([])
|
||||
// 下册
|
||||
const volumeTwo = ref([])
|
||||
// 当前节点
|
||||
const currentNode = reactive({
|
||||
data: {}
|
||||
|
@ -115,40 +113,38 @@ const getSubjectContent = async () => {
|
|||
const params = {
|
||||
edusubject,
|
||||
edustage,
|
||||
entpcourseedituserid: userId,
|
||||
pageSize: 500
|
||||
// entpcourseedituserid: userId,
|
||||
itemgroup: 'textbook',
|
||||
orderby: 'orderidx asc',
|
||||
pageSize: 10000
|
||||
}
|
||||
|
||||
let data;
|
||||
if (localStorage.getItem('evaluationList')) {
|
||||
evaluationList.value = JSON.parse(localStorage.getItem('evaluationList'))
|
||||
data = evaluationList.value
|
||||
}
|
||||
else {
|
||||
const { rows } = await listEvaluation(params)
|
||||
localStorage.setItem('evaluationList', JSON.stringify(rows))
|
||||
evaluationList.value = rows
|
||||
data = rows
|
||||
}
|
||||
const { rows } = await listEvaluation(params)
|
||||
localStorage.setItem('evaluationList', JSON.stringify(rows))
|
||||
evaluationList.value = rows
|
||||
data = rows
|
||||
|
||||
//获取教材版本
|
||||
getSubject()
|
||||
//上册
|
||||
volumeOne.value = data.filter(item => item.level == 1 && item.semester == '上册')
|
||||
//下册
|
||||
volumeTwo.value = data.filter(item => item.level == 1 && item.semester == '下册')
|
||||
/**
|
||||
* 不区分上下册
|
||||
* 2024/08/20调整
|
||||
*/
|
||||
// volumeOne.value = data.filter(item => item.level == 1)
|
||||
|
||||
getTreeData()
|
||||
}
|
||||
|
||||
const getSubject = async () => {
|
||||
subjectList.value = JSON.parse(localStorage.getItem('subjectList'))
|
||||
|
||||
|
||||
if (localStorage.getItem('subjectList')) {
|
||||
subjectList.value = JSON.parse(localStorage.getItem('subjectList'))
|
||||
}
|
||||
else {
|
||||
const { rows } = await listEvaluation({ itemkey: "version", pageSize: 500 })
|
||||
subjectList.value = rows.filter(item => item.edustage == edustage && item.edusubject == edusubject && isHaveUnit(item.id))
|
||||
const { rows } = await listEvaluation({ itemkey: "version", edusubject, edustage, pageSize: 10000, orderby: 'orderidx asc', })
|
||||
subjectList.value = rows
|
||||
localStorage.setItem('subjectList', JSON.stringify(subjectList.value))
|
||||
}
|
||||
|
||||
|
@ -166,14 +162,9 @@ const isHaveUnit = (id) => {
|
|||
|
||||
const getTreeData = () => {
|
||||
//数据过滤
|
||||
let upData = transData(volumeOne.value)
|
||||
let downData = transData(volumeTwo.value)
|
||||
|
||||
if(upData.length && downData.length){
|
||||
treeData.value = [...upData,...downData]
|
||||
}
|
||||
else if(upData.length || downData.length){
|
||||
treeData.value = upData.length ? upData : downData
|
||||
let upData = transData(evaluationList.value)
|
||||
if(upData.length){
|
||||
treeData.value = [...upData]
|
||||
}
|
||||
else{
|
||||
treeData.value = []
|
||||
|
@ -259,26 +250,35 @@ const handleNodeClick = (data, node) => {
|
|||
|
||||
const transData = (data) => {
|
||||
let ary = []
|
||||
|
||||
data.forEach(item => {
|
||||
let obj = {}
|
||||
|
||||
// 根据当前教材ID 过滤出对应的单元、章节
|
||||
if (item.rootid == curBookId.value) {
|
||||
obj.label = item.itemtitle
|
||||
obj.id = item.id
|
||||
let ary2 = []
|
||||
evaluationList.value.forEach(el => {
|
||||
let obj2 = {}
|
||||
if (item.id == el.parentid) {
|
||||
obj2 = {
|
||||
label: el.itemtitle,
|
||||
id: el.id
|
||||
if(item.level == 1){
|
||||
obj.label = item.itemtitle
|
||||
obj.id = item.id
|
||||
obj.itemtitle = item.itemtitle
|
||||
obj.edudegree = item.edudegree
|
||||
obj.edustage = item.edustage
|
||||
obj.edusubject = item.edusubject
|
||||
let ary2 = []
|
||||
evaluationList.value.forEach(el => {
|
||||
let obj2 = {}
|
||||
if (item.id == el.parentid) {
|
||||
obj2 = {
|
||||
label: el.itemtitle,
|
||||
id: el.id,
|
||||
itemtitle : el.itemtitle,
|
||||
edudegree : el.edudegree,
|
||||
edustage : el.edustage,
|
||||
edusubject : el.edusubject,
|
||||
}
|
||||
ary2.push(obj2)
|
||||
}
|
||||
ary2.push(obj2)
|
||||
}
|
||||
obj.children = ary2
|
||||
})
|
||||
ary.push(obj)
|
||||
obj.children = ary2
|
||||
})
|
||||
ary.push(obj)
|
||||
}
|
||||
}
|
||||
})
|
||||
return ary
|
||||
|
|
|
@ -0,0 +1,419 @@
|
|||
pdfAdnFabric<template>
|
||||
<div class="canvasitem">
|
||||
<div class="pdfAdnFabric" id="pdfAdnFabric" >
|
||||
<div :class="ispointer ? 'ispointer' : ''">
|
||||
<canvas ref="fabriccanvas" />
|
||||
</div>
|
||||
<div v-if="props.pdfObj.numberOfPdf === 2" :class="ispointer ? 'ispointer' : ''">
|
||||
<canvas ref="fabriccanvas1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup >
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
watch,
|
||||
reactive,
|
||||
defineProps,
|
||||
defineExpose,
|
||||
nextTick,
|
||||
defineEmits,watchEffect
|
||||
} from 'vue'
|
||||
// import { fabric } from 'fabric'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { handleevent, savecanvsStore, initcanvasdata, displayData } from '@/utils/pdfAndFabric'
|
||||
import { fabricVue, TYPES } from '@/plugins/fabric'
|
||||
import { updateSmartBookMarkContent, addsmartBookMark,getBookMarkById } from '@/api/eTextbook/index'
|
||||
import {useToolState} from '@/store/modules/tool'
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
const toolState = useToolState();
|
||||
const props = defineProps({
|
||||
pdfObj: {
|
||||
type: Object,
|
||||
default: {
|
||||
numberOfPdf: 2, //pdf数量 只有 1 2
|
||||
pdfUrl: null,
|
||||
numPages: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
const ispointer = ref(false) //是否画笔模式
|
||||
// canvas的所有数据
|
||||
const canvsStore = reactive({
|
||||
pageArr: []
|
||||
})
|
||||
const fabriccanvas = ref(null)
|
||||
const fabriccanvas1 = ref(null)
|
||||
|
||||
const canvasFabricVue = ref(null)
|
||||
const canvas1FabricVue = ref(null)
|
||||
// 页面总数
|
||||
const numPagesTotal = ref(0)
|
||||
const imgarr = ref([])
|
||||
// 多个pdf的对象
|
||||
const canvasNumbsValue = ref([])
|
||||
const emit = defineEmits(['update:numPagesTotal'])
|
||||
const renderPage = async (canvasobj) => {
|
||||
if (canvasobj.page > numPagesTotal.value) return
|
||||
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
||||
// 渲染当前页
|
||||
const page = await pdf.getPage(canvasobj.page)
|
||||
var screenWidth = window.innerWidth / 2 - 100
|
||||
var screenHeight = window.innerHeight
|
||||
const viewport = page.getViewport({ scale: 2 })
|
||||
|
||||
const canvasElement = canvasobj.canvas
|
||||
canvasElement.width = viewport.width
|
||||
canvasElement.height = viewport.height
|
||||
const renderContext = {
|
||||
canvasContext: canvasobj.context,
|
||||
viewport: viewport
|
||||
}
|
||||
// console.log(renderContext,22222222222222222222)
|
||||
// const textContent = await page.getTextContent();
|
||||
// console.log(textContent);
|
||||
// const annotations = await page.getAnnotations();
|
||||
// console.log(annotations);
|
||||
page.render(renderContext).promise.then((res) => {
|
||||
const img = document.createElement('img')
|
||||
img.src = canvasobj.canvas.toDataURL('image/png')
|
||||
canvasobj.canvas.remove()
|
||||
imgarr.value.push({ src: img.src, page: canvasobj.page, JSONdata: {}, index: canvasobj.index })
|
||||
img.onload = () => {
|
||||
// 在这里执行图像加载完成后的操作
|
||||
// 根据传过来的pdf对象 判断改渲染哪一个fabric
|
||||
if (props.pdfObj.numberOfPdf == 2) {
|
||||
if (canvasobj.index == 0) {
|
||||
canvasFabricVue.value.canvas.setWidth(screenWidth)
|
||||
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||
// updateCanvasBackgroundImage(canvasFabricVue,img)
|
||||
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||
} else {
|
||||
canvas1FabricVue.value.canvas.setWidth(screenWidth)
|
||||
canvas1FabricVue.value.canvas.setHeight(screenHeight)
|
||||
displayData(canvas1FabricVue, canvsStore, canvasobj, fabric, img)
|
||||
}
|
||||
} else {
|
||||
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||
}
|
||||
img.remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
// 保存数据
|
||||
const savaDataStore = () => {
|
||||
if(!toolState.isToolWin){
|
||||
toolState.isPdfWin=false
|
||||
toolState.showBoardAll=true //恢复默认值
|
||||
// ipcRenderer.invoke('tool-sphere:reset') //重置tool状态
|
||||
ipcRenderer.send('open-PDF:minimize')
|
||||
return
|
||||
}
|
||||
imgarr.value.forEach((a) => {
|
||||
if (a.index == 0) {
|
||||
a.JSONdata = canvasFabricVue.value.canvas.toJSON()
|
||||
} else {
|
||||
a.JSONdata = canvas1FabricVue.value.canvas.toJSON()
|
||||
}
|
||||
})
|
||||
const nameMap = new Map(canvsStore.pageArr.map((item) => [item.page, item.id]))
|
||||
// 创建一个用于存储所有异步操作的数组
|
||||
let promises = []
|
||||
imgarr.value.forEach((item) => {
|
||||
if (nameMap.has(item.page)) {
|
||||
const params = {
|
||||
id: nameMap.get(item.page),
|
||||
contentData: JSON.stringify(item.JSONdata.objects)
|
||||
}
|
||||
promises.push(updateSmartBookMarkContent([params]))
|
||||
} else {
|
||||
promises.push(addsmartBookMark({
|
||||
pageNum: item.page,
|
||||
contentData: JSON.stringify(item.JSONdata.objects),
|
||||
bookId: props.pdfObj.bookId,
|
||||
type: '教材',
|
||||
source: 'smarttalk'
|
||||
}))
|
||||
|
||||
}
|
||||
})
|
||||
Promise.all(promises).then(res=>{
|
||||
toolState.isPdfWin=false
|
||||
toolState.showBoardAll=true //恢复默认值
|
||||
// ipcRenderer.invoke('tool-sphere:reset') //重置tool状态
|
||||
ipcRenderer.send('open-PDF:minimize')
|
||||
})
|
||||
}
|
||||
const updatePage = (canvasobj) => {
|
||||
renderPage(canvasobj)
|
||||
}
|
||||
const loadPdf = async (canvasobj) => {
|
||||
updatePage(canvasobj)
|
||||
}
|
||||
|
||||
const initPdf = async (type = 'default') => {
|
||||
imgarr.value.forEach((a) => {
|
||||
if (a.index == 0) {
|
||||
a.JSONdata = canvasFabricVue.value.canvas.toJSON()
|
||||
} else {
|
||||
a.JSONdata = canvas1FabricVue.value.canvas.toJSON()
|
||||
}
|
||||
})
|
||||
// 判断是否翻页以及工具窗口是否打开 满足 翻页+打开工具才能保存数据
|
||||
if (type != 'default' && toolState.isToolWin) {
|
||||
const nameMap = new Map(canvsStore.pageArr.map((item) => [item.page, item.id]))
|
||||
// 创建一个用于存储所有异步操作的数组
|
||||
let promises = []
|
||||
imgarr.value.forEach((item) => {
|
||||
if (nameMap.has(item.page)) {
|
||||
const params = {
|
||||
id: nameMap.get(item.page),
|
||||
contentData: JSON.stringify(item.JSONdata.objects)
|
||||
}
|
||||
promises.push(updateSmartBookMarkContent([params]))
|
||||
} else {
|
||||
promises.push(addsmartBookMark({
|
||||
pageNum: item.page,
|
||||
contentData: JSON.stringify(item.JSONdata.objects),
|
||||
bookId: props.pdfObj.bookId,
|
||||
type: '教材',
|
||||
source: 'smarttalk'
|
||||
}))
|
||||
|
||||
}
|
||||
})
|
||||
Promise.all(promises).then(res=>{
|
||||
getBookMarkById(props.pdfObj.bookId).then(res=>{
|
||||
const pageArr=getUniqueArrayByLastOccurrence(res.data)
|
||||
canvsStore.pageArr=[]
|
||||
pageArr.forEach((a) => {
|
||||
canvsStore.pageArr.push({ page: a.pageNum, id: a.id, JSONdata: a.contentData })
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
// 保存数据
|
||||
// savecanvsStore(imgarr, canvsStore)
|
||||
|
||||
if (props.pdfObj.numberOfPdf == 1) {
|
||||
// imgarr.value[0]
|
||||
canvasFabricVue.value.history.clean()
|
||||
} else {
|
||||
canvasFabricVue.value.history.clean()
|
||||
canvas1FabricVue.value.history.clean()
|
||||
}
|
||||
// 清除现有 canvas 元素的内容
|
||||
canvasNumbsValue.value.forEach((canvasObj) => {
|
||||
const context = canvasObj.context
|
||||
context.clearRect(0, 0, canvasObj.canvas.width, canvasObj.canvas.height)
|
||||
})
|
||||
// 清除所有对象
|
||||
imgarr.value = []
|
||||
canvasNumbsValue.value = []
|
||||
if (props.pdfObj.pdfUrl) {
|
||||
await nextTick() // 确保 DOM 渲染完成
|
||||
if (props.pdfObj.numberOfPdf == 1) {
|
||||
canvasNumbsValue.value = [{}]
|
||||
const canvasElement = document.createElement('canvas')
|
||||
canvasNumbsValue.value[0].canvas = canvasElement
|
||||
canvasNumbsValue.value[0].context = canvasNumbsValue.value[0].canvas.getContext('2d')
|
||||
canvasNumbsValue.value[0].page = props.pdfObj.numPages
|
||||
canvasNumbsValue.value[0].index = 0
|
||||
await loadPdf(canvasNumbsValue.value[0])
|
||||
} else {
|
||||
for (var i = 0; i < props.pdfObj.numberOfPdf; i++) {
|
||||
canvasNumbsValue.value[i] = {}
|
||||
const canvasElement = document.createElement('canvas')
|
||||
canvasNumbsValue.value[i].canvas = canvasElement
|
||||
canvasNumbsValue.value[i].context = canvasNumbsValue.value[i].canvas.getContext('2d')
|
||||
// 设置页数
|
||||
if (i == 0) {
|
||||
canvasNumbsValue.value[i].page = props.pdfObj.numPages
|
||||
} else {
|
||||
canvasNumbsValue.value[i].page = props.pdfObj.numPages + 1
|
||||
}
|
||||
canvasNumbsValue.value[i].index = i
|
||||
// 加载FabricVue
|
||||
await loadPdf(canvasNumbsValue.value[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//根据page去重
|
||||
const getUniqueArrayByLastOccurrence=(array)=> {
|
||||
const uniqueItems = array.reduce((acc, current) => {
|
||||
// 使用 Map 来跟踪最后一个出现的 pageNum 和它的对象
|
||||
acc.set(current.pageNum, current);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
// 将 Map 的值转换回数组
|
||||
const resultArray = Array.from(uniqueItems.values());
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
const initPdfone = async () => {
|
||||
setTimeout(async () => {
|
||||
const option = { freeDrawingCursor: 'default' }
|
||||
const canvas2 = new fabricVue()
|
||||
await canvas2.initCanvas(fabriccanvas1.value, option)
|
||||
canvas2.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
canvas1FabricVue.value = canvas2
|
||||
await initPdf('addOnePage')
|
||||
}, 0)
|
||||
}
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 创建canvas转化成图片
|
||||
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
||||
numPagesTotal.value = pdf.numPages
|
||||
// 初始化fabriccanvas
|
||||
const option = { freeDrawingCursor: 'default' }
|
||||
const canvas1 = new fabricVue()
|
||||
// canvas1.boardConfig.mode= TYPES.ActionMode.OTHER
|
||||
// canvas1.boardConfig.mode= TYPES.ActionMode.ERASE
|
||||
await canvas1.initCanvas(fabriccanvas.value, option)
|
||||
canvas1.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
canvasFabricVue.value = canvas1
|
||||
const canvas2 = new fabricVue()
|
||||
await canvas2.initCanvas(fabriccanvas1.value, option)
|
||||
canvas2.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
// canvas2.canvas.isDrawingMode=false
|
||||
canvas1FabricVue.value = canvas2
|
||||
window.test = { canvas1, canvas2 }
|
||||
emit('update:numPagesTotal', pdf.numPages)
|
||||
|
||||
if (props.pdfObj.allPageData.length) {
|
||||
props.pdfObj.allPageData.forEach((a) => {
|
||||
if (a.pageNum == 1 || a.pageNum == 2) {
|
||||
canvsStore.pageArr.push({ page: a.pageNum, id: a.id, JSONdata: a.contentData })
|
||||
}
|
||||
})
|
||||
}
|
||||
await initPdf()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
ElMessage.error('pdf文件错误')
|
||||
}
|
||||
setToolStatus()
|
||||
// 监听2个canvas事件
|
||||
// handleevent(fabriccanvas.value, imgarr)
|
||||
// handleevent(fabriccanvas1.value, imgarr, 'two')
|
||||
})
|
||||
// zdg: 设置-底部工具栏-状态
|
||||
const setToolStatus = () => {
|
||||
toolState.showBoardAll = false
|
||||
}
|
||||
// 判断元素是否加载完成
|
||||
const handleMode = (vale,type)=>{
|
||||
if(vale=='select'){
|
||||
ispointer.value=true
|
||||
}else{
|
||||
ispointer.value=false
|
||||
}
|
||||
switch(vale) {
|
||||
case 'select': // 选择模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.OTHER)
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.OTHER)
|
||||
break
|
||||
case 'brush': // 画笔模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.DRAW)
|
||||
canvasFabricVue.value.canvas.freeDrawingCursor = 'default'
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.DRAW)
|
||||
canvas1FabricVue.value.canvas.freeDrawingCursor = 'default'
|
||||
break
|
||||
case 'erase': // 板擦模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.ERASE)
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.ERASE)
|
||||
break
|
||||
case 'clear': // 清空画布
|
||||
clearCanvas()
|
||||
// canvas1FabricVue.value.history?.clean()
|
||||
break
|
||||
}
|
||||
}
|
||||
// 清空canvas
|
||||
const clearCanvas=()=>{
|
||||
if(canvasFabricVue.value){
|
||||
const objects = canvasFabricVue.value.canvas.getObjects();
|
||||
objects.forEach((obj) => {
|
||||
// 检查对象是否是背景
|
||||
if (obj !== canvasFabricVue.value.canvas.backgroundImage) {
|
||||
// 删除背景之外的对象
|
||||
canvasFabricVue.value.canvas.remove(obj);
|
||||
}
|
||||
});
|
||||
|
||||
canvasFabricVue.value.canvas.renderAll();
|
||||
}
|
||||
if(canvas1FabricVue.value){
|
||||
const objects = canvas1FabricVue.value.canvas.getObjects();
|
||||
objects.forEach((obj) => {
|
||||
// 检查对象是否是背景
|
||||
if (obj !== canvas1FabricVue.value.canvas.backgroundImage) {
|
||||
// 删除背景之外的对象
|
||||
canvas1FabricVue.value.canvas.remove(obj);
|
||||
}
|
||||
});
|
||||
|
||||
canvas1FabricVue.value.canvas.renderAll();
|
||||
}
|
||||
}
|
||||
const watchToolState=()=>{
|
||||
if(toolState.showBoardAll){
|
||||
setTimeout(() => {
|
||||
toolState.showBoardAll=false
|
||||
}, 200);
|
||||
}
|
||||
// 加载工具
|
||||
handleMode(toolState.model)
|
||||
|
||||
}
|
||||
defineExpose({
|
||||
initPdf,
|
||||
initPdfone,
|
||||
savaDataStore
|
||||
})
|
||||
watchEffect(() => {
|
||||
setTimeout(() => {
|
||||
console.log(toolState,'监听')
|
||||
|
||||
}, 300)
|
||||
if(toolState.isPdfWin){
|
||||
// if(toolState.isToolWin){
|
||||
// ispointer.value=false
|
||||
// }else{
|
||||
// ispointer.value=true
|
||||
// }
|
||||
watchToolState() //监听工具栏
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.canvasitem {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
max-height: 100vh;
|
||||
}
|
||||
.pdfAdnFabric {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
align-items: center;
|
||||
:deep(> div:nth-of-type(1)) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.ispointer {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
|
@ -1,8 +1,12 @@
|
|||
<template>
|
||||
pdfAdnFabric<template>
|
||||
<div class="canvasitem">
|
||||
<div class="pdfAdnFabric" id="pdfAdnFabric">
|
||||
<canvas id="pdf-fabric"></canvas>
|
||||
<canvas id="pdf-fabric1" v-if="props.pdfObj.numberOfPdf == 2"></canvas>
|
||||
<div class="pdfAdnFabric" id="pdfAdnFabric" >
|
||||
<div :class="ispointer ? 'ispointer' : ''">
|
||||
<canvas ref="fabriccanvas" />
|
||||
</div>
|
||||
<div v-if="props.pdfObj.numberOfPdf === 2" :class="ispointer ? 'ispointer' : ''">
|
||||
<canvas ref="fabriccanvas1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -16,12 +20,17 @@ import {
|
|||
defineProps,
|
||||
defineExpose,
|
||||
nextTick,
|
||||
defineEmits
|
||||
defineEmits,watchEffect
|
||||
} from 'vue'
|
||||
import { fabric } from 'fabric'
|
||||
// import { fabric } from 'fabric'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { handleevent, savecanvsStore, initcanvasdata, displayData } from '@/utils/pdfAndFabric'
|
||||
import { fabricVue, TYPES } from '@/plugins/fabric'
|
||||
import { updateSmartBookMarkContent, addsmartBookMark,getBookMarkById } from '@/api/eTextbook/index'
|
||||
import {useToolState} from '@/store/modules/tool'
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
const toolState = useToolState();
|
||||
const props = defineProps({
|
||||
pdfObj: {
|
||||
type: Object,
|
||||
|
@ -32,13 +41,16 @@ const props = defineProps({
|
|||
}
|
||||
}
|
||||
})
|
||||
const ispointer = ref(false) //是否画笔模式
|
||||
// canvas的所有数据
|
||||
const canvsStore = reactive({
|
||||
id: 'xxxx',
|
||||
pageArr: []
|
||||
})
|
||||
const fabriccanvas = ref(null)
|
||||
const fabriccanvas1 = ref(null)
|
||||
|
||||
const canvasFabricVue = ref(null)
|
||||
const canvas1FabricVue = ref(null)
|
||||
// 页面总数
|
||||
const numPagesTotal = ref(0)
|
||||
const imgarr = ref([])
|
||||
|
@ -50,9 +62,9 @@ const renderPage = async (canvasobj) => {
|
|||
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
||||
// 渲染当前页
|
||||
const page = await pdf.getPage(canvasobj.page)
|
||||
var screenWidth = window.innerWidth/2-100;
|
||||
var screenHeight = window.innerHeight;
|
||||
const viewport = page.getViewport({ scale:2})
|
||||
var screenWidth = window.innerWidth / 2 - 100
|
||||
var screenHeight = window.innerHeight
|
||||
const viewport = page.getViewport({ scale: 2 })
|
||||
|
||||
const canvasElement = canvasobj.canvas
|
||||
canvasElement.width = viewport.width
|
||||
|
@ -71,35 +83,84 @@ const renderPage = async (canvasobj) => {
|
|||
// 根据传过来的pdf对象 判断改渲染哪一个fabric
|
||||
if (props.pdfObj.numberOfPdf == 2) {
|
||||
if (canvasobj.index == 0) {
|
||||
|
||||
fabriccanvas.value.setWidth(screenWidth)
|
||||
fabriccanvas.value.setHeight(screenHeight)
|
||||
displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
|
||||
canvasFabricVue.value.canvas.setWidth(screenWidth)
|
||||
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||
} else {
|
||||
fabriccanvas1.value.setWidth(screenWidth)
|
||||
fabriccanvas1.value.setHeight(screenHeight)
|
||||
displayData(fabriccanvas1, canvsStore, canvasobj, fabric, img)
|
||||
canvas1FabricVue.value.canvas.setWidth(screenWidth)
|
||||
canvas1FabricVue.value.canvas.setHeight(screenHeight)
|
||||
displayData(canvas1FabricVue, canvsStore, canvasobj, fabric, img)
|
||||
}
|
||||
} else {
|
||||
fabriccanvas.value.setWidth(screenWidth)
|
||||
fabriccanvas.value.setHeight(screenHeight)
|
||||
displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
|
||||
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||
}
|
||||
// console.log(imgarr.value)
|
||||
img.remove()
|
||||
}
|
||||
// 判断imgarr的JSONdata在canvsStore.pageArr有没有
|
||||
canvsStore.pageArr.forEach((item) => {
|
||||
if (item.page == canvasobj.page) {
|
||||
imgarr.value.forEach((img) => {
|
||||
if (img.page == canvasobj.page) {
|
||||
img.JSONdata = item.JSONdata
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
// 保存数据
|
||||
const savaDataStore = async (type) => {
|
||||
if(!toolState.isToolWin){
|
||||
toolState.isPdfWin = false
|
||||
await sleep(20) // 延时
|
||||
toolState.showBoardAll = true //恢复默认值
|
||||
await sleep(50) // 延时
|
||||
if(type=='rest'){
|
||||
// ipcRenderer.invoke('tool-sphere:reset') //重置tool状态-废弃
|
||||
ipcRenderer.send('open-PDF:close')
|
||||
}else{
|
||||
ipcRenderer.invoke('open-PDF:minimize')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
imgarr.value.forEach((a) => {
|
||||
if (a.index == 0) {
|
||||
a.JSONdata = canvasFabricVue.value.canvas.toJSON()
|
||||
} else {
|
||||
a.JSONdata = canvas1FabricVue.value.canvas.toJSON()
|
||||
}
|
||||
})
|
||||
const nameMap = new Map(canvsStore.pageArr.map((item) => [item.page, item.id]))
|
||||
// 创建一个用于存储所有异步操作的数组
|
||||
let promises = []
|
||||
imgarr.value.forEach((item) => {
|
||||
if (nameMap.has(item.page)) {
|
||||
const params = {
|
||||
id: nameMap.get(item.page),
|
||||
contentData: JSON.stringify(item.JSONdata.objects)
|
||||
}
|
||||
promises.push(updateSmartBookMarkContent([params]))
|
||||
} else {
|
||||
promises.push(addsmartBookMark({
|
||||
pageNum: item.page,
|
||||
contentData: JSON.stringify(item.JSONdata.objects),
|
||||
bookId: props.pdfObj.bookId,
|
||||
type: '教材',
|
||||
source: 'smarttalk'
|
||||
}))
|
||||
|
||||
}
|
||||
})
|
||||
Promise.all(promises).then(async res=>{
|
||||
toolState.isPdfWin=false
|
||||
await sleep(20) // 延时
|
||||
toolState.showBoardAll=true //恢复默认值
|
||||
await sleep(50) // 延时
|
||||
// ipcRenderer.send('open-PDF:minimize')
|
||||
if(type=='rest'){
|
||||
// ipcRenderer.invoke('tool-sphere:reset') //重置tool状态-废弃
|
||||
ipcRenderer.send('open-PDF:close')
|
||||
}else{
|
||||
ipcRenderer.invoke('open-PDF:minimize')
|
||||
}
|
||||
// ipcRenderer.send('open-PDF:close')
|
||||
|
||||
})
|
||||
}
|
||||
// 延时
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const updatePage = (canvasobj) => {
|
||||
renderPage(canvasobj)
|
||||
}
|
||||
|
@ -108,16 +169,52 @@ const loadPdf = async (canvasobj) => {
|
|||
}
|
||||
|
||||
const initPdf = async (type = 'default') => {
|
||||
imgarr.value.forEach((a) => {
|
||||
if (a.index == 0) {
|
||||
a.JSONdata = canvasFabricVue.value.canvas.toJSON()
|
||||
} else {
|
||||
a.JSONdata = canvas1FabricVue.value.canvas.toJSON()
|
||||
}
|
||||
})
|
||||
// 判断是否翻页以及工具窗口是否打开 满足 翻页+打开工具才能保存数据
|
||||
if (type != 'default' && toolState.isToolWin) {
|
||||
const nameMap = new Map(canvsStore.pageArr.map((item) => [item.page, item.id]))
|
||||
// 创建一个用于存储所有异步操作的数组
|
||||
let promises = []
|
||||
imgarr.value.forEach((item) => {
|
||||
if (nameMap.has(item.page)) {
|
||||
const params = {
|
||||
id: nameMap.get(item.page),
|
||||
contentData: JSON.stringify(item.JSONdata.objects)
|
||||
}
|
||||
promises.push(updateSmartBookMarkContent([params]))
|
||||
} else {
|
||||
promises.push(addsmartBookMark({
|
||||
pageNum: item.page,
|
||||
contentData: JSON.stringify(item.JSONdata.objects),
|
||||
bookId: props.pdfObj.bookId,
|
||||
type: '教材',
|
||||
source: 'smarttalk'
|
||||
}))
|
||||
|
||||
}
|
||||
})
|
||||
Promise.all(promises).then(res=>{
|
||||
getBookMarkById(props.pdfObj.bookId).then(res=>{
|
||||
const pageArr=getUniqueArrayByLastOccurrence(res.data)
|
||||
canvsStore.pageArr=[]
|
||||
pageArr.forEach((a) => {
|
||||
canvsStore.pageArr.push({ page: a.pageNum, id: a.id, JSONdata: a.contentData })
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
// 保存数据
|
||||
savecanvsStore(imgarr, canvsStore)
|
||||
// initcanvasdata(fabriccanvas)
|
||||
// initcanvasdata(fabriccanvas1)
|
||||
// 单页模式
|
||||
if (type == 'restone') {
|
||||
// 清除 canvas 上的所有对象
|
||||
fabriccanvas1.value.clear()
|
||||
// 释放 canvas 的资源
|
||||
fabriccanvas1.value.dispose()
|
||||
if (props.pdfObj.numberOfPdf == 1) {
|
||||
canvasFabricVue.value.history.clean()
|
||||
} else {
|
||||
canvasFabricVue.value.history.clean()
|
||||
canvas1FabricVue.value.history.clean()
|
||||
}
|
||||
// 清除现有 canvas 元素的内容
|
||||
canvasNumbsValue.value.forEach((canvasObj) => {
|
||||
|
@ -149,7 +246,6 @@ const initPdf = async (type = 'default') => {
|
|||
} else {
|
||||
canvasNumbsValue.value[i].page = props.pdfObj.numPages + 1
|
||||
}
|
||||
|
||||
canvasNumbsValue.value[i].index = i
|
||||
// 加载FabricVue
|
||||
await loadPdf(canvasNumbsValue.value[i])
|
||||
|
@ -157,47 +253,145 @@ const initPdf = async (type = 'default') => {
|
|||
}
|
||||
}
|
||||
}
|
||||
//根据page去重
|
||||
const getUniqueArrayByLastOccurrence=(array)=> {
|
||||
const uniqueItems = array.reduce((acc, current) => {
|
||||
// 使用 Map 来跟踪最后一个出现的 pageNum 和它的对象
|
||||
acc.set(current.pageNum, current);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
// 将 Map 的值转换回数组
|
||||
const resultArray = Array.from(uniqueItems.values());
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
const initPdfone = async () => {
|
||||
setTimeout(() => {
|
||||
fabriccanvas1.value = new fabric.Canvas('pdf-fabric1')
|
||||
fabriccanvas1.value.isDrawingMode = true
|
||||
fabriccanvas1.value.freeDrawingBrush.color = '#A33AFE'
|
||||
fabriccanvas1.value.freeDrawingCursor = 'default'
|
||||
fabriccanvas1.value.setWidth(595)
|
||||
handleevent(fabriccanvas1.value, imgarr, 'two')
|
||||
setTimeout(async () => {
|
||||
const option = { freeDrawingCursor: 'default' }
|
||||
const canvas2 = new fabricVue()
|
||||
await canvas2.initCanvas(fabriccanvas1.value, option)
|
||||
canvas2.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
canvas1FabricVue.value = canvas2
|
||||
await initPdf('addOnePage')
|
||||
}, 0)
|
||||
initPdf('addOnePage')
|
||||
}
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 创建canvas转化成图片
|
||||
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
||||
numPagesTotal.value = pdf.numPages
|
||||
// console.log(pdf)
|
||||
// 初始化fabriccanvas
|
||||
fabriccanvas.value = new fabric.Canvas('pdf-fabric')
|
||||
fabriccanvas.value.setWidth(595)
|
||||
fabriccanvas.value.isDrawingMode = true
|
||||
fabriccanvas.value.freeDrawingBrush.color = '#A33AFE'
|
||||
fabriccanvas.value.freeDrawingCursor = 'default'
|
||||
|
||||
fabriccanvas1.value = new fabric.Canvas('pdf-fabric1')
|
||||
fabriccanvas1.value.isDrawingMode = true
|
||||
fabriccanvas1.value.freeDrawingBrush.color = '#A33AFE'
|
||||
fabriccanvas1.value.freeDrawingCursor = 'default'
|
||||
fabriccanvas1.value.setWidth(595)
|
||||
const option = { freeDrawingCursor: 'default' }
|
||||
const canvas1 = new fabricVue()
|
||||
// canvas1.boardConfig.mode= TYPES.ActionMode.OTHER
|
||||
// canvas1.boardConfig.mode= TYPES.ActionMode.ERASE
|
||||
await canvas1.initCanvas(fabriccanvas.value, option)
|
||||
canvas1.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
canvasFabricVue.value = canvas1
|
||||
const canvas2 = new fabricVue()
|
||||
await canvas2.initCanvas(fabriccanvas1.value, option)
|
||||
canvas2.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
// canvas2.canvas.isDrawingMode=false
|
||||
canvas1FabricVue.value = canvas2
|
||||
window.test = { canvas1, canvas2 }
|
||||
emit('update:numPagesTotal', pdf.numPages)
|
||||
initPdf()
|
||||
|
||||
if (props.pdfObj.allPageData.length) {
|
||||
props.pdfObj.allPageData.forEach((a) => {
|
||||
if (a.pageNum == 1 || a.pageNum == 2) {
|
||||
canvsStore.pageArr.push({ page: a.pageNum, id: a.id, JSONdata: a.contentData })
|
||||
}
|
||||
})
|
||||
}
|
||||
await initPdf()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
ElMessage.error('pdf文件错误')
|
||||
}
|
||||
setToolStatus()
|
||||
// 监听2个canvas事件
|
||||
handleevent(fabriccanvas.value, imgarr)
|
||||
handleevent(fabriccanvas1.value, imgarr, 'two')
|
||||
// handleevent(fabriccanvas.value, imgarr)
|
||||
// handleevent(fabriccanvas1.value, imgarr, 'two')
|
||||
})
|
||||
// zdg: 设置-底部工具栏-状态
|
||||
const setToolStatus = () => {
|
||||
toolState.showBoardAll = false
|
||||
}
|
||||
// 判断元素是否加载完成
|
||||
const handleMode = (vale,type)=>{
|
||||
if(vale=='select'){
|
||||
ispointer.value=true
|
||||
}else{
|
||||
ispointer.value=false
|
||||
}
|
||||
switch(vale) {
|
||||
case 'select': // 选择模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.OTHER)
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.OTHER)
|
||||
break
|
||||
case 'brush': // 画笔模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.DRAW)
|
||||
canvasFabricVue.value.canvas.freeDrawingCursor = 'default'
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.DRAW)
|
||||
canvas1FabricVue.value.canvas.freeDrawingCursor = 'default'
|
||||
break
|
||||
case 'erase': // 板擦模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.ERASE)
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.ERASE)
|
||||
break
|
||||
case 'clear': // 清空画布
|
||||
clearCanvas()
|
||||
// canvas1FabricVue.value.history?.clean()
|
||||
break
|
||||
}
|
||||
}
|
||||
// 清空canvas
|
||||
const clearCanvas=()=>{
|
||||
if(canvasFabricVue.value){
|
||||
const objects = canvasFabricVue.value.canvas.getObjects();
|
||||
objects.forEach((obj) => {
|
||||
// 检查对象是否是背景
|
||||
if (obj !== canvasFabricVue.value.canvas.backgroundImage) {
|
||||
// 删除背景之外的对象
|
||||
canvasFabricVue.value.canvas.remove(obj);
|
||||
}
|
||||
});
|
||||
|
||||
canvasFabricVue.value.canvas.renderAll();
|
||||
}
|
||||
if(canvas1FabricVue.value){
|
||||
const objects = canvas1FabricVue.value.canvas.getObjects();
|
||||
objects.forEach((obj) => {
|
||||
// 检查对象是否是背景
|
||||
if (obj !== canvas1FabricVue.value.canvas.backgroundImage) {
|
||||
// 删除背景之外的对象
|
||||
canvas1FabricVue.value.canvas.remove(obj);
|
||||
}
|
||||
});
|
||||
|
||||
canvas1FabricVue.value.canvas.renderAll();
|
||||
}
|
||||
}
|
||||
const watchToolState=()=>{
|
||||
if(toolState.showBoardAll){
|
||||
setTimeout(() => {
|
||||
toolState.showBoardAll=false
|
||||
}, 200);
|
||||
}
|
||||
// 加载工具
|
||||
handleMode(toolState.model)
|
||||
|
||||
}
|
||||
defineExpose({
|
||||
initPdf,
|
||||
initPdfone
|
||||
initPdfone,
|
||||
savaDataStore
|
||||
})
|
||||
watchEffect(() => {
|
||||
if(toolState.isPdfWin){
|
||||
watchToolState() //监听工具栏
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -207,6 +401,8 @@ defineExpose({
|
|||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
max-height: 100vh;
|
||||
}
|
||||
.pdfAdnFabric {
|
||||
position: relative;
|
||||
|
@ -217,4 +413,7 @@ defineExpose({
|
|||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.ispointer {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,118 @@
|
|||
<script setup name="ReFilePreview">
|
||||
import "@vue-office/docx/lib/index.css";
|
||||
import "@vue-office/excel/lib/index.css";
|
||||
import { defineAsyncComponent, defineProps, onMounted } from "vue";
|
||||
// import type { FileProps } from "@/components/RefilePreview/types";
|
||||
import { useHooks } from "@/components/refile-preview/useReadFile";
|
||||
|
||||
|
||||
import VueOfficeDocx from '@vue-office/docx'
|
||||
import VueOfficeExcel from '@vue-office/excel'
|
||||
import VueOfficePdf from '@vue-office/pdf'
|
||||
|
||||
// const VueOfficeDocx = defineAsyncComponent(() => import("@vue-office/docx"));
|
||||
// const VueOfficeExcel = defineAsyncComponent(() => import("@vue-office/excel"));
|
||||
// const VueOfficePdf = defineAsyncComponent(() => import("@vue-office/pdf"));
|
||||
// const RePlayer = defineAsyncComponent(
|
||||
// () => import("@/components/RePlayer/index.vue")
|
||||
// );
|
||||
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
raw: () => new File([], ""),
|
||||
filePath: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
textContent:{
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
excelOptions,
|
||||
src,
|
||||
filePreviewRef,
|
||||
renderedHandler,
|
||||
errorHandler,
|
||||
renderTheFile,
|
||||
isImage,
|
||||
isVideo,
|
||||
isText,
|
||||
isAudio
|
||||
} = useHooks(props);
|
||||
|
||||
onMounted(() => {
|
||||
renderTheFile(); // 渲染文件
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
filePreviewRef
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="filePreviewRef" class="file-preview">
|
||||
<vue-office-docx
|
||||
v-if="props.fileType === 'docx' || props.fileType === 'doc'"
|
||||
:src="props.filePath"
|
||||
@rendered="renderedHandler"
|
||||
@error="errorHandler"
|
||||
/>
|
||||
<vue-office-excel
|
||||
v-if="props.fileType === 'xlsx' || props.fileType === 'xls'"
|
||||
:src="props.filePath"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:auto-resize="true"
|
||||
:enable-scrollbars="true"
|
||||
:options="excelOptions"
|
||||
@rendered="renderedHandler"
|
||||
@error="errorHandler"
|
||||
/>
|
||||
<vue-office-pdf
|
||||
v-if="props.fileType === 'pdf'"
|
||||
:src="props.filePath"
|
||||
@rendered="renderedHandler"
|
||||
@error="errorHandler"
|
||||
/>
|
||||
<el-image
|
||||
v-if="isImage(props.fileType)"
|
||||
:preview-teleported="true"
|
||||
fit="cover"
|
||||
class="w-[200px] align-left"
|
||||
:src="props.filePath"
|
||||
title="点击查看大图"
|
||||
:preview-src-list="[src]"
|
||||
/>
|
||||
<video v-if="isVideo(props.fileType)" :src="props.filePath" style="width: 400px; height: 400px;" controls/>
|
||||
<div v-if="isText(props.fileType)">
|
||||
<pre v-html="props.textContent" />
|
||||
</div>
|
||||
<audio v-if="isAudio(props.fileType)" :src="props.filePath" controls />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.file-preview {
|
||||
width: 90%;
|
||||
height: 60vh;
|
||||
overflow: auto;
|
||||
text-align: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,7 @@
|
|||
export interface FileProps {
|
||||
id?: number;
|
||||
type?: string;
|
||||
fileType?: string;
|
||||
raw?: File;
|
||||
filePath?: string;
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
import type { FileProps } from "@/components/refile-preview/types";
|
||||
import { onUnmounted, ref } from "vue";
|
||||
import axios from "axios";
|
||||
|
||||
export function useHooks(props: FileProps) {
|
||||
const excelOptions = {
|
||||
xls: props.fileType !== "xlsx", //预览xlsx文件设为false;预览xls文件设为true
|
||||
minColLength: 0, // excel最少渲染多少列,如果想实现xlsx文件内容有几列,就渲染几列,可以将此值设置为0.
|
||||
minRowLength: 0, // excel最少渲染多少行,如果想实现根据xlsx实际函数渲染,可以将此值设置为0.
|
||||
widthOffset: 10, //如果渲染出来的结果感觉单元格宽度不够,可以在默认渲染的列表宽度上再加 Npx宽
|
||||
heightOffset: 10, //在默认渲染的列表高度上再加 Npx高
|
||||
beforeTransformData: workbookData => {
|
||||
return workbookData;
|
||||
}, //底层通过exceljs获取excel文件内容,通过该钩子函数,可以对获取的excel文件内容进行修改,比如某个单元格的数据显示不正确,可以在此自行修改每个单元格的value值。
|
||||
transformData: workbookData => {
|
||||
return workbookData;
|
||||
} //将获取到的excel数据进行处理之后且渲染到页面之前,可通过transformData对即将渲染的数据及样式进行修改,此时每个单元格的text值就是即将渲染到页面上的内容
|
||||
};
|
||||
const src = ref();
|
||||
const filePreviewRef = ref();
|
||||
/**
|
||||
* 渲染完成
|
||||
*/
|
||||
const renderedHandler = () => {
|
||||
console.log("渲染完成");
|
||||
};
|
||||
/**
|
||||
* 渲染失败
|
||||
* @param e
|
||||
*/
|
||||
const errorHandler = e => {
|
||||
console.log("渲染失败", e);
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染文件
|
||||
*/
|
||||
async function renderTheFile() {
|
||||
if (props.type === "local") {
|
||||
console.log("本地文件" + props.fileType);
|
||||
isImage(props.fileType) && localImagePreview(props.raw);
|
||||
isDoc(props.fileType) && localOfficePreview(props.raw);
|
||||
isText(props.fileType) && localTextPreview(props.raw);
|
||||
isVideo(props.fileType) && localVideoPreview(props.raw);
|
||||
isAudio(props.fileType) && localAudioPreview(props.raw);
|
||||
} else {
|
||||
if (isVideo(props.fileType)) {
|
||||
src.value =
|
||||
import.meta.env.VITE_APP_BASE_URL +
|
||||
"/upload/attachments/getTeamOfVideo?id=" +
|
||||
props.id;
|
||||
return;
|
||||
}
|
||||
if (isText(props.fileType)) {
|
||||
const response = await axios.get(
|
||||
import.meta.env.VITE_STATIC_URL + props.filePath,
|
||||
{
|
||||
responseType: "text"
|
||||
}
|
||||
);
|
||||
src.value = response.data;
|
||||
return;
|
||||
}
|
||||
src.value = import.meta.env.VITE_STATIC_URL + props.filePath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验图片类型
|
||||
* @param type
|
||||
*/
|
||||
function isImage(type: string) {
|
||||
const types = [
|
||||
"jpg",
|
||||
"png",
|
||||
"gif",
|
||||
"jpeg",
|
||||
"bmp",
|
||||
"webp",
|
||||
"svg",
|
||||
"tiff",
|
||||
"tif",
|
||||
"jpeg",
|
||||
"jfif",
|
||||
"pjpeg",
|
||||
"pjp"
|
||||
];
|
||||
return types.includes(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验文档类型
|
||||
* @param type
|
||||
*/
|
||||
function isDoc(type: string) {
|
||||
const types = ["docx", "doc", "xlsx", "xls", "pdf"];
|
||||
return types.includes(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验文本类型
|
||||
* @param type
|
||||
*/
|
||||
function isText(type: string) {
|
||||
const types = [
|
||||
"txt",
|
||||
"md",
|
||||
"log",
|
||||
"json",
|
||||
"xml",
|
||||
"html",
|
||||
"css",
|
||||
"js",
|
||||
"java",
|
||||
"c",
|
||||
"cpp",
|
||||
"h",
|
||||
"hpp",
|
||||
"py",
|
||||
"rb",
|
||||
"go",
|
||||
"sh",
|
||||
"bat",
|
||||
"ps1",
|
||||
"psm1",
|
||||
"ps1xml",
|
||||
"psc1",
|
||||
"psd1",
|
||||
"psm1",
|
||||
"ps1xml",
|
||||
"psc1",
|
||||
"psd1",
|
||||
"ps1xml",
|
||||
"psc1",
|
||||
"ps1xml",
|
||||
"psc1",
|
||||
"psd1"
|
||||
];
|
||||
return types.includes(type);
|
||||
}
|
||||
|
||||
// 检验视频格式
|
||||
function isVideo(type: string) {
|
||||
const types = [
|
||||
"mp4",
|
||||
"avi",
|
||||
"rmvb",
|
||||
"mkv",
|
||||
"flv",
|
||||
"wmv",
|
||||
"mov",
|
||||
"webm",
|
||||
"m4v",
|
||||
"mpg",
|
||||
"mpeg",
|
||||
"3gp",
|
||||
"3g2",
|
||||
"vob",
|
||||
"ogv",
|
||||
"ogg",
|
||||
"mts",
|
||||
"m2ts",
|
||||
"ts",
|
||||
"m2v",
|
||||
"mpe",
|
||||
"mpv",
|
||||
"m4p",
|
||||
"m4v",
|
||||
"mpv2",
|
||||
"m4v",
|
||||
"m4p",
|
||||
"m4v",
|
||||
"m4p"
|
||||
];
|
||||
return types.includes(type);
|
||||
}
|
||||
|
||||
// 检验音频文件
|
||||
function isAudio(type: string) {
|
||||
const types = ["mp3", "wav", "ogg", "flac", "aac", "wma", "m4a", "wma"];
|
||||
return types.includes(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览本地Office文件
|
||||
* @param file
|
||||
*/
|
||||
function localOfficePreview(file: File) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onload = loadEvent => {
|
||||
const arrayBuffer = loadEvent.target.result;
|
||||
const blob = new Blob([arrayBuffer], {
|
||||
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
});
|
||||
src.value = URL.createObjectURL(blob);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览本地图片
|
||||
* @param file
|
||||
*/
|
||||
function localImagePreview(file: File) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
src.value = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览本地文本
|
||||
* @param file
|
||||
*/
|
||||
function localTextPreview(file: File) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
src.value = e.target.result;
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览本地视频
|
||||
* @param file
|
||||
*/
|
||||
function localVideoPreview(file: File) {
|
||||
src.value = URL.createObjectURL(file);
|
||||
}
|
||||
|
||||
function localAudioPreview(file: File) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const blob = new Blob([reader.result], { type: "audio/mpeg" });
|
||||
src.value = URL.createObjectURL(blob);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
// 清理工作:当组件卸载时释放URL对象
|
||||
onUnmounted(() => {
|
||||
if (src.value) {
|
||||
isVideo(props.fileType) && URL.revokeObjectURL(src.value);
|
||||
isAudio(props.fileType) && URL.revokeObjectURL(src.value);
|
||||
}
|
||||
});
|
||||
return {
|
||||
excelOptions,
|
||||
src,
|
||||
filePreviewRef,
|
||||
isImage,
|
||||
renderedHandler,
|
||||
errorHandler,
|
||||
renderTheFile,
|
||||
isVideo,
|
||||
isText,
|
||||
isAudio
|
||||
};
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="500"
|
||||
<el-dialog v-model="model" append-to-body :show-close="false" width="500"
|
||||
top="25vh"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
|
@ -34,12 +34,14 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { listEvaluation } from '@/api/subject'
|
||||
import { updateUserInfo } from '@/api/system/user'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { userId, userName } = userStore.user
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
||||
const props = defineProps({
|
||||
loginData: {
|
||||
|
@ -48,17 +50,11 @@ const props = defineProps({
|
|||
return {}
|
||||
}
|
||||
},
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
})
|
||||
|
||||
const model = defineModel();
|
||||
const subjectLoading = ref(false)
|
||||
|
||||
// 定义要发送的emit事件
|
||||
const emit = defineEmits(['update:modelValue', 'onSuccess'])
|
||||
|
||||
// 默认学段 前端写死 参照web AIx
|
||||
const gradeList = ref([
|
||||
{
|
||||
label: '高中',
|
||||
|
@ -85,7 +81,6 @@ gradeVal.value = gradeList.value[0].value
|
|||
//学科列表数据
|
||||
const subjectList = ref([])
|
||||
const allSubject = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
//切换年级
|
||||
const changeGrade = ()=>{
|
||||
|
@ -98,8 +93,8 @@ const changeGrade = ()=>{
|
|||
// 默认选中第一个学科
|
||||
subjectVal.value = subjectList.value[0].itemtitle
|
||||
}
|
||||
// 获取学科数据
|
||||
|
||||
// 获取学科数据
|
||||
const getSubject = async ()=>{
|
||||
const { rows } = await listEvaluation({ itemkey: "subject", pageSize: 500 })
|
||||
// 所有学科
|
||||
|
@ -119,7 +114,6 @@ const editUserInfo = async () =>{
|
|||
edustage: gradeVal.value,
|
||||
edusubject: subjectVal.value
|
||||
}
|
||||
|
||||
// 修改之后需要重新登录 查询用户信息,否则不登录 查询的用户信息是未修改之前的
|
||||
// 接口如此,我也不知道为啥要这样
|
||||
subjectLoading.value = true
|
||||
|
@ -128,14 +122,15 @@ const editUserInfo = async () =>{
|
|||
await updateUserInfo(data)
|
||||
await userStore.login(props.loginData)
|
||||
await userStore.getInfo()
|
||||
ElMessage.success('登录成功')
|
||||
model.value = false
|
||||
ipcRenderer && ipcRenderer.send('openMainWindow')
|
||||
} finally {
|
||||
subjectLoading.value = false
|
||||
}
|
||||
emit('onSuccess')
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
dialogVisible.value = newVal
|
||||
watch(() => model.value, (newVal) => {
|
||||
if(newVal){
|
||||
getSubject()
|
||||
}
|
||||
|
@ -166,5 +161,7 @@ watch(() => props.modelValue, (newVal) => {
|
|||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
|
||||
.el-select-dropdown__item{
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,311 @@
|
|||
<template>
|
||||
<el-dialog v-model="model" center top="10vh" width="600px" :show-close="false" destroy-on-close append-to-body
|
||||
style="border-radius: 10px; padding: 10px 15px">
|
||||
<template #header>
|
||||
<div class="homerwork-header flex">
|
||||
<span>{{ title }}</span>
|
||||
<i class="iconfont icon-guanbi" @click="cloneDialog(ruleFormRef)"></i>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="setLoading">
|
||||
<el-form :model="form" label-width="80px" ref="ruleFormRef" :rules="rules">
|
||||
<el-form-item label="班级" prop="grade">
|
||||
<el-scrollbar max-height="150px" style="width: 100%">
|
||||
<el-tree :props="defaultProps" :load="getLoad" node-key="id" highlight-current @check="handleCheckChange"
|
||||
lazy show-checkbox />
|
||||
</el-scrollbar>
|
||||
</el-form-item>
|
||||
<el-form-item label="选中学生" prop="student">
|
||||
<el-scrollbar max-height="130px">
|
||||
<el-tag v-for="(tag, index) in studentList" :key="tag.studentid" closable type="primary"
|
||||
@close="delStudent(index)">
|
||||
{{ tag.name }}
|
||||
</el-tag>
|
||||
</el-scrollbar>
|
||||
</el-form-item>
|
||||
<el-form-item label="完成要求" prop="feedback">
|
||||
<el-radio-group v-model="form.feedback">
|
||||
<el-radio value="必做" size="large">必做</el-radio>
|
||||
<el-radio value="选做" size="large">选做</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="截止时间" prop="deaddate">
|
||||
<el-date-picker v-model="form.deaddate" value-format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
|
||||
time-format="HH:mm" type="datetime" :clearable="false" placeholder="请选择截止时间" />
|
||||
</el-form-item>
|
||||
<el-form-item label="推荐用时" prop="timelength">
|
||||
<el-input-number v-model="form.timelength" :min="1" :max="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click.stop="cloneDialog(ruleFormRef)">取消</el-button>
|
||||
<el-button type="primary" @click.stop="onSubmit(ruleFormRef)"> 确定 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { listClassmain, listClassgroup } from '@/api/classManage/index'
|
||||
import { saveByClassWorkArray } from '@/api/teaching/classwork'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { getCurrentTime } from '@/utils/date'
|
||||
import { uniqBy, groupBy } from 'lodash'
|
||||
|
||||
const model = defineModel({ type: Boolean, default: false })
|
||||
const props = defineProps({
|
||||
entpcourseid: {
|
||||
default: ''
|
||||
},
|
||||
row: {
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '布置作业'
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['on-close', 'on-success'])
|
||||
const ruleFormRef = ref('')
|
||||
// 配置项
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
isLeaf: 'leaf'
|
||||
}
|
||||
|
||||
// loading
|
||||
const setLoading = ref(false)
|
||||
// 用户信息
|
||||
const userInfo = useUserStore().user
|
||||
// 班级列表
|
||||
const gradeList = ref([])
|
||||
// 选中的学生列表
|
||||
const studentList = ref([])
|
||||
// 表单
|
||||
const form = reactive({
|
||||
feedback: '必做',
|
||||
deaddate: '',
|
||||
timelength: 1
|
||||
})
|
||||
// 表单校验
|
||||
const validateGrade = (rule, value, callback) => {
|
||||
if (studentList.value.length == 0) {
|
||||
callback(new Error('请勾选班级或者学生'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validateStudent = (rule, value, callback) => {
|
||||
if (studentList.value.length == 0) {
|
||||
callback(new Error('学生不能为空'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const rules = reactive({
|
||||
grade: [{ validator: validateGrade, trigger: 'blur' }],
|
||||
student: [{ validator: validateStudent, trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 获取班级
|
||||
const getGradeList = async () => {
|
||||
let { rows } = await listClassmain({
|
||||
classuserid: userInfo.userId,
|
||||
pageSize: 100,
|
||||
status: 'open'
|
||||
})
|
||||
rows.forEach((item) => {
|
||||
item.label = item.caption
|
||||
item.level = 0
|
||||
item.leaf = false
|
||||
item.children = []
|
||||
item.classstudentlist = JSON.parse('[' + item.classstudentlist + ']')
|
||||
item.classstudentlist.forEach((el) => {
|
||||
el.classId = item.id
|
||||
})
|
||||
})
|
||||
return rows
|
||||
}
|
||||
// 获取节点下一级
|
||||
const getLoad = async (node, resolve) => {
|
||||
// 获取一级节点 班级
|
||||
if (node.level == 0) {
|
||||
gradeList.value = await getGradeList()
|
||||
resolve(gradeList.value)
|
||||
}
|
||||
// 获取二级节点 小组
|
||||
if (node.level == 1) {
|
||||
listClassgroup({ classid: node.key, orderby: 'orderidx', pageSize: 100 }).then((res) => {
|
||||
if (res.rows.length > 0) {
|
||||
let ary = []
|
||||
res.rows.forEach((item) => {
|
||||
if (item.parentid === 0) {
|
||||
//studentGroup 小组学生 为三级节点
|
||||
let studentGroup = JSON.parse('[' + item.studentlist + ']')
|
||||
studentGroup.forEach((el) => {
|
||||
el.label = el.name
|
||||
el.leaf = true
|
||||
el.level = 2
|
||||
el.id = el.studentid
|
||||
el.classId = item.classid
|
||||
})
|
||||
ary.push({
|
||||
label: item.groupname,
|
||||
leaf: false,
|
||||
...item,
|
||||
level: 1,
|
||||
// children 小组学生 三级节点 这样就不用再从接口拿
|
||||
children: studentGroup
|
||||
})
|
||||
}
|
||||
})
|
||||
resolve(ary)
|
||||
} else {
|
||||
// 班级下面没有小组 直接取学生
|
||||
let students = node.data.classstudentlist
|
||||
students.forEach((item) => {
|
||||
item.label = item.name
|
||||
item.level = 2
|
||||
item.leaf = true
|
||||
})
|
||||
resolve(students)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 三级节点 小组学生
|
||||
if (node.level == 2) {
|
||||
resolve(node.data.children)
|
||||
}
|
||||
}
|
||||
//节点勾选后触发 拿学生
|
||||
const handleCheckChange = (data, checked) => {
|
||||
studentList.value = []
|
||||
// 选中节点集合
|
||||
let checkNodes = checked.checkedNodes
|
||||
let ary = []
|
||||
checkNodes.forEach((item) => {
|
||||
// 一级节点 班级
|
||||
if (item.level == 0) {
|
||||
ary = [...ary, ...item.classstudentlist]
|
||||
}
|
||||
// 二级节点 班级下面的小组
|
||||
if (item.level == 1) {
|
||||
ary = [...ary, ...item.children]
|
||||
}
|
||||
// 三级节点 小组下面的学生
|
||||
if (item.level == 2) {
|
||||
ary = [...ary, item]
|
||||
}
|
||||
})
|
||||
studentList.value = uniqBy(ary, 'studentid')
|
||||
}
|
||||
// 删除学生
|
||||
const delStudent = (index) => {
|
||||
studentList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const onSubmit = (formEl) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
/**
|
||||
* 根据学生列表中的classId分班
|
||||
* studentList 为选中的所有学生 这些学生可能来自不同班级
|
||||
*/
|
||||
let gradeObj = groupBy(studentList.value, 'classId')
|
||||
// 处理要提交的参数
|
||||
let ary = []
|
||||
for (const value in gradeObj) {
|
||||
//这些参数 参照AIx web端 作业推送
|
||||
let obj = {
|
||||
id: 0,
|
||||
parentid: props.row.id,
|
||||
classid: value,
|
||||
classcourseid: 0,
|
||||
entpcourseid: props.entpcourseid,
|
||||
studentlist: JSON.stringify(gradeObj[value]),
|
||||
feedback: form.feedback,
|
||||
workkey: '',
|
||||
timelength: form.timelength,
|
||||
weights: 1,
|
||||
deaddate: form.deaddate,
|
||||
workdate: getCurrentTime('YYYY-MM-DD'),
|
||||
uniquekey: props.row.uniquekey,
|
||||
entpcourseworklist: '[' + props.row.entpcourseworklist + ']',
|
||||
needMsgNotifine: 'false',
|
||||
msgkey: 'newclasswork',
|
||||
title: '作业任务',
|
||||
msgcontent: '',
|
||||
teachername: userInfo.nickName,
|
||||
unixstamp: new Date().getTime(),
|
||||
worktype: props.row.worktype,
|
||||
status: '1'
|
||||
}
|
||||
ary.push(obj)
|
||||
}
|
||||
setLoading.value = true
|
||||
saveByClassWorkArray({
|
||||
classworkarray: JSON.stringify(ary)
|
||||
})
|
||||
.then((res) => {
|
||||
setLoading.value = false
|
||||
ElMessage.success('操作成功')
|
||||
emit('on-success', res.data)
|
||||
cloneDialog(formEl)
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading.value = false
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const expandedKeys = ref([])
|
||||
const checkedKeys = ref([])
|
||||
// 关闭弹窗
|
||||
const cloneDialog = (formEl) => {
|
||||
studentList.value = []
|
||||
checkedKeys.value = []
|
||||
expandedKeys.value = []
|
||||
formEl.resetFields()
|
||||
model.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 默认当前
|
||||
form.deaddate = getCurrentTime('YYYY-MM-DD HH:mm')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.homerwork-header {
|
||||
justify-content: space-between;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
|
||||
.icon-guanbi {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
</style>
|
|
@ -24,8 +24,7 @@
|
|||
<div class="flex-type flex">
|
||||
<span class="name">类别:</span>
|
||||
<el-select v-model="item.fileData.fileFlag" placeholder="Select" style="width: 100px">
|
||||
<el-option v-for="el in resourceType" :key="el.alue" :label="el.label" :value="el.value"
|
||||
:disabled="checkFile(el, item)" />
|
||||
<el-option v-for="el in resourceType" :key="el.alue" :label="el.label" :value="el.value" />
|
||||
</el-select>
|
||||
|
||||
</div>
|
||||
|
@ -50,7 +49,6 @@
|
|||
import { ref, watch } from 'vue'
|
||||
import FileImage from '@/components/file-image/index.vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { resourceType } from '@/utils/resourceDict'
|
||||
import { getFileSuffix, getFileName } from '@/utils/ruoyi'
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -59,6 +57,22 @@ const props = defineProps({
|
|||
default: false
|
||||
},
|
||||
})
|
||||
const resourceType = ref([
|
||||
{
|
||||
label: '素材',
|
||||
value: '素材'
|
||||
},
|
||||
|
||||
{
|
||||
label: '课件',
|
||||
value: '课件'
|
||||
},
|
||||
|
||||
{
|
||||
label: '教案',
|
||||
value: '教案'
|
||||
}
|
||||
])
|
||||
const dialogValue = ref(false)
|
||||
const limit = ref(5)
|
||||
// 定义要发送的emit事件
|
||||
|
@ -98,7 +112,7 @@ const hanleFileChange = (file) => {
|
|||
// 验证文件大小
|
||||
// B < KB < MB < GB
|
||||
// file.raw.size 单位是B
|
||||
const fileSize = file.raw.size / 1024 / 1024 > 100
|
||||
const fileSize = file.raw.size / 1024 / 1024 > 500
|
||||
if (fileSize) {
|
||||
ElMessage.error('文件大小错误! 请上传小于100M的文件!')
|
||||
return false
|
||||
|
|
|
@ -51,6 +51,8 @@ const closeWindow = () => {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.header-tool {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
span {
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
import { nextTick, toRaw } from 'vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { listEvaluation } from '@/api/subject'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { edustage, edusubject } = userStore.user
|
||||
|
||||
let evaluationList = []; // 教材版本list
|
||||
let subjectList = []; // 教材list
|
||||
//当前教材ID
|
||||
let curBookId = -1;
|
||||
|
||||
/**
|
||||
* 外部链接初始化获取 跳转web端的 unitId 专用,
|
||||
* 暂时: 初始化作业设计专用,后期可能会取消
|
||||
*/
|
||||
export const useGetClassWork = async () => {
|
||||
|
||||
const params = {
|
||||
edusubject,
|
||||
edustage,
|
||||
// entpcourseedituserid: userId,
|
||||
itemgroup: 'textbook',
|
||||
orderby: 'orderidx asc',
|
||||
pageSize: 10000
|
||||
}
|
||||
|
||||
if(localStorage.getItem('evaluationList')){
|
||||
evaluationList = JSON.parse(localStorage.getItem('evaluationList'))
|
||||
}else{
|
||||
const { rows } = await listEvaluation(params)
|
||||
localStorage.setItem('evaluationList', JSON.stringify(rows))
|
||||
evaluationList = rows
|
||||
}
|
||||
|
||||
//获取教材版本
|
||||
await getSubject()
|
||||
//上册
|
||||
/**
|
||||
* 不区分上下册
|
||||
* 2024/08/20调整
|
||||
*/
|
||||
// volumeOne = data.filter(item => item.level == 1 && item.semester == '上册')
|
||||
// volumeTwo = data.filter(item => item.level == 1 && item.semester == '下册')
|
||||
getTreeData()
|
||||
}
|
||||
|
||||
//获取教材
|
||||
const getSubject = async () => {
|
||||
if(localStorage.getItem('subjectList')){
|
||||
subjectList = JSON.parse(localStorage.getItem('subjectList'))
|
||||
}else{
|
||||
const { rows } = await listEvaluation({ itemkey: "version", edusubject, edustage, pageSize: 10000,orderby: 'orderidx asc', })
|
||||
|
||||
// subjectList = rows.filter(item => item.edustage == edustage && item.edusubject == edusubject)
|
||||
subjectList = rows
|
||||
localStorage.setItem('subjectList', JSON.stringify(subjectList))
|
||||
}
|
||||
|
||||
// 默认第一个
|
||||
if(!subjectList.length) return
|
||||
// curBookName = subjectList[0].itemtitle
|
||||
curBookId = subjectList[0].id
|
||||
// curBookImg = BaseUrl + subjectList[0].avartar
|
||||
// curBookPath = subjectList[0].fileurl
|
||||
}
|
||||
|
||||
|
||||
const getTreeData = () => {
|
||||
//数据过滤
|
||||
let upData = transData(evaluationList)
|
||||
|
||||
if(upData.length){
|
||||
// treeData = [...upData]
|
||||
}else{
|
||||
// treeData = []
|
||||
return
|
||||
}
|
||||
nextTick(() => {
|
||||
// defaultExpandedKeys = [treeData[0].id]
|
||||
// let currentNodeObj = {...getLastLevelData(upData)[0]}
|
||||
let currentNodeId = getLastLevelData(upData)[0].id
|
||||
let currentNodeName = getLastLevelData(upData)[0].label
|
||||
|
||||
let curNode = {
|
||||
id: currentNodeId,
|
||||
label: currentNodeName,
|
||||
// itemtitle: currentNodeObj.itemtitle,
|
||||
// edudegree: currentNodeObj.edudegree,
|
||||
// edustage: currentNodeObj.edustage,
|
||||
// edusubject: currentNodeObj.edusubject,
|
||||
}
|
||||
let parentNode = findParentByChildId(upData, currentNodeId)
|
||||
curNode.parentNode = toRaw(parentNode)
|
||||
|
||||
let levelFirstId = '';
|
||||
let levelSecondId = '';
|
||||
|
||||
if (curNode.parentNode) {
|
||||
levelFirstId = curNode.parentNode.id
|
||||
} else {
|
||||
levelFirstId = curNode.id
|
||||
levelSecondId = ''
|
||||
}
|
||||
let bookeId = curBookId
|
||||
// 头部 教材分析、作业设计:打开外部链接需要当前章节ID
|
||||
localStorage.setItem('unitId', JSON.stringify({ levelFirstId, levelSecondId, bookeId}))
|
||||
|
||||
// const data = {
|
||||
// textBook: {
|
||||
// curBookId: curBookId,
|
||||
// curBookName: curBookName,
|
||||
// curBookImg: curBookImg,
|
||||
// curBookPath: curBookPath
|
||||
// },
|
||||
// node: curNode
|
||||
// }
|
||||
// emit('changeBook', data)
|
||||
})
|
||||
}
|
||||
|
||||
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 transData = (data) => {
|
||||
let ary = []
|
||||
data.forEach(item => {
|
||||
let obj = {}
|
||||
// 根据当前教材ID 过滤出对应的单元、章节
|
||||
if (item.rootid == curBookId) {
|
||||
if(item.level == 1){
|
||||
obj.label = item.itemtitle
|
||||
obj.id = item.id
|
||||
obj.itemtitle = item.itemtitle
|
||||
obj.edudegree = item.edudegree
|
||||
obj.edustage = item.edustage
|
||||
obj.edusubject = item.edusubject
|
||||
let ary2 = []
|
||||
evaluationList.forEach(el => {
|
||||
let obj2 = {}
|
||||
if (item.id == el.parentid) {
|
||||
obj2 = {
|
||||
label: el.itemtitle,
|
||||
id: el.id,
|
||||
itemtitle : el.itemtitle,
|
||||
edudegree : el.edudegree,
|
||||
edustage : el.edustage,
|
||||
edusubject : el.edusubject,
|
||||
}
|
||||
ary2.push(obj2)
|
||||
}
|
||||
obj.children = ary2
|
||||
})
|
||||
ary.push(obj)
|
||||
}
|
||||
}
|
||||
})
|
||||
return ary
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
import useUserStore from '@/store/modules/user'
|
||||
import { homeworklist, listEntpcourse } from '@/api/teaching/classwork'
|
||||
import { addEntpcourse } from '@/api/teaching/classwork'
|
||||
|
||||
const userStore = useUserStore()
|
||||
// 当前选中教材章节节点
|
||||
let curNode = null
|
||||
// 查询作业列表所需要的ID
|
||||
let chapterId = null
|
||||
|
||||
export const useGetHomework = async (node) => {
|
||||
/**
|
||||
* node
|
||||
* 左侧选择章节的节点数据
|
||||
*/
|
||||
if (!node) return
|
||||
curNode = node
|
||||
|
||||
// 获取查询作业列表所需ID
|
||||
let { rows } = await getChapterId()
|
||||
if (rows.length > 0) {
|
||||
chapterId = rows[0].id
|
||||
} else {
|
||||
// 没有则 传教新的entpcourse 再次查询
|
||||
await createEntpcourse()
|
||||
let { rows } = await getChapterId()
|
||||
chapterId = rows[0].id
|
||||
}
|
||||
|
||||
return new Promise(async (resolve) =>{
|
||||
const res = await getHomeWorkList()
|
||||
resolve({res,chapterId})
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 根据教材章节ID 查询作业列表所需ID
|
||||
const getChapterId = () => {
|
||||
return listEntpcourse({
|
||||
evalid: curNode.id ? curNode.id : curNode.parentNode.id,
|
||||
edituserid: userStore.user.userId,
|
||||
pageSize: 500
|
||||
})
|
||||
}
|
||||
|
||||
// 创建新的entpcourse
|
||||
const createEntpcourse = () => {
|
||||
// 参照 web AIx 传入参数
|
||||
var cform = {}
|
||||
cform.entpid = userStore.user.deptId
|
||||
cform.level = 1
|
||||
cform.parentid = 0
|
||||
cform.dictid = 0
|
||||
cform.evalid = curNode.id
|
||||
cform.evalparentid = 0
|
||||
cform.edusubject = curNode.edusubject
|
||||
cform.edudegree = curNode.edudegree
|
||||
cform.edustage = curNode.edustage
|
||||
cform.coursetype = '课标学科'
|
||||
cform.coursetitle = curNode.itemtitle
|
||||
cform.coursedesc = ''
|
||||
cform.status = ''
|
||||
cform.dflag = 0
|
||||
cform.edituserid = userStore.user.userId
|
||||
cform.createblankfile = 'yes'
|
||||
return addEntpcourse(cform)
|
||||
}
|
||||
|
||||
const getHomeWorkList = async () => {
|
||||
return await homeworklist({
|
||||
entpcourseid: chapterId,
|
||||
edituserid: userStore.user.userId,
|
||||
pageSize: 100,
|
||||
status: '10'
|
||||
}).then((res) => {
|
||||
//以下代码 参照AIx web端 作业布置
|
||||
let list = []
|
||||
for (var i = 0; i < res.rows.length; i++) {
|
||||
res.rows[i].taskconfig = []
|
||||
|
||||
// 找child
|
||||
for (var j = 0; j < res.rows.length; j++) {
|
||||
if (res.rows[j].parentid == res.rows[i].id) {
|
||||
var ss = []
|
||||
if (res.rows[j].classworkdatastudentids != null) {
|
||||
ss = JSON.parse('[' + res.rows[j].classworkdatastudentids + ']')
|
||||
}
|
||||
var js = {
|
||||
id: res.rows[j].id,
|
||||
classid: res.rows[j].classid,
|
||||
classcaption: res.rows[j].classcaption,
|
||||
parentid: 0,
|
||||
worktype: '',
|
||||
workkey: res.rows[j].workkey,
|
||||
worktag: '',
|
||||
entpcourseid: 0,
|
||||
evalid: 0,
|
||||
edusubject: '',
|
||||
edudegree: '',
|
||||
workdate: '',
|
||||
title: '',
|
||||
workcodes: '',
|
||||
studentlist: ss,
|
||||
deaddate: res.rows[j].deaddate,
|
||||
timelength: res.rows[j].timelength,
|
||||
weights: res.rows[j].weights,
|
||||
feedtype: res.rows[j].feedtype
|
||||
}
|
||||
res.rows[i].taskconfig.push(js)
|
||||
}
|
||||
}
|
||||
res.rows[i].fileShowName = res.rows[i].uniquekey
|
||||
|
||||
// 注意slideid>0的,这一些作业是添加到PPT页面的,所以在作业管理中不能出现
|
||||
// 2024-05-15,酉阳,jackyshen
|
||||
if (res.rows[i].classid == 0 && res.rows[i].slideid == 0) {
|
||||
list.push(res.rows[i])
|
||||
}
|
||||
|
||||
// 如果是习题训练任务,则检查一共有多少道
|
||||
if (res.rows[i].entpcourseworklist != '') {
|
||||
res.rows[i].entpcourseworklistarray = JSON.parse('[' + res.rows[i].entpcourseworklist + ']')
|
||||
} else {
|
||||
res.rows[i].entpcourseworklistarray = []
|
||||
}
|
||||
}
|
||||
return list
|
||||
})
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import { ref } from 'vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { listEvaluation } from '@/api/subject'
|
||||
|
||||
export const useGetSubject = async () =>{
|
||||
|
||||
// user store
|
||||
const userStore = useUserStore()
|
||||
const { edustage, edusubject, userId } = userStore.user
|
||||
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
// 章节List
|
||||
const unitList = ref([])
|
||||
// 教材List
|
||||
let subjectList = null
|
||||
// 单元章节树结构
|
||||
let treeData = null
|
||||
|
||||
|
||||
// 根据 学科 + 学段 获取所有单元章节
|
||||
const getSubjectUnit = async () =>{
|
||||
let strUnit = localStorage.getItem('unitList')
|
||||
if(strUnit){
|
||||
unitList.value = JSON.parse(strUnit)
|
||||
}
|
||||
else{
|
||||
const unitParams = {
|
||||
edusubject,
|
||||
edustage,
|
||||
itemgroup: 'textbook',
|
||||
orderby: 'orderidx asc',
|
||||
pageSize: 10000
|
||||
}
|
||||
const { rows } = await listEvaluation(unitParams)
|
||||
unitList.value = rows
|
||||
localStorage.setItem('unitList', JSON.stringify(rows))
|
||||
}
|
||||
|
||||
await getSubject()
|
||||
}
|
||||
|
||||
// 获取 学科 + 学段 获取教材
|
||||
const getSubject = async () =>{
|
||||
|
||||
let strSubject = localStorage.getItem('subjectList')
|
||||
if(strSubject){
|
||||
subjectList = JSON.parse(strSubject)
|
||||
}
|
||||
else{
|
||||
const subjectParams = {
|
||||
itemkey: "version",
|
||||
edusubject,
|
||||
edustage,
|
||||
pageSize: 10000,
|
||||
orderby: 'orderidx asc'
|
||||
}
|
||||
const { rows } = await listEvaluation(subjectParams)
|
||||
subjectList = rows
|
||||
localStorage.setItem('subjectList', JSON.stringify(rows))
|
||||
}
|
||||
|
||||
// 默认选中第一个教材
|
||||
if(subjectList && subjectList.length){
|
||||
treeData = getTreeData(subjectList[0].id)
|
||||
}
|
||||
}
|
||||
|
||||
// 单元章节数据转为“树”结构
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
await getSubjectUnit()
|
||||
|
||||
return { subjectList, treeData, getTreeData }
|
||||
|
||||
}
|
|
@ -0,0 +1,514 @@
|
|||
export const isJson = (str) => {
|
||||
if (typeof str == 'string') {
|
||||
try {
|
||||
let obj = JSON.parse(str)
|
||||
if (typeof obj == 'object' && obj) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description processList 格式化试题
|
||||
* @param {*} row
|
||||
*/
|
||||
export const processList = (row) => {
|
||||
for (var i = 0; i < row.length; i++) {
|
||||
if (isJson(row[i].workanalysis)) {
|
||||
//1、先默认格式化 格式化各项内容(待优化, 后续界面显示的为format的值)
|
||||
row[i].titleFormat = row[i].title // 题目
|
||||
row[i].examdateFormat = row[i].examdate // ?考试日期 eg: 2024-07-11 14:39:27"
|
||||
row[i].workdescFormat = row[i].workdesc // 题目选项
|
||||
row[i].workanswerFormat = row[i].workanswer // 题目正确答案
|
||||
if (row[i].workanswerFormat == null || row[i].workanswerFormat == '') {
|
||||
row[i].workanswerFormat = '见试题解答内容'
|
||||
}
|
||||
|
||||
// workanalysis 解析内容(analyse:解答; method:分析; discuss:点评; )
|
||||
var jjj = JSON.parse(row[i].workanalysis)
|
||||
row[i].analyse = jjj.analyse
|
||||
row[i].method = jjj.method
|
||||
row[i].discuss = jjj.discuss
|
||||
//row[i].discusscollapse = false;
|
||||
if (row[i].examdate !== null && row[i].examdate !== undefined) {
|
||||
row[i].examdate = row[i].examdate.substring(0, 10)
|
||||
}
|
||||
|
||||
// 具体题型数据结构处理
|
||||
if (row[i].worktype == '复合题') {
|
||||
// 旧类型
|
||||
if (row[i].title.indexOf('!@#$%') !== -1) {
|
||||
// 1.选项解析替换
|
||||
const options = JSON.parse(row[i].workdesc)
|
||||
// 题目(背景材料+复合题目)
|
||||
const bjTitle = row[i].title.split('!@#$%')[0]
|
||||
const tmTitles = row[i].title.split('!@#$%').filter((it, ix) => ix > 0)
|
||||
// console.log(bjTitle,'背景标题');
|
||||
// console.log(tmTitles,'复合题目');
|
||||
let titls = []
|
||||
options.forEach((element, index1) => {
|
||||
const workDescArr = element.split('#&')
|
||||
let tmp = ''
|
||||
let j = 0
|
||||
for (; j < workDescArr.length; j++) {
|
||||
if (j % 2 == 0) {
|
||||
tmp += `<div style='width:80%;display:flex;'>`
|
||||
}
|
||||
const char = String.fromCharCode(65 + j)
|
||||
tmp += `<div style='display:flex;margin-left:2%;width:35%;overflow:hidden;text-overflow:ellipsis;font-size:0.9em;'>${char}.${workDescArr[j]}</div>`
|
||||
if (j % 2 == 1) {
|
||||
tmp += '</div>'
|
||||
}
|
||||
}
|
||||
// j此刻已自增1, 故当选项为单数时, 需要补充结束标签
|
||||
if (j % 2 == 1) {
|
||||
tmp += '</div>'
|
||||
}
|
||||
|
||||
// workDescArr为 [''] 表示为 判断题或者填空题,这里不需要选项
|
||||
if (workDescArr[0] != '') {
|
||||
titls.splice(index1, 1, tmp)
|
||||
} else {
|
||||
titls.splice(index1, 1, '')
|
||||
}
|
||||
})
|
||||
const s = []
|
||||
tmTitles.map((it, ix) => {
|
||||
s.push(it)
|
||||
titls.map((it2, ix2) => {
|
||||
if (ix == ix2) {
|
||||
s.push(it2)
|
||||
}
|
||||
})
|
||||
})
|
||||
// console.log(s,'?????????????????')
|
||||
|
||||
row[i].titleFormat = bjTitle + s.join('')
|
||||
row[i].workdescFormat = ''
|
||||
|
||||
//2.答案 - 数字转为ABCD
|
||||
const answerArr = JSON.parse(row[i].workanswer)
|
||||
let indexLabel = 1
|
||||
let arr = []
|
||||
answerArr.forEach((item) => {
|
||||
const arrTmp = item.answer.split('#&')
|
||||
let value = `(${indexLabel})`
|
||||
arrTmp.forEach((element, i) => {
|
||||
if (item.type == '单选题' || item.type == '多选题') {
|
||||
value += `${String.fromCharCode(65 + Number(element))}`
|
||||
}
|
||||
if (item.type == '判断题' || item.type == '填空题') {
|
||||
// 去除下 html标签
|
||||
value += `${element.replace(/<[^>]+>/g, '')}` + (i == arrTmp.length - 1 ? '' : '、')
|
||||
}
|
||||
if (item.type == '主观题') {
|
||||
if (element) {
|
||||
console.log(element, 'element')
|
||||
value += item.answer
|
||||
} else {
|
||||
value += '答案不唯一,请参考分析解答点评!'
|
||||
}
|
||||
}
|
||||
})
|
||||
arr.push(value)
|
||||
indexLabel++
|
||||
})
|
||||
const answer = arr.join('<br />')
|
||||
|
||||
row[i].workanswerFormat = answer
|
||||
} else {
|
||||
// 处理[题干显示] - 不再需要处理
|
||||
// row[i].titleFormat = row[i].title; // 仅占位提示
|
||||
|
||||
/**
|
||||
* 处理[选项显示] - 特殊结构
|
||||
* [
|
||||
* {type: '单选题', title: '题目1', options: ['ABC123','ABC123']},
|
||||
* {type: '多选题', title: '题目1', options: ['ABC123','ABC123']},
|
||||
* {type: '填空题', title: '题目1', options: []},
|
||||
* {type: '判断题', title: '题目1', options: []},
|
||||
* {type: '主观题', title: '题目1', options: []},
|
||||
* ]
|
||||
*/
|
||||
let workDescArr = JSON.parse(row[i].workdesc)
|
||||
let workDescHtml = `<div style='width:80%;display:flex;>`
|
||||
workDescArr.map((item, index) => {
|
||||
if (item.type == '单选题' || item.type == '多选题') {
|
||||
workDescHtml += `<div style='width:80%;display:flex;'>${index + 1}. ${item.title}</div>`
|
||||
let tmp = ''
|
||||
let j = 0
|
||||
let optionsArr = item.options
|
||||
for (; j < optionsArr.length; j++) {
|
||||
if (j % 2 == 0) {
|
||||
tmp += `<div style='width:80%;display:flex;'>`
|
||||
}
|
||||
const char = String.fromCharCode(65 + j)
|
||||
tmp += `<div style='display:flex;margin-left: 2%; width: 36%'>${char}.${optionsArr[j]}</div>`
|
||||
if (j % 2 == 1) {
|
||||
tmp += '</div>'
|
||||
}
|
||||
}
|
||||
// j此刻已自增1, 故当选项为单数时, 需要补充结束标签
|
||||
if (j % 2 == 1) {
|
||||
tmp += '</div>'
|
||||
}
|
||||
|
||||
workDescHtml += tmp
|
||||
} else if (item.type == '填空题' || item.type == '判断题' || item.type == '主观题') {
|
||||
workDescHtml += `<div style='width:80%;display:flex;'>${index + 1}. ${item.title}</div>`
|
||||
}
|
||||
})
|
||||
workDescHtml += '</div>'
|
||||
row[i].workdescFormat = workDescHtml
|
||||
|
||||
/**
|
||||
* 处理[答案显示] - 特殊结构
|
||||
* [
|
||||
* {type: '单选题', answer: ['0']},
|
||||
* {type: '多选题', answer: ['0','1']},
|
||||
* {type: '填空题', answer: ['填空1','填空2']},
|
||||
* {type: '判断题', answer: ['0'/'1']},
|
||||
* {type: '主观题', answer: [xxxx]},
|
||||
* ]
|
||||
*/
|
||||
let workAnswerArr = JSON.parse(row[i].workanswer)
|
||||
let workAnswerHtml = ``
|
||||
workAnswerArr.map((item, index) => {
|
||||
const answerArr = item.answer //JSON.parse(item.answer);
|
||||
if (item.type == '单选题' || item.type == '多选题') {
|
||||
const answer = answerArr
|
||||
.map((item) => {
|
||||
return String.fromCharCode(65 + Number(item))
|
||||
})
|
||||
.join('')
|
||||
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}</div>`
|
||||
} else if (item.type == '填空题') {
|
||||
const answer = answerArr.join('、')
|
||||
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}</div>`
|
||||
} else if (item.type == '判断题') {
|
||||
const answer = answerArr
|
||||
.map((item) => {
|
||||
return item === '1' ? '正确' : '错误'
|
||||
})
|
||||
.join('、')
|
||||
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}</div>`
|
||||
} else if (item.type == '主观题') {
|
||||
// 复合题里面的主观题只有一个答案,或没填
|
||||
const answer = answerArr.join('、')
|
||||
if (answerArr[0]) {
|
||||
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}</div>`
|
||||
} else {
|
||||
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}答案不唯一,请参考分析解答点评!</div>`
|
||||
}
|
||||
}
|
||||
})
|
||||
row[i].workanswerFormat = workAnswerHtml
|
||||
}
|
||||
} else if (
|
||||
row[i].worktype == '主观题' ||
|
||||
(row[i].worktype !== '单选题' &&
|
||||
row[i].worktype !== '多选题' &&
|
||||
row[i].worktype !== '填空题' &&
|
||||
row[i].worktype !== '判断题')
|
||||
) {
|
||||
// 处理[选项显示] - 主观题中无选项, 故置空
|
||||
row[i].workdescFormat = ''
|
||||
row[i].workanswerFormat = ''
|
||||
// 答案处理- eg: "\"不唯一的答案,参考\""
|
||||
if (row[i].workanswer && row[i].workanswer != '') {
|
||||
row[i].workanswerFormat = JSON.parse(row[i].workanswer)
|
||||
}
|
||||
} else {
|
||||
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||
// 通用选项结构 ['ABC123','ABC123'] | ['ABC123','ABC123'] | [](填空题无选项) | [](判断题无选项)
|
||||
let workDescArr = []
|
||||
if (
|
||||
row[i].workdesc.charAt(0) === '[' &&
|
||||
row[i].workdesc.charAt(row[i].workdesc.length - 1) === ']'
|
||||
) {
|
||||
//123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||
workDescArr = JSON.parse(row[i].workdesc)
|
||||
} else if (row[i].workdesc.indexOf('#&') !== -1) {
|
||||
workDescArr = row[i].workdesc.split('#&')
|
||||
} else if (row[i].workdesc.indexOf(',') !== -1) {
|
||||
workDescArr = row[i].workdesc.split(',')
|
||||
} else {
|
||||
// 单字符串直接添加至空数组(待考虑确认)
|
||||
workDescArr.push(row[i].workdesc)
|
||||
}
|
||||
|
||||
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||
// 通用答案结构 ['0'] | ['0','1'] | ['填空1','填空2'] | ['0'/'1']
|
||||
let workAnswerArr = []
|
||||
if (
|
||||
row[i].workanswer.charAt(0) === '[' &&
|
||||
row[i].workanswer.charAt(row[i].workanswer.length - 1) === ']'
|
||||
) {
|
||||
// 123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||
workAnswerArr = JSON.parse(row[i].workanswer)
|
||||
} else if (row[i].workanswer.indexOf('#&') !== -1) {
|
||||
workAnswerArr = row[i].workanswer.split('#&')
|
||||
} else if (row[i].workanswer.indexOf(',') !== -1) {
|
||||
workAnswerArr = row[i].workanswer.split(',')
|
||||
} else {
|
||||
// 单字符串直接添加至空数组(待考虑确认)
|
||||
workAnswerArr.push(row[i].workanswer)
|
||||
}
|
||||
|
||||
// 具体题型处理
|
||||
if (row[i].worktype == '单选题' || row[i].worktype == '多选题') {
|
||||
// 处理[选项显示] - 拼接ABCD首序号
|
||||
let tmp = ''
|
||||
let j = 0
|
||||
for (; j < workDescArr.length; j++) {
|
||||
if (j % 2 == 0) {
|
||||
tmp += `<div style='width:80%;display:flex;'>`
|
||||
}
|
||||
const char = String.fromCharCode(65 + j)
|
||||
tmp += `<div style='display:flex;margin-left: 2%; width: 36%'>${char}.${workDescArr[j]}</div>`
|
||||
if (j % 2 == 1) {
|
||||
tmp += '</div>'
|
||||
}
|
||||
}
|
||||
if (j % 2 == 0) {
|
||||
tmp += '</div>'
|
||||
}
|
||||
row[i].workdescFormat = tmp
|
||||
|
||||
// 处理[答案显示] - 转换ABCD
|
||||
let arr2Char = workAnswerArr
|
||||
.map((item) => {
|
||||
return String.fromCharCode(65 + Number(item))
|
||||
})
|
||||
.join('')
|
||||
row[i].workanswerFormat = arr2Char
|
||||
} else if (row[i].worktype == '填空题') {
|
||||
// 处理[选项显示] - 填空题中无选项, 故置空
|
||||
row[i].workdescFormat = ''
|
||||
|
||||
// 处理[答案显示] - 逗号连接
|
||||
row[i].workanswerFormat = workAnswerArr.join('、')
|
||||
} else if (row[i].worktype == '判断题') {
|
||||
// 处理[选项显示] - 判断题中无选项, 故置空
|
||||
row[i].workdescFormat = ''
|
||||
|
||||
// 处理[答案显示] - 1-正常 0-错误
|
||||
const answer = workAnswerArr
|
||||
.map((item) => {
|
||||
return item === '1' ? '正确' : '错误'
|
||||
})
|
||||
.join('、')
|
||||
row[i].workanswerFormat = answer
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//2、处理单选题
|
||||
if(row[i].worktype == '单选题' || row[i].worktype == '多选题' ){
|
||||
//1.选项前增加ABCD workdesc: "①②#&①③#&②④#&③④" || "<div>为了活着</div>#&<div>为了填报肚子</div>#&<div>为了吃饭而吃饭</div>"
|
||||
let workDescArr = [];
|
||||
if(row[i].workdesc.indexOf('[')!==-1 && row[i].workdesc.indexOf(']')!==-1) {
|
||||
//123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||
workDescArr = JSON.parse(row[i].workdesc);
|
||||
}
|
||||
else if(row[i].workdesc.indexOf('#&')) {
|
||||
workDescArr = row[i].workdesc.split('#&');
|
||||
}
|
||||
else if(row[i].workdesc.indexOf(',')){
|
||||
workDescArr = row[i].workdesc.split(',');
|
||||
}
|
||||
else {
|
||||
// 待考虑
|
||||
workDescArr.push(item.workdesc)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//2.答案 - 数字转为ABCD
|
||||
if(row[i].worktype == '单选题') {
|
||||
const str2Char = String.fromCharCode(65+Number(row[i].workanswer));
|
||||
row[i].workanswerFormat = str2Char;
|
||||
} else if (row[i].worktype == '多选题') {
|
||||
const answerArr = row[i].workanswer.split('#&');
|
||||
let arr2Char = '';
|
||||
for(let k=0; k<answerArr.length; k++){
|
||||
arr2Char += String.fromCharCode(65+Number(answerArr[k]));
|
||||
}
|
||||
row[i].workanswerFormat = arr2Char;
|
||||
}
|
||||
}
|
||||
else if(row[i].worktype == '填空题') {
|
||||
// console.log(row[i].workanswer.replace(/<[^>]*>/g, "").split('#&'),'????')
|
||||
// 填空题答案
|
||||
row[i].workanswerFormat = row[i].workanswer.replace(/#&/g,", ");
|
||||
// 填空选项不需要展示,
|
||||
row[i].workdescFormat = '';
|
||||
}
|
||||
else if(row[i].worktype == '判断题'){
|
||||
// console.log(row[i].workanswer.replace(/<[^>]*>/g, "").split('#&'),'????')
|
||||
// 判断题答案
|
||||
row[i].workanswerFormat = row[i].workanswer.replace(/#&/g,", ");
|
||||
// 判断选项不需要展示,
|
||||
row[i].workdescFormat = '';
|
||||
}
|
||||
else if(row[i].worktype == '复合题') {
|
||||
// 1.选项解析替换
|
||||
const options = JSON.parse(row[i].workdesc);
|
||||
// 题目(背景材料+复合题目)
|
||||
const bjTitle = row[i].title.split('!@#$%')[0];
|
||||
const tmTitles = row[i].title.split('!@#$%').filter((it,ix)=>ix>0);
|
||||
// console.log(bjTitle,'背景标题');
|
||||
// console.log(tmTitles,'复合题目');
|
||||
let titls = [];
|
||||
options.forEach((element,index1) => {
|
||||
const workDescArr = element.split('#&');
|
||||
let tmp = '';
|
||||
let j=0;
|
||||
for(; j<jsonArr.length; j++){
|
||||
if(j%2 == 0){
|
||||
tmp += `<div style='width:80%;display:flex;'>`;
|
||||
}
|
||||
const char = String.fromCharCode(65+j);
|
||||
tmp += `<div style='display:flex;margin-left: 2%; width: 36%'>${char}.${jsonArr[j]}</div>`;
|
||||
if(j%2 == 1){
|
||||
tmp += '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if(j%2== 0){
|
||||
tmp += '</div>';
|
||||
}
|
||||
workdesc = tmp;
|
||||
}
|
||||
|
||||
row[i].workdescFormat = workdesc; // 题目选项
|
||||
|
||||
|
||||
// 答案处理
|
||||
let workanswer = '';
|
||||
if(row[i].workanswer && row[i].workanswer != '') {
|
||||
// 因答案内容存在多种格式: 1.["123","1234"] 2.123#&1234 3.123
|
||||
if(row[i].workanswer.indexOf('[')!==-1 && row[i].workanswer.indexOf(']')!==-1) {
|
||||
//123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||
let json = JSON.parse(row[i].workanswer);
|
||||
// 单选、多选 需要 数字转为ABCD
|
||||
if(row[i].worktype == '单选题') {
|
||||
const str2Char = String.fromCharCode(65+Number(json[0]));
|
||||
workanswer = str2Char;
|
||||
} else if (row[i].worktype == '多选题') {
|
||||
// const answerArr = row[i].workanswer.split('#&');
|
||||
let arr2Char = '';
|
||||
for(let k=0; k<json.length; k++){
|
||||
arr2Char += String.fromCharCode(65+Number(json[k]));
|
||||
}
|
||||
workanswer = arr2Char;
|
||||
} else if(row[i].worktype == '主观题' ) {
|
||||
let arr2Char = '';
|
||||
for(let k=0; k<json.length; k++){
|
||||
const itemArr = json[k];
|
||||
arr2Char += '('+ (parseInt(k) + 1) +')'+ itemArr.join('、')+ '<br />';
|
||||
}
|
||||
workanswer = arr2Char;
|
||||
row[i].titleFormat = row[i].titleFormat.replace(/!@#\$%/g, '');
|
||||
} else {
|
||||
workanswer = json.join('、');
|
||||
}
|
||||
} else if(row[i].workanswer.indexOf('#&')) {
|
||||
// 意味着多个答案或者填空内容
|
||||
let workanswerList = row[i].workanswer.split('#&');
|
||||
if(row[i].worktype == '多选题') {
|
||||
// 数字转为ABCD
|
||||
let arr2Char = '';
|
||||
for(let k=0; k<workanswerList.length; k++){
|
||||
arr2Char += String.fromCharCode(65+Number(workanswerList[k]));
|
||||
}
|
||||
workanswer = arr2Char;
|
||||
}else{
|
||||
workanswer = workanswerList.join('、');
|
||||
}
|
||||
} else if(row[i].workanswer.indexOf(',')){
|
||||
// 意味这同样多个答案或者填空内容
|
||||
let workanswerList = row[i].workanswer.split(',');
|
||||
if(row[i].worktype == '多选题') {
|
||||
// 数字转为ABCD
|
||||
let arr2Char = '';
|
||||
for(let k=0; k<workanswerList.length; k++){
|
||||
arr2Char += String.fromCharCode(65+Number(workanswerList[k]));
|
||||
}
|
||||
workanswer = arr2Char;
|
||||
}else{
|
||||
workanswer = workanswerList.join('、');
|
||||
}
|
||||
} else {
|
||||
// 待考虑
|
||||
workanswer = row[i].workanswer;
|
||||
}
|
||||
}
|
||||
|
||||
row[i].workanswerFormat = workanswer; // 题目正确答案
|
||||
|
||||
|
||||
//2.答案 - 数字转为ABCD
|
||||
const answerArr = JSON.parse(row[i].workanswer);
|
||||
let indexLabel = 1;
|
||||
let arr = [];
|
||||
answerArr.forEach(item => {
|
||||
const arrTmp = item.answer.split('#&');
|
||||
let value = `(${indexLabel})`;
|
||||
arrTmp.forEach((element,i) => {
|
||||
if(item.type == '单选题' || item.type == '多选题'){
|
||||
value += `${String.fromCharCode(65+Number(element))}`;
|
||||
}
|
||||
if(item.type == '判断题' || item.type == '填空题'){
|
||||
// 去除下 html标签
|
||||
value += `${element.replace(/<[^>]+>/g, '')}`+ (i==arrTmp.length-1?'':'、');
|
||||
}
|
||||
})
|
||||
arr.push(value);
|
||||
indexLabel++;
|
||||
})
|
||||
const answer = arr.join('<br />');
|
||||
|
||||
row[i].workanswerFormat = answer;
|
||||
}
|
||||
else if(row[i].worktype == '主观题') {
|
||||
// 1.选项解析替换---主观题没选项
|
||||
// 题目(背景材料+主观题目)
|
||||
const bjTitle = row[i].title.split('!@#$%')[0];
|
||||
const tmTitles = row[i].title.split('!@#$%').filter((it,ix)=>ix>0);
|
||||
// console.log(bjTitle,'背景标题');
|
||||
// console.log(tmTitles,'主观题目');
|
||||
let titls = [];
|
||||
const s = [];
|
||||
tmTitles.map((it,ix)=>{
|
||||
s.push(it);
|
||||
})
|
||||
// console.log(s,'?????????????????')
|
||||
|
||||
row[i].titleFormat = bjTitle + s.join('');
|
||||
// 填空选项不需要展示,
|
||||
row[i].workdescFormat = '';
|
||||
|
||||
//2.答案
|
||||
// 填空题答案
|
||||
const workanswerList = JSON.parse(row[i].workanswer);
|
||||
let tmp='';
|
||||
workanswerList&&workanswerList.map((item,index)=>{
|
||||
tmp += '<div>'+(index+1)+'.'+item.replace(/#&/g, ',')+'</div>';
|
||||
})
|
||||
row[i].workanswerFormat = tmp;
|
||||
|
||||
}
|
||||
else {
|
||||
//处理答案
|
||||
row[i].workanswerFormat = '见试题解答内容';
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<section class="app-main">
|
||||
<div class="app-main-left no-select">
|
||||
<!-- <div class="app-main-left no-select">
|
||||
<div v-for="(item, index) in title" :key="index" :class="item.active?'active':''" class="app-main-left-item" @click="active(index)">
|
||||
<div class="app-main-left-item-icon">
|
||||
<i :class="item.img"></i>
|
||||
</div>
|
||||
<div class="app-main-left-item-text">{{item.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<transition mode="out-in" name="fade-transform">
|
||||
<div v-show="$route != null" style="height: 100%; flex: 1">
|
||||
<div v-show="$route != null" style="height: 100%; flex: 1;width: 100%">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<keep-alive>
|
||||
<component :is="Component" v-if="route.meta.keepAlive" :key="route.name" />
|
||||
|
@ -72,8 +72,9 @@ const title = reactive([
|
|||
child1: []
|
||||
},
|
||||
{
|
||||
name: '高考研究',
|
||||
url: '/education/colentrance',
|
||||
name: '考试分析',
|
||||
url: '/examReport',
|
||||
type: 'hash',
|
||||
img: 'iconfont icon-icon_kaoshifenxi',
|
||||
child1: []
|
||||
}
|
||||
|
@ -120,7 +121,7 @@ const active = (index) => {
|
|||
})
|
||||
}
|
||||
onMounted(()=>{
|
||||
active(0)
|
||||
// active(0)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
<template>
|
||||
<div class="title-bar flex">
|
||||
<div class="left-section">
|
||||
<h3 class="title" @click="changeTab">AIX智慧课堂</h3>
|
||||
<div class="flex title-box">
|
||||
<el-image style="width: 23px; height: 23px" :src="logoIco" />
|
||||
<span class="title" @click="changeTab">{{homeTitle}}</span>
|
||||
</div>
|
||||
<div class="change-tab">
|
||||
<ul class="flex">
|
||||
<li
|
||||
v-for="(item, index) in routeHeader.nowRouter"
|
||||
:key="index"
|
||||
class="flex"
|
||||
:style="{'color' : item.color}"
|
||||
:class="currentRoute === item.url ? 'active-li' : ''"
|
||||
@click="handleOutLink(item.url,item.type)"
|
||||
>
|
||||
<i :class="item.img"></i>
|
||||
<span class="text">{{ item.name }}</span>
|
||||
<li class="flex" :class="[activeId == menu.path ? 'active-li' : '', menu.disabled ? 'disabled' : '']"
|
||||
v-for="menu in headerMenus" :key="menu.id" @click="clickMenu(menu)">
|
||||
<i class="iconfont" :class="menu.icon"></i>
|
||||
<span class="text">{{ menu.name }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -23,25 +20,42 @@
|
|||
<WindowTools />
|
||||
<div class="user flex">
|
||||
<div class="avatar-container">
|
||||
<el-dropdown
|
||||
class="right-menu-item hover-effect"
|
||||
trigger="click"
|
||||
@command="handleCommand"
|
||||
>
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="userStore.user.avatar" class="user-avatar" style="float: left" />
|
||||
<div style="margin-top: 18px; font-size: 0.8em">{{ userStore.user.nickName }}</div>
|
||||
<div class="avatar-wrapper flex">
|
||||
<el-dropdown class="right-menu-item hover-effect" @command="handleCommand">
|
||||
<img :src="dev_api + userStore.user.avatar" class="user-avatar" style="float: left" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="changePage('/profile')">个人中心</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePage('/classReserv')">课程预约</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePage('/class')">班级中心</el-dropdown-item>
|
||||
<el-dropdown-item divided command="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div class="user-info flex">
|
||||
<span class="user-name">{{ userStore.user.nickName }}</span>
|
||||
<div class="flex">
|
||||
|
||||
<el-dropdown @command="changeSubject">
|
||||
<div class="user-subject">{{ userStore.user.edusubject }}
|
||||
<el-icon class="el-icon--right"><arrow-down />
|
||||
</el-icon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="item in userSubjectList" :key="item.id" :command="item">
|
||||
{{ item.edustage }}-{{ item.edusubject }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div class="user-depname">{{ userStore.user.deptName }}</div>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="changePage('/profile')">个人中心</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePage('/class')">班级中心</el-dropdown-item>
|
||||
<el-dropdown-item divided command="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -49,20 +63,26 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { ArrowDown } from '@element-plus/icons-vue'
|
||||
import WindowTools from '@/components/window-tools/index.vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import routerStore from '@/store/modules/route'
|
||||
import { updateUserInfo } from '@/api/system/user'
|
||||
import outLink from '@/utils/linkConfig'
|
||||
const routeHeader = routerStore()
|
||||
import logoIco from '@/assets/images/logo.png'
|
||||
import { listEvaluation } from '@/api/classManage/index'
|
||||
import { clearBookInfo } from '@/utils/ruoyi'
|
||||
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const currentRoute = ref('')
|
||||
const dev_api = ref(import.meta.env.VITE_APP_BASE_API)
|
||||
const userSubjectList = ref([])
|
||||
|
||||
const handleOutLink = (path, type) => {
|
||||
const handleOutLink = (path, type, name) => {
|
||||
if (!path) return
|
||||
if (type === 'hash') {
|
||||
router.push(path)
|
||||
|
@ -71,41 +91,60 @@ const handleOutLink = (path, type) => {
|
|||
let configObj = outLink().getBaseData()
|
||||
let fullPath = configObj.fullPath + path
|
||||
fullPath = fullPath.replaceAll('//', '/')
|
||||
const { levelFirstId, levelSecondId } = JSON.parse(localStorage.getItem('unitId'))
|
||||
let unitId = levelSecondId ? levelSecondId : levelFirstId
|
||||
if (name == '教材分析' || name == '考试分析') {
|
||||
fullPath += `?unitId=${unitId}`
|
||||
}
|
||||
// 通知主进程
|
||||
ipcRenderer.send('openWindow', {
|
||||
key: path,
|
||||
fullPath: fullPath,
|
||||
cookieData: { ...configObj.data }
|
||||
})
|
||||
}
|
||||
}
|
||||
/*const menus = ref([
|
||||
|
||||
const activeId = ref('/home')
|
||||
const headerMenus = [
|
||||
{
|
||||
icon: 'icon-zhuye2 icon-homepage',
|
||||
name: '主页',
|
||||
path: '/homepage'
|
||||
name: '工作台',
|
||||
id: 1,
|
||||
icon: 'icon-gongzuotai',
|
||||
path: '/home'
|
||||
},
|
||||
{
|
||||
icon: 'icon-jiaoxueziyuan icon-resource',
|
||||
name: '资源',
|
||||
name: '研究室',
|
||||
id: 2,
|
||||
icon: 'icon-yanjiushi',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
name: '资源库',
|
||||
id: 3,
|
||||
icon: 'icon-saoyisao',
|
||||
path: '/resource'
|
||||
},
|
||||
{
|
||||
icon: 'icon-beike icon-prepare',
|
||||
name: '备课',
|
||||
path: '/prepare'
|
||||
},
|
||||
{
|
||||
icon: 'icon-jiangke1 icon-teach',
|
||||
name: '授课',
|
||||
path: '/teach'
|
||||
name: '朋友圈',
|
||||
id: 4,
|
||||
icon: 'icon-pengyouquan1',
|
||||
disabled: true
|
||||
}
|
||||
])*/
|
||||
]
|
||||
|
||||
const clickMenu = ({ id, disabled, path }) => {
|
||||
if (disabled) return
|
||||
activeId.value = id
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
// 监听当前路由
|
||||
watch(
|
||||
() => router.currentRoute.value,
|
||||
(newValue) => {
|
||||
currentRoute.value = newValue.path
|
||||
currentRoute.value = newValue
|
||||
activeId.value = newValue.path
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
@ -144,20 +183,48 @@ function logout() {
|
|||
ipcRenderer && ipcRenderer.send('openLoginWindow')
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
.catch(() => { })
|
||||
}
|
||||
|
||||
const emits = defineEmits(['setLayout'])
|
||||
function setLayout() {
|
||||
emits('setLayout')
|
||||
}
|
||||
// 切换学科
|
||||
const changeSubject = async (command) =>{
|
||||
clearBookInfo()
|
||||
const { userId, userName, phonenumber, plainpwd } = userStore.user
|
||||
const data = {
|
||||
userId,
|
||||
userName,
|
||||
edustage: command.edustage,
|
||||
edusubject: command.edusubject
|
||||
}
|
||||
await updateUserInfo(data)
|
||||
await userStore.login({username: phonenumber, password: plainpwd})
|
||||
await userStore.getInfo()
|
||||
router.go()
|
||||
}
|
||||
|
||||
// 获取学科
|
||||
const getAllSubject = async () => {
|
||||
const { rows } = await listEvaluation({ itemkey: "subject", pageSize: 500 })
|
||||
if(!userStore.user.subject) return
|
||||
const subject = userStore.user.subject.split(',')
|
||||
userSubjectList.value = rows.filter(item =>
|
||||
subject.some(el => item.id == el)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getAllSubject()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title-bar {
|
||||
height: 80px;
|
||||
background: #ebf0f9;
|
||||
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
-webkit-app-region: drag;
|
||||
|
@ -165,27 +232,41 @@ function setLayout() {
|
|||
|
||||
.left-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
width: 50%;
|
||||
|
||||
.title-box {
|
||||
padding-top: 8px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
|
||||
.title {
|
||||
color: #4b73df;
|
||||
font-size: 13px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.change-tab {
|
||||
-webkit-app-region: no-drag;
|
||||
margin-left: 20px;
|
||||
|
||||
ul {
|
||||
li {
|
||||
padding: 3px 13px;
|
||||
cursor: pointer;
|
||||
flex-direction: column;
|
||||
border-radius: 8px;
|
||||
margin: 0 5px;
|
||||
margin: 0 10px;
|
||||
|
||||
.text {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-size: 22px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.icon-resource {
|
||||
|
@ -205,12 +286,20 @@ function setLayout() {
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background: #d3e3fb;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #bfbfbf;
|
||||
|
||||
&:hover {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
.active-li {
|
||||
background: #d3e3fb;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
@ -220,11 +309,7 @@ function setLayout() {
|
|||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #4b73df;
|
||||
font-size: 18px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.right-section {
|
||||
|
@ -236,11 +321,25 @@ function setLayout() {
|
|||
flex-direction: column;
|
||||
|
||||
.user {
|
||||
padding-right: 10px;
|
||||
|
||||
.user-info {
|
||||
padding-right: 5px;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
justify-content: space-around;
|
||||
|
||||
.user-depname {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.user-subject {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -250,12 +349,12 @@ function setLayout() {
|
|||
|
||||
.avatar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.user-avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 10px;
|
||||
margin-top: 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ export default {
|
|||
file.callback(res)
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const MAX_SIZE = 100 * 1024 * 1024 // 2MB
|
||||
const MAX_SIZE = 500 * 1024 * 1024 // 2MB
|
||||
if (file.size > MAX_SIZE) {
|
||||
this.$message.error('文件大小不能超过 100MB!')
|
||||
return false
|
||||
|
|
|
@ -4,20 +4,47 @@
|
|||
<Header />
|
||||
</el-header>
|
||||
<el-main>
|
||||
<AppMain />
|
||||
<template v-if="currentRoute.path != '/home'">
|
||||
<el-page-header @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large mr-3"> {{ currentRoute.meta.title }} </span>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</template>
|
||||
<AppMain :style="{ height: currentRoute.path == '/home' ? '100%' : 'calc(100% - 45px)'}" />
|
||||
</el-main>
|
||||
<Uploader v-if="uploaderStore.uploadList && uploaderStore.uploadList.length > 0" />
|
||||
<AiChart/>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Header from './components/Header.vue'
|
||||
import AppMain from './components/AppMain.vue'
|
||||
import Uploader from './components/Uploader.vue'
|
||||
import AiChart from '@/components/ai-chart/index.vue'
|
||||
import uploaderState from '@/store/modules/uploader'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const router = useRouter()
|
||||
const currentRoute = ref('')
|
||||
|
||||
watch(
|
||||
() => router.currentRoute.value,
|
||||
(newValue) => {
|
||||
currentRoute.value = newValue
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
let uploaderStore = ref(uploaderState())
|
||||
|
||||
const goBack = () =>{
|
||||
router.back()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -32,6 +59,15 @@ let uploaderStore = ref(uploaderState())
|
|||
height: 80px;
|
||||
}
|
||||
.el-main {
|
||||
--el-main-padding: 0 20px 0 0;
|
||||
--el-main-padding: 0 20px 20px 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.el-page-header{
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.text-large{
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,14 +8,36 @@ import 'element-plus/dist/index.css'
|
|||
import './assets/iconfont/iconfont.css'
|
||||
import './assets/iconfont/iconfont'
|
||||
import 'virtual:windi.css'
|
||||
import request from "@/utils/request";
|
||||
|
||||
import { store } from '@/store'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import log from 'electron-log/renderer' // 渲染进程日志-文件记录
|
||||
import customComponent from '@/components/common' // 自定义组件
|
||||
|
||||
if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件
|
||||
Object.assign(console, log.functions) // 渲染进程日志-控制台替换
|
||||
}
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
|
||||
//专为菁优网配置的请求转发
|
||||
app.config.globalProperties.$requestGetJYW = (url,config)=>{
|
||||
config.params = config.params?config.params:{}
|
||||
config.params["getjypath"] = url;
|
||||
return request({
|
||||
url: "/jy/proxy",
|
||||
method: config.method||"get",
|
||||
params: config.params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
app.use(router)
|
||||
.use(store)
|
||||
.use(ElementPlus, { locale: zhLocale }).mount('#app')
|
||||
.use(ElementPlus, { locale: zhLocale })
|
||||
.use(customComponent) // 自定义组件
|
||||
.mount('#app')
|
|
@ -1615,6 +1615,7 @@ export class fabricVue {
|
|||
break
|
||||
case TYPES.ActionMode.OTHER: // 其他(工具选择)
|
||||
this.canvas.isDrawingMode = false
|
||||
objectSet.selectable = false
|
||||
this.canvas.freeDrawingCursor = 'default'
|
||||
break
|
||||
default:
|
||||
|
|
|
@ -0,0 +1,420 @@
|
|||
/**
|
||||
* @description im 事件监听
|
||||
* @author zdg
|
||||
* @date 2023-07-07
|
||||
*/
|
||||
// @ts-ignore
|
||||
const API = window?.api || {}
|
||||
const timRenderInstance = API?.getTimRender?.() || {}
|
||||
|
||||
// im 事件监听
|
||||
export class IMListeners {
|
||||
// 初始化监听
|
||||
static initListeners(callback) {
|
||||
/**
|
||||
* 注销消息监听事件
|
||||
*/
|
||||
timRenderInstance.TIMRemoveRecvNewMsgCallback()
|
||||
/**
|
||||
* @brief 增加接收新消息回调
|
||||
* @param cb 新消息回调函数,请参考[TIMRecvNewMsgCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 如果用户是登录状态,ImSDK收到新消息会通过此接口设置的回调抛出,另外需要注意,抛出的消息不一定是未读的消息,
|
||||
* 只是本地曾经没有过的消息(例如在另外一个终端已读,拉取最近联系人消息时可以获取会话最后一条消息,如果本地没有,会通过此方法抛出)。
|
||||
* 在用户登录之后,ImSDK会拉取离线消息,为了不漏掉消息通知,需要在登录之前注册新消息通知。
|
||||
*/
|
||||
timRenderInstance.TIMAddRecvNewMsgCallback({
|
||||
callback:(args)=>{
|
||||
callback({
|
||||
type: 'TIMAddRecvNewMsgCallback',
|
||||
data: JSON.parse(args[0])
|
||||
})
|
||||
},
|
||||
user_data: "test"
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.3 设置消息已读回执回调
|
||||
* @param cb 消息已读回执回调,请参考[TIMMsgReadedReceiptCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 发送方发送消息,接收方调用接口[TIMMsgReportReaded]()上报该消息已读,发送方ImSDK会通过此接口设置的回调抛出。
|
||||
*/
|
||||
timRenderInstance.TIMSetMsgReadedReceiptCallback({
|
||||
callback:(args)=>{
|
||||
callback({
|
||||
type: 'TIMSetMsgReadedReceiptCallback',
|
||||
data: JSON.parse(args[0])
|
||||
})
|
||||
},
|
||||
user_data: "test"
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.4 设置接收的消息被撤回回调
|
||||
* @param cb 消息撤回通知回调,请参考[TIMMsgRevokeCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 发送方发送消息,接收方收到消息。此时发送方调用接口[TIMMsgRevoke]()撤回该消息,接收方的ImSDK会通过此接口设置的回调抛出。
|
||||
*/
|
||||
timRenderInstance.TIMSetMsgRevokeCallback({
|
||||
callback:(args)=>{
|
||||
callback({
|
||||
type: 'TIMSetMsgRevokeCallback',
|
||||
data: JSON.parse(args[0])
|
||||
});
|
||||
},
|
||||
user_data: "test"
|
||||
})
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.5 设置消息内元素相关文件上传进度回调
|
||||
* @param cb 文件上传进度回调,请参考[TIMMsgElemUploadProgressCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 设置消息元素上传进度回调。当消息内包含图片、声音、文件、视频元素时,ImSDK会上传这些文件,并触发此接口设置的回调,用户可以根据回调感知上传的进度
|
||||
*/
|
||||
timRenderInstance.TIMSetMsgElemUploadProgressCallback({
|
||||
callback:(args)=>{
|
||||
try{
|
||||
const [message, index, cur_size, total_size, user_data] = JSON.parse(args)
|
||||
callback({
|
||||
type: 'TIMSetMsgElemUploadProgressCallback',
|
||||
data: {
|
||||
message: JSON.parse(message),
|
||||
index,
|
||||
cur_size,
|
||||
total_size,
|
||||
user_data
|
||||
}
|
||||
})
|
||||
}catch(err){
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
user_data: "test"
|
||||
})
|
||||
|
||||
/**
|
||||
* @brief 1.6 设置群组系统消息回调
|
||||
* @param cb 群消息回调,请参考[TIMGroupTipsEventCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 群组系统消息事件包括 加入群、退出群、踢出群、设置管理员、取消管理员、群资料变更、群成员资料变更。此消息是针对所有群组成员下发的
|
||||
*/
|
||||
timRenderInstance.TIMSetGroupTipsEventCallback({
|
||||
callback:(args)=>{
|
||||
callback({
|
||||
type: 'TIMSetGroupTipsEventCallback',
|
||||
data: JSON.parse(args[0])
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.7 设置群组属性变更回调
|
||||
* @param cb 群组属性变更回调,请参考[TIMGroupAttributeChangedCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 某个已加入的群的属性被修改了,会返回所在群组的所有属性(该群所有的成员都能收到)
|
||||
*/
|
||||
timRenderInstance.TIMSetGroupAttributeChangedCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetGroupAttributeChangedCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.8 设置会话事件回调
|
||||
* @param cb 会话事件回调,请参考[TIMConvEventCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* > 会话事件包括:
|
||||
* >> 会话新增
|
||||
* >> 会话删除
|
||||
* >> 会话更新。
|
||||
* >> 会话开始
|
||||
* >> 会话结束
|
||||
* > 任何产生一个新会话的操作都会触发会话新增事件,例如调用接口[TIMConvCreate]()创建会话,接收到未知会话的第一条消息等。
|
||||
* 任何已有会话变化的操作都会触发会话更新事件,例如收到会话新消息,消息撤回,已读上报等。
|
||||
* 调用接口[TIMConvDelete]()删除会话成功时会触发会话删除事件。
|
||||
*/
|
||||
timRenderInstance.TIMSetConvEventCallback({
|
||||
callback:(args)=>{
|
||||
callback({
|
||||
type: 'TIMSetConvEventCallback',
|
||||
data: {
|
||||
type:args[0],
|
||||
data:args[1]!=="" ? JSON.parse(args[1]) : []
|
||||
}
|
||||
})
|
||||
},
|
||||
user_data:"TIMSetConvEventCallback"
|
||||
})
|
||||
|
||||
/**
|
||||
* @brief 1.9 设置会话未读消息总数变更的回调
|
||||
* @param cb 会话未读消息总数变更的回调,请参考[TIMConvTotalUnreadMessageCountChangedCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
*/
|
||||
timRenderInstance.TIMSetConvTotalUnreadMessageCountChangedCallback({
|
||||
callback:(args)=>{
|
||||
callback({
|
||||
type: 'TIMSetConvTotalUnreadMessageCountChangedCallback',
|
||||
data: args[0]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.10 设置网络连接状态监听回调
|
||||
* @param cb 连接事件回调,请参考[TIMNetworkStatusListenerCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* > 当调用接口 [TIMInit]() 时,ImSDK会去连接云后台。此接口设置的回调用于监听网络连接的状态。
|
||||
* > 网络连接状态包含四个:正在连接、连接失败、连接成功、已连接。这里的网络事件不表示用户本地网络状态,仅指明ImSDK是否与即时通信IM云Server连接状态。
|
||||
* > 可选设置,如果要用户感知是否已经连接服务器,需要设置此回调,用于通知调用者跟通讯后台链接的连接和断开事件,另外,如果断开网络,等网络恢复后会自动重连,自动拉取消息通知用户,用户无需关心网络状态,仅作通知之用
|
||||
* > 只要用户处于登录状态,ImSDK内部会进行断网重连,用户无需关心。
|
||||
*/
|
||||
timRenderInstance.TIMSetNetworkStatusListenerCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetNetworkStatusListenerCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
/**
|
||||
* @brief 1.11 设置被踢下线通知回调
|
||||
* @param cb 踢下线回调,请参考[TIMKickedOfflineCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* > 用户如果在其他终端登录,会被踢下线,这时会收到用户被踢下线的通知,出现这种情况常规的做法是提示用户进行操作(退出,或者再次把对方踢下线)。
|
||||
* > 用户如果在离线状态下被踢,下次登录将会失败,可以给用户一个非常强的提醒(登录错误码ERR_IMSDK_KICKED_BY_OTHERS:6208),开发者也可以选择忽略这次错误,再次登录即可。
|
||||
* > 用户在线情况下的互踢情况:
|
||||
* + 用户在设备1登录,保持在线状态下,该用户又在设备2登录,这时用户会在设备1上强制下线,收到 TIMKickedOfflineCallback 回调。
|
||||
* 用户在设备1上收到回调后,提示用户,可继续调用login上线,强制设备2下线。这里是在线情况下互踢过程。
|
||||
* > 用户离线状态互踢:
|
||||
* + 用户在设备1登录,没有进行logout情况下进程退出。该用户在设备2登录,此时由于用户不在线,无法感知此事件,
|
||||
* 为了显式提醒用户,避免无感知的互踢,用户在设备1重新登录时,会返回(ERR_IMSDK_KICKED_BY_OTHERS:6208)错误码,表明之前被踢,是否需要把对方踢下线。
|
||||
* 如果需要,则再次调用login强制上线,设备2的登录的实例将会收到 TIMKickedOfflineCallback 回调。
|
||||
*/
|
||||
timRenderInstance.TIMSetKickedOfflineCallback({
|
||||
callback: (...args) => {
|
||||
callback({
|
||||
type: 'TIMSetKickedOfflineCallback',
|
||||
data: args
|
||||
});
|
||||
},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.12 设置票据过期回调
|
||||
* @param cb 票据过期回调,请参考[TIMUserSigExpiredCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 用户票据,可能会存在过期的情况,如果用户票据过期,此接口设置的回调会调用。
|
||||
* [TIMLogin]()也将会返回70001错误码。开发者可根据错误码或者票据过期回调进行票据更换
|
||||
*/
|
||||
timRenderInstance.TIMSetUserSigExpiredCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetUserSigExpiredCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.13 设置添加好友的回调
|
||||
* @param cb 添加好友回调,请参考[TIMOnAddFriendCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 此回调为了多终端同步。例如A设备、B设备都登录了同一帐号的ImSDK,A设备添加了好友,B设备ImSDK会收到添加好友的推送,ImSDK通过此回调告知开发者。
|
||||
*/
|
||||
timRenderInstance.TIMSetOnAddFriendCallback({
|
||||
callback: (...args) => {
|
||||
console.log('=====添加好友=====', args)
|
||||
callback({ type: 'TIMSetOnAddFriendCallback', data: args })
|
||||
},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.14 设置删除好友的回调
|
||||
* @param cb 删除好友回调,请参考[TIMOnDeleteFriendCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 此回调为了多终端同步。例如A设备、B设备都登录了同一帐号的ImSDK,A设备删除了好友,B设备ImSDK会收到删除好友的推送,ImSDK通过此回调告知开发者。
|
||||
*/
|
||||
timRenderInstance.TIMSetOnDeleteFriendCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetOnDeleteFriendCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
/**
|
||||
* @brief 1.15 设置更新好友资料的回调
|
||||
* @param cb 更新好友资料回调,请参考[TIMUpdateFriendProfileCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 此回调为了多终端同步。例如A设备、B设备都登录了同一帐号的ImSDK,A设备更新了好友资料,B设备ImSDK会收到更新好友资料的推送,ImSDK通过此回调告知开发者。
|
||||
*/
|
||||
timRenderInstance.TIMSetUpdateFriendProfileCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetUpdateFriendProfileCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.16 设置好友添加请求的回调
|
||||
* @param cb 好友添加请求回调,请参考[TIMFriendAddRequestCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 当前登入用户设置添加好友需要确认时,如果有用户请求加当前登入用户为好友,会收到好友添加请求的回调,ImSDK通过此回调告知开发者。如果多终端登入同一帐号,每个终端都会收到这个回调。
|
||||
*/
|
||||
timRenderInstance.TIMSetFriendAddRequestCallback({
|
||||
callback: (...args) => {
|
||||
console.log('=====添加好友请求=====', JSON.parse(args[0][0]));
|
||||
callback({
|
||||
type: 'TIMSetFriendAddRequestCallback',
|
||||
data: JSON.parse(args[0][0])
|
||||
})
|
||||
},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.17 设置好友申请删除的回调
|
||||
* @param cb 好友申请删除回调,请参考[TIMFriendApplicationListDeletedCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 1. 主动删除好友申请
|
||||
* 2. 拒绝好友申请
|
||||
* 3. 同意好友申请
|
||||
* 4. 申请加别人好友被拒绝
|
||||
*/
|
||||
timRenderInstance.TIMSetFriendApplicationListDeletedCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetFriendApplicationListDeletedCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
/**
|
||||
* @brief 1.18 设置好友申请已读的回调
|
||||
* @param cb 好友申请已读回调,请参考[TIMFriendApplicationListReadCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* 如果调用 setFriendApplicationRead 设置好友申请列表已读,会收到这个回调(主要用于多端同步)
|
||||
*/
|
||||
|
||||
timRenderInstance.TIMSetFriendApplicationListReadCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetFriendApplicationListReadCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.19 设置黑名单新增的回调
|
||||
* @param cb 黑名单新增的回调,请参考[TIMFriendBlackListAddedCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
*/
|
||||
timRenderInstance.TIMSetFriendBlackListAddedCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetFriendBlackListAddedCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
/**
|
||||
* @brief 1.20 设置黑名单删除的回调
|
||||
* @param cb 黑名单删除的回调,请参考[TIMFriendBlackListDeletedCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
*/
|
||||
timRenderInstance.TIMSetFriendBlackListDeletedCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetFriendBlackListDeletedCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @brief 1.22 设置消息在云端被修改后回传回来的消息更新通知回调
|
||||
* @param cb 消息更新回调,请参考[TIMMsgUpdateCallback](TIMCloudCallback.h)
|
||||
* @param user_data 用户自定义数据,ImSDK只负责传回给回调函数cb,不做任何处理
|
||||
*
|
||||
* @note
|
||||
* > 当您发送的消息在服务端被修改后,ImSDK会通过该回调通知给您
|
||||
* > 您可以在您自己的服务器上拦截所有即时通信IM消息 [发单聊消息之前回调](https://cloud.tencent.com/document/product/269/1632)
|
||||
* > 设置成功之后,即时通信IM服务器会将您的用户发送的每条消息都同步地通知给您的业务服务器。
|
||||
* > 您的业务服务器可以对该条消息进行修改(例如过滤敏感词),如果您的服务器对消息进行了修改,ImSDK就会通过此回调通知您。
|
||||
*/
|
||||
timRenderInstance.TIMSetMsgUpdateCallback({
|
||||
callback: (...args) => {callback({ type: 'TIMSetMsgUpdateCallback', data: args })},
|
||||
userData: ""
|
||||
})
|
||||
timRenderInstance.TIMOnInvited({
|
||||
callback:(data)=>{
|
||||
callback({
|
||||
type: 'TIMOnInvited',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
})
|
||||
timRenderInstance.TIMOnRejected({
|
||||
callback:(data)=>{
|
||||
callback({
|
||||
type: 'TIMOnRejected',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
})
|
||||
timRenderInstance.TIMOnAccepted({
|
||||
callback:(data)=>{
|
||||
callback({
|
||||
type: 'TIMOnAccepted',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
})
|
||||
timRenderInstance.TIMOnCancelled({
|
||||
callback:(data)=>{
|
||||
callback({
|
||||
type: 'TIMOnCancelled',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
timRenderInstance.TIMOnTimeout({
|
||||
callback:(data)=>{
|
||||
callback({
|
||||
type: 'TIMOnTimeout',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default { timRenderInstance, initListeners: IMListeners.initListeners }
|
|
@ -0,0 +1,342 @@
|
|||
/**
|
||||
* @description imChat 腾讯-即时通讯(无ui)
|
||||
* 文档地址:https://cloud.tencent.com/document/product/269/75285
|
||||
* 文档地址:https://cloud.tencent.com/document/product/269/63007 (electron)
|
||||
* @author: zdg
|
||||
* @date 2023-07-03
|
||||
*/
|
||||
// const TimRender = require('im_electron_sdk/dist/render')
|
||||
import * as TYPES from './enumbers' // sdk相关枚举
|
||||
import MsgEnum from './msgEnum' // 消息相关枚举(自定义)
|
||||
import IMListeners from './imLiseners' // im消息-监听器
|
||||
// @ts-ignore
|
||||
const API = window.api
|
||||
// TIM生成签名
|
||||
// import * as GenerateUserSig from './userSig' // 引入签名生成器
|
||||
const isDev = process.env.NODE_ENV == 'development' // 环境
|
||||
export class ImChat {
|
||||
timChat // imChat对象
|
||||
SDKAppID // sdkID
|
||||
secretKey // key
|
||||
userID // 用户id
|
||||
timGroupId // 群组id
|
||||
userSig // 签名
|
||||
status = { // 状态
|
||||
isLogin: false, // 是否登录
|
||||
isConnect: false, // 是否连接
|
||||
}
|
||||
defOption = { // 默认配置
|
||||
// 日志等级-全量日志
|
||||
log_level: TYPES.TIMLogLevel.kTIMLog_Off,
|
||||
// 群组类型-会议群(Meeting),成员上限 6000 人
|
||||
group_type: TYPES.TIMGroupType.kTIMGroup_ChatRoom,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 构造函数
|
||||
* @param {number} SDKAppID
|
||||
* @param {string} userSig
|
||||
* @param {string} userID
|
||||
* @param {boolean} isInit
|
||||
*/
|
||||
constructor(SDKAppID, userSig, userID, isInit) {
|
||||
this.SDKAppID = SDKAppID
|
||||
this.userSig = userSig
|
||||
this.userID = userID
|
||||
// window.test = this
|
||||
if (isInit) this.init()
|
||||
}
|
||||
// 设置配置
|
||||
async setConfig() {
|
||||
const log_level = TYPES.TIMLogLevel.kTIMLog_Error
|
||||
await this.timChat.TIMSetConfig({ // TIMSetConfigParam
|
||||
json_config: { // JSONCongfig
|
||||
set_config_log_level: log_level,
|
||||
set_config_callback_log_level: log_level,
|
||||
set_config_is_log_output_console: true,
|
||||
// set_config_user_config: { // 用户配置
|
||||
// user_config_is_read_receipt: true, // true表示要收已读回执事件
|
||||
// user_config_is_sync_report: true, // true表示服务端要删掉已读状态
|
||||
// user_config_is_ignore_grouptips_unread: true, // true表示群tips不计入群消息已读计数
|
||||
// user_config_is_is_disable_storage: false, // 是否禁用本地数据库,true表示禁用,false表示不禁用。默认是false
|
||||
// user_config_group_getinfo_option // 获取群组信息默认选项
|
||||
// user_config_group_member_getinfo_option // 获取群组成员信息默认选项
|
||||
// },
|
||||
// set_config_user_define_data // 自定义数据,如果需要,初始化前设置
|
||||
},
|
||||
user_data: '',
|
||||
})
|
||||
// 日志监听
|
||||
this.timChat.TIMSetLogCallback({
|
||||
callback: data => {
|
||||
this.setConsole('%cchat-log ', data[1])
|
||||
},
|
||||
user_data: ''
|
||||
})
|
||||
}
|
||||
// 初始化-imChat
|
||||
init() {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
if(!API) reject('preload api获取失败, 初始化-未完成')
|
||||
// this.timChat = await API.getTimRender()
|
||||
this.timChat = IMListeners.timRenderInstance
|
||||
const code = await this.timChat.TIMInit()
|
||||
if (code == 0) { // 初始化成功
|
||||
this.setConsole('%cim-chat: init', '初始化成功')
|
||||
this.status.isConnect = true
|
||||
this.setConfig() // 设置日志级别
|
||||
resolve(this)
|
||||
} else { // 失败:具体请看code
|
||||
console.error('[im-chat]:初始化失败', code)
|
||||
reject(code)
|
||||
}
|
||||
} catch (error) {reject(error)}
|
||||
})
|
||||
}
|
||||
// 生成签名
|
||||
genTestUserSig() {
|
||||
// const options = {
|
||||
// SDKAppID: this.SDKAppID,
|
||||
// secretKey: this.secretKey,
|
||||
// userID: this.userID,
|
||||
// }
|
||||
// const { userSig } = GenerateUserSig.genTestUserSig(options)
|
||||
// this.userSig = userSig
|
||||
}
|
||||
/**
|
||||
* @description 监听消息
|
||||
* @param {Function} callback
|
||||
*/
|
||||
watch(callback) {
|
||||
// // 先移除监听
|
||||
// this.timChat.TIMRemoveRecvNewMsgCallback()
|
||||
// // 消息监听
|
||||
// this.timChat.TIMAddRecvNewMsgCallback({
|
||||
// callback, user_data: this.toStr('msg')
|
||||
// })
|
||||
// // 群消息监听
|
||||
// // 群组系统消息事件包括 加入群、退出群、踢出群、设置管理员、取消管理员、群资料变更、群成员资料变更。此消息是针对所有群组成员下发的
|
||||
// this.timChat.TIMSetGroupTipsEventCallback({
|
||||
// // callback, user_data: this.toStr('msg-group')
|
||||
// callback: (data) => {
|
||||
// // console.log('群消息', group_tips_event)
|
||||
// this.setConsole('%c群消息', data)
|
||||
// },
|
||||
// })
|
||||
if (this.timChat && this.status.isConnect) { // 连接成功允许监听
|
||||
this.setConsole('%cim-chat: watch', '监听成功')
|
||||
IMListeners.initListeners(callback)
|
||||
} else {
|
||||
this.setConsole('%cim-chat: watch', '监听失败, 未连接')
|
||||
}
|
||||
}
|
||||
// 登录
|
||||
login() {
|
||||
const fn = async (resolve, reject) => {
|
||||
const option = {
|
||||
userID: this.userID,
|
||||
userSig: this.userSig,
|
||||
}
|
||||
// 获取登录状态
|
||||
// [1,2,3,4] | [已登陆,登录中,未登录,登出中]
|
||||
// console.log('登录', this)
|
||||
const status = await this.timChat.TIMGetLoginStatus()
|
||||
if (status == 3) { // 未登录
|
||||
const res = await this.timChat.TIMLogin(option)
|
||||
if (res && res.code == 0) {
|
||||
// console.log('登录成功', res)
|
||||
this.setConsole('%cim-chat: login', '登录成功')
|
||||
this.status.isLogin = true
|
||||
resolve({status:0, msg:'登录成功', data:res})
|
||||
} else reject(res)
|
||||
} else {
|
||||
if (status == 1) { // 已登录
|
||||
this.setConsole('%cim-chat: login', '已登录')
|
||||
resolve({status, msg:'已登录'})
|
||||
} else if (status == 2) { // 登录中
|
||||
this.setConsole('%cim-chat: login', '登录中')
|
||||
resolve({status, msg:'登录中'})
|
||||
} else if (status == 4) { // 登出中
|
||||
this.setConsole('%cim-chat: login', '登出中')
|
||||
resolve({status, msg:'登出中'})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return new Promise(fn)
|
||||
}
|
||||
// 登出
|
||||
logout() {
|
||||
if (!this.timChat) return
|
||||
return this.timChat.TIMLogout().then(res => {
|
||||
this.setConsole('%cim-chat: logout', '登出成功')
|
||||
this.status.isLogin = false
|
||||
this.status.isConnect = false
|
||||
this.timChat.TIMUninit() // 反初始化
|
||||
return res
|
||||
}).catch(error => {
|
||||
this.setConsole('%cim-chat: logout', '登出失败', error)
|
||||
return error
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 创建群组 群名和初始成员 userID
|
||||
* @param {any} name
|
||||
* @param {any[]} memberList
|
||||
*/
|
||||
createGroup(name, memberList=[]) {
|
||||
if (!this.timChat) return
|
||||
if (!!this.timGroupId) return console.log('群组已存在')
|
||||
// 转换初始成员id,userID 转 group_member_info_identifier
|
||||
if (memberList && memberList.length) {
|
||||
memberList = memberList.map(o => ({group_member_info_identifier:o.userID}))
|
||||
}
|
||||
const option = { // CreateGroupParams
|
||||
params: { // GroupParams
|
||||
// create_group_param_group_name: 群组名称(必填)
|
||||
// create_group_param_group_id: 群组ID,不填时创建成功回调会返回一个后台分配的群ID
|
||||
// create_group_param_group_type 群组类型,默认为Public
|
||||
// create_group_param_group_member_array 群组初始成员数组
|
||||
// create_group_param_notification 群组公告
|
||||
// create_group_param_introduction 群组简介
|
||||
// create_group_param_face_url 群组头像URL
|
||||
// create_group_param_add_option 加群选项,默认为Any
|
||||
// create_group_param_max_member_num 群组最大成员数
|
||||
// create_group_param_custom_info 请参考自定义字段
|
||||
create_group_param_group_name: name,
|
||||
create_group_param_group_type: this.defOption.group_type,
|
||||
create_group_param_max_member_num: 200,
|
||||
create_group_param_group_member_array: memberList
|
||||
},
|
||||
data: '', // 用户自定义数据
|
||||
}
|
||||
// @TGS#3XVNI6ZOG
|
||||
return this.timChat.TIMGroupCreate(option).then(res => {
|
||||
if (res && res.code == 0) {
|
||||
this.setConsole('%c创建群组成功', res)
|
||||
const timGroupId = res?.json_param?.create_group_result_groupid
|
||||
if (!!timGroupId && timGroupId != 'undefined'){
|
||||
// this.setConsole('%c创建群组成功', timGroupId)
|
||||
this.timGroupId = timGroupId
|
||||
// this.setGroupMsgReceive()
|
||||
}
|
||||
}
|
||||
return res
|
||||
})
|
||||
}
|
||||
// 删除群组
|
||||
deleteGroup() {
|
||||
if (!this.timGroupId) return
|
||||
return this.timChat.TIMGroupDelete({
|
||||
groupId: this.timGroupId,
|
||||
data: '', // 用户自定义数据
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 设置群消息接收
|
||||
* @param {string} timGroupId
|
||||
*/
|
||||
setGroupMsgReceive(timGroupId) {
|
||||
if (!this.timGroupId) this.timGroupId = timGroupId || ''
|
||||
if (!this.timGroupId) return console.log('timGroupId为空')
|
||||
return this.timChat.TIMMsgSetGroupReceiveMessageOpt({
|
||||
groupId: this.timGroupId,
|
||||
opt: TYPES.TIMReceiveMessageOpt.kTIMRecvMsgOpt_Receive,
|
||||
data: '', // 用户自定义数据
|
||||
})
|
||||
}
|
||||
// 获取群组列表
|
||||
getGroupList() {
|
||||
return this.timChat.getGroupList().then(res => {
|
||||
console.log('获取群组列表', res)
|
||||
return res
|
||||
}).catch(error => {
|
||||
console.log('获取群组列表失败', error)
|
||||
return error
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 发送消息
|
||||
* @param {any} conv_id
|
||||
* @param {any} msg
|
||||
*/
|
||||
sendMsg(conv_id, msg) {
|
||||
if (!conv_id) return console.log('conv_id为空')
|
||||
if (typeof msg == 'object') msg = JSON.stringify(msg)
|
||||
const option = {
|
||||
conv_id,
|
||||
conv_type: TYPES.TIMConvType.kTIMConv_Group,
|
||||
params: {
|
||||
message_elem_array: [{
|
||||
elem_type: TYPES.TIMElemType.kTIMElem_Text,
|
||||
text_elem_content: msg
|
||||
}],
|
||||
// message_conv_id: conv_id,
|
||||
// message_conv_type: TYPES.TIMConvType.kTIMConv_Group,
|
||||
// message_sender: this.userID
|
||||
},
|
||||
user_data: '', // 用户自定义数据
|
||||
// callback: (data) => {}
|
||||
}
|
||||
// console.log('发送消息', option)
|
||||
this.setConsole('%cim-chat: 发送消息', option)
|
||||
return this.timChat.TIMMsgSendMessageV2(option)
|
||||
}
|
||||
/**
|
||||
* @description 发送群消息
|
||||
* @param {any} msg
|
||||
* @param {*} head
|
||||
* @param {*} type
|
||||
*/
|
||||
sendMsgGroup(msg, head, type) {
|
||||
const msgObj = this.getMsgObj(head, msg, type)
|
||||
// console.log('发送群消息', msgObj)
|
||||
return this.sendMsg(this.timGroupId, msgObj)
|
||||
}
|
||||
// 发送关闭(下课)消息
|
||||
sendMsgClosed(){
|
||||
const msg = this.getMsgObj(MsgEnum.HEADS.MSG_closed, '下课')
|
||||
return this.sendMsg(this.timGroupId, msg)
|
||||
}
|
||||
/**
|
||||
* @description 获取消息对象
|
||||
* @param {string} msgHead
|
||||
* @param {string} msg
|
||||
* @param {string} type
|
||||
* @param {string|number} sender
|
||||
*/
|
||||
getMsgObj(msgHead, msg, type, sender, option={}) {
|
||||
if (!msgHead) throw new Error('msgHead is required')
|
||||
if (!msg) throw new Error('msg is required')
|
||||
if (typeof msg === 'object') msg = JSON.stringify(msg)
|
||||
return {
|
||||
msgKey: msgHead,
|
||||
msgcontent: msg,
|
||||
msgType: type || MsgEnum.TYPES.TEACHER, // 默认为老师
|
||||
senduserid: sender ?? this.userID,
|
||||
...option
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @description 设置控制台样式
|
||||
* @param {string} hearStr
|
||||
* @param {string[]} args
|
||||
*/
|
||||
setConsole(hearStr,...args) {
|
||||
const css = 'color: #fff;background-color:#2ccb92;padding:3px 5px;border-radius:3px;'
|
||||
const time = new Date().toLocaleTimeString()
|
||||
if (!hearStr) hearStr = '%c' + time
|
||||
console.log(hearStr, css, ...args)
|
||||
}
|
||||
/**
|
||||
* @description 获取数据字符串
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
toStr = (data) => {
|
||||
if (typeof data === 'string') data = {type: data}
|
||||
return JSON.stringify(data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* @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_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_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_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,
|
||||
}
|
||||
}
|
||||
|
||||
export { MsgEnum as default }
|
|
@ -1,34 +1,118 @@
|
|||
/**
|
||||
* 共享数据状态-多窗口
|
||||
*/
|
||||
const { ipcRenderer } = require('electron') // app使用
|
||||
const isNode = typeof require !== 'undefined' // 是否支持node函数
|
||||
const { ipcRenderer } = isNode?require('electron'):{} // app使用
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import CircularJSON from 'circular-json'
|
||||
// import { diff } from 'jsondiffpatch'
|
||||
// const Remote = isNode?require('@electron/remote'):{} // 远程模块
|
||||
|
||||
export function shareStorePlugin({store}) {
|
||||
store.$subscribe(() => { // 自动同步
|
||||
store.$subscribe((mutation, state) => { // 自动同步
|
||||
// mutation 变量包含了变化前后的状态
|
||||
// mutation.events: key newValue target oldValue oldTarget
|
||||
// state 是变化后的状态
|
||||
// console.log('store.$subscribe', mutation, state, store)
|
||||
// 在存储变化的时候执行
|
||||
const storeName = store.$id
|
||||
// const storeName = store.$id
|
||||
const storeName = mutation.storeId
|
||||
// 用于多窗口共享(需要共享的状态名称)
|
||||
const names = ['tool']
|
||||
if (names.includes(storeName)) stateSync(store) // 需要同步
|
||||
if (names.includes(storeName)) {
|
||||
const { storeId: storeName, payload, events, type } = mutation // direct
|
||||
// if (!Object.keys(payload).length) return
|
||||
// if (type != 'direct' || !events || Array.isArray(events) || !events.key) return
|
||||
stateSyncWatch(storeName, state) // 需要同步
|
||||
}
|
||||
})
|
||||
|
||||
// 暴露方法-手动同步
|
||||
store.stateSync = () => stateSync(store)
|
||||
store.stateSync = (storeName, key, value) => {
|
||||
const state = store.$state
|
||||
if (!storeName && !!key && !!value) stateSync(storeName, key, value, state)
|
||||
else stateSyncAll(store, state)
|
||||
}
|
||||
// 暴露方法-发送当前状态-新窗口
|
||||
store.stateSyncInit = wid => stateSyncInit(wid, store)
|
||||
// 监听主线程消息-同步数据
|
||||
stateChange(store)
|
||||
}
|
||||
// 同步数据-发送给主线程
|
||||
function stateSync(store) {
|
||||
const storeName = store.$id
|
||||
const jsonStr = JSON.stringify(store.$state)
|
||||
// 通知主线程更新
|
||||
ipcRenderer.invoke('pinia-state-change', storeName, jsonStr)
|
||||
|
||||
// 同步数据-发送给主线程-单独
|
||||
function stateSync(storeName, key, value, state) {
|
||||
// console.log('state-change', storeName, key, value)
|
||||
try {
|
||||
const { data, keystr } = filterByKey(state, key, value)
|
||||
const jsonStr = JSON.stringify(data) // 从新组装-json数据
|
||||
// 更新本地数据-session
|
||||
sessionStore.set(keystr, value)
|
||||
// 通知主线程更新
|
||||
ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
|
||||
// console.log('======',keystr, jsonStr )
|
||||
} catch (error) {
|
||||
console.log('state-change-error', error)
|
||||
}
|
||||
}
|
||||
// 同步数据-发送给主线程-单独($subscribe-监听使用)
|
||||
function stateSyncWatch(storeName, newState) {
|
||||
const oldState = sessionStore.store // 旧数据
|
||||
const diffData = findDifferences(oldState, newState)
|
||||
// console.log('state-change-diffData', diffData)
|
||||
try {
|
||||
for(const key in diffData) {
|
||||
const value = diffData[key] || null
|
||||
const newValue = {} // 重新组装pinia需要的数据 {a:{b:1}} 这种
|
||||
const keyArr = key.split('.') || []
|
||||
keyArr.reduce((o,c,i)=>{o[c] = i === keyArr.length-1 ? value : {};return o[c]}, newValue)
|
||||
const jsonStr = JSON.stringify(newValue) // 从新组装-json数据
|
||||
// 更新本地数据-session
|
||||
// console.log('state-change-update:', key, value)
|
||||
sessionStore.set(key, value)
|
||||
// 通知主线程更新
|
||||
ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
|
||||
// console.log('======',key, value, jsonStr )
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('state-change-error', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 同步数据-发送给主线程-全量更新
|
||||
function stateSyncAll(store) {
|
||||
const storeName = store.$id
|
||||
const jsonStr = circularSafeStringify(store.$state)
|
||||
// 通知主线程更新
|
||||
ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
|
||||
}
|
||||
|
||||
// 发送当前数据状态
|
||||
function stateSyncInit(wid, store) {
|
||||
const storeName = store.$id
|
||||
const curJson = JSON.stringify(store.$state) // 当前数据
|
||||
// 发送同步数据给新窗口-更新状态
|
||||
ipcRenderer.invoke('pinia-state-init', wid, storeName, curJson)
|
||||
}
|
||||
|
||||
// 监听session数据变化
|
||||
function sessionWatch(store) {
|
||||
const unsubscribe = sessionStore.onDidAnyChange((newV, oldV) => {
|
||||
if (newV !== oldV) {
|
||||
console.log('session-change', newV, oldV)
|
||||
// 通知主线程更新
|
||||
// ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
|
||||
}
|
||||
})
|
||||
// unsubscribe() 取消监听
|
||||
}
|
||||
|
||||
// 同步数据-接收主线程消息
|
||||
function stateChange(store) {
|
||||
const storeName = store.$id
|
||||
ipcRenderer.on('pinia-state-set', (e, sName, jsonStr) => {
|
||||
ipcRenderer?.on('pinia-state-set', (e, sName, jsonStr) => {
|
||||
if (sName == storeName) { // 更新对应数据
|
||||
console.log('state-set', jsonStr, sName)
|
||||
const curJson = JSON.stringify(store.$state) // 当前数据
|
||||
const curJson = circularSafeStringify(store.$state) // 当前数据
|
||||
const isUp = curJson != jsonStr // 不同的时候才写入,不然会导致触发数据变化监听,导致死循环
|
||||
if (!isUp) return
|
||||
const stateJson = JSON.parse(jsonStr) // 新数据
|
||||
|
@ -39,3 +123,90 @@ function stateChange(store) {
|
|||
}
|
||||
})
|
||||
}
|
||||
//解决JSON.stringify嵌套循环产生的bug
|
||||
const circularSafeStringify = (obj) => {
|
||||
const cache = new Set();
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (cache.has(value)) {
|
||||
// 当前对象已经存在于缓存中,说明存在循环引用,返回占位符或其他处理方式
|
||||
return "[Circular Reference]";
|
||||
}
|
||||
cache.add(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤对象
|
||||
const filterByKey = (obj, key, value) => {
|
||||
let res = { data:{}, keystr:'' }
|
||||
for (let k in obj) {
|
||||
if (obj.hasOwnProperty(k)) {
|
||||
const isEqual = JSON.stringify(obj[k]) === JSON.stringify(value) // 值是否相同
|
||||
if (k === key && isEqual) {
|
||||
// 如果匹配,则添加到新对象中
|
||||
res.data[k] = obj[k];
|
||||
res.keystr = k;
|
||||
} else {
|
||||
if (obj[k] !== null && typeof obj[k] === 'object') {
|
||||
// 如果是对象,则递归处理
|
||||
const {data, keystr} = filterByKey(obj[k], key, value)
|
||||
if(!!keystr) {
|
||||
res.data[k] = data
|
||||
res.keystr = `${k}.${keystr}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 获取对象值
|
||||
const getObjValue = (obj, key) => {
|
||||
|
||||
}
|
||||
|
||||
// 找出两个对象之间的差异
|
||||
const findDifferences = (obj1, obj2) => {
|
||||
const differences = {};
|
||||
function compareObjects(o1, o2, path = '') {
|
||||
if (o1 == null) return
|
||||
if (o2 == null) return
|
||||
for (const key in o1) {
|
||||
if (o1.hasOwnProperty(key)) {
|
||||
const newPath = path ? `${path}.${key}` : key;
|
||||
if(!o2) return
|
||||
if (o2.hasOwnProperty(key)) {
|
||||
const v1 = toJsonStr(o1[key])
|
||||
const v2 = toJsonStr(o2[key])
|
||||
if (typeof o1[key] === 'object' && typeof o2[key] === 'object' && !Array.isArray(o1[key])) {
|
||||
compareObjects(o1[key], o2[key], newPath);
|
||||
} else if (v1 !== v2) {
|
||||
differences[newPath] = o2[key];
|
||||
}
|
||||
} else {
|
||||
differences[newPath] = o2[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in o2) {
|
||||
if (o2.hasOwnProperty(key) && !o1.hasOwnProperty(key)) {
|
||||
const newPath = path ? `${path}.${key}` : key;
|
||||
differences[newPath] = o2[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compareObjects(objClone(obj1), objClone(obj2));
|
||||
// 特殊处理
|
||||
|
||||
|
||||
return differences;
|
||||
}
|
||||
// 对象克隆
|
||||
const objClone = (obj) => JSON.parse(CircularJSON.stringify(obj))
|
||||
// 转换为json
|
||||
const toJsonStr = (obj) => CircularJSON.stringify(obj)
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
* @Author: 苦逼程序猿
|
||||
* @Date: 2024-09-06 16:15:32
|
||||
* @Warning: 千行代码,Bug露锋芒。
|
||||
*/
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
import Layout from '../layout/index.vue'
|
||||
|
@ -13,8 +18,14 @@ export const constantRoutes = [
|
|||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/homepage',
|
||||
redirect: '/home',
|
||||
children: [
|
||||
{
|
||||
path: '/home',
|
||||
component: () => import('@/views/desktop/index.vue'),
|
||||
name: 'desktop',
|
||||
meta: {title: '主页'}
|
||||
},
|
||||
{
|
||||
path: '/homepage',
|
||||
component: () => import('@/views/homePage/index.vue'),
|
||||
|
@ -25,13 +36,13 @@ export const constantRoutes = [
|
|||
path: '/resource',
|
||||
component: () => import('@/views/resource/index.vue'),
|
||||
name: 'resource',
|
||||
meta: {title: '资源'}
|
||||
meta: {title: '资源库'}
|
||||
},
|
||||
{
|
||||
path: '/prepare',
|
||||
component: () => import('@/views/prepare/index.vue'),
|
||||
name: 'prepare',
|
||||
meta: {title: '备课',keepAlive:true}
|
||||
meta: {title: '教学实践'}
|
||||
},
|
||||
{
|
||||
path: '/teach',
|
||||
|
@ -45,12 +56,42 @@ export const constantRoutes = [
|
|||
name: 'profile',
|
||||
meta: {title: '个人中心'}
|
||||
},
|
||||
{
|
||||
path: '/classReserv',
|
||||
component: () => import('@/views/classManage/classReserv.vue'),
|
||||
name: 'classReserv',
|
||||
meta: {title: '课程预约'}
|
||||
},
|
||||
{
|
||||
path: '/class',
|
||||
component: () => import('@/views/classManage/index.vue'),
|
||||
name: 'class',
|
||||
meta: {title: '班级中心'},
|
||||
},
|
||||
{
|
||||
path: '/classTaskAssign',
|
||||
component: () => import('@/views/classTaskAssign/index.vue'),
|
||||
name: 'classTaskAssign',
|
||||
meta: {title: '作业设计'},
|
||||
},
|
||||
{
|
||||
path: '/classTask',
|
||||
component: () => import('@/views/classTask/classTask.vue'),
|
||||
name: 'classCorrect',
|
||||
meta: {title: '作业批改'},
|
||||
},
|
||||
{
|
||||
path: '/newClassTask',
|
||||
component: () => import('@/views/classTask/newClassTask.vue'),
|
||||
name: 'newClassCorrect',
|
||||
meta: {title: '作业设计'},
|
||||
},
|
||||
{
|
||||
path: '/examReport',
|
||||
component: () => import('@/views/examReport/index.vue'),
|
||||
name: 'examReport',
|
||||
meta: {title: '考试分析'}
|
||||
},
|
||||
]
|
||||
},
|
||||
...toolRouters
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { } from '@/api/classTask/index.js'
|
||||
import { listClassmain } from '@/api/classManage/index'
|
||||
|
||||
const useClassTaskStore = defineStore('classTask',{
|
||||
state: () => ({
|
||||
classListIds: [],
|
||||
}),
|
||||
actions: {
|
||||
listClassmain(params) {
|
||||
// 获取班级列表
|
||||
return new Promise((resolve, reject) => {
|
||||
listClassmain(params)
|
||||
.then((res) => {
|
||||
this.classListIds = res.rows&&res.rows.map((item) => item.id)
|
||||
resolve(res)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
persist: true
|
||||
})
|
||||
export default useClassTaskStore
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { defineStore } from 'pinia'
|
||||
const overviewStore = defineStore(
|
||||
'overview',
|
||||
{
|
||||
state: () => {
|
||||
return {
|
||||
tableList:[]
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
getTableList(data){
|
||||
this.tableList = [...data]
|
||||
}
|
||||
}
|
||||
})
|
||||
export default overviewStore
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
const useThirdStore = defineStore('third', {
|
||||
state: () => ({
|
||||
activeGrade:'',
|
||||
gradeName:'',
|
||||
subjectName:'',
|
||||
textbookVersionId:''
|
||||
}),
|
||||
actions: {
|
||||
// 登录
|
||||
getSelectBookInfo(params){
|
||||
this.activeGrade = params.activeGrade
|
||||
this.gradeName = params.gradeName
|
||||
this.subjectName = params.subjectName
|
||||
this.textbookVersionId = params.textbookVersionId
|
||||
}
|
||||
|
||||
},
|
||||
persist: true
|
||||
})
|
||||
|
||||
export default useThirdStore
|
|
@ -2,12 +2,32 @@
|
|||
* 工具类-窗口-状态管理
|
||||
*/
|
||||
import { defineStore } from 'pinia'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
|
||||
// 默认数据
|
||||
const defData = sessionStore.store || {}
|
||||
// 延时
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
export const useToolState = defineStore('tool', {
|
||||
state: () => ({
|
||||
model: 'select', // 悬浮球-当前模式
|
||||
showBoardAll: false, // 全屏画板-是否显示
|
||||
model: 'select', // 悬浮球-当前模式
|
||||
showBoardAll: false, // 全屏画板-是否显示
|
||||
isPdfWin: false, // pdf窗口是否打开
|
||||
isToolWin: false, // 工具窗口是否打开
|
||||
curSubjectNode: {
|
||||
data: {}, // 当前教材节点 (包含当前教材 单元)
|
||||
querySearch: {} // 查询资源所需参数
|
||||
},
|
||||
...defData // 默认数据-覆盖上面的配置(不要删除, 会导致新窗口-获取状态失败)
|
||||
}),
|
||||
actions: {
|
||||
async resetDef() { // 重置数据-下课
|
||||
this.model = 'select' // 悬浮球-当前模式
|
||||
await sleep(20) // 休眠20ms
|
||||
this.showBoardAll = false // 全屏画板-是否显示
|
||||
await sleep(20) // 休眠20ms
|
||||
this.isToolWin = false // 工具窗口是否打开
|
||||
}
|
||||
}
|
||||
})
|
|
@ -17,7 +17,7 @@ const uploaderStore = defineStore('uploader', {
|
|||
pushFile(payload) {
|
||||
let _this = this
|
||||
let arr = payload.filter((item) => {
|
||||
const MAX_SIZE = 100 * 1024 * 1024; // 2MB
|
||||
const MAX_SIZE = 500 * 1024 * 1024; // 2MB
|
||||
if (item.size > MAX_SIZE) {
|
||||
ElMessage.error('文件大小不能超过 100MB!');
|
||||
return false;
|
||||
|
|
|
@ -37,7 +37,7 @@ const useUserStore = defineStore('user', {
|
|||
return new Promise((resolve, reject) => {
|
||||
getInfo()
|
||||
.then((res) => {
|
||||
res.user.avatar = import.meta.env.VITE_APP_BASE_API + res.user.avatar
|
||||
// res.user.avatar = import.meta.env.VITE_APP_BASE_API + res.user.avatar
|
||||
const user = res.user
|
||||
this.user = user
|
||||
const avatar = user.avatar == '' || user.avatar == null ? defAva : user.avatar
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
|
||||
/**
|
||||
* @description 公共工具类
|
||||
* @author zdg
|
||||
* @date 2024-4-26
|
||||
*/
|
||||
|
||||
// ============= 文件工具--相关 ===================
|
||||
/**
|
||||
* 获取上传文件
|
||||
*/
|
||||
export function getFiles() {
|
||||
const cb = resolve => {
|
||||
const fileDom = document.createElement('input')
|
||||
fileDom.type = 'file'
|
||||
fileDom.onchange = e => {
|
||||
resolve(e.target.files)
|
||||
return fileDom.remove()
|
||||
}
|
||||
fileDom.click()
|
||||
}
|
||||
return new Promise(cb)
|
||||
}
|
||||
|
||||
// ============= 数学公式--相关 ===================
|
||||
/**
|
||||
* @description 计算两点直线距离 (获取直径)
|
||||
* (欧几里得距离公式): [ \text{distance} = \sqrt{(x2 - x1)^2 + (y2 - y1)^2} ]
|
||||
* @param {*} x1
|
||||
* @param {*} y1
|
||||
* @param {*} x2
|
||||
* @param {*} y2
|
||||
*/
|
||||
export function getDistance(x1,y1,x2,y2) {
|
||||
return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2))
|
||||
}
|
||||
// 获取半径
|
||||
export function getRadius(x1,y1,x2,y2) { return getDistance(x1,y1,x2,y2) / 2 }
|
||||
|
||||
/**
|
||||
* 计算某个值在总数中所占的百分比。
|
||||
*
|
||||
* 此函数用于根据给定的值和总数,计算该值占总数的百分比。它还支持指定百分比的小数位数。
|
||||
* 如果计算结果小于0,则返回0;如果大于100,则返回100,以确保百分比的合理范围。
|
||||
*
|
||||
* @param {number} v - 待计算的值。
|
||||
* @param {number} total - 总数。
|
||||
* @param {number} [step=2] - 百分比的小数位数,默认为2。
|
||||
* @returns {number} - 返回计算后的百分比,保证在0到100之间。
|
||||
*/
|
||||
export function getPercent(v, total, step=2) {
|
||||
!v && (v = 0)
|
||||
!total && (total = 1)
|
||||
// 计算百分比,保留指定的小数位,并转换为数字类型
|
||||
let res = (v / total * 100).toFixed(step)-0
|
||||
|
||||
// 确保百分比在0到100之间
|
||||
return res < 0 ? 0 : res > 100 ? 100 : res
|
||||
}
|
||||
|
||||
// ============= 格式化--相关 ===================
|
||||
|
||||
/**
|
||||
* @description 手机号隐藏中间几位
|
||||
* @param {*} phone 手机号
|
||||
* @param {*} start 前面保留几位 默认 3位
|
||||
* @param {*} end 后面保留几位 默认 3位
|
||||
* @param {*} rstr 替换字符 默认 ****
|
||||
* @returns
|
||||
*/
|
||||
export function phoneHideFormat(phone, start = 3, end = 4, rstr = '****') {
|
||||
// const reg = /^(\d{3})\d*(\d{4})$/
|
||||
if (!phone) return ''
|
||||
const reg = new RegExp(`(\\d\{${start}\})\\d*(\\d\{${end}\})`)
|
||||
return phone.replace(reg, `$1${rstr}$2`)
|
||||
}
|
||||
|
||||
// ============= 习题工具--相关 ===================
|
||||
/**
|
||||
* @description 将字符串转换为数组
|
||||
* @param {*} str
|
||||
* @returns
|
||||
*/
|
||||
export function quizStrToList(str = '') {
|
||||
if (!str) return []
|
||||
let resList = []
|
||||
if (isJson(str, true)) resList = JSON.parse(str) // 数组对象
|
||||
else if (str.includes('#&')) resList = str.split('#&') // 字符串数组 #&
|
||||
else if (str.includes(',')) resList = str.split(',') // 字符串数组 ,
|
||||
else resList = [str]
|
||||
return resList
|
||||
}
|
||||
|
||||
// ============= 常用工具--相关 ===================
|
||||
|
||||
export function isJson(str, isArray = false) {
|
||||
if(typeof str == 'string'){
|
||||
try {
|
||||
const res = JSON.parse(str)
|
||||
let isBool = typeof res == 'object' && res
|
||||
if(isBool && isArray) isBool = Array.isArray(res)
|
||||
return isBool
|
||||
} catch (error) {}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//获取临时唯一ID
|
||||
export const generateUniqueID = ()=> {
|
||||
const date = new Date();
|
||||
const timestamp = date.getTime().toString(36); // 使用36进制转换时间戳
|
||||
const random = Math.random().toString(36).substring(2, 9); // 获取随机数的一部分并转换为36进制
|
||||
return timestamp + random;
|
||||
}
|
||||
|
||||
// 清理参数中的undefined、null
|
||||
export const removePropertyOf = function(obj){
|
||||
Object.keys(obj).forEach(item=>{
|
||||
if(obj[item] === undefined || obj[item] === null) delete obj[item]
|
||||
})
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除children 为空 -- tree
|
||||
*/
|
||||
export function removeTree(list) {
|
||||
var this_ = this
|
||||
for (var i in list) {
|
||||
if (list[i].children.length == 0) {
|
||||
list[i].children = undefined
|
||||
} else {
|
||||
this_.removeTree(list[i].children)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ============= 校验工具--相关 ===================
|
||||
|
||||
// url校验
|
||||
export const validateUrl = (url) => {
|
||||
const regex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
|
||||
if (!regex.test(url.trim())) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ============= 时间工具--相关 ===================
|
||||
|
||||
/**
|
||||
* @description 时间 秒 转化 时分秒
|
||||
* @param {*} seconds
|
||||
* @returns
|
||||
*/
|
||||
export function formatTime(seconds) {
|
||||
seconds = parseInt(seconds) // 转换整数
|
||||
const h = (Math.floor(seconds / 3600)+'').padStart(2,'0')
|
||||
const m = (Math.floor((seconds % 3600) / 60)+'').padStart(2,'0')
|
||||
const s = ((seconds % 60)+'').padStart(2,'0')
|
||||
return `${h}:${m}:${s}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 时间格式化
|
||||
* @param {*} time
|
||||
* @param {*} fmt
|
||||
* @returns
|
||||
*/
|
||||
export function formatDate(time, fmt = 'yyyy-MM-dd') {
|
||||
let date
|
||||
if (time) {
|
||||
if (typeof time === 'object') {
|
||||
date = time
|
||||
} else {
|
||||
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||
time = parseInt(time)
|
||||
}
|
||||
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||
time = time * 1000
|
||||
}
|
||||
date = new Date(time)
|
||||
}
|
||||
} else {
|
||||
date = new Date()
|
||||
}
|
||||
if (/(y+)/.test(fmt)) {
|
||||
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
|
||||
}
|
||||
const o = {
|
||||
'M+': date.getMonth() + 1,
|
||||
'd+': date.getDate(),
|
||||
'h+': date.getHours(),
|
||||
'm+': date.getMinutes(),
|
||||
's+': date.getSeconds(),
|
||||
'a': date.getDay()
|
||||
}
|
||||
for (const k in o) {
|
||||
if (new RegExp(`(${k})`).test(fmt)) {
|
||||
if (k === 'a') {
|
||||
const str = ['日', '一', '二', '三', '四', '五', '六'][o[k]]
|
||||
fmt = fmt.replace(RegExp.$1, str)
|
||||
continue
|
||||
}
|
||||
const str = o[k] + ''
|
||||
// fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str))
|
||||
fmt = fmt.replace(RegExp.$1, str.padStart(2, '0'))
|
||||
}
|
||||
}
|
||||
return fmt
|
||||
}
|
||||
/**
|
||||
* 快速获取日期
|
||||
* num: 1 今日 2 昨日 3 本周 4 上周 5 本月
|
||||
* bool: true 有时分秒 false | null 不要时分秒
|
||||
*/
|
||||
export function getDateStr(num, bool) {
|
||||
let now = new Date() // 当前日期
|
||||
let nowDayOfWeek = now.getDay() // 今天本周的第几天
|
||||
let nowDay = now.getDate() // 当前日
|
||||
let nowMonth = now.getMonth() // 当前月
|
||||
let nowYear = now.getYear() // 当前年
|
||||
nowYear += (nowYear < 2000) ? 1900 : 0 //
|
||||
let monthStartDate = new Date(nowYear, nowMonth, 1)
|
||||
let monthEndDate = new Date(nowYear, nowMonth + 1, 1)
|
||||
let days = (monthEndDate - monthStartDate) / (1000 * 60 * 60 * 24)
|
||||
|
||||
let fmt = 'yyyy-MM-dd'
|
||||
let startTime = ''
|
||||
let endTime = ''
|
||||
|
||||
if (num == 1) { // 今日
|
||||
startTime = formatDate(now, fmt)
|
||||
endTime = startTime
|
||||
}
|
||||
if (num == 2) { // 昨日
|
||||
now.setDate(nowDay - 1)
|
||||
startTime = formatDate(now, fmt)
|
||||
endTime = startTime
|
||||
}
|
||||
if (num == 3) { // 本周
|
||||
startTime = formatDate(new Date(nowYear, nowMonth, nowDay - nowDayOfWeek), fmt)
|
||||
endTime = formatDate(new Date(nowYear, nowMonth, nowDay + (6 - nowDayOfWeek)), fmt)
|
||||
}
|
||||
if (num == 4) { // 上周
|
||||
startTime = formatDate(new Date(nowYear, nowMonth, nowDay - nowDayOfWeek - 7), fmt)
|
||||
endTime = formatDate(new Date(nowYear, nowMonth, nowDay - nowDayOfWeek - 1), fmt)
|
||||
}
|
||||
if (num == 5) { // 本月
|
||||
startTime = formatDate(new Date(nowYear, nowMonth, 1), fmt)
|
||||
endTime = formatDate(new Date(nowYear, nowMonth, days), fmt)
|
||||
}
|
||||
if (num == 6) { // 本年
|
||||
startTime = formatDate(new Date(nowYear, 0, 1), fmt)
|
||||
endTime = formatDate(new Date(nowYear, 11, 31), fmt)
|
||||
}
|
||||
if (bool) {
|
||||
startTime += ' 00:00:00'
|
||||
endTime += ' 23:59:59'
|
||||
}
|
||||
return [startTime, endTime]
|
||||
}
|
||||
/**
|
||||
* 获取 昨天 今天 明天 一周前 一月前 1-5
|
||||
*/
|
||||
export function getDateStr1(num, fmt = 'yyyy-MM-dd hh:mm:ss') {
|
||||
const now = new Date() // 当前日期
|
||||
const curr = Date.now() // 当前时间戳
|
||||
if (num == 1) return formatDate(new Date(curr-(24*60*60*1000)),fmt) // 昨天
|
||||
else if (num == 2) return formatDate(now,fmt) // 今天
|
||||
else if (num == 3) return formatDate(new Date(curr+(24*60*60*1000)),fmt) // 明天
|
||||
else if (num == 4) return formatDate(new Date(curr-(7*24*60*60*1000)),fmt) // 一周前
|
||||
else if (num == 5) return formatDate(new Date(curr-(30*24*60*60*1000)),fmt) // 一月前
|
||||
return ''
|
||||
}
|
||||
/**
|
||||
* 获取当前 0:0:0:0 时间
|
||||
* @param {*} [fmt] 格式 'yyyy-MM-dd hh:mm:ss'
|
||||
*/
|
||||
export function getDateNow(fmt) {
|
||||
const date = new Date()
|
||||
date.setHours(0, 0, 0, 0)
|
||||
if (fmt) return formatDate(date, fmt)
|
||||
return date
|
||||
}
|
||||
/** 默认加上-时分秒 */
|
||||
export function toTimeStr(arr = ['','']) {
|
||||
return [`${arr[0]} 00:00:00`, `${arr[1]} 23:59:59`]
|
||||
}
|
||||
|
||||
/**
|
||||
* 秒转时分
|
||||
*/
|
||||
export function timeToStr(time,str = '时分秒', isPad = false) {
|
||||
let s = parseInt(time)
|
||||
let h = 0, m = 0 // 初始化时|分
|
||||
if (s >= 60) {
|
||||
// 如果秒数大于60,将秒数转换成整数
|
||||
m = parseInt(s / 60) // 获取分钟,除以60取整数,得到整数分钟
|
||||
s = parseInt(s % 60) // 获取秒数,秒数取佘,得到整数秒数
|
||||
if (m >= 60) { // 如果分钟大于60,将分钟转换成小时
|
||||
h = parseInt(m / 60) // 获取小时,获取分钟除以60,得到整数小时
|
||||
m = parseInt(m % 60) // 获取小时后取佘的分,获取分钟除以60取佘的分
|
||||
}
|
||||
}
|
||||
const toStr = v => v.toString().padStart(2, '0') // 转换字符
|
||||
const arr = str.split('')
|
||||
if (isPad) return `${h?toStr(h)+arr[0]:''}${m?toStr(m)+arr[1]:''}${toStr(s)}${arr[2]||''}`
|
||||
return `${h?h+arr[0]:''}${m?m+arr[1]:''}${s?s+arr[2]||'':''}`
|
||||
}
|
|
@ -76,3 +76,83 @@ export const toTimeText = (timeStamp, simple) => {
|
|||
}
|
||||
return timeText
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns 当前年-月-日
|
||||
*/
|
||||
export const getCurrentTime = (format)=> {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = now.getDate().toString().padStart(2, '0');
|
||||
const hours = now.getHours().toString().padStart(2, '0');
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0');
|
||||
if(format == 'YYYY-MM-DD HH:mm'){
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
if(format == 'YYYY-MM-DD'){
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
if(format == 'HH:mm'){
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
if(format == 'MMDD'){
|
||||
return `${month}${day}`;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {number} m 指定时间
|
||||
* @returns 指定时间之后的 小时:分钟
|
||||
*/
|
||||
export const getAfterMinutes = (m) => {
|
||||
const now = new Date();
|
||||
const afterMinutes = new Date(now.getTime() + m * 60 * 1000);
|
||||
let hours = afterMinutes.getHours();
|
||||
hours = hours < 10 ? ('0' + hours) : hours
|
||||
let minutes = afterMinutes.getMinutes();
|
||||
minutes = minutes < 10 ? ('0' + minutes) : minutes
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格时间格式化
|
||||
*/
|
||||
export function formatDate(cellValue) {
|
||||
if (cellValue == null || cellValue == "") return "";
|
||||
var date = new Date(cellValue)
|
||||
var year = date.getFullYear()
|
||||
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
|
||||
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
|
||||
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
|
||||
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
|
||||
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
|
||||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
|
||||
}
|
||||
export function getTimeDate() {
|
||||
var date = new Date()
|
||||
var year = date.getFullYear()
|
||||
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
|
||||
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
|
||||
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
|
||||
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
|
||||
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
|
||||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取明天日期
|
||||
* @returns
|
||||
*/
|
||||
export function getTomorrow() {
|
||||
let date = new Date();
|
||||
var year = date.getFullYear()
|
||||
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
|
||||
var day = date.getDate()+1 < 10 ? '0' + date.getDate()+1 : date.getDate()+1
|
||||
|
||||
// 获取明天的日期 .getDate() + 1
|
||||
let tomorrow = `${year}-${month}-${day}`;
|
||||
|
||||
return tomorrow;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
const JY_TOKEN = 'CA82641DA86072DEFD39E287335E035FDA6AEEC0549B58F54F4408734C8683FFAF0585CFA3B25091E588A03A65C66A80F5FF613F539D600954007A35DFFBFDC3C7BB982771C5E13F0918642CFD7596CE3718F06E5579238D92EC809AC6F4C82A9FE4B0E232A67DD3594D4DAC1C219CCBC4A7A093344446107EB11DC317526D0594249DEBBD82B740C794CF5A7065E1982B7779AF16AD25D7';
|
||||
const JY_SUBJECT = [
|
||||
{id: 10, subject: 'math3', name: '小学数学'},
|
||||
{id: 11, subject: 'chinese3', name: '小学语文'},
|
||||
{id: 12, subject: 'english3', name: '小学英语'},
|
||||
{id: 14, subject: 'science3', name: '小学科学'},
|
||||
{id: 15, subject: 'politics3', name: '小学道德与法治'},
|
||||
{id: 20, subject: 'math', name: '初中数学'},
|
||||
{id: 21, subject: 'physics', name: '初中物理'},
|
||||
{id: 22, subject: 'chemistry', name: '初中化学'},
|
||||
{id: 23, subject: 'bio', name: '初中生物'},
|
||||
{id: 24, subject: 'science', name: '初中科学'},
|
||||
{id: 25, subject: 'geography', name: '初中地理'},
|
||||
{id: 26, subject: 'chinese', name: '初中语文'},
|
||||
{id: 27, subject: 'english', name: '初中英语'},
|
||||
{id: 28, subject: 'politics', name: '初中道德与法治'},
|
||||
{id: 29, subject: 'history', name: '初中历史'},
|
||||
{id: 30, subject: 'math2', name: '高中数学'},
|
||||
{id: 31, subject: 'physics2', name: '高中物理'},
|
||||
{id: 32, subject: 'chemistry2', name: '高中化学'},
|
||||
{id: 33, subject: 'bio2', name: '高中生物'},
|
||||
{id: 35, subject: 'geography2', name: '高中地理'},
|
||||
{id: 36, subject: 'chinese2', name: '高中语文'},
|
||||
{id: 37, subject: 'english2', name: '高中英语'},
|
||||
{id: 38, subject: 'politics2', name: '高中政治'},
|
||||
{id: 39, subject: 'history2', name: '高中历史'},
|
||||
];
|
||||
|
||||
|
||||
export const JYApiListCT = async (_this, name = '高中历史') => {
|
||||
if (name === '初中政治') {
|
||||
name = '初中道德与法治';
|
||||
}
|
||||
const obj = JY_SUBJECT.filter(item => item.name == name);
|
||||
if(obj.length < 1) {
|
||||
return [];
|
||||
}
|
||||
const res = await _this.$requestGetJYW(`/${obj[0].subject}/common`, {
|
||||
headers: {
|
||||
authorization: `Token ${JY_TOKEN}`
|
||||
},
|
||||
params: {
|
||||
tp: 1,
|
||||
},
|
||||
});
|
||||
if (res.code !== 200) {
|
||||
return [];
|
||||
}
|
||||
let arrCT = [{
|
||||
label: "不限",
|
||||
value: 0
|
||||
}]
|
||||
res.data.forEach(item=> {
|
||||
if (item.Value === '选择题') {
|
||||
item.Value = '单选题';
|
||||
}
|
||||
const tmp = {
|
||||
label: item.Value,
|
||||
value: item.Key,
|
||||
}
|
||||
arrCT.push(tmp);
|
||||
})
|
||||
return arrCT;
|
||||
}
|
||||
|
||||
export const JYApiListOriginYear = () => {
|
||||
const arrYear = [{label: '不限', value: '-1'}];
|
||||
let i = 0;
|
||||
for( ; i < 10; i++) {
|
||||
const year = new Date().getFullYear();
|
||||
const s ={
|
||||
label: `${year - i}`,
|
||||
value: `${year - i}`,
|
||||
}
|
||||
arrYear.push(s);
|
||||
};
|
||||
//arrYear.push({label: '更早', value: '0'})
|
||||
return arrYear;
|
||||
}
|
||||
|
||||
export const JYApiListSO = async (_this, name = '高中历史') => {
|
||||
if (name === '初中政治') {
|
||||
name = '初中道德与法治';
|
||||
}
|
||||
|
||||
const obj = JY_SUBJECT.filter(item => item.name == name);
|
||||
if(obj.length < 1) {
|
||||
return [];
|
||||
}
|
||||
const res = await _this.$requestGetJYW(`/${obj[0].subject}/common`, {
|
||||
headers: {
|
||||
authorization: `Token ${JY_TOKEN}`
|
||||
},
|
||||
params: {
|
||||
tp: 2,
|
||||
},
|
||||
});
|
||||
if (res.code !== 200) {
|
||||
return [];
|
||||
}
|
||||
const arrSO = [{"Value": "不限", "Key": 0}, ...res.data];
|
||||
return arrSO;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const JYApiListPoint = async (_this, name = '高中历史') => {
|
||||
if (name === '初中政治') {
|
||||
name = '初中道德与法治';
|
||||
}
|
||||
|
||||
const obj = JY_SUBJECT.filter(item => item.name == name);
|
||||
if(obj.length < 1) {
|
||||
return [];
|
||||
}
|
||||
const res = await _this.$requestGetJYW(`/${obj[0].subject}/point`, {
|
||||
headers: {
|
||||
authorization: `Token ${JY_TOKEN}`
|
||||
},
|
||||
});
|
||||
if (res.code !== 200) {
|
||||
return [];
|
||||
}
|
||||
return res.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @desc: 获取菁优网的版本内容
|
||||
* @return: {*}
|
||||
*/
|
||||
export const JYApiListVersion = async (_this, query, hasPoints=true) => {
|
||||
const listVersion = {
|
||||
status: 0,
|
||||
msg: '',
|
||||
data: [],
|
||||
};
|
||||
if (query.stage === '' || query.subject === '') {
|
||||
listVersion.msg = '未选中学段或学科!';
|
||||
return listVersion;
|
||||
}
|
||||
let name = `${query.stage}${query.subject}`;
|
||||
if (name === '初中政治') {
|
||||
name = '初中道德与法治';
|
||||
}
|
||||
const result = JY_SUBJECT.filter( item => item.name===name);
|
||||
if (result.length === 0) {
|
||||
listVersion.msg = `[${name}]未找到对应菁优网教材版本, 请检查学段或学科是否匹配!`;
|
||||
return listVersion;
|
||||
}
|
||||
const JYBook = await _this.$requestGetJYW(`/${result[0].subject}/book2`, {
|
||||
headers: {
|
||||
// JYToken仅占位, 实际后续已未使用该token
|
||||
authorization: `Token ${JY_TOKEN}`
|
||||
},
|
||||
});
|
||||
if (JYBook.code !== 200) {
|
||||
listVersion.msg = `[${name}]获取菁优网教材版本失败!`;
|
||||
return listVersion;
|
||||
}
|
||||
query.list.forEach(ele => {
|
||||
if (ele.thirdkey == null) {
|
||||
listVersion.msg = `[${name}-${ele.itemtitle}]菁优网教材字段标识未设置!`;
|
||||
return;
|
||||
}
|
||||
let nPos = ele.thirdkey.indexOf('-');
|
||||
if (nPos === -1) {
|
||||
listVersion.msg = `[${name}-${ele.itemtitle}-${ele.thirdkey}]菁优网教材字段标识设置有误!`;
|
||||
return;
|
||||
}
|
||||
let editionName = ele.thirdkey.substring(0, nPos);
|
||||
let typeName = ele.thirdkey.substring(nPos+1);
|
||||
const resVersion = JYBook.data.filter( item => {
|
||||
// 高中以typeName为标识, 而小学/初中以Name为标识
|
||||
const JYTypeName = query.stage=='高中' ? item.TypeName : item.Name;
|
||||
if (item.EditionName.trim() === editionName.trim() && JYTypeName.trim() === typeName.trim()) {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
|
||||
if (resVersion.length === 0) {
|
||||
listVersion.msg = `[${name}-${ele.thirdkey}]菁优网教材字段标识查询不存在!`;
|
||||
return;
|
||||
}
|
||||
if (resVersion[0].Children.length === 0) {
|
||||
listVersion.msg = `[${name}]菁优网教材字段标识查询单元内容为空!`;
|
||||
return;
|
||||
}
|
||||
const children = JYApiListLession(resVersion[0], hasPoints);
|
||||
// 高中以typeName为标识, 而小学/初中以Name为标识
|
||||
let JYTypeName = query.stage=='高中' ? resVersion[0].TypeName : resVersion[0].Name;
|
||||
JYTypeName = JYTypeName.trim();
|
||||
const reset = {
|
||||
id: resVersion[0].ID,
|
||||
label: resVersion[0].Name.trim(),
|
||||
nodeType: 'version',
|
||||
children: children,
|
||||
editionName: resVersion[0].EditionName.trim(),
|
||||
typeName: JYTypeName,
|
||||
}
|
||||
listVersion.data.push(reset);
|
||||
})
|
||||
|
||||
if(listVersion.data.length > 0){
|
||||
listVersion.status = 1;
|
||||
}
|
||||
return listVersion;
|
||||
}
|
||||
|
||||
|
||||
const JYApiListLession = (item, hasPoints) => {
|
||||
const list = [];
|
||||
item.Children.forEach(element => {
|
||||
const tempList = formatCurJYLession(element, hasPoints);
|
||||
list.push(tempList);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
const formatCurJYLession = (item, hasPoints) => {
|
||||
const obj = {
|
||||
id: item.ID,
|
||||
label: item.Name.trim(),
|
||||
nodeType: 'unit',
|
||||
children: [],
|
||||
}
|
||||
if(hasPoints) {
|
||||
if (item.Children.length === 0 && !item.hasOwnProperty('Points')) {
|
||||
obj.nodeType = 'points';
|
||||
obj.id = item.No;
|
||||
return obj;
|
||||
}
|
||||
else if (item.Children.length > 0 ){
|
||||
for(let i=0; i<item.Children.length; i++){
|
||||
const child = formatCurJYLession(item.Children[i], hasPoints);
|
||||
obj.children.push(child);
|
||||
}
|
||||
}
|
||||
else if (item.Children.length === 0 && item.Points.length > 0){
|
||||
obj.nodeType = 'lession';
|
||||
for(let i=0; i<item.Points.length; i++){
|
||||
const child = formatCurJYLession(item.Points[i], hasPoints);
|
||||
obj.children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (item.Children.length === 0) {
|
||||
obj.nodeType = 'lession';
|
||||
obj.id = item.ID;
|
||||
return obj;
|
||||
}
|
||||
else if (item.Children.length > 0 ){
|
||||
for(let i=0; i<item.Children.length; i++){
|
||||
const child = formatCurJYLession(item.Children[i], hasPoints);
|
||||
obj.children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
|
@ -0,0 +1,310 @@
|
|||
export const isJson = (str) =>{
|
||||
if (typeof str == 'string') {
|
||||
try {
|
||||
let obj=JSON.parse(str);
|
||||
if(typeof obj == 'object' && obj ){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @description processExamQuestion 格式化试题
|
||||
* @param {*} row
|
||||
*/
|
||||
export const processExamQuestion = (row) => {
|
||||
for (var i=0; i<row.length; i++) {
|
||||
if (isJson(row[i].workanalysis)) {
|
||||
//1、先默认格式化 格式化各项内容(待优化, 后续界面显示的为format的值)
|
||||
row[i].titleFormat = row[i].title; // 题目
|
||||
row[i].examdateFormat = row[i].examdate; // ?考试日期 eg: 2024-07-11 14:39:27"
|
||||
row[i].workdescFormat = row[i].workdesc; // 题目选项
|
||||
row[i].workanswerFormat = row[i].workanswer; // 题目正确答案
|
||||
if (row[i].workanswerFormat == null || row[i].workanswerFormat == '') {
|
||||
row[i].workanswerFormat = '见试题解答内容';
|
||||
}
|
||||
|
||||
// workanalysis 解析内容(analyse:解答; method:分析; discuss:点评; )
|
||||
var jjj = JSON.parse(row[i].workanalysis);
|
||||
row[i].analyse = jjj.analyse;
|
||||
row[i].method = jjj.method;
|
||||
row[i].discuss = jjj.discuss;
|
||||
//row[i].discusscollapse = false;
|
||||
if(row[i].examdate !== null && row[i].examdate !== undefined ){
|
||||
row[i].examdate = row[i].examdate.substring(0, 10);
|
||||
}
|
||||
|
||||
// 具体题型数据结构处理
|
||||
if (row[i].worktype == '复合题') {
|
||||
// 旧类型
|
||||
if(row[i].title.indexOf('!@#$%') !== -1) {
|
||||
// 1.选项解析替换
|
||||
const options = JSON.parse(row[i].workdesc);
|
||||
// 题目(背景材料+复合题目)
|
||||
const bjTitle = row[i].title.split('!@#$%')[0];
|
||||
const tmTitles = row[i].title.split('!@#$%').filter((it,ix)=>ix>0);
|
||||
// console.log(bjTitle,'背景标题');
|
||||
// console.log(tmTitles,'复合题目');
|
||||
let titls = [];
|
||||
options.forEach((element,index1) => {
|
||||
const workDescArr = element.split('#&');
|
||||
let tmp = '';
|
||||
let j=0;
|
||||
for(; j<workDescArr.length; j++){
|
||||
if(j%2 == 0){
|
||||
tmp += `<div style='width:80%;display:flex;'>`;
|
||||
}
|
||||
const char = String.fromCharCode(65+j);
|
||||
tmp += `<div style='display:flex;margin-left:2%;width:35%;overflow:hidden;text-overflow:ellipsis;font-size:0.9em;'>${char}.${workDescArr[j]}</div>`;
|
||||
if(j%2 == 1){
|
||||
tmp += '</div>';
|
||||
}
|
||||
|
||||
}
|
||||
// j此刻已自增1, 故当选项为单数时, 需要补充结束标签
|
||||
if(j%2 == 1){
|
||||
tmp += '</div>';
|
||||
}
|
||||
|
||||
// workDescArr为 [''] 表示为 判断题或者填空题,这里不需要选项
|
||||
if(workDescArr[0] != ''){
|
||||
titls.splice(index1, 1, tmp);
|
||||
}else{
|
||||
titls.splice(index1, 1, '');
|
||||
}
|
||||
});
|
||||
const s = [];
|
||||
tmTitles.map((it,ix)=>{
|
||||
s.push(it);
|
||||
titls.map((it2,ix2)=>{
|
||||
if(ix == ix2){
|
||||
s.push(it2);
|
||||
}
|
||||
})
|
||||
})
|
||||
// console.log(s,'?????????????????')
|
||||
|
||||
row[i].titleFormat = bjTitle + s.join('');
|
||||
row[i].workdescFormat = '';
|
||||
|
||||
|
||||
//2.答案 - 数字转为ABCD
|
||||
const answerArr = JSON.parse(row[i].workanswer);
|
||||
let indexLabel = 1;
|
||||
let arr = [];
|
||||
answerArr.forEach(item => {
|
||||
const arrTmp = item.answer.split('#&');
|
||||
let value = `(${indexLabel})`;
|
||||
arrTmp.forEach((element,i) => {
|
||||
if(item.type == '单选题' || item.type == '多选题'){
|
||||
value += `${String.fromCharCode(65+Number(element))}`;
|
||||
}
|
||||
if(item.type == '判断题' || item.type == '填空题'){
|
||||
// 去除下 html标签
|
||||
value += `${element.replace(/<[^>]+>/g, '')}`+ (i==arrTmp.length-1?'':'、');
|
||||
}
|
||||
if(item.type == '主观题') {
|
||||
if(element){
|
||||
console.log(element,'element')
|
||||
value += item.answer;
|
||||
}else{
|
||||
value += '答案不唯一,请参考分析解答点评!';
|
||||
}
|
||||
}
|
||||
})
|
||||
arr.push(value);
|
||||
indexLabel++;
|
||||
})
|
||||
const answer = arr.join('<br />');
|
||||
|
||||
row[i].workanswerFormat = answer;
|
||||
}
|
||||
else {
|
||||
// 处理[题干显示] - 不再需要处理
|
||||
// row[i].titleFormat = row[i].title; // 仅占位提示
|
||||
|
||||
/**
|
||||
* 处理[选项显示] - 特殊结构
|
||||
* [
|
||||
* {type: '单选题', title: '题目1', options: ['ABC123','ABC123']},
|
||||
* {type: '多选题', title: '题目1', options: ['ABC123','ABC123']},
|
||||
* {type: '填空题', title: '题目1', options: []},
|
||||
* {type: '判断题', title: '题目1', options: []},
|
||||
* {type: '主观题', title: '题目1', options: []},
|
||||
* ]
|
||||
*/
|
||||
let workDescArr = JSON.parse(row[i].workdesc);
|
||||
let workDescHtml = `<div style='width:80%;display:flex;>`;
|
||||
workDescArr.map( (item, index) => {
|
||||
if(item.type == '单选题' || item.type == '多选题'){
|
||||
workDescHtml += `<div style='width:80%;display:flex;'>${index+1}. ${item.title}</div>`;
|
||||
let tmp = '';
|
||||
let j=0;
|
||||
let optionsArr = item.options;
|
||||
for(; j<optionsArr.length; j++){
|
||||
if(j%2 == 0){
|
||||
tmp += `<div style='width:80%;display:flex;'>`;
|
||||
}
|
||||
const char = String.fromCharCode(65+j);
|
||||
tmp += `<div style='display:flex;margin-left: 2%; width: 36%'>${char}.${optionsArr[j]}</div>`;
|
||||
if(j%2 == 1){
|
||||
tmp += '</div>';
|
||||
}
|
||||
}
|
||||
// j此刻已自增1, 故当选项为单数时, 需要补充结束标签
|
||||
if(j%2 == 1){
|
||||
tmp += '</div>';
|
||||
}
|
||||
|
||||
workDescHtml += tmp;
|
||||
}
|
||||
else if(item.type == '填空题' || item.type == '判断题' || item.type == '主观题'){
|
||||
workDescHtml += `<div style='width:80%;display:flex;'>${index+1}. ${item.title}</div>`;
|
||||
}
|
||||
})
|
||||
workDescHtml += '</div>';
|
||||
row[i].workdescFormat = workDescHtml;
|
||||
|
||||
/**
|
||||
* 处理[答案显示] - 特殊结构
|
||||
* [
|
||||
* {type: '单选题', answer: ['0']},
|
||||
* {type: '多选题', answer: ['0','1']},
|
||||
* {type: '填空题', answer: ['填空1','填空2']},
|
||||
* {type: '判断题', answer: ['0'/'1']},
|
||||
* {type: '主观题', answer: [xxxx]},
|
||||
* ]
|
||||
*/
|
||||
let workAnswerArr = JSON.parse(row[i].workanswer);
|
||||
let workAnswerHtml = ``;
|
||||
workAnswerArr.map( (item, index) => {
|
||||
const answerArr = item.answer; //JSON.parse(item.answer);
|
||||
if(item.type == '单选题' || item.type == '多选题'){
|
||||
const answer = answerArr.map( (item) => {
|
||||
return String.fromCharCode(65+Number(item))
|
||||
}).join('');
|
||||
workAnswerHtml += `<div style='display:flex;'>${index+1}. ${answer}</div>`;
|
||||
}
|
||||
else if(item.type == '填空题' ){
|
||||
const answer = answerArr.join('、');
|
||||
workAnswerHtml += `<div style='display:flex;'>${index+1}. ${answer}</div>`;
|
||||
}
|
||||
else if(item.type == '判断题' ){
|
||||
const answer = answerArr.map( (item) => {
|
||||
return item === '1' ? '正确' : '错误'
|
||||
}).join('、');
|
||||
workAnswerHtml += `<div style='display:flex;'>${index+1}. ${answer}</div>`;
|
||||
}
|
||||
else if(item.type == '主观题' ){
|
||||
// 复合题里面的主观题只有一个答案,或没填
|
||||
const answer = answerArr.join('、');
|
||||
if(answerArr[0]){
|
||||
workAnswerHtml += `<div style='display:flex;'>${index+1}. ${answer}</div>`;
|
||||
}else{
|
||||
workAnswerHtml += `<div style='display:flex;'>${index+1}. ${answer}答案不唯一,请参考分析解答点评!</div>`;
|
||||
}
|
||||
}
|
||||
})
|
||||
row[i].workanswerFormat = workAnswerHtml;
|
||||
}
|
||||
|
||||
}
|
||||
else if(row[i].worktype == '主观题' || (row[i].worktype!=='单选题' && row[i].worktype!=='多选题' && row[i].worktype!=='填空题' && row[i].worktype!=='判断题')) {
|
||||
// 处理[选项显示] - 主观题中无选项, 故置空
|
||||
row[i].workdescFormat = '';
|
||||
row[i].workanswerFormat = '';
|
||||
// 答案处理- eg: "\"不唯一的答案,参考\""
|
||||
if (row[i].workanswer && row[i].workanswer != '') {
|
||||
row[i].workanswerFormat = JSON.parse(row[i].workanswer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||
// 通用选项结构 ['ABC123','ABC123'] | ['ABC123','ABC123'] | [](填空题无选项) | [](判断题无选项)
|
||||
let workDescArr = [];
|
||||
if (row[i].workdesc.charAt(0) === '[' && row[i].workdesc.charAt(row[i].workdesc.length - 1) === ']') {
|
||||
//123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||
workDescArr = JSON.parse(row[i].workdesc);
|
||||
}
|
||||
else if(row[i].workdesc.indexOf('#&') !== -1) {
|
||||
workDescArr = row[i].workdesc.split('#&');
|
||||
}
|
||||
else if(row[i].workdesc.indexOf(',') !== -1){
|
||||
workDescArr = row[i].workdesc.split(',');
|
||||
}
|
||||
else {
|
||||
// 单字符串直接添加至空数组(待考虑确认)
|
||||
workDescArr.push(row[i].workdesc);
|
||||
}
|
||||
|
||||
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||
// 通用答案结构 ['0'] | ['0','1'] | ['填空1','填空2'] | ['0'/'1']
|
||||
let workAnswerArr = [];
|
||||
if (row[i].workanswer.charAt(0) === '[' && row[i].workanswer.charAt(row[i].workanswer.length - 1) === ']') {
|
||||
// 123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||
workAnswerArr = JSON.parse(row[i].workanswer);
|
||||
}
|
||||
else if(row[i].workanswer.indexOf('#&') !== -1) {
|
||||
workAnswerArr = row[i].workanswer.split('#&');
|
||||
}
|
||||
else if(row[i].workanswer.indexOf(',') !== -1){
|
||||
workAnswerArr = row[i].workanswer.split(',');
|
||||
}
|
||||
else {
|
||||
// 单字符串直接添加至空数组(待考虑确认)
|
||||
workAnswerArr.push(row[i].workanswer);
|
||||
}
|
||||
|
||||
// 具体题型处理
|
||||
if(row[i].worktype == '单选题' || row[i].worktype == '多选题' ){
|
||||
// 处理[选项显示] - 拼接ABCD首序号
|
||||
let tmp = '';
|
||||
let j=0;
|
||||
for(; j<workDescArr.length; j++){
|
||||
if(j%2 == 0){
|
||||
tmp += `<div style='width:80%;display:flex;'>`;
|
||||
}
|
||||
const char = String.fromCharCode(65+j);
|
||||
tmp += `<div style='display:flex;margin-left: 2%; width: 36%'>${char}.${workDescArr[j]}</div>`;
|
||||
if(j%2 == 1){
|
||||
tmp += '</div>';
|
||||
}
|
||||
}
|
||||
if(j%2== 0){
|
||||
tmp += '</div>';
|
||||
}
|
||||
row[i].workdescFormat = tmp;
|
||||
|
||||
// 处理[答案显示] - 转换ABCD
|
||||
let arr2Char = workAnswerArr.map( (item) => {
|
||||
return String.fromCharCode(65+Number(item))
|
||||
}).join('');
|
||||
row[i].workanswerFormat = arr2Char;
|
||||
}
|
||||
else if(row[i].worktype == '填空题'){
|
||||
// 处理[选项显示] - 填空题中无选项, 故置空
|
||||
row[i].workdescFormat = '';
|
||||
|
||||
// 处理[答案显示] - 逗号连接
|
||||
row[i].workanswerFormat = workAnswerArr.join('、');
|
||||
}
|
||||
else if(row[i].worktype == '判断题'){
|
||||
// 处理[选项显示] - 判断题中无选项, 故置空
|
||||
row[i].workdescFormat = '';
|
||||
|
||||
// 处理[答案显示] - 1-正常 0-错误
|
||||
const answer = workAnswerArr.map( (item) => {
|
||||
return item === '1' ? '正确' : '错误'
|
||||
}).join('、');
|
||||
row[i].workanswerFormat = answer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ export const hasPermission = (value, def = true) => {
|
|||
if (!value) {
|
||||
return def
|
||||
}
|
||||
|
||||
const allCodeList = useUserStore().roles
|
||||
// 如果不是数组,直接判断pinia里的权限数组有没有相同的元素即可
|
||||
if (!Array.isArray(value)) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import useUserStore from '@/store/modules/user'
|
||||
const baseConfig = () => {
|
||||
const userStore = useUserStore()
|
||||
export const baseConfig = (token) => {
|
||||
const userStore = token ? {} : useUserStore()
|
||||
return {
|
||||
// Electron 设置cookie
|
||||
url: import.meta.env.VITE_APP_BUILD_BASE_PATH,
|
||||
|
@ -8,7 +8,7 @@ const baseConfig = () => {
|
|||
//cookie 名称 这里为 token
|
||||
name: 'Admin-Token',
|
||||
//cookie 值
|
||||
value: userStore.token,
|
||||
value: token ? '' : userStore.token,
|
||||
// 域名
|
||||
domain: import.meta.env.VITE_APP_DOMAIN
|
||||
}
|
||||
|
@ -47,6 +47,12 @@ export default () => {
|
|||
data: { ...baseConfig() },
|
||||
fullPath: `${baseConfig().url}/platofai`
|
||||
},
|
||||
// 文件资源 布置作业
|
||||
filehomework: {
|
||||
data: { ...baseConfig() },
|
||||
|
||||
fullPath: `${baseConfig().url}/teaching/classtaskassign?titleName=作业布置`
|
||||
},
|
||||
|
||||
getBaseData: () => {
|
||||
return {
|
||||
|
|
|
@ -1,108 +1,187 @@
|
|||
|
||||
// 所有事件
|
||||
export function handleevent(canvas, imgarr, type = 'defalut') {
|
||||
// 鼠标按下
|
||||
canvas.on('mouse:down', function (e) {})
|
||||
// // 监听鼠标移动事件
|
||||
// canvas.on('mouse:move', (options) => {
|
||||
// console.log('Mouse move event:', options);
|
||||
// });
|
||||
// // 鼠标按下
|
||||
// canvas.on('mouse:down', function (e) {})
|
||||
// // // 监听鼠标移动事件
|
||||
// // canvas.on('mouse:move', (options) => {
|
||||
// // console.log('Mouse move event:', options);
|
||||
// // });
|
||||
|
||||
// 监听鼠标释放事件
|
||||
canvas.on('mouse:up', (options) => {
|
||||
//判断是点击的哪一个
|
||||
if (type == 'defalut') {
|
||||
if (imgarr.value[0].index == 0) {
|
||||
imgarr.value[0].JSONdata = canvas.toJSON()
|
||||
}
|
||||
if (imgarr.value[1]?.index == 0) {
|
||||
imgarr.value[1].JSONdata = canvas.toJSON()
|
||||
}
|
||||
} else {
|
||||
if (imgarr.value[0].index == 1) {
|
||||
imgarr.value[0].JSONdata = canvas.toJSON()
|
||||
}
|
||||
if (imgarr.value[1]?.index == 1) {
|
||||
imgarr.value[1].JSONdata = canvas.toJSON()
|
||||
}
|
||||
}
|
||||
console.log(imgarr.value)
|
||||
// // 监听鼠标释放事件
|
||||
// canvas.on('mouse:up', (options) => {
|
||||
// //判断是点击的哪一个
|
||||
// if (type == 'defalut') {
|
||||
// if (imgarr.value[0].index == 0) {
|
||||
// imgarr.value[0].JSONdata = canvas.toJSON()
|
||||
// }
|
||||
// if (imgarr.value[1]?.index == 0) {
|
||||
// imgarr.value[1].JSONdata = canvas.toJSON()
|
||||
// }
|
||||
// } else {
|
||||
// if (imgarr.value[0].index == 1) {
|
||||
// imgarr.value[0].JSONdata = canvas.toJSON()
|
||||
// }
|
||||
// if (imgarr.value[1]?.index == 1) {
|
||||
// imgarr.value[1].JSONdata = canvas.toJSON()
|
||||
// }
|
||||
// }
|
||||
// console.log(imgarr.value)
|
||||
|
||||
})
|
||||
// })
|
||||
}
|
||||
// 保存数据
|
||||
export function savecanvsStore(imgarr, canvsStore) {
|
||||
canvsStore.pageArr = mergeAndReplace(canvsStore.pageArr, imgarr.value)
|
||||
}
|
||||
// 重显数据
|
||||
export function displayData(canvas, canvsStore, canvasobj, fabric, img) {
|
||||
export function displayData(FabricVue, canvsStore, canvasobj, fabric, img) {
|
||||
|
||||
// 初始化
|
||||
if (!canvsStore.pageArr.length) {
|
||||
fabric.Image.fromURL(img.src, (img) => {
|
||||
img.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
scaleX: canvas.value.width / img.width,
|
||||
scaleY: canvas.value.height / img.height
|
||||
})
|
||||
canvas.value.setBackgroundImage(img, canvas.value.renderAll.bind(canvas.value))
|
||||
})
|
||||
const canvas = FabricVue.value.canvas
|
||||
if (!canvas) {
|
||||
return
|
||||
}
|
||||
canvsStore.pageArr.forEach((item) => {
|
||||
//初始化
|
||||
if (item.page == canvasobj.page) {
|
||||
// canvas.value.clear() // 清除 Canvas
|
||||
// console.log(item.JSONdata, '找到一样的数据')
|
||||
canvas.value.loadFromJSON(item.JSONdata, () => {
|
||||
// 在所有对象加载完成后重新渲染画布
|
||||
canvas.value.renderAll.bind(canvas.value)
|
||||
canvas.value.renderAll()
|
||||
// requestAnimationFrame(() => {
|
||||
// // 渲染所有对象
|
||||
if (!canvsStore.pageArr.length) {
|
||||
fabric.Image.fromURL(
|
||||
img.src,
|
||||
(image) => {
|
||||
image.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
scaleX: canvas.width / img.width,
|
||||
scaleY: canvas.height / img.height,
|
||||
erasable: false // 不允许擦拭
|
||||
})
|
||||
// FabricVue.setBackgroundImage(image, FabricVue.renderAll.bind(FabricVue))
|
||||
// setBackgroundImage(image,FabricVue)
|
||||
// canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas));
|
||||
|
||||
// })
|
||||
})
|
||||
} else {
|
||||
// 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
|
||||
// // 清除 Canvas
|
||||
canvas.value.clear()
|
||||
requestAnimationFrame(function () {
|
||||
fabric.Image.fromURL(img.src, (img) => {
|
||||
img.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
scaleX: canvas.value.width / img.width,
|
||||
scaleY: canvas.value.height / img.height
|
||||
})
|
||||
canvas.value.setBackgroundImage(img, canvas.value.renderAll.bind(canvas.value))
|
||||
canvas.setBackgroundImage(image, () => {
|
||||
canvas.renderAll.bind(canvas)
|
||||
FabricVue.value.render()
|
||||
})
|
||||
// 渲染所有对象
|
||||
canvas.value.renderAll.bind(canvas.value)
|
||||
canvas.value.renderAll()
|
||||
})
|
||||
}
|
||||
},
|
||||
{crossOrigin: 'anonymous'}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const isfind=findObjectByPage(canvsStore.pageArr,canvasobj.page)
|
||||
if(isfind){
|
||||
const canvdata={
|
||||
objects:JSON.parse(isfind.JSONdata),
|
||||
version: "5.3.0"
|
||||
}
|
||||
requestAnimationFrame(function () {
|
||||
canvas.loadFromJSON(canvdata, () => {
|
||||
// 在所有对象加载完成后重新渲染画布
|
||||
// Utils.handleCanvasJSONLoaded(canvas)
|
||||
canvas.requestRenderAll() // 批量重绘
|
||||
})
|
||||
fabric.Image.fromURL(
|
||||
img.src,
|
||||
(image) => {
|
||||
image.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
scaleX: canvas.width / img.width,
|
||||
scaleY: canvas.height / img.height,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
erasable: false // 不允许擦拭
|
||||
})
|
||||
// canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas));
|
||||
canvas.setBackgroundImage(image, () => {
|
||||
canvas.renderAll.bind(canvas)
|
||||
FabricVue.value.render()
|
||||
})
|
||||
},
|
||||
{crossOrigin: 'anonymous'}
|
||||
)
|
||||
// 渲染所有对象
|
||||
canvas.requestRenderAll() // 批量重绘
|
||||
})
|
||||
}else{
|
||||
// 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
|
||||
// // 清除 Canvas
|
||||
// canvas.clear()
|
||||
requestAnimationFrame(function () {
|
||||
fabric.Image.fromURL(
|
||||
img.src,
|
||||
(image) => {
|
||||
image.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
scaleX: canvas.width / img.width,
|
||||
scaleY: canvas.height / img.height,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
erasable: false // 不允许擦拭
|
||||
})
|
||||
// canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas));
|
||||
canvas.setBackgroundImage(image, () => {
|
||||
canvas.renderAll.bind(canvas)
|
||||
FabricVue.value.render()
|
||||
})
|
||||
},
|
||||
{crossOrigin: 'anonymous'}
|
||||
)
|
||||
// 渲染所有对象
|
||||
canvas.requestRenderAll() // 批量重绘
|
||||
})
|
||||
}
|
||||
|
||||
// canvsStore.pageArr.forEach((item) => {
|
||||
// //初始化
|
||||
// if (item.page == canvasobj.page) {
|
||||
// // item.JSONdata
|
||||
// canvas.loadFromJSON(JSON.parse(item.JSONdata), () => {
|
||||
// // 在所有对象加载完成后重新渲染画布
|
||||
// // Utils.handleCanvasJSONLoaded(canvas)
|
||||
// canvas.requestRenderAll() // 批量重绘
|
||||
// })
|
||||
// } else {
|
||||
|
||||
// // 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
|
||||
// // // 清除 Canvas
|
||||
// // canvas.clear()
|
||||
// requestAnimationFrame(function () {
|
||||
// fabric.Image.fromURL(
|
||||
// img.src,
|
||||
// (image) => {
|
||||
// image.set({
|
||||
// left: 0,
|
||||
// top: 0,
|
||||
// scaleX: canvas.width / img.width,
|
||||
// scaleY: canvas.height / img.height,
|
||||
// selectable: false,
|
||||
// evented: false,
|
||||
// erasable: false // 不允许擦拭
|
||||
// })
|
||||
// // canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas));
|
||||
// canvas.setBackgroundImage(image, () => {
|
||||
// canvas.renderAll.bind(canvas)
|
||||
// FabricVue.value.render()
|
||||
// })
|
||||
// },
|
||||
// {crossOrigin: 'anonymous'}
|
||||
// )
|
||||
// // 渲染所有对象
|
||||
// canvas.requestRenderAll() // 批量重绘
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
}
|
||||
// 找到page一样的
|
||||
const findObjectByPage=(array, page)=> {
|
||||
const foundObject = array.find(obj => obj.page === page);
|
||||
return foundObject || false;
|
||||
}
|
||||
|
||||
//page 一样替换
|
||||
const mergeAndReplace = (arr1, arr2) => {
|
||||
// // 用于存储替换后的数组
|
||||
// const resultArray = array1.map(item1 => {
|
||||
// // 在 array2 中查找 page 相同的对象
|
||||
// const replacement = array2.find(item2 => item2.page == item1.page);
|
||||
// // 如果找到替换对象,则返回替换对象,否则返回原对象
|
||||
// return replacement ? replacement : item1;
|
||||
// });
|
||||
|
||||
// // 将 array2 中 page 不在 array1 中的对象追加到结果数组中
|
||||
// array2.forEach(item2 => {
|
||||
// const existsInArray1 = array1.some(item1 => item1.page == item2.page);
|
||||
// if (!existsInArray1) {
|
||||
// resultArray.push(item2);
|
||||
// }
|
||||
// });
|
||||
|
||||
// return resultArray;
|
||||
|
||||
// 创建一个映射,将 arr2 中的对象按 page 属性存储
|
||||
let map = new Map(arr2.map((item) => [item.page, item]))
|
||||
|
|