Compare commits
388 Commits
Author | SHA1 | Date |
---|---|---|
zhangxuelin | d0cbb5cbcd | |
zhangxuelin | 5686669563 | |
zhangxuelin | eb7d6df355 | |
lyc | 960ab581c3 | |
lyc | 0d6767170d | |
zhangxuelin | ae839825d4 | |
zhangxuelin | ef670dee99 | |
朱浩 | c81f393715 | |
zhangxuelin | 06bb6284c4 | |
朱浩 | b5caa7ba80 | |
朱浩 | 53ff508635 | |
zouyf | 9cce9c224c | |
“zouyf” | 171dc72ee7 | |
zhangxuelin | c2750e3da6 | |
zhangxuelin | 5550ca570c | |
lyc | 62a419356c | |
lyc | 4b2d13db8e | |
zhangxuelin | ab9bad467a | |
zhengdegang | f63bb69919 | |
zdg | b3ffa9bdaa | |
zdg | 5510021566 | |
lyc | 89e0b2f97c | |
lyc | 406e581182 | |
lyc | ea0e1c91cf | |
lyc | d4dce61555 | |
lyc | d6d5d13232 | |
zhengdegang | 33e38f8992 | |
zdg | 11968879d8 | |
zdg | db86299523 | |
zdg | c152a3d3ee | |
zhangxuelin | a366d5d4d6 | |
zhangxuelin | e0ec22c3ac | |
lyc | 9c8872579f | |
lyc | 4f32927b49 | |
zhangxuelin | e132f3927b | |
朱浩 | c290c170d3 | |
朱浩 | 3867a9603f | |
zhangxuelin | f732520d8d | |
lyc | 01fbb9d05c | |
lyc | 1334becb54 | |
lyc | c95508ed59 | |
zhangxuelin | cf540d73df | |
baigl | 35bf246975 | |
白了个白 | e3c4706880 | |
白了个白 | c507331fb5 | |
lyc | abd7be0698 | |
lyc | 92e8aa64d7 | |
zhangxuelin | 83d3cd5df8 | |
朱浩 | b1407706a8 | |
朱浩 | 996b4006a2 | |
朱浩 | 4531533382 | |
朱浩 | a601a9f8dd | |
zhengdegang | 086836e016 | |
zdg | acb0231304 | |
baigl | 94e23c3d69 | |
白了个白 | ea5ed4b6ac | |
白了个白 | 6b45fa61dd | |
白了个白 | 7aa75794dc | |
朱浩 | a2c2c4eec7 | |
CYS | 4095390f45 | |
朱浩 | 1983dafbba | |
朱浩 | 6c7284383a | |
白了个白 | f7a3aa6e53 | |
baigl | 28f79f7ab0 | |
白了个白 | 1488d7cc03 | |
白了个白 | 045b44b833 | |
baigl | b47a3bb63a | |
白了个白 | 5e954bab0b | |
白了个白 | 7322056dad | |
zhengdegang | 18370129df | |
zdg | 0bf94af5a3 | |
zdg | 7b042fd001 | |
CYS | 3bf381f5c5 | |
baigl | 016e87277b | |
白了个白 | 502c772b46 | |
白了个白 | 57cd8ea45a | |
白了个白 | 8bbd01e2b2 | |
白了个白 | 622a71253f | |
zouyf | a2591c60bc | |
“zouyf” | da8d6c4b32 | |
朱浩 | 795c4d82c7 | |
朱浩 | 8e0b9f72d5 | |
白了个白 | 4ad59a8b4e | |
白了个白 | 3de2ddb094 | |
zouyf | 6dbd140f80 | |
“zouyf” | 7bca2fdc2d | |
lyc | ee96250af2 | |
lyc | 54be3cad4f | |
zouyf | 90161e0396 | |
“zouyf” | 3a6c7dfa47 | |
zhengdegang | c0e82cd329 | |
zdg | e5667fac2d | |
zdg | f9bb7ed672 | |
朱浩 | 0ab34e7aa3 | |
朱浩 | 7751692061 | |
朱浩 | c69ae97a48 | |
朱浩 | 051dbdf4a2 | |
zhengdegang | d9b4b5c886 | |
zdg | bc195e1f51 | |
yangws | ba35677947 | |
lyc | 4b393ecec9 | |
lyc | 2565481306 | |
lyc | f708e2f741 | |
lyc | 94276db542 | |
zhengdegang | 6ef5d4575e | |
zdg | dfc10aebbe | |
zdg | ff6fab0bcc | |
zdg | 45abab7a41 | |
朱浩 | 6128f267e1 | |
朱浩 | e0a406c497 | |
lyc | cebf864b82 | |
lyc | 0ed69394ef | |
lyc | 0b6039f9d8 | |
lyc | 627c70d0e1 | |
zhangxuelin | 64272467a2 | |
zhengdegang | 792ab3284f | |
zdg | 38a50d18bf | |
zdg | 6fa0aa6e5f | |
zdg | b0fca4ad9b | |
lyc | 9381785991 | |
lyc | 28561b5016 | |
lyc | 2e2ebbd47f | |
baigl | 2b5b365e2f | |
白了个白 | 90ac7a49c7 | |
白了个白 | 75ddbf6f26 | |
白了个白 | de9235751f | |
zhengdegang | c33c0d923e | |
zdg | 94fa1a2457 | |
zdg | abce344d42 | |
zdg | 0d38a12094 | |
白了个白 | dfd53637be | |
白了个白 | c01a29bf3f | |
zhangxuelin | 436bdfe7c8 | |
zhangxuelin | 2d0be935bf | |
zhangxuelin | d6dfe966c0 | |
zdg | cf7f985020 | |
zdg | 5d6c946e08 | |
yangws | 55efc7f3e1 | |
小杨 | 122c5341e9 | |
朱浩 | c2a4f2c708 | |
yangws | eb18344b6b | |
小杨 | e080e39e1c | |
小杨 | 25f09cb4e0 | |
朱浩 | 3b67c34ccb | |
朱浩 | 187c222fe5 | |
lyc | a255b1af71 | |
lyc | 546c8ad72f | |
zhengdegang | e58cb334e4 | |
zdg | c298e1c0a2 | |
朱浩 | ed8051d0e0 | |
朱浩 | 628055fb58 | |
zhengdegang | 1698ce30dd | |
zdg | a973d296fe | |
zhangxuelin | bf4b857eb7 | |
zhangxuelin | 79839458f0 | |
zhangxuelin | d018a28200 | |
zhengdegang | 2214c5d805 | |
zdg | 9ad959dfb7 | |
lyc | 0b9fce21f3 | |
lyc | 37d0592dcc | |
yangws | a421ca94bc | |
小杨 | 6eda37c8c9 | |
lyc | 2a11173874 | |
lyc | 88869e9a8a | |
lyc | 08db70a3f2 | |
yangws | f9efb76282 | |
小杨 | ec3d728896 | |
yangws | cb0ab7f2e9 | |
小杨 | 43e9c9f44e | |
yangws | 809c639ce9 | |
小杨 | 243561dfd8 | |
zhengdegang | e5bc693f66 | |
zdg | 29888b0674 | |
zdg | 645f9ab120 | |
朱浩 | 74767b44df | |
朱浩 | ef4ec36bbe | |
zdg | c4a15e9c53 | |
yangws | 15b9f38810 | |
小杨 | a34883abbb | |
小杨 | 8f8016cb0b | |
lyc | a3079a87d0 | |
lyc | e43573bae2 | |
lyc | 83d885bcbe | |
lyc | b5669a97f1 | |
lyc | be2afadd43 | |
朱浩 | a62dde1b93 | |
lyc | b4864bc109 | |
lyc | a379176496 | |
zdg | 6a9392d498 | |
zdg | e8ecc27441 | |
zhengdegang | c2d5f2e99a | |
lyc | eaa14b1666 | |
lyc | 99c26145d8 | |
zdg | e7990dc175 | |
zdg | 31de0da3c5 | |
朱浩 | 82548ce937 | |
朱浩 | aa3be39721 | |
baigl | c3b975fee9 | |
白了个白 | ea2a8ebfda | |
白了个白 | 308465adb4 | |
yangws | 469116701b | |
小杨 | 7861727e92 | |
zdg | 80b66ffb28 | |
zhangxuelin | c5e71e6e7b | |
zhangxuelin | c7ebc6de65 | |
zhangxuelin | b906a1e688 | |
baigl | f75ab621d2 | |
白了个白 | a711eb2745 | |
白了个白 | 363b284b7c | |
zdg | 75b5884210 | |
zdg | 03f160464c | |
baigl | dc4c0fd334 | |
白了个白 | fb3ffd5dda | |
白了个白 | 5771533039 | |
zhangxuelin | 75ea11e171 | |
zhangxuelin | 851a39897b | |
zhangxuelin | 69bfc68b28 | |
zhangxuelin | 590b318757 | |
CYS | 4421ee7fea | |
baigl | 25b6da7f26 | |
白了个白 | 892b4ee3b0 | |
zdg | 354e0b44c9 | |
zdg | 5e04a4c2c2 | |
zdg | ca93eb72ac | |
yangws | 3d8f7c2a3a | |
小杨 | 78cc5edd39 | |
朱浩 | 9f4e232d6f | |
朱浩 | afc9fe89d1 | |
朱浩 | 894ed269ce | |
zdg | b15fa29ffa | |
zhangxuelin | 64d2dbcabb | |
zhangxuelin | f4cb3a8916 | |
yangws | 69db6bd8b1 | |
小杨 | e522fc8ee5 | |
yangws | 5b50404d3c | |
小杨 | bbed835ffe | |
zdg | 9ef4812acb | |
zhangxuelin | d9bdd5424f | |
zhangxuelin | 979621b515 | |
zdg | d313f901a3 | |
zdg | 8990ffdc58 | |
zdg | 13b1a895a4 | |
朱浩 | 9c7aeb1a27 | |
lyc | 7545e68a62 | |
lyc | dad772713f | |
朱浩 | a9faeea3fd | |
朱浩 | 5493de7a61 | |
朱浩 | 02ab1ffa8e | |
朱浩 | 10e76add35 | |
lyc | c1325db9e7 | |
lyc | cbc12a3d43 | |
baigl | 44b341f7ac | |
白了个白 | c8dd19382e | |
zhangxuelin | f2faa882a8 | |
zhangxuelin | e4afb26e4e | |
zhangxuelin | d60d8822a6 | |
朱浩 | 90137bc3a2 | |
朱浩 | 82c61a3fa7 | |
朱浩 | 48d631a17c | |
zhangxuelin | 1835f76e56 | |
zhangxuelin | 432c1ff71d | |
朱浩 | 7f595c09a9 | |
朱浩 | c8e10d4fe1 | |
朱浩 | f916660156 | |
zhengdegang | e0107814ea | |
zdg | bb0aa82e82 | |
zdg | 8ed13a3146 | |
zhangxuelin | 82b70558c2 | |
zhangxuelin | bace41e12e | |
zhangxuelin | 04e204928d | |
zdg | 9e5609fbdd | |
lyc | 50aff2f158 | |
lyc | a1a4e11de7 | |
lyc | 20fe5be502 | |
lyc | 7e9ac2bf74 | |
lyc | 013669dd35 | |
yangws | 3ca20cd327 | |
小杨 | c159d6be1a | |
yangws | cc44a86437 | |
小杨 | e7c6ab9e8d | |
小杨 | 85d06f306a | |
朱浩 | 656a58693f | |
zouyf | 34cfcf5a6f | |
“zouyf” | 9603406a0b | |
“zouyf” | 10a7e73c64 | |
“zouyf” | 2c238b5706 | |
“zouyf” | b47feb4a3a | |
朱浩 | 772b196b31 | |
zhangxuelin | 91e9867b68 | |
zhangxuelin | 80ac4a6e1c | |
朱浩 | 757edb0f67 | |
“zouyf” | c79911cc99 | |
zhangxuelin | 1987783fda | |
“zouyf” | 755cfc615a | |
baigl | fa776d2a8c | |
白了个白 | 122487cf8b | |
白了个白 | b7f11c5338 | |
白了个白 | c78233a2f4 | |
“zouyf” | 2e81c706b1 | |
“zouyf” | 2360d95f1c | |
zdg | 4f68ae27b3 | |
zdg | 38c041465e | |
zhangxuelin | f035cf87ea | |
zhangxuelin | e56eb059be | |
yangws | ffd6d6fab9 | |
小杨 | 23e59531ea | |
小杨 | 9e021edc67 | |
zouyf | 238f08d9ac | |
“zouyf” | d90b7c695a | |
“zouyf” | f936e726c0 | |
“zouyf” | d07dd4455c | |
zouyf | b319aeb4b2 | |
“zouyf” | 23c397e18a | |
“zouyf” | 78858111bb | |
zhengdegang | 3c6ac1f77d | |
zdg | 97962d591f | |
zdg | 86154148c6 | |
zdg | 44092a21bf | |
“zouyf” | 728ce16c8b | |
小杨 | 5b1d921378 | |
lyc | f2637c7b15 | |
lyc | 93fa916084 | |
lyc | 24d6d5887f | |
朱浩 | 222b0f54f8 | |
朱浩 | e0a56b37ef | |
baigl | 045bc5e198 | |
白了个白 | 464f39dac8 | |
白了个白 | 8e72b50c68 | |
小杨 | 0aa09e9d9a | |
lyc | 97f55ca333 | |
lyc | cb5445f9a9 | |
lyc | 560e6f0e70 | |
“zouyf” | bbe2281781 | |
“zouyf” | 66881b0025 | |
baigl | 4010411c9f | |
“zouyf” | e43c9fd038 | |
白了个白 | da1c80406f | |
白了个白 | e3199b43de | |
朱浩 | a78497b263 | |
朱浩 | 65670af54f | |
zhengdegang | e5be55f12e | |
zdg | 209ab7fafc | |
朱浩 | 87dcd9d5c3 | |
zouyf | 1f96b9b09e | |
小杨 | 0e18d74bb9 | |
小杨 | 399c4b5461 | |
“zouyf” | 35c088c25a | |
“zouyf” | 028eb0f752 | |
zhengdegang | 9a68661fb0 | |
zdg | b28abdff50 | |
zdg | b428260703 | |
lyc | b5f824ceea | |
lyc | d3b3e3bcb5 | |
zhengdegang | dcabb80757 | |
zdg | 53f26d96d4 | |
zdg | 7d7b50fa3e | |
baigl | eae2171c70 | |
白了个白 | 5fe9359d64 | |
白了个白 | be9d33fcd3 | |
lyc | 09e3264ee7 | |
lyc | 7845d9bb1a | |
zhengdegang | 2fb6828154 | |
zdg | 03d1a683be | |
zdg | d542064ee3 | |
朱浩 | 9803c09c43 | |
zhengdegang | 57fdba4578 | |
zdg | 0e5d84fcd2 | |
朱浩 | 7811bbfe3e | |
zhengdegang | 7ddf71c044 | |
zdg | 91ceb712bf | |
zdg | a192640899 | |
zdg | 08a16929f7 | |
lyc | 42e8149443 | |
lyc | 8b429a4174 | |
白了个白 | 23795f2419 | |
白了个白 | ea204de407 | |
白了个白 | a31b8d7376 | |
小杨 | 8d03c927b9 | |
小杨 | edabc0336f | |
白了个白 | df474db4a5 | |
白了个白 | 328e623db7 | |
白了个白 | 8ad041e963 | |
zhengdegang | c22b360e0f | |
zdg | 3b851887ca | |
zdg | e6333e49f0 | |
CYS | cee81102da | |
lyc | 3ad4391e10 | |
“zouyf” | 7b59d78ce3 |
|
@ -16,4 +16,10 @@ VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktx
|
|||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'
|
||||
|
||||
# websocket 地址
|
||||
# VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
|
||||
VITE_APP_WS_URL = 'wss://file.ysaix.com:7868'
|
||||
# VITE_APP_WS_URL = 'ws://192.168.2.16:7865'
|
||||
|
||||
# 是否显示开发工具
|
||||
VITE_SHOW_DEV_TOOLS = 'true'
|
||||
|
|
|
@ -18,4 +18,8 @@ VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktx
|
|||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||
|
||||
# websocket 地址
|
||||
VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
|
||||
|
||||
# 是否显示开发工具
|
||||
VITE_SHOW_DEV_TOOLS = 'false'
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# 页面标题
|
||||
VITE_APP_TITLE = 文枢课堂
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
|
||||
# AIx融合数字管理系统/生产环境
|
||||
VITE_APP_BASE_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||
|
||||
VITE_APP_DOMAIN = 'prev.ysaix.com'
|
||||
|
||||
VITE_APP_UPLOAD_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS = gzip
|
||||
|
||||
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||
|
||||
# websocket 地址
|
||||
VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
|
||||
|
||||
# 是否显示开发工具
|
||||
VITE_SHOW_DEV_TOOLS = 'false'
|
|
@ -0,0 +1,25 @@
|
|||
# 页面标题
|
||||
VITE_APP_TITLE = 实训教学
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
|
||||
# AIx融合数字管理系统/生产环境
|
||||
VITE_APP_BASE_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||
|
||||
VITE_APP_DOMAIN = 'prev.ysaix.com'
|
||||
|
||||
VITE_APP_UPLOAD_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS = gzip
|
||||
|
||||
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||
|
||||
# websocket 地址
|
||||
VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
|
||||
|
||||
# 是否显示开发工具
|
||||
VITE_SHOW_DEV_TOOLS = 'false'
|
|
@ -0,0 +1,54 @@
|
|||
appId: com.electron.app.yc
|
||||
productName: 文枢课堂
|
||||
directories:
|
||||
output: dist
|
||||
buildResources: build
|
||||
win:
|
||||
executableName: 文枢课堂
|
||||
icon: resources/yc-logo.png
|
||||
files:
|
||||
- '!**/.vscode/*'
|
||||
- '!src/*'
|
||||
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
nsis:
|
||||
oneClick: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
artifactName: ${name}-yc-${version}-setup.${ext}
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
extendInfo:
|
||||
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
notarize: false
|
||||
dmg:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
linux:
|
||||
target:
|
||||
- AppImage
|
||||
- snap
|
||||
- deb
|
||||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
appImage:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://prev.ysaix.com:7868/src/assets/smarttalkyc/
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
# 额外依赖打包到输出目录
|
||||
extraFiles:
|
||||
- from: ./node_modules/im_electron_sdk/lib/
|
||||
to: ./resources
|
||||
filter:
|
||||
- '**/*'
|
|
@ -0,0 +1,54 @@
|
|||
appId: com.electron.app.yc2
|
||||
productName: 实训教学
|
||||
directories:
|
||||
output: dist
|
||||
buildResources: build
|
||||
win:
|
||||
executableName: 实训教学
|
||||
icon: resources/yc-logo.png
|
||||
files:
|
||||
- '!**/.vscode/*'
|
||||
- '!src/*'
|
||||
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
nsis:
|
||||
oneClick: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
artifactName: ${name}-ycsx-${version}-setup.${ext}
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
extendInfo:
|
||||
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
notarize: false
|
||||
dmg:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
linux:
|
||||
target:
|
||||
- AppImage
|
||||
- snap
|
||||
- deb
|
||||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
appImage:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://prev.ysaix.com:7868/src/assets/smarttalkycsx/
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
# 额外依赖打包到输出目录
|
||||
extraFiles:
|
||||
- from: ./node_modules/im_electron_sdk/lib/
|
||||
to: ./resources
|
||||
filter:
|
||||
- '**/*'
|
|
@ -33,8 +33,9 @@ export default defineConfig({
|
|||
proxy: {
|
||||
'/dev-api': {
|
||||
target: 'http://27.128.240.72:7865',
|
||||
// target: 'https://prev.ysaix.com:7868/prod-api/',
|
||||
// target: 'http://36.134.181.164:7863',
|
||||
// target: 'http://192.168.2.52:7863',
|
||||
// target: 'http://192.168.0.102:7865',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||
},
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
declare module '*.vue' {
|
||||
import { ComponentOptions } from 'vue'
|
||||
const componentOptions: ComponentOptions
|
||||
export default componentOptions
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "aix-win-ws",
|
||||
"version": "2.5.4",
|
||||
"version": "2.5.9",
|
||||
"description": "",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "上海交大重庆人工智能研究院",
|
||||
|
@ -16,6 +16,8 @@
|
|||
"build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
|
||||
"build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml",
|
||||
"build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml",
|
||||
"build:yc": "electron-vite build --mode yc && electron-builder --win --config ./electron-builder-yc.yml",
|
||||
"build:yc2": "electron-vite build --mode yc2 && electron-builder --win --config ./electron-builder-yc2.yml",
|
||||
"build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml",
|
||||
"build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml",
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
|
@ -57,6 +59,7 @@
|
|||
"file-saver": "^2.0.5",
|
||||
"hfmath": "^0.0.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
"html2canvas": "^1.4.1",
|
||||
"im_electron_sdk": "^8.0.5904",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsencrypt": "^3.3.2",
|
||||
|
@ -91,6 +94,8 @@
|
|||
"tinycolor2": "^1.6.0",
|
||||
"tinymce": "6.8.3",
|
||||
"tippy.js": "^6.3.7",
|
||||
"v-viewer": "^3.0.11",
|
||||
"viewerjs": "^1.11.7",
|
||||
"vite-plugin-electron": "^0.28.8",
|
||||
"vue": "^3.4.34",
|
||||
"vue-cropper": "1.0.3",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
|
@ -7,6 +7,7 @@ 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'
|
||||
|
@ -41,19 +42,19 @@ if(!gotTheLock){
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
let logoIco = import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?'../../resources/yc-logo.png':'../../resources/logo2.ico'
|
||||
//登录窗口
|
||||
function createLoginWindow() {
|
||||
if (loginWindow) return
|
||||
loginWindow = new BrowserWindow({
|
||||
width: 888,
|
||||
width: import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?1060:888,
|
||||
height: 520,
|
||||
show: false,
|
||||
frame: false,
|
||||
autoHideMenuBar: true,
|
||||
maximizable: false,
|
||||
resizable: false,
|
||||
icon: join(__dirname, '../../resources/logo2.ico'),
|
||||
icon: join(__dirname, logoIco),
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
defaultEncoding: 'utf-8',
|
||||
|
@ -95,7 +96,7 @@ function createMainWindow() {
|
|||
frame: false, // 无边框
|
||||
autoHideMenuBar: true,
|
||||
maximizable: false,
|
||||
icon: join(__dirname, '../../resources/logo2.ico'),
|
||||
icon: join(__dirname, logoIco),
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
defaultEncoding: 'utf-8',
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
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 * blob: data:; default-src 'self'; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net;" />
|
||||
<meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; frame-src 'self' *; default-src 'self' https://wzyzoss.eos-chongqing-3.cmecloud.cn/; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net;" />
|
||||
|
||||
</head>
|
||||
|
||||
|
|
|
@ -13097,15 +13097,15 @@ const PDFViewerApplication = {
|
|||
}
|
||||
if (isValidSpreadMode(spread)) {
|
||||
//默认双页
|
||||
// this.pdfViewer.spreadMode = spread;
|
||||
this.pdfViewer.spreadMode = 1;
|
||||
this.pdfViewer.spreadMode = spread;
|
||||
// this.pdfViewer.spreadMode = 1;
|
||||
}
|
||||
};
|
||||
this.isInitialViewSet = true;
|
||||
this.pdfSidebar?.setInitialView(sidebarView);
|
||||
//默认双页
|
||||
// setViewerModes(scrollMode, spreadMode);
|
||||
setViewerModes(scrollMode, 1);
|
||||
setViewerModes(scrollMode, spreadMode);
|
||||
// setViewerModes(scrollMode, 1);
|
||||
if (this.initialBookmark) {
|
||||
setRotation(this.initialRotation);
|
||||
delete this.initialRotation;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import 'animate.css'
|
||||
import { ref, onMounted, watch, onBeforeMount } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from './store'
|
||||
|
@ -27,8 +28,9 @@ import msgUtils from '@/plugins/modal' // 消息工具
|
|||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||
import { PPTApi } from './api'
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
import './api/watcher' // 监听
|
||||
|
||||
import watcher from './api/watcher' // 监听
|
||||
import emitter from '@/utils/mitt' //mitt 事件总线
|
||||
watcher() // 监听启动
|
||||
const loading = ref(true)
|
||||
const _isPC = isPC()
|
||||
|
||||
|
@ -38,8 +40,8 @@ const slidesStore = useSlidesStore()
|
|||
const { databaseId } = storeToRefs(mainStore)
|
||||
const { screening } = storeToRefs(useScreenStore())
|
||||
|
||||
if (import.meta.env.MODE !== 'development') {
|
||||
window.onbeforeunload = () => false
|
||||
if (import.meta.env.MODE === 'development') {
|
||||
// window.onbeforeunload = () => false
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
@ -60,14 +62,6 @@ window.addEventListener('unload', () => {
|
|||
const newDiscardedDB = JSON.stringify(discardedDBList)
|
||||
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
|
||||
})
|
||||
/** 接口类型 */
|
||||
interface Result {
|
||||
code?: number,
|
||||
msg?: string,
|
||||
data?: any
|
||||
rows?: Array<any>,
|
||||
total?: number
|
||||
}
|
||||
// 获取参数
|
||||
const initLoad: Function = () => {
|
||||
// 获取缓存的ppt 资源数据
|
||||
|
@ -89,5 +83,8 @@ const initLoad: Function = () => {
|
|||
<style lang="scss">
|
||||
#app {
|
||||
height: 100%;
|
||||
svg, canvas, img, audio, video, iframe {
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 统一处理消息 发送 避免找不到
|
||||
*/
|
||||
|
||||
import ChatWs from '@/plugins/socket' // 聊天socket
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
import { useClasscourseStore } from '../store'
|
||||
import * as API_classcourse from '@/api/teaching/classcourse' // 后端api
|
||||
import { MsgEnum } from './types'
|
||||
// import msgUtils from '@/plugins/modal' // 消息工具
|
||||
|
||||
export default () => {
|
||||
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
|
||||
const courseId = classcourse?.id // 课堂id
|
||||
const timgroupid = classcourse?.timgroupid // 群组id
|
||||
const classcourseStore = useClasscourseStore() // 课堂信息-状态管理
|
||||
// 上课状态才-初始化socket
|
||||
if (!ChatWs.ws && !!courseId) ChatWs.init()
|
||||
// 开课消息
|
||||
const startCourse = async() => {
|
||||
// await API_classcourse.updateClasscourse({ id: classcourse.id, status: 'open' })
|
||||
ChatWs.sendMsg('open', {id: courseId})
|
||||
return Promise.resolve()
|
||||
}
|
||||
// 下课消息
|
||||
const exitCourse = async() => {
|
||||
if(!timgroupid) throw new Error('未获取到群组ID')
|
||||
await API_classcourse.updateClasscourse({ id: courseId, status: 'closed' })
|
||||
return ChatWs.closedCourse(timgroupid)
|
||||
}
|
||||
// 翻页消息
|
||||
const slideFlapping = (msg:object) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const isWs = !!ChatWs.ws && ChatWs.ws.readyState === 1 // 是否有socket连接
|
||||
if(!timgroupid) return reject('未获取到群组ID')
|
||||
else if(!isWs) return reject('信异常,请重试!')
|
||||
const {current: paging, animationSteps: cartoonTimes} = msg || {}
|
||||
const head = MsgEnum.HEADS.MSG_slideFlapping
|
||||
ChatWs.sendMsg(head, msg) // 发送消息
|
||||
API_classcourse.setPaging({ id: courseId, paging, cartoonTimes})
|
||||
// 更新本地缓存
|
||||
sessionStore.set('curr.classcourse.paging', paging)
|
||||
sessionStore.set('curr.classcourse.cartoonTimes', cartoonTimes)
|
||||
classcourseStore.classcourse.paging = paging
|
||||
classcourseStore.classcourse.cartoonTimes = cartoonTimes
|
||||
return resolve(true)
|
||||
})
|
||||
}
|
||||
return {
|
||||
groupid: timgroupid,
|
||||
classcourse,
|
||||
exitCourse,
|
||||
slideFlapping,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* @author zdg
|
||||
* @description 上课相关内容
|
||||
*/
|
||||
// import type { Classcourse } from './types'
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
import * as useStore from '../store' // pptist-状态管理
|
||||
import ChatWs from '@/plugins/socket' // 聊天socket
|
||||
import msgUtils from '@/plugins/modal' // 消息工具
|
||||
import emitter from '@/utils/mitt' //mitt 事件总线
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
const slidesStore = useStore.useSlidesStore() // 幻灯片-状态管理
|
||||
const screenStore = useStore.useScreenStore() // 全屏-状态管理
|
||||
const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理
|
||||
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
|
||||
const isPublic = sessionStore.get('curr.isPublic') // 是否公屏开课
|
||||
|
||||
export class Classcourse {
|
||||
msgObj:ElMessageBox = null // 提示消息对象
|
||||
classcourse:any = null // 课堂信息
|
||||
id: number|string = null // 课堂id
|
||||
|
||||
constructor() {
|
||||
this.load()
|
||||
}
|
||||
// 延时
|
||||
sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
/**
|
||||
* @description 加载
|
||||
*/
|
||||
async load() {
|
||||
console.log('classcourse-load', classcourse)
|
||||
// 打开全屏
|
||||
const isCourse = !!classcourse
|
||||
screenStore.setScreening(isCourse)
|
||||
// 如果课堂信息有值,则连接socket
|
||||
if (isCourse) {
|
||||
// 连接socket
|
||||
ChatWs.id = classcourse.timgroupid // 群组id
|
||||
if (!ChatWs.ws) {
|
||||
ChatWs.init().then(_ => {
|
||||
isPublic && ChatWs.sendMsg('open', {id: classcourse.id})
|
||||
// isPublic && console.log('socket-开课消息-已发送')
|
||||
})
|
||||
}
|
||||
this.classcourse = classcourse // 课堂信息
|
||||
this.id = classcourse.id // 课堂id
|
||||
// 如果课堂信息有paging,则更新当前页码
|
||||
const { paging, cartoonTimes } = classcourse
|
||||
const isPaging = !!paging || paging === 0
|
||||
// 如果课堂信息有paging,则更新动画播放状态
|
||||
const isAnim = !!cartoonTimes || cartoonTimes === 0
|
||||
if (isPaging) slidesStore.updateSlideIndex(paging)
|
||||
if (isAnim) slidesStore.updateAnimationIndex(cartoonTimes)
|
||||
// 课堂信息-状态管理
|
||||
classcourseStore.setClasscourse(classcourse)
|
||||
// 待上课提示
|
||||
if (!classcourse.status) {
|
||||
this.msgObj = {
|
||||
type: 'success',
|
||||
title: '系统提示',
|
||||
message: '公屏课堂已准备完毕,等待老师开启课堂...',
|
||||
center: true,
|
||||
showClose: false,
|
||||
showCancelButton: false,
|
||||
showConfirmButton: false,
|
||||
beforeClose: () => {}
|
||||
}
|
||||
msgUtils.ElMessageBox(this.msgObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new Classcourse()
|
|
@ -0,0 +1,47 @@
|
|||
export default class gridPic {
|
||||
private static Instance: gridPic | null = null;
|
||||
private gridPicRef: any = null;
|
||||
|
||||
constructor(elRef?: any) {
|
||||
if (elRef) {
|
||||
this.gridPicRef = elRef;
|
||||
}
|
||||
if (!gridPic.Instance) {
|
||||
gridPic.Instance = this;
|
||||
}
|
||||
return gridPic.Instance;
|
||||
}
|
||||
|
||||
// 初始化
|
||||
init(elRef) {
|
||||
if (elRef) {
|
||||
this.gridPicRef = elRef;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
addPIc(data) {
|
||||
if (this.gridPicRef && this.gridPicRef.value && typeof this.gridPicRef.value.addPic === 'function') {
|
||||
this.gridPicRef.value.addPic(data);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// 静态方法 - 初始化
|
||||
static init(elRef) {
|
||||
if (!gridPic.Instance) {
|
||||
gridPic.Instance = new gridPic(elRef);
|
||||
} else {
|
||||
gridPic.Instance.init(elRef);
|
||||
}
|
||||
return gridPic.Instance;
|
||||
}
|
||||
|
||||
// 静态方法 - 打开推图上屏幕
|
||||
static addPIc(data) {
|
||||
if (gridPic.Instance) {
|
||||
return gridPic.Instance.addPIc(data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -3,16 +3,25 @@
|
|||
* @author zdg
|
||||
* @date 2024-11-26
|
||||
*/
|
||||
import { toRaw } from 'vue'
|
||||
import { toRaw, nextTick } from 'vue'
|
||||
import type { Result } from './types' // 接口类型
|
||||
import msgUtils from '@/plugins/modal' // 消息工具
|
||||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||
import * as API_smarttalk from '@/api/file' // 相关api
|
||||
import * as API_classwork from '@/api/teaching/classwork' // 相关api
|
||||
import * as useStore from '../store' // pptist-状态管理
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
import useUserStore from '@/store/modules/user' // 外部-用户信息
|
||||
|
||||
import * as Api_server from '@/api/apiService' // 相关api
|
||||
import * as commUtils from '@/utils/comm.js' // 工具
|
||||
import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
|
||||
const slidesStore = useStore.useSlidesStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
import { getClassWorkList,getStudentClassWorkData } from '@/views/tool/createHomework'
|
||||
import {createWindow} from '@/utils/tool'
|
||||
import { useToolState } from '@/store/modules/tool'
|
||||
const toolStore = useToolState()
|
||||
/** 工具类 */
|
||||
export class Utils {
|
||||
static mxData: any = {
|
||||
|
@ -41,6 +50,8 @@ export class Utils {
|
|||
}, delay)
|
||||
}
|
||||
}
|
||||
// 延时
|
||||
static sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
/** ppt相关后端接口处理 */
|
||||
|
@ -48,23 +59,39 @@ export class PPTApi {
|
|||
// 变量
|
||||
static isUpdate = true // 是否更新数据
|
||||
|
||||
// 获取所有幻灯片列表
|
||||
static getSlideList(parentid: (Number | String)): Promise<Boolean> {
|
||||
// 获取所有幻灯片列表 isUpdate为true不更新
|
||||
static getSlideList(parentid: (Number | String),isUpdate?:Boolean): Promise<Boolean> {
|
||||
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
|
||||
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
|
||||
if (res.code === 200) {
|
||||
const slides = (res.rows || []).map(o => {
|
||||
if (!!o.datacontent) {
|
||||
const json = JSON.parse(o.datacontent)
|
||||
!!json && (json.id = o.id)
|
||||
return json
|
||||
}
|
||||
// 如果没有数据,默认空白页
|
||||
return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}}
|
||||
})
|
||||
slidesStore.updateSlideIndex(0) // 下标0 为第一页
|
||||
slidesStore.setSlides(slides) // 写入数据
|
||||
if (res.code === 200) {
|
||||
if(!isUpdate){
|
||||
const slides = (res.rows || []).map(o => {
|
||||
if (!!o.datacontent) {
|
||||
const json = JSON.parse(o.datacontent)
|
||||
!!json && (json.id = o.id)
|
||||
return json
|
||||
}
|
||||
// 如果没有数据,默认空白页
|
||||
return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}}
|
||||
})
|
||||
// slidesStore.updateSlideIndex(0) // 下标0 为第一页
|
||||
slidesStore.setSlides(slides) // 写入数据
|
||||
}
|
||||
// 活动列表处理
|
||||
// const workList = (res.rows || []).map(o => o.activityContent)
|
||||
const workItem = res.rows ? [...res.rows] : []
|
||||
// 获取所有的pptlist的数据
|
||||
slidesStore.setWorkItem(workItem)
|
||||
// 没有上课时调用-作业列表
|
||||
if(!classcourse) this.updateWorkList()
|
||||
// 没有上课时调用-批量更新缩略图
|
||||
if(!classcourse) {
|
||||
Utils.sleep(1500).then(() => {
|
||||
this.batchUpdateThumUrl()
|
||||
})
|
||||
}
|
||||
resolve(true)
|
||||
} else msgUtils.msgError(res.msg || '获取数据失败');resolve(false)
|
||||
})
|
||||
|
@ -117,21 +144,34 @@ export class PPTApi {
|
|||
|
||||
const currentSlide = toRaw(slidesStore.currentSlide)
|
||||
const isAdd = !/^\d+$/.test(currentSlide.id) // 是否新增
|
||||
const currInd = toRaw(slidesStore.slideIndex) // 当前页索引-new
|
||||
const oldInd = oldData.findIndex(o => o.id == currentSlide.id) // 当前页索引-old
|
||||
const isBatch = oldVal && oldVal.length && currInd != oldInd // 是否批量更新-排序
|
||||
if (isAdd) { // 新增的幻灯片(id 为非数字,说明是新增的幻灯片)
|
||||
const bool = await this.addSlide(currentSlide)
|
||||
bool && this.batchUpdateSlides(newData, true) // 批量更新-排序
|
||||
bool && await this.batchUpdateSlides(newData, true) // 批量更新-排序
|
||||
const resource = sessionStore.get('curr.resource')||{}
|
||||
await PPTApi.getSlideList(resource.id)
|
||||
} else { // 防抖-更新
|
||||
if (!this.isUpdate) return this.isUpdate = true // 下次更新数据
|
||||
const params = {
|
||||
id: currentSlide.id,
|
||||
datacontent: JSON.stringify(currentSlide),
|
||||
if (isBatch) { // 批量更新-排序
|
||||
this.batchUpdateSlides(newData, true)
|
||||
} else { // 更新当前页幻灯片
|
||||
const params = {
|
||||
id: currentSlide.id,
|
||||
datacontent: JSON.stringify(currentSlide),
|
||||
}
|
||||
Utils.mxThrottle(() => {this.updateSlide(params)}, 200, 2)
|
||||
}
|
||||
Utils.mxThrottle(() => {this.updateSlide(params)}, 1000, 2)
|
||||
}
|
||||
}
|
||||
// 更新幻灯片
|
||||
static updateSlide(data: object): Promise<Boolean> {
|
||||
// 更新幻灯片 isThum 是否更新缩略图
|
||||
static updateSlide(data: object, isThum = true): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (isThum) { // 更新缩略图
|
||||
const thumUrl = await this.getSlideThumUrl()
|
||||
data.base64Code = thumUrl // 更新缩略图
|
||||
}
|
||||
const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data)
|
||||
if (res.code === 200) {
|
||||
resolve(true)
|
||||
|
@ -166,6 +206,101 @@ export class PPTApi {
|
|||
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
|
||||
})
|
||||
}
|
||||
|
||||
// 更新-备课资源 标题
|
||||
static updateSmarttalk(data: object): Promise<Boolean> {
|
||||
return API_smarttalk.updateSmarttalk(data).then(res => {
|
||||
if (res.code === 200) return true
|
||||
else msgUtils.msgError(res.msg || '更新失败');return false
|
||||
})
|
||||
}
|
||||
|
||||
// 更新-活动列表
|
||||
static async updateWorkList(): Promise<Boolean> {
|
||||
const resolveData = (resolve, data = []) => {
|
||||
slidesStore.setWorkList(data)
|
||||
return resolve()
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const workItem = slidesStore.workItem // 所有的pptlist的数据-原始数据
|
||||
const currentSlide = slidesStore.currentSlide // 当前页幻灯片
|
||||
const slideId = currentSlide.id // 当前页幻灯片id
|
||||
if (!slideId) return resolveData(resolve)
|
||||
// slide详情获取-作业id
|
||||
// const res = await API_entpcoursefile.getEntpcoursefile(slideId)
|
||||
// const workIds = res?.data?.activityContent||''
|
||||
// workItem-获取作业id
|
||||
const workIds = workItem.find(o => o.id == slideId)?.activityContent
|
||||
if (!workIds) return resolveData(resolve)
|
||||
// 获取作业列表
|
||||
const resW = await API_classwork.homeworklist({ ids: workIds, pageSize: 1000 })
|
||||
if (resW && resW.rows) return resolveData(resolve, resW.rows)
|
||||
else msgUtils.msgError(resW.msg || '更新失败');return resolveData(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
// 批量更新缩略图-异步
|
||||
static batchUpdateThumUrl() {
|
||||
return nextTick().then(async () => {
|
||||
const list = slidesStore.workItem || []
|
||||
if (!list.length) return
|
||||
const upList = []
|
||||
for (const [ind,o] of list.entries()) {
|
||||
const isCreate = !o.fileurl // 是否创建
|
||||
if (isCreate) {
|
||||
const thumUrl = await this.getSlideThumUrl(ind)
|
||||
upList.push({ id: o.id, fileurl: thumUrl })
|
||||
}
|
||||
}
|
||||
if (!upList.length) return
|
||||
// 批量更新
|
||||
return await API_entpcoursefile.batchUpdateNew(upList)
|
||||
})
|
||||
}
|
||||
|
||||
// thumbnail-slide thumbnail 缩略图
|
||||
static getSlideThumUrl(index?:number): Promise<Boolean> {
|
||||
return nextTick().then(async() => {
|
||||
const slideIndex = index ?? slidesStore.slideIndex
|
||||
const elements = document.querySelectorAll('.thumbnail-slide')
|
||||
if (elements.length && slideIndex >= 0) {
|
||||
const element = elements[slideIndex]
|
||||
return await toPng(element)
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
// 图片|音频|视频 转换为在线地址
|
||||
static toRousrceUrl =async (o:any) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', o)
|
||||
formData.append('ral', true)
|
||||
const res = await Api_server.Other.uploadFile(formData)
|
||||
if (res && res.code == 200){
|
||||
const url = res?.url
|
||||
url &&(o.src = url)
|
||||
return url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Homework{
|
||||
static win: null // 作业弹窗
|
||||
// 作业弹窗
|
||||
static async showHomework(id: any) {
|
||||
let result = await getClassWorkList(id)
|
||||
result = await getStudentClassWorkData()
|
||||
localStorage.setItem('teachClassWorkItem', JSON.stringify(result[0]));
|
||||
toolStore.isTaskWin=true; // 设置打开批改窗口
|
||||
// emit('closeActive')
|
||||
// 重复打开,先关闭弹窗
|
||||
// if (this.win) this.win?.close?.()
|
||||
this.win = await createWindow('open-taskwin',{url:'/teachClassTask'})
|
||||
return this.win;
|
||||
}
|
||||
static closeHomework() {
|
||||
if (this.win) this.win?.close?.()
|
||||
this.win = null
|
||||
}
|
||||
}
|
||||
export default PPTApi
|
|
@ -2,17 +2,18 @@
|
|||
* @description api 无store循环引用
|
||||
* @author zdg
|
||||
*/
|
||||
import type { Result } from './types' // 接口类型
|
||||
import msgUtils from '@/plugins/modal' // 消息工具
|
||||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||
|
||||
export default class {
|
||||
// 删除幻灯片
|
||||
static delSlide(id: string): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const res: Result = await API_entpcoursefile.delEntpcoursefile(id)
|
||||
if (res.code === 200) {
|
||||
resolve(true)
|
||||
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
|
||||
})
|
||||
}
|
||||
// 删除幻灯片
|
||||
static delSlide(id: string): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const res: Result = await API_entpcoursefile.delEntpcoursefile(id)
|
||||
if (res.code === 200) {
|
||||
resolve(true)
|
||||
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
/** 返回-接口类型 */
|
||||
export interface Result {
|
||||
code?: number,
|
||||
msg?: string,
|
||||
data?: any
|
||||
rows?: Array<any>,
|
||||
total?: number
|
||||
}
|
||||
|
||||
/** 课程信息 */
|
||||
export interface Classcourse {
|
||||
id?: number|string, // 课程id
|
||||
coursetitle?: string, // 课程名称
|
||||
coursetype?: string, // 课程类型
|
||||
courseverid?: string, // 课程版本id
|
||||
coursedesc?: string, // 课程描述
|
||||
status?: number, // 课程状态
|
||||
teacherid?: number|string, // 教师id
|
||||
entpcoursefileid?: number|string, // 课程文件id
|
||||
classid?: number|string, // 班级id
|
||||
entpcourseid?: number|string, // 章节中间表id
|
||||
timgroupid?: number|string, // ws 群组id
|
||||
plandate?: string, // 计划时间
|
||||
opendate?: string, // 开课时间
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 消息枚举
|
||||
* @author zdg
|
||||
* @date 2021-07-05 14:07:01
|
||||
*/
|
||||
|
||||
export class MsgEnum {
|
||||
/**
|
||||
* @description: 消息类型
|
||||
*
|
||||
* | 名称 | 含义 | 值(enum) |
|
||||
* | ---- | ---- | ---- |
|
||||
* | SYSTEM | 系统消息 | system |
|
||||
* | TEACHER | 老师消息 | teacher |
|
||||
* | STUDENT | 学生消息 | student |
|
||||
* | NOTICE | 通知消息 | notice |
|
||||
*/
|
||||
static TYPES = {
|
||||
/** @desc: 系统消息 */
|
||||
SYSTEM: 'system',
|
||||
/** @desc: 老师消息 */
|
||||
TEACHER: 'teacher',
|
||||
/** @desc: 学生消息 */
|
||||
STUDENT: 'student',
|
||||
/** @desc: 通知消息 */
|
||||
NOTICE: 'notice'
|
||||
}
|
||||
/**
|
||||
* @description: 消息头-类型
|
||||
*
|
||||
* | 名称 | 含义 | 值(enum) |
|
||||
* | ---- | ---- | ---- |
|
||||
* | --- | 以下为旧定义-消息头 | --- |
|
||||
* | MSG_closed | 结束课程(下课) | closed |
|
||||
* | MSG_onlineStatus | 在线状态 | onlineStatus |
|
||||
* | MSG_pushQuizOfClassWorkdata2Public | 老师端:把选中的学生习题作业,推到大屏 | pushQuizOfClassWorkdata2Public |
|
||||
* | MSG_pushClassWorkdata2Public | 老师端:把选中的学生作业,推到大屏 | pushClassWorkdata2Public |
|
||||
* | MSG_shareStudentPresentdata2All | 把某个学生的展示成果数据推给全班所有学生 | shareStudentPresentdata2All |
|
||||
* | MSG_pushStudentPresentdata2Public | 老师端:课堂展示活动,把选中的学生展示数据,推到大屏 | pushStudentPresentdata2Public |
|
||||
* | MSG_pushClassWorkPresentList2Public | 老师端:课堂展示活动,任务列表,推到大屏 | pushClassWorkPresentList2Public |
|
||||
* | MSG_activePageType | 课标研读-分页切换 | activePageType |
|
||||
* | MSG_slideFlapping | 幻灯片-切换 | slideFlapping |
|
||||
* | MSG_anmationclick | 幻灯片-动画切换 | anmationclick |
|
||||
* | MSG_classcourseopen | 群组创建成功 | classcourseopen |
|
||||
* | MSG_classquizfeedback | 学生的测练结果反馈 | classquizfeedback |
|
||||
* | MSG_classtaskfeedback | 老师端:接收到学生反馈消息-课堂测练中的其他任务 | classtaskfeedback |
|
||||
* | MSG_studentfeedback | 老师端:学生反馈的消息,具体要看其中的feedbackkey,类别较繁杂 | studentfeedback |
|
||||
* | MSG_studentfeedbackcancel | 老师端:学生反馈的消息取消,如取消学会了,取消困惑 | studentfeedbackcancel |
|
||||
* | MSG_classshowdata | 学生提交的课堂展示数据-要在老师端显示,再由老师选择推送到公屏上 | classshowdata |
|
||||
* | MSG_classWorkOfPresentDataUpdate | 学生在公屏上展示并完善后,保存后,老师端要更新 | classWorkOfPresentDataUpdate |
|
||||
* | MSG_classlecturePagesrc | 课堂讲授活动,选择不同的内容 | classlecturePagesrc |
|
||||
* | --- | 以下为新定义-消息头 | --- |
|
||||
* | MSG_0001 | 点赞 | 0x0001 |
|
||||
* | MSG_0002 | xx | 0x0002 |
|
||||
* | MSG_0003 | xx | 0x0003 |
|
||||
*/
|
||||
static HEADS = {
|
||||
// === 旧定义-消息头(兼容以前) ===
|
||||
/** @desc: 开课 */
|
||||
MSG_open : 'open',
|
||||
/** @desc: 结束课程(下课) */
|
||||
MSG_closed : 'closed',
|
||||
/** @desc: 在线状态 */
|
||||
MSG_onlineStatus : 'onlineStatus',
|
||||
/** @desc: 老师端:把选中的学生习题作业,推到大屏 */
|
||||
MSG_pushQuizOfClassWorkdata2Public : 'pushQuizOfClassWorkdata2Public',
|
||||
/** @desc: 老师端:把选中的学生作业,推到大屏 */
|
||||
MSG_pushClassWorkdata2Public : 'pushClassWorkdata2Public',
|
||||
/** @desc: 把某个学生的展示成果数据推给全班所有学生 */
|
||||
MSG_shareStudentPresentdata2All : 'shareStudentPresentdata2All',
|
||||
/** @desc: 老师端:课堂展示活动,把选中的学生展示数据,推到大屏 */
|
||||
MSG_pushStudentPresentdata2Public : 'pushStudentPresentdata2Public',
|
||||
/** @desc: 老师端:课堂展示活动,任务列表,推到大屏 */
|
||||
MSG_pushClassWorkPresentList2Public : 'pushClassWorkPresentList2Public',
|
||||
/** @desc: 课标研读-分页切换 */
|
||||
MSG_activePageType : 'activePageType',
|
||||
/** @desc: 幻灯片-切换 */
|
||||
MSG_slideFlapping : 'slideFlapping',
|
||||
/** @desc: 幻灯片-动画切换 */
|
||||
MSG_anmationclick : 'anmationclick',
|
||||
/** @desc: 群组创建成功 */
|
||||
MSG_classcourseopen : 'classcourseopen',
|
||||
/** @desc: 学生提交作业 */
|
||||
MSG_finishHomework : 'finishHomework',
|
||||
/** @desc: 学生的测练结果反馈 */
|
||||
MSG_classquizfeedback : 'classquizfeedback',
|
||||
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
|
||||
MSG_classtaskfeedback : 'classtaskfeedback',
|
||||
/** @desc: 老师端:学生反馈的消息,具体要看其中的feedbackkey,类别较繁杂 */
|
||||
MSG_studentfeedback : 'studentfeedback',
|
||||
/** @desc: 老师端:学生反馈的消息取消,如取消学会了,取消困惑 */
|
||||
MSG_studentfeedbackcancel : 'studentfeedbackcancel',
|
||||
/** @desc: 学生提交的课堂展示数据-要在老师端显示,再由老师选择推送到公屏上 */
|
||||
MSG_classshowdata : 'classshowdata',
|
||||
/** @desc: 学生在公屏上展示并完善后,保存后,老师端要更新 */
|
||||
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
|
||||
/** @desc: 课堂讲授活动,选择不同的内容 */
|
||||
MSG_classlecturePagesrc : 'classlecturePagesrc',
|
||||
/** @desc: 课堂作业|活动 */
|
||||
MSG_homework : 'HOMEWORK',
|
||||
/** @desc: 公屏 - 课堂作业|活动 */
|
||||
MSG_pushSreen_work : 'pushSreen_work',
|
||||
/** @desc: 公屏 - 实验 */
|
||||
MSG_pushSreen_experiment : 'pushSreen_experiment',
|
||||
/** @desc: 点赞 */
|
||||
MSG_dz : 'dz',
|
||||
/** @desc: 疑惑 */
|
||||
MSG_yh : 'yh',
|
||||
/** @desc: 推图片上屏 */
|
||||
MSG_pushSreen_ImgList : 'pushSreen_ImgList',
|
||||
// === 新定义-消息头 ===
|
||||
/** @desc: 课程创建-待开课 */
|
||||
MSG_0000: 0x0000,
|
||||
/** @desc: 点赞 */
|
||||
MSG_0001: 0x0001,
|
||||
/** @desc: 疑惑 */
|
||||
MSG_0002: 0x0002,
|
||||
MSG_0003: 0x0003,
|
||||
MSG_0004: 0x0004,
|
||||
MSG_0005: 0x0005,
|
||||
MSG_0006: 0x0006,
|
||||
MSG_0007: 0x0007,
|
||||
MSG_0008: 0x0008,
|
||||
MSG_0009: 0x0009,
|
||||
MSG_0010: 0x000a,
|
||||
MSG_0011: 0x000b,
|
||||
MSG_0012: 0x000c,
|
||||
MSG_0013: 0x000d,
|
||||
MSG_0014: 0x000e,
|
||||
MSG_0015: 0x000f,
|
||||
/** @desc: 作业推送 */
|
||||
MSG_0016: 0x0010,
|
||||
MSG_0017: 0x0011,
|
||||
MSG_0018: 0x0012,
|
||||
MSG_0019: 0x0013,
|
||||
MSG_0020: 0x0014,
|
||||
MSG_0021: 0x0015,
|
||||
MSG_0022: 0x0016,
|
||||
MSG_0023: 0x0017,
|
||||
MSG_0024: 0x0018,
|
||||
MSG_0025: 0x0019,
|
||||
MSG_0026: 0x001a,
|
||||
MSG_0027: 0x001b,
|
||||
MSG_0028: 0x001c,
|
||||
MSG_0029: 0x001d,
|
||||
MSG_0030: 0x001e,
|
||||
MSG_0031: 0x001f,
|
||||
MSG_0032: 0x0020,
|
||||
MSG_0033: 0x0021,
|
||||
MSG_0034: 0x0022,
|
||||
MSG_0035: 0x0023,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* 点赞组件-相关
|
||||
*/
|
||||
export default class Upvote {
|
||||
instance: any = null // 自身实例
|
||||
upvoteRef: any = null // 点赞组件
|
||||
constructor(elRef?: any) {
|
||||
if(!!elRef) this.upvoteRef = elRef // 点赞组件
|
||||
if (!Upvote.Instance) {
|
||||
Upvote.Instance = this
|
||||
}
|
||||
return Upvote.Instance
|
||||
}
|
||||
|
||||
// 初始化
|
||||
init(elRef) {
|
||||
if(!!elRef) this.upvoteRef = elRef // 点赞组件
|
||||
return this
|
||||
}
|
||||
// 打开点赞或者疑问 1点赞 2疑问
|
||||
trigger(type) {
|
||||
this.upvoteRef?.value?.trigger?.(type)
|
||||
return this
|
||||
}
|
||||
// 静态方法-初始化
|
||||
static init(elRef) {
|
||||
return new Upvote(elRef)
|
||||
}
|
||||
// 静态方法-打开点赞或者疑问 1点赞 2疑问
|
||||
static trigger(type) {
|
||||
return new Upvote().trigger(type)
|
||||
}
|
||||
}
|
|
@ -6,26 +6,156 @@ import { watch } from 'vue'
|
|||
import { PPTApi } from './index'
|
||||
import * as store from '../store'
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
const slidesStore = store.useSlidesStore()
|
||||
const resource = sessionStore.get('curr.resource')
|
||||
import { MsgEnum } from './types' // 消息枚举
|
||||
import ChatWs from '@/plugins/socket' // 聊天socket
|
||||
import Classcourse from './classcourse' // 课程相关
|
||||
import msgUtils from '@/plugins/modal' // 消息工具
|
||||
import * as dialogUtils from '@/utils/dialog' // 弹窗-函数
|
||||
import { Homework } from './index' // api-作业相关
|
||||
import emitter from '@/utils/mitt' //mitt 事件总线
|
||||
import useExecPlay from '../views/Screen/hooks/useExecPlay' // 播放控制
|
||||
import hooksUpvote from './upvote' // 点赞-工具
|
||||
import gridPic from './gridPic' // 上屏-工具
|
||||
|
||||
/**
|
||||
* @description 监听器
|
||||
*/
|
||||
export default () => {
|
||||
const slidesStore = store.useSlidesStore()
|
||||
const classcourseStore = store.useClasscourseStore() // 课堂信息-状态管理
|
||||
const resource = sessionStore.get('curr.resource') // apt 资源
|
||||
const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源
|
||||
const { execNext, turnPrevSlide } = useExecPlay(false) // 不加载钩子
|
||||
// 监听幻灯片内容变化
|
||||
watch(() => slidesStore.slides, (newVal, oldVal) => {
|
||||
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
|
||||
},{ deep: true })
|
||||
|
||||
// 监听标题变化
|
||||
watch(() => slidesStore.title, (newVal, oldVal) => {
|
||||
if (oldVal == '未命名演示文稿') return // 初始加载,不需要更新数据
|
||||
updatePPT({title: newVal})
|
||||
})
|
||||
|
||||
// 监听幻灯片下标变化
|
||||
watch(() => slidesStore.slideIndex, (newVal, oldVal) => {
|
||||
if (!!Classcourse.id) return // 上课状态,不更新右侧作业列表
|
||||
PPTApi.updateWorkList() // 更新作业列表
|
||||
})
|
||||
// 监听幻灯片下画布尺寸比例变化
|
||||
watch(() => slidesStore.viewportRatio, (newVal, oldVal) => {
|
||||
const width = slidesStore.viewportSize
|
||||
const widthandration={width, ratio:newVal}
|
||||
const data = { id: resource.id, parentContent: JSON.stringify(widthandration)}
|
||||
PPTApi.updateSlide(data)
|
||||
})
|
||||
|
||||
// 监听幻灯片内容变化
|
||||
watch(() => slidesStore.slides, (newVal, oldVal) => {
|
||||
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
|
||||
},{ deep: true })
|
||||
// 消息监听ws
|
||||
// console.log('监听器已开启', ChatWs)
|
||||
if (!!ChatWs.ws) {
|
||||
ChatWs.watch((data, e) => {
|
||||
try {
|
||||
handleMessage(JSON.parse(data)?.msg)
|
||||
} catch (error) {
|
||||
console.error('socket 解析异常 ', error, e)
|
||||
handleMessage(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新ppt内容
|
||||
const updatePPT = async (data) => {
|
||||
if (!resource) return
|
||||
data.id = resource.id
|
||||
await PPTApi.updateSlide(data) // 更新ppt内容
|
||||
sessionStore.set('curr.resource.title', data.title)
|
||||
// 更新smarttalk内容
|
||||
if (!!smarttalk && !!data.title) {
|
||||
const {id, fileFlag} = smarttalk
|
||||
const params = { id, fileShowName: `${data.title}.${fileFlag}` }
|
||||
await PPTApi.updateSmarttalk(params) // 更新ppt内容
|
||||
sessionStore.set('curr.smarttalk.fileShowName', params.fileShowName)
|
||||
}
|
||||
}
|
||||
|
||||
// ws消息处理
|
||||
const handleMessage = (msg) => {
|
||||
console.log('ws消息处理', msg)
|
||||
if (typeof msg === 'object'){
|
||||
const { head, content, ...other } = msg
|
||||
switch (head) {
|
||||
case MsgEnum.HEADS.MSG_open: // 开课
|
||||
// 课堂信息不一致
|
||||
if (Classcourse.id !== content.id) {
|
||||
msgUtils.alertError('老师开课信息异常,请重新进入公屏!')
|
||||
.then(() => { // 点击确定按钮,关闭窗口
|
||||
close()
|
||||
})
|
||||
} else { // 正常更新数据
|
||||
classcourseStore.classcourse.status = 'open'
|
||||
sessionStore.set('curr.classcourse.status', 'open')
|
||||
// 更新课堂信息-关闭警告框
|
||||
Classcourse?.msgObj?.onVanish()
|
||||
}
|
||||
break
|
||||
case MsgEnum.HEADS.MSG_slideFlapping: // 幻灯片翻页
|
||||
emitter.emit('closegridPic') //如果有推图片窗口 就关闭
|
||||
const slideIndex = content?.current || 0
|
||||
const type = content?.animation // 上下动作
|
||||
const steps = content?.animationSteps // 动画步骤
|
||||
if (type === 'Nextsteps') execNext(true) // 下一步-异步动画
|
||||
else if (type === 'Previoustep') turnPrevSlide() // 上一步清空-动画
|
||||
else slidesStore.updateSlideIndex(slideIndex) // 更新幻灯片下标
|
||||
// 更新本地缓存
|
||||
sessionStore.set('curr.classcourse.paging', slideIndex)
|
||||
sessionStore.set('curr.classcourse.cartoonTimes', steps)
|
||||
classcourseStore.classcourse.paging = slideIndex
|
||||
classcourseStore.classcourse.cartoonTimes = steps
|
||||
break
|
||||
// case MsgEnum.HEADS.MSG_homework: // 作业|活动-布置 不处理
|
||||
case MsgEnum.HEADS.MSG_pushSreen_work: // 打开-作业|活动
|
||||
if (!content.id) return
|
||||
Homework.showHomework(content.id)
|
||||
break
|
||||
case MsgEnum.HEADS.MSG_pushSreen_experiment: // 打开实验:
|
||||
if (!content.url) return
|
||||
dialogUtils.openLink(content.url)
|
||||
break
|
||||
case MsgEnum.HEADS.MSG_closed: // 下课:
|
||||
close()
|
||||
break
|
||||
case MsgEnum.HEADS.MSG_dz: // 点赞
|
||||
hooksUpvote.trigger(1)
|
||||
break
|
||||
case MsgEnum.HEADS.MSG_yh: // 疑惑
|
||||
hooksUpvote.trigger(2)
|
||||
break
|
||||
case MsgEnum.HEADS.MSG_pushSreen_ImgList: // 推图片上屏
|
||||
const imgArray = content.ImgList.map((obj) => obj.url);
|
||||
emitter.emit('opengridPic',{arr:imgArray}) // 打开推图片上屏窗口
|
||||
break
|
||||
case MsgEnum.HEADS.MSG_0010: // 备用
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// 关闭窗口
|
||||
const close = () => {
|
||||
ChatWs?.close() // 关闭ws
|
||||
setTimeout(() => {
|
||||
window.close() // 关闭窗口
|
||||
}, 1000)
|
||||
}
|
||||
// setTimeout(async () => {
|
||||
// emitter.emit('opengridPic',{arr:['https://prev.ysaix.com:7868/src/assets/images/homecard4.jpg']})
|
||||
// }, 3000)
|
||||
|
||||
// 监听标题变化
|
||||
watch(() => slidesStore.title, (newVal, oldVal) => {
|
||||
if (oldVal == '未命名演示文稿') return // 初始加载,不需要更新数据
|
||||
updatePPT({title: newVal})
|
||||
})
|
||||
|
||||
const updatePPT = async (data) => {
|
||||
if (!resource) return
|
||||
data.id = resource.id
|
||||
await PPTApi.updateSlide(data) // 更新ppt内容
|
||||
sessionStore.set('curr.resource.title', data.title)
|
||||
// setTimeout(async () => {
|
||||
// emitter.emit('closegridPic')
|
||||
// }, 6000)
|
||||
// setTimeout(async () => {
|
||||
// emitter.emit('opengridPic',{arr:['https://prev.ysaix.com:7868/src/assets/images/homecard4.jpg','https://prev.ysaix.com:7868/src/assets/images/homecard4.jpg']})
|
||||
// }, 9000)
|
||||
}
|
|
@ -33,6 +33,48 @@ const { theme } = storeToRefs(useSlidesStore())
|
|||
|
||||
const { addSlidesFromData } = useAddSlidesOrElements()
|
||||
const { isEmptySlide } = useSlideHandler()
|
||||
const parseLineElement = (el: Shape) => {
|
||||
let start: [number, number] = [0, 0]
|
||||
let end: [number, number] = [0, 0]
|
||||
|
||||
if (!el.isFlipV && !el.isFlipH) { // 右下
|
||||
start = [0, 0]
|
||||
end = [el.width, el.height]
|
||||
}
|
||||
else if (el.isFlipV && el.isFlipH) { // 左上
|
||||
start = [el.width, el.height]
|
||||
end = [0, 0]
|
||||
}
|
||||
else if (el.isFlipV && !el.isFlipH) { // 右上
|
||||
start = [0, el.height]
|
||||
end = [el.width, 0]
|
||||
}
|
||||
else { // 左下
|
||||
start = [el.width, 0]
|
||||
end = [0, el.height]
|
||||
}
|
||||
|
||||
const data: PPTLineElement = {
|
||||
type: 'line',
|
||||
id: nanoid(10),
|
||||
width: el.borderWidth || 1,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
start,
|
||||
end,
|
||||
style: el.borderType,
|
||||
color: el.borderColor,
|
||||
points: ['', /straightConnector/.test(el.shapType) ? 'arrow' : '']
|
||||
}
|
||||
if (/bentConnector/.test(el.shapType)) {
|
||||
data.broken2 = [
|
||||
Math.abs(start[0] - end[0]) / 2,
|
||||
Math.abs(start[1] - end[1]) / 2,
|
||||
]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
export default () => {
|
||||
|
||||
const exporting = ref(false)
|
||||
|
@ -59,48 +101,7 @@ export default () => {
|
|||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
const parseLineElement = (el: Shape) => {
|
||||
let start: [number, number] = [0, 0]
|
||||
let end: [number, number] = [0, 0]
|
||||
|
||||
if (!el.isFlipV && !el.isFlipH) { // 右下
|
||||
start = [0, 0]
|
||||
end = [el.width, el.height]
|
||||
}
|
||||
else if (el.isFlipV && el.isFlipH) { // 左上
|
||||
start = [el.width, el.height]
|
||||
end = [0, 0]
|
||||
}
|
||||
else if (el.isFlipV && !el.isFlipH) { // 右上
|
||||
start = [0, el.height]
|
||||
end = [el.width, 0]
|
||||
}
|
||||
else { // 左下
|
||||
start = [el.width, 0]
|
||||
end = [0, el.height]
|
||||
}
|
||||
|
||||
const data: PPTLineElement = {
|
||||
type: 'line',
|
||||
id: nanoid(10),
|
||||
width: el.borderWidth || 1,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
start,
|
||||
end,
|
||||
style: el.borderType,
|
||||
color: el.borderColor,
|
||||
points: ['', /straightConnector/.test(el.shapType) ? 'arrow' : '']
|
||||
}
|
||||
if (/bentConnector/.test(el.shapType)) {
|
||||
data.broken2 = [
|
||||
Math.abs(start[0] - end[0]) / 2,
|
||||
Math.abs(start[1] - end[1]) / 2,
|
||||
]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
// 导入PPTX文件
|
||||
const importPPTXFile = (files: FileList) => {
|
||||
|
@ -493,6 +494,7 @@ export default () => {
|
|||
importPPTXFile,
|
||||
PPTXFileToJson,
|
||||
exporting,
|
||||
parseLineElement
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -664,6 +666,7 @@ export const PPTXFileToJson = (data: File|ArrayBuffer) => {
|
|||
}
|
||||
else if (el.type === 'shape') {
|
||||
if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
|
||||
// 从返回对象中解构出 xx 函数并调用
|
||||
const lineElement = parseLineElement(el)
|
||||
slide.elements.push(lineElement)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { useScreenStore, useSlidesStore } from '../store'
|
||||
import { useScreenStore, useSlidesStore, useClasscourseStore } from '../store'
|
||||
import { enterFullscreen, exitFullscreen, isFullscreen } from '../utils/fullscreen'
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
import ChatWs from '@/plugins/socket' // 聊天socket
|
||||
|
||||
export default () => {
|
||||
const screenStore = useScreenStore()
|
||||
const slidesStore = useSlidesStore()
|
||||
const classcourseStore = useClasscourseStore() // 课堂信息
|
||||
|
||||
// 进入放映状态(从当前页开始)
|
||||
const enterScreening = () => {
|
||||
|
@ -19,7 +22,17 @@ export default () => {
|
|||
|
||||
// 退出放映状态
|
||||
const exitScreening = () => {
|
||||
screenStore.setScreening(false)
|
||||
const classcourse = classcourseStore.classcourse
|
||||
if (!!classcourse) { //DOTO 有课堂,执行退相关操作
|
||||
console.log('退出放映状态')
|
||||
ChatWs?.close() // 关闭ws
|
||||
sessionStore.delete('curr.classcourse') // 清除课堂信息
|
||||
sessionStore.delete('curr.resource') // 清除课件信息
|
||||
sessionStore.delete('curr.isPublic') // 清除公屏状态
|
||||
setTimeout(() => {
|
||||
window.close() // 关闭窗口
|
||||
}, 1000)
|
||||
} else screenStore.setScreening(false)
|
||||
if (isFullscreen()) exitFullscreen()
|
||||
}
|
||||
|
||||
|
|
|
@ -125,6 +125,8 @@ import {
|
|||
User,
|
||||
Switch,
|
||||
More,
|
||||
Material,
|
||||
AddPicture
|
||||
} from '@icon-park/vue-next'
|
||||
|
||||
export interface Icons {
|
||||
|
@ -255,6 +257,8 @@ export const icons: Icons = {
|
|||
IconUser: User,
|
||||
IconSwitch: Switch,
|
||||
IconMore: More,
|
||||
IconMaterial: Material,
|
||||
IconAddPicture: AddPicture
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { Classcourse } from '../api/types'
|
||||
|
||||
export interface ClasscourseState {
|
||||
classcourse: Classcourse | any, // 课堂信息
|
||||
isEmit: boolean, // 是否加载监听事件(动画播放)
|
||||
}
|
||||
|
||||
export const useClasscourseStore = defineStore('classcourse', {
|
||||
state: (): ClasscourseState => ({
|
||||
classcourse: null, // 课堂信息
|
||||
isEmit: false, // 是否加载监听事件(动画播放)
|
||||
}),
|
||||
|
||||
actions: {
|
||||
setClasscourse(classcourse: Classcourse) {
|
||||
this.classcourse = classcourse
|
||||
},
|
||||
setIsEmit(isEmit: boolean) {
|
||||
this.isEmit = isEmit
|
||||
},
|
||||
},
|
||||
})
|
|
@ -3,6 +3,7 @@ import { useSlidesStore } from './slides'
|
|||
import { useSnapshotStore } from './snapshot'
|
||||
import { useKeyboardStore } from './keyboard'
|
||||
import { useScreenStore } from './screen'
|
||||
import { useClasscourseStore } from './classcourse'
|
||||
|
||||
export {
|
||||
useMainStore,
|
||||
|
@ -10,4 +11,5 @@ export {
|
|||
useSnapshotStore,
|
||||
useKeyboardStore,
|
||||
useScreenStore,
|
||||
useClasscourseStore,
|
||||
}
|
|
@ -1,17 +1,26 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
export interface ScreenState {
|
||||
screening: boolean
|
||||
screening: boolean,
|
||||
like: number,
|
||||
doubt: number,
|
||||
}
|
||||
|
||||
export const useScreenStore = defineStore('screen', {
|
||||
state: (): ScreenState => ({
|
||||
screening: false, // 是否进入放映状态
|
||||
like:0, // 点赞数量
|
||||
doubt:0, // 疑问数量
|
||||
}),
|
||||
|
||||
actions: {
|
||||
setScreening(screening: boolean) {
|
||||
this.screening = screening
|
||||
},
|
||||
// 打开点赞或疑问
|
||||
openUpvote(type: 'like' | 'doubt'){
|
||||
if (type === 'like') this.like++
|
||||
else this.doubt++
|
||||
}
|
||||
},
|
||||
})
|
|
@ -5,9 +5,12 @@ import type { Slide, SlideTheme, PPTElement, PPTAnimation } from '../types/slide
|
|||
import { slides } from '../mocks/slides'
|
||||
import { theme } from '../mocks/theme'
|
||||
import { layouts } from '../mocks/layout'
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
import useUserStore from '@/store/modules/user' // 外部-用户信息
|
||||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||
|
||||
import PPTApi from '../api/store'
|
||||
|
||||
const userStore = useUserStore()
|
||||
interface RemovePropData {
|
||||
id: string
|
||||
propName: string | string[]
|
||||
|
@ -30,7 +33,10 @@ export interface SlidesState {
|
|||
slides: Slide[]
|
||||
slideIndex: number
|
||||
viewportSize: number
|
||||
viewportRatio: number
|
||||
viewportRatio: number,
|
||||
animationIndex: number, // 不是从0开始
|
||||
workList:Object[],
|
||||
workItem:Object[],
|
||||
}
|
||||
|
||||
export const useSlidesStore = defineStore('slides', {
|
||||
|
@ -41,6 +47,9 @@ export const useSlidesStore = defineStore('slides', {
|
|||
slideIndex: 0, // 当前页面索引
|
||||
viewportSize: 1000, // 可视区域宽度基数
|
||||
viewportRatio: 0.5625, // 可视区域比例,默认16:9
|
||||
animationIndex: 0, // 不是从0开始
|
||||
workList:[],// 活动的列表
|
||||
workItem:[],// 获取到的所有pptlist
|
||||
}),
|
||||
|
||||
getters: {
|
||||
|
@ -131,18 +140,23 @@ export const useSlidesStore = defineStore('slides', {
|
|||
setSlides(slides: Slide[]) {
|
||||
this.slides = slides
|
||||
},
|
||||
// 更新活动列表
|
||||
setWorkList(list: Object[]) {
|
||||
this.workList = list
|
||||
},
|
||||
setWorkItem(list: Object[]) {
|
||||
this.workItem = list
|
||||
},
|
||||
|
||||
addSlide(slide: Slide | Slide[]) {
|
||||
const slides = Array.isArray(slide) ? slide : [slide]
|
||||
for (const slide of slides) {
|
||||
if (slide.sectionTag) delete slide.sectionTag
|
||||
}
|
||||
|
||||
const addIndex = this.slideIndex + 1
|
||||
this.slides.splice(addIndex, 0, ...slides)
|
||||
this.slideIndex = addIndex
|
||||
},
|
||||
|
||||
updateSlide(props: Partial<Slide>, slideId?: string) {
|
||||
const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex
|
||||
this.slides[slideIndex] = { ...this.slides[slideIndex], ...props }
|
||||
|
@ -177,6 +191,7 @@ export const useSlidesStore = defineStore('slides', {
|
|||
const isDel = await PPTApi.delSlide(deletedId)
|
||||
if (isDel) {
|
||||
// 后端删除成功,更新页面数据
|
||||
this.workItem.splice(index, 1)
|
||||
deleteSlidesIndex.push(index)
|
||||
slides.splice(index, 1)
|
||||
}
|
||||
|
@ -193,6 +208,9 @@ export const useSlidesStore = defineStore('slides', {
|
|||
updateSlideIndex(index: number) {
|
||||
this.slideIndex = index
|
||||
},
|
||||
updateAnimationIndex(index: number) {
|
||||
this.animationIndex = index
|
||||
},
|
||||
|
||||
addElement(element: PPTElement | PPTElement[]) {
|
||||
const elements = Array.isArray(element) ? element : [element]
|
||||
|
|
|
@ -6,4 +6,5 @@ export const enum ToolbarStates {
|
|||
SLIDE_DESIGN = 'slideDesign',
|
||||
SLIDE_ANIMATION = 'slideAnimation',
|
||||
MULTI_POSITION = 'multiPosition',
|
||||
EL_ACTIVE = 'elActive',
|
||||
}
|
|
@ -72,4 +72,55 @@ export const isSVGString = (text: string): boolean => {
|
|||
export const svg2File = (svg: string): File => {
|
||||
const blob = new Blob([svg], { type: 'image/svg+xml' })
|
||||
return new File([blob], `${Date.now()}.svg`, { type: 'image/svg+xml' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的时间
|
||||
* @returns
|
||||
*/
|
||||
export const getTime=()=>{
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = ('0' + (now.getMonth() + 1)).slice(-2);
|
||||
const day = ('0' + now.getDate()).slice(-2);
|
||||
const hours = ('0' + now.getHours()).slice(-2);
|
||||
const minutes = ('0' + now.getMinutes()).slice(-2);
|
||||
const seconds = ('0' + now.getSeconds()).slice(-2);
|
||||
return `${year}-${month}-${day}_${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* base64转图片File
|
||||
* @param {String} base64 图片base64
|
||||
* @param {String} fileName 图片名称| 默认 → myimg
|
||||
* @returns File 返回转换后的file数据类型
|
||||
*/
|
||||
export const base64ToFile = (base64: string, fileName = '试题图片') => {
|
||||
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
|
||||
let data = base64.split(','),
|
||||
// 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
|
||||
type = data[0].match(/:(.*?);/)[1],
|
||||
// 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp)
|
||||
suffix = type.split('/')[1],
|
||||
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
|
||||
bstr = window.atob(data[1]),
|
||||
// 获取解码结果字符串的长度
|
||||
n = bstr.length,
|
||||
// 根据解码结果字符串的长度创建一个等长的整形数字数组
|
||||
// 但在创建时 所有元素初始值都为 0
|
||||
u8arr = new Uint8Array(n)
|
||||
|
||||
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
|
||||
while (n--) {
|
||||
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
|
||||
u8arr[n] = bstr.charCodeAt(n)
|
||||
}
|
||||
const filename = fileName+getTime()
|
||||
// 利用构造函数创建File文件对象
|
||||
// new File(bits, name, options)
|
||||
const file = new File([u8arr], `${filename}.${suffix}`, {
|
||||
type: type
|
||||
})
|
||||
// 返回file
|
||||
return file
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<header class="flex material-header">
|
||||
<span>选择素材</span>
|
||||
<i class="iconfont icon-guanbi" @click="onClose"></i>
|
||||
</header>
|
||||
<div class="flex material-list" v-loading="loading">
|
||||
<div class="flex material-item" v-for="item in list" :key="item.id" >
|
||||
<div class="flex">
|
||||
<el-image :src="fileUrl(item)" class="img" />
|
||||
<el-text truncated>{{ item.fileShowName }}</el-text>
|
||||
</div>
|
||||
<el-button type="primary" @click="onInsert(item)">插入</el-button>
|
||||
</div>
|
||||
<el-empty description="暂无素材" v-if="!list.length" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import { getSmarttalkPage } from '@/api/file'
|
||||
import * as commUtils from '@/utils/comm.js'
|
||||
import { getFileSuffix } from '@/utils/ruoyi.js'
|
||||
import { PPTApi } from '../../../api'
|
||||
|
||||
const emit = defineEmits(['insertMaterial', 'close'])
|
||||
|
||||
const curNode = reactive({})
|
||||
let params = {
|
||||
textbookId: '',
|
||||
levelFirstId: '',
|
||||
levelSecondId: null,
|
||||
fileSource: '个人',
|
||||
fileRoot: '备课',
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc',
|
||||
pageSize: 500
|
||||
}
|
||||
|
||||
const suffixAry = [ 'jpeg','jpg','png','gif','mp3','mp4','avi','mov']
|
||||
const videoSuffix = ['mp3','mp4','avi','mov']
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
const init = () => {
|
||||
loading.value = true
|
||||
getSmarttalkPage(params).then(res => {
|
||||
loading.value = false
|
||||
if(res.rows && res.rows.length){
|
||||
// 过滤出图片和视频
|
||||
list.value = res.rows.filter( item => suffixAry.indexOf(getFileSuffix(item.fileShowName)) != -1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const fileUrl = computed(() => (item) =>{
|
||||
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
|
||||
return item.coverPic
|
||||
}
|
||||
else{
|
||||
return item.fileFullPath
|
||||
}
|
||||
})
|
||||
|
||||
// 插入
|
||||
const onInsert = async (item) =>{
|
||||
loading.value = true
|
||||
const res = await fetch(item.fileFullPath)
|
||||
const bolb = await res.blob()
|
||||
const file = commUtils.blobToFile(bolb, item.fileShowName)
|
||||
|
||||
try {
|
||||
const data = await PPTApi.toRousrceUrl(file)
|
||||
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
|
||||
emit('insertMaterial',{ type: 'video', data })
|
||||
}
|
||||
else{
|
||||
emit('insertMaterial',{ type: 'img', data })
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭
|
||||
const onClose = () =>{
|
||||
emit('close')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data);
|
||||
params.textbookId = curNode.rootid
|
||||
if (curNode.parentNode) {
|
||||
params.levelFirstId = curNode.parentNode.id
|
||||
params.levelSecondId = curNode.id
|
||||
}
|
||||
else {
|
||||
params.levelFirstId = curNode.id
|
||||
}
|
||||
init()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.material-header {
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.icon-guanbi{
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.material-list{
|
||||
flex-direction: column;
|
||||
min-height: 150px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto
|
||||
}
|
||||
|
||||
.material-item {
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
.img{
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -7,19 +7,44 @@
|
|||
/>
|
||||
|
||||
<template v-if="type === 'video'">
|
||||
<Input v-model:value="videoSrc" placeholder="请输入视频地址,e.g. https://xxx.mp4"></Input>
|
||||
<div class="btns">
|
||||
<el-tabs :tab-position="'left'" class="demo-tabs" v-model="tabvalue">
|
||||
<el-tab-pane label="常规" >
|
||||
<Input v-model:value="videoSrc" style="width:100%" placeholder="请输入视频地址,e.g. https://xxx.mp4"></Input>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="上传" >
|
||||
<FileInput accept="video/*" @change="files => insertImageElementvideo(files)">
|
||||
<div class="updivs">+点击上传视频</div>
|
||||
</FileInput>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
|
||||
|
||||
<div class="btns" v-if="tabvalue=='0'">
|
||||
<Button @click="emit('close')" style="margin-right: 10px;">取消</Button>
|
||||
<Button type="primary" @click="insertVideo()">确认</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="type === 'audio'">
|
||||
<Input v-model:value="audioSrc" placeholder="请输入音频地址,e.g. https://xxx.mp3"></Input>
|
||||
<div class="btns">
|
||||
<el-tabs :tab-position="'left'" class="demo-tabs" v-model="tabvalue1">
|
||||
<el-tab-pane label="常规" >
|
||||
<Input v-model:value="audioSrc" style="width:100%" placeholder="请输入音频地址,e.g. https://xxx.mp3"></Input>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="上传" >
|
||||
<FileInput accept="audio/*" @change="files => insertImageElementaudio(files)">
|
||||
<div class="updivs">+点击上传音频</div>
|
||||
</FileInput>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="btns" v-if="tabvalue1=='0'">
|
||||
<Button @click="emit('close')" style="margin-right: 10px;">取消</Button>
|
||||
<Button type="primary" @click="insertAudio()">确认</Button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -30,6 +55,8 @@ import message from '../../../utils/message'
|
|||
import Tabs from '../../../components/Tabs.vue'
|
||||
import Input from '../../../components/Input.vue'
|
||||
import Button from '../../../components/Button.vue'
|
||||
import FileInput from '../../../components/FileInput.vue'
|
||||
import { PPTApi } from '../../../api'
|
||||
|
||||
type TypeKey = 'video' | 'audio'
|
||||
interface TabItem {
|
||||
|
@ -45,9 +72,33 @@ const emit = defineEmits<{
|
|||
|
||||
const type = ref<TypeKey>('video')
|
||||
|
||||
const videoSrc = ref('https://mazwai.com/videvo_files/video/free/2019-01/small_watermarked/181004_04_Dolphins-Whale_06_preview.webm')
|
||||
const audioSrc = ref('https://freesound.org/data/previews/614/614107_11861866-lq.mp3')
|
||||
const videoSrc = ref('')
|
||||
const audioSrc = ref('')
|
||||
// https://freesound.org/data/previews/614/614107_11861866-lq.mp3
|
||||
const tabvalue = ref('0')
|
||||
const tabvalue1 = ref('0')
|
||||
|
||||
const insertImageElementvideo = (files: FileList) => {
|
||||
console.log('files', files)
|
||||
const imageFile = files[0]
|
||||
if (!imageFile) return
|
||||
PPTApi.toRousrceUrl(imageFile).then(data=>{
|
||||
videoSrc.value=data
|
||||
insertVideo()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const insertImageElementaudio = (files: FileList) => {
|
||||
console.log('files', files)
|
||||
const imageFile = files[0]
|
||||
if (!imageFile) return
|
||||
PPTApi.toRousrceUrl(imageFile).then(data=>{
|
||||
videoSrc.value=data
|
||||
insertAudio()
|
||||
})
|
||||
|
||||
}
|
||||
const tabs: TabItem[] = [
|
||||
{ key: 'video', label: '视频' },
|
||||
{ key: 'audio', label: '音频' },
|
||||
|
@ -74,4 +125,33 @@ const insertAudio = () => {
|
|||
margin-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
.updivs{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f5f7fa;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.demo-tabs{
|
||||
:deep(.el-tabs__content){
|
||||
display: flex;
|
||||
align-items: center;
|
||||
div{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
:deep( .el-tabs__item.is-active) {
|
||||
color: #d14424;
|
||||
}
|
||||
:deep( .el-tabs__item:hover) {
|
||||
color: #d14424;
|
||||
}
|
||||
:deep(.el-tabs__active-bar) {
|
||||
background-color: #d14424;
|
||||
height: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -81,6 +81,9 @@
|
|||
</template>
|
||||
<IconVideoTwo class="handler-item" v-tooltip="'插入音视频'" />
|
||||
</Popover>
|
||||
<IconPreviewOpen class="handler-item" v-tooltip="'插入试题'" @click="classWorkTaskVisible = true" />
|
||||
<IconMaterial class="handler-item" v-tooltip="'插入素材'" @click="materiaVisible = true"/>
|
||||
<IconAddPicture class="handler-item" v-tooltip="'文生图'" @click="imgVisible = true" />
|
||||
</div>
|
||||
|
||||
<div class="right-handler">
|
||||
|
@ -110,6 +113,27 @@
|
|||
@update="data => { createLatexElement(data); latexEditorVisible = false }"
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<!--插入试题-->
|
||||
<el-dialog v-model="classWorkTaskVisible" append-to-body :show-close="false" width="70%">
|
||||
<QuestToPPTist
|
||||
class="class-work-task-modal"
|
||||
@close="classWorkTaskVisible = false"
|
||||
@update="data => { onhtml2canvas(data); classWorkTaskVisible = false }"
|
||||
/>
|
||||
</el-dialog>
|
||||
<!--插入素材-->
|
||||
<Modal
|
||||
v-model:visible="materiaVisible"
|
||||
:width="880">
|
||||
<MaterialDialog @close="materiaVisible = false" @insertMaterial="insertMaterial"/>
|
||||
</Modal>
|
||||
<!--文生图-->
|
||||
<Modal
|
||||
v-model:visible="imgVisible"
|
||||
:width="1300">
|
||||
<TextCreateImg hasPPt @insertImg="insertImg" />
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -117,7 +141,7 @@
|
|||
import { ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSnapshotStore } from '../../../store'
|
||||
import { getImageDataURL } from '../../../utils/image'
|
||||
import { getImageDataURL, base64ToFile } from '../../../utils/image'
|
||||
import type { ShapePoolItem } from '../../../configs/shapes'
|
||||
import type { LinePoolItem } from '../../../configs/lines'
|
||||
import useScaleCanvas from '../../../hooks/useScaleCanvas'
|
||||
|
@ -135,6 +159,12 @@ import Modal from '../../../components/Modal.vue'
|
|||
import Divider from '../../../components/Divider.vue'
|
||||
import Popover from '../../../components/Popover.vue'
|
||||
import PopoverMenuItem from '../../../components/PopoverMenuItem.vue'
|
||||
import QuestToPPTist from '@/views/classTask/newClassTaskAssign/questToPPTist/index.vue'
|
||||
import MaterialDialog from './MaterialDialog.vue'
|
||||
import { PPTApi } from '../../../api'
|
||||
import TextCreateImg from '@/components/ai-kolors/index.vue'
|
||||
import { toPng } from 'html-to-image' // 引入html-to-image库
|
||||
|
||||
|
||||
const mainStore = useMainStore()
|
||||
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
|
||||
|
@ -167,9 +197,27 @@ const {
|
|||
} = useCreateElement()
|
||||
|
||||
const insertImageElement = (files: FileList) => {
|
||||
console.log('files', files)
|
||||
const imageFile = files[0]
|
||||
if (!imageFile) return
|
||||
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
|
||||
// 上传图片转为线上地址
|
||||
PPTApi.toRousrceUrl(imageFile).then(data=>{
|
||||
createImageElement(data)
|
||||
})
|
||||
// getImageDataURL(imageFile).then(dataURL => {
|
||||
// createImageElement(dataURL)
|
||||
// })
|
||||
}
|
||||
|
||||
const onhtml2canvas = async (html: HTMLElement) => {
|
||||
const base64Dta = await toPng(html);
|
||||
// base64转图片File
|
||||
const toFile = base64ToFile(base64Dta)
|
||||
// 上传图片转为线上地址
|
||||
PPTApi.toRousrceUrl(toFile).then(data=>{
|
||||
createImageElement(data)
|
||||
})
|
||||
// createImageElement(ele);
|
||||
}
|
||||
|
||||
const shapePoolVisible = ref(false)
|
||||
|
@ -178,9 +226,11 @@ const chartPoolVisible = ref(false)
|
|||
const tableGeneratorVisible = ref(false)
|
||||
const mediaInputVisible = ref(false)
|
||||
const latexEditorVisible = ref(false)
|
||||
const classWorkTaskVisible = ref(false)
|
||||
const textTypeSelectVisible = ref(false)
|
||||
const shapeMenuVisible = ref(false)
|
||||
const moreVisible = ref(false)
|
||||
const materiaVisible = ref(false)
|
||||
|
||||
// 绘制文字范围
|
||||
const drawText = (vertical = false) => {
|
||||
|
@ -227,6 +277,32 @@ const toggleSraechPanel = () => {
|
|||
const toggleNotesPanel = () => {
|
||||
mainStore.setNotesPanelState(!showNotesPanel.value)
|
||||
}
|
||||
|
||||
// 插入素材
|
||||
interface MaterialParams {
|
||||
type: string,
|
||||
data: string
|
||||
}
|
||||
const insertMaterial = async (item: MaterialParams) =>{
|
||||
const { type, data } = item
|
||||
if(type == 'video'){
|
||||
createVideoElement(data)
|
||||
}
|
||||
else{
|
||||
createImageElement(data)
|
||||
}
|
||||
materiaVisible.value = false
|
||||
}
|
||||
|
||||
// 文生图
|
||||
const imgVisible = ref(false)
|
||||
const insertImg = async (file: any) =>{
|
||||
PPTApi.toRousrceUrl(file).then(data=>{
|
||||
createImageElement(data)
|
||||
imgVisible.value = false
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -343,6 +419,9 @@ const toggleNotesPanel = () => {
|
|||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.class-work-task-modal{
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
@media screen and (width <= 1200px) {
|
||||
.right-handler .text {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="left">
|
||||
<Popover trigger="click" placement="bottom-start" v-model:value="mainMenuVisible">
|
||||
<template #content>
|
||||
<FileInput accept=".pptist" @change="files => {
|
||||
<!-- <FileInput accept=".pptist" @change="files => {
|
||||
importSpecificFile(files)
|
||||
mainMenuVisible = false
|
||||
}">
|
||||
|
@ -15,8 +15,8 @@
|
|||
}">
|
||||
<PopoverMenuItem>导入 pptx 文件</PopoverMenuItem>
|
||||
</FileInput>
|
||||
<PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem>
|
||||
<PopoverMenuItem @click="resetSlides(); mainMenuVisible = false">重置幻灯片</PopoverMenuItem>
|
||||
<PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem> -->
|
||||
<!-- <PopoverMenuItem @click="resetSlides(); mainMenuVisible = false">重置幻灯片</PopoverMenuItem> -->
|
||||
<!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/issues')">意见反馈</PopoverMenuItem> -->
|
||||
<!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/blob/master/doc/Q&A.md')">常见问题</PopoverMenuItem> -->
|
||||
<PopoverMenuItem @click="mainMenuVisible = false; hotkeyDrawerVisible = true">快捷操作</PopoverMenuItem>
|
||||
|
@ -54,9 +54,9 @@
|
|||
<div class="arrow-btn"><IconDown class="arrow" /></div>
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
|
||||
<!-- <div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
|
||||
<IconDownload class="icon" />
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="menu-item" v-tooltip="`${userStore.user.parentDeptName}-${userStore.user.nickName}`">
|
||||
<el-avatar size="small" :src="avatar" />
|
||||
</div>
|
||||
|
@ -162,6 +162,10 @@ const setDialogForExport = (type: DialogForExportTypes) => {
|
|||
.icon {
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
|
||||
:deep(svg) {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
</Draggable>
|
||||
|
||||
<div class="page-number">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -90,7 +91,7 @@ const mainStore = useMainStore()
|
|||
const slidesStore = useSlidesStore()
|
||||
const keyboardStore = useKeyboardStore()
|
||||
const { selectedSlidesIndex: _selectedSlidesIndex, thumbnailsFocus } = storeToRefs(mainStore)
|
||||
const { slides, slideIndex, currentSlide } = storeToRefs(slidesStore)
|
||||
const { slides, slideIndex, currentSlide, workList, workItem } = storeToRefs(slidesStore)
|
||||
const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)
|
||||
|
||||
const { slidesLoadLimit } = useLoadSlides()
|
||||
|
@ -123,6 +124,7 @@ const {
|
|||
updateSectionTitle,
|
||||
} = useSectionHandler()
|
||||
|
||||
|
||||
// 页面被切换时
|
||||
const thumbnailsRef = ref<InstanceType<typeof Draggable>>()
|
||||
watch(() => slideIndex.value, () => {
|
||||
|
@ -145,6 +147,7 @@ watch(() => slideIndex.value, () => {
|
|||
|
||||
// 切换页面
|
||||
const changeSlideIndex = (index: number) => {
|
||||
|
||||
mainStore.setActiveElementIdList([])
|
||||
|
||||
if (slideIndex.value === index) return
|
||||
|
@ -393,12 +396,17 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => {
|
|||
.icon {
|
||||
margin-right: 3px;
|
||||
font-size: 14px;
|
||||
|
||||
:deep(svg) {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.thumbnail-list {
|
||||
padding: 5px 0;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
}
|
||||
.thumbnail-item {
|
||||
display: flex;
|
||||
|
@ -477,7 +485,6 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => {
|
|||
.page-number {
|
||||
height: 40px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid $borderColor;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
|
|
|
@ -245,7 +245,6 @@ const runAnimation = (elId: string, effect: string, duration: number) => {
|
|||
const animationName = `${ANIMATION_CLASS_PREFIX}${effect}`
|
||||
document.documentElement.style.setProperty('--animate-duration', `${duration}ms`)
|
||||
elRef.classList.add(`${ANIMATION_CLASS_PREFIX}animated`, animationName)
|
||||
|
||||
const handleAnimationEnd = () => {
|
||||
document.documentElement.style.removeProperty('--animate-duration')
|
||||
elRef.classList.remove(`${ANIMATION_CLASS_PREFIX}animated`, animationName)
|
||||
|
|
|
@ -0,0 +1,346 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="display: flex;flex-wrap: wrap;">
|
||||
<el-button size="small" title="活动引用" text style="height: 54px" @click="openList()">
|
||||
<div class="buttonDiv">
|
||||
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M133.36576 308.12842667v407.74314666c0 96.39253333 78.31552 174.76266667 174.76266667 174.76266667h407.74314666c96.39253333 0 174.76266667-78.31552 174.76266667-174.76266667V308.12842667c0-96.39253333-78.31552-174.76266667-174.76266667-174.76266667H308.12842667a174.87189333 174.87189333 0 0 0-174.76266667 174.76266667zM75.09333333 308.12842667A233.19893333 233.19893333 0 0 1 308.12842667 75.09333333h407.74314666A233.19893333 233.19893333 0 0 1 948.90666667 308.12842667v407.74314666A233.19893333 233.19893333 0 0 1 715.87157333 948.90666667H308.12842667A233.19893333 233.19893333 0 0 1 75.09333333 715.87157333V308.12842667z m706.9696 192.07509333c0 24.46677333-5.67978667 48.22357333-16.98474666 71.21578667-11.35957333 22.99221333-26.76053333 43.52682667-46.25749334 61.54922666-19.44234667 18.0224-42.05226667 32.44032-67.61130666 43.25376a207.20298667 207.20298667 0 0 1-81.21002667 16.16554667h-26.54208c-10.37653333 0-21.62688 0.10922667-33.64181333 0.27306667-12.01493333 0.21845333-23.86602667 0.32768-35.66250667 0.32768h-31.9488c-13.1072 0-23.53834667 1.25610667-31.23882667 3.71370666-7.70048 2.51221333-11.35957333 7.91893333-10.92266666 16.16554667 0 6.22592-0.10922667 13.38026667-0.32768 21.46304-0.21845333 8.08277333-0.32768 15.45557333-0.32768 22.06378667 0 10.81344-3.16757333 17.74933333-9.50272 20.86229333-6.33514667 3.11296-14.7456 0.92842667-25.12213334-6.5536-10.92266667-7.42741333-23.37450667-16.27477333-37.41013333-26.43285333a36528.56490667 36528.56490667 0 0 0-126.42986667-91.09504c-10.37653333-7.42741333-15.72864-14.47253333-15.94709333-21.13536-0.27306667-6.60821333 4.36906667-13.65333333 13.9264-21.13536 9.93962667-7.48202667 21.62688-16.27477333 34.95253333-26.43285334 13.38026667-10.15808 27.30666667-20.58922667 41.83381334-31.40266666l42.81685333-31.67573334c14.03562667-10.37653333 26.48746667-19.71541333 37.41013333-28.01664 11.74186667-9.12042667 21.62688-12.61568 29.4912-10.54037333 7.97354667 2.07530667 11.96032 8.73813333 11.96032 19.87925333 0 3.2768 0.10922667 7.3728 0.32768 12.12416a2794.40042667 2794.40042667 0 0 1 1.69301334 43.85450667c0 7.86432 2.62144 12.72490667 7.80970666 14.58176 5.24288 1.85685333 12.34261333 2.83989333 21.40842667 2.83989333 20.42538667-0.43690667 43.30837333-0.65536 68.64896-0.65536h70.66965333c10.43114667 0 20.86229333-2.18453333 31.29344-6.5536 10.37653333-4.36906667 19.93386667-10.10346667 28.56277334-17.36704 8.57429333-7.26357333 15.61941333-15.67402667 21.02613333-25.17674666 5.46133333-9.55733333 8.192-19.6608 8.192-30.47424v-43.52682667c0-16.60245333-0.10922667-32.65877333-0.32768-48.22357333a2840.00256 2840.00256 0 0 1-0.38229333-40.68693334V341.6064c0-9.12042667 3.05834667-16.65706667 9.17504-22.66453333 6.11669333-6.00746667 13.81717333-10.59498667 23.10144-13.70794667 9.28426667-3.11296 19.38773333-4.64213333 30.25578666-4.64213333 10.92266667 0 20.97152 1.41994667 30.25578667 4.36906666 9.28426667 2.83989333 16.98474667 7.09973333 23.10144 12.72490667 6.11669333 5.57056 9.17504 12.56106667 9.17504 20.80768v46.03904c0 10.75882667 0.10922667 22.28224 0.32768 34.51562667 0.27306667 12.23338667 0.38229333 23.92064 0.38229333 35.11637333V500.20352z" p-id="7882"></path></svg>
|
||||
<div style="margin-top: 10px">活动引用</div>
|
||||
</div>
|
||||
</el-button>
|
||||
<el-button size="small" title="习题训练" text style="height: 54px" @click="showDialog('习题训练')">
|
||||
<div class="buttonDiv">
|
||||
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M473.6 204.8c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4H243.2c-21.2 0-38.4-17.2-38.4-38.4s17.2-38.4 38.4-38.4h230.4z m-51.2 153.6c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4H243.2c-21.2 0-38.4-17.2-38.4-38.4s17.2-38.4 38.4-38.4h179.2zM371.2 512c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4h-128c-21.2 0-38.4-17.2-38.4-38.4S222 512 243.2 512h128z m192-384H153.6c-12.6 0-23 9.1-25.2 21l-0.4 4.6v512c0 12.6 9.1 23 21 25.2l4.6 0.4h257.1c-0.7-8.5-1.1-17-1.1-25.6 0-124 73.5-230.8 179.2-279.4V153.6c0-12.6-9.1-23-21-25.2l-4.6-0.4zM768 460.8H665.6v153.6H512v102.4h153.6v153.6H768V716.8h153.6V614.4H768V460.8zM563.2 51.2c56.6 0 102.4 45.8 102.4 102.4v209c16.6-2.8 33.7-4.2 51.2-4.2 169.7 0 307.2 137.5 307.2 307.2S886.5 972.8 716.8 972.8c-133.7 0-247.5-85.5-289.7-204.8H153.6C97 768 51.2 722.2 51.2 665.6v-512C51.2 97 97 51.2 153.6 51.2h409.6z" p-id="13225"></path></svg>
|
||||
<div style="margin-top: 10px">习题训练</div>
|
||||
</div>
|
||||
</el-button>
|
||||
<el-button size="small" title="课堂展示" text style="height: 54px" @click="showDialog('课堂展示')">
|
||||
<div class="buttonDiv">
|
||||
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M741.75298 604.605763l3.836774 0.697596 17.43988 3.767014c154.656857 35.089039 260.133252 103.732407 260.133252 194.070985 0 88.455072-101.360583 156.261326-251.134274 191.838682l-9.068737 2.092785-17.370121 3.836774-17.788678 3.487976c-23.927515 4.39485-48.831664 8.092104-74.642686 11.022004l-19.462907 2.023026-24.69487 2.092786-5.022685 0.348797-20.090742 1.325431-10.18489 0.488317-10.25465 0.418557-20.579058 0.697595c-10.324409 0.209279-20.788337 0.348798-31.322025 0.348798l-15.695892-0.139519-15.556373-0.279038-20.648818-0.627836-10.18489-0.418557-10.18489-0.488317-20.160501-1.325431-9.975612-0.697595-19.741944-1.743988a1100.665713 1100.665713 0 0 1-75.968118-9.905852l-18.137475-3.139178-17.788678-3.487976-17.37012-3.767014C105.406635 961.983786 0 893.410178 0 803.141358c0-88.594591 101.360583-156.261326 251.134273-191.9782l8.998979-2.092785 17.43988-3.767014 3.767014-0.697596v93.756796l-10.952245 2.581102-15.207575 3.906533-14.6495 4.046052-13.951904 4.185572c-75.340282 23.857756-121.660604 61.946454-121.660603 89.989781 0 27.206213 43.250903 61.527897 114.196335 85.036855l7.394509 2.371824 14.021663 4.255331 14.6495 4.046052 15.207575 3.906533c8.580421 2.092786 17.43988 4.115812 26.578377 5.999319l13.951904 2.790381 17.021323 3.139178 8.7897 1.39519 17.788678 2.790381 9.068737 1.255672 18.556033 2.302064 14.161182 1.604469 14.370462 1.39519 19.532665 1.53471a1139.731044 1139.731044 0 0 0 142.867498 1.255671l19.881463-1.255671 19.532666-1.53471c8.580421-0.767355 17.021323-1.674228 25.392466-2.581102l12.417194-1.53471 18.276995-2.441583 17.858437-2.790381 8.71994-1.39519 17.091082-3.139178c9.417535-1.813748 18.486273-3.697255 27.415492-5.720281l13.11479-3.069419 15.207575-3.906533 14.649499-4.046052 13.951904-4.185572c75.340282-23.857756 121.590844-59.365352 121.590845-87.408679 0-27.206213-43.250903-64.178759-114.196335-87.617957l-7.39451-2.371824-14.021663-4.255331-14.649499-4.115811-15.207576-3.836774-10.952245-2.581102V604.605763zM451.204578 588.072757l121.660604 69.82928-131.078139 86.153008 9.417535-155.982288z m280.921589-484.131072l121.660603 69.82928-257.552149 443.810069-121.590844-69.899039 257.48239-443.74031zM842.904285 6.278357l40.530281 23.22992a46.459841 46.459841 0 0 1 17.160842 63.690442l-23.439198 40.321003L755.565365 63.620683l23.36944-40.321003a46.948157 46.948157 0 0 1 63.96948-17.021323z" p-id="59383"></path></svg>
|
||||
<div style="margin-top: 10px">课堂展示</div>
|
||||
</div>
|
||||
</el-button>
|
||||
<el-button size="small" title="常规作业" text style="height: 54px;margin-left: 0" @click="showDialog('常规作业')">
|
||||
<div class="buttonDiv">
|
||||
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M901.705143 511.926857h-55.954286a8.045714 8.045714 0 0 1-8.045714-8.045714V183.881143H181.686857v656.091428H501.76c4.388571 0 8.045714 3.510857 8.045714 7.899429v56.027429c0 4.388571-3.657143 8.045714-8.045714 8.045714H141.750857a31.963429 31.963429 0 0 1-32.036571-32.036572V143.872c0-17.627429 14.336-31.963429 32.036571-31.963429H877.714286c17.700571 0 32.036571 14.336 32.036571 31.963429V503.954286c0 4.388571-3.657143 8.045714-8.045714 8.045714zM731.428571 911.945143a36.571429 36.571429 0 0 1-36.571428-36.571429v-109.714285H585.142857a36.571429 36.571429 0 0 1 0-73.142858h109.714286v-109.714285a36.571429 36.571429 0 0 1 73.142857 0v109.714285H877.714286a36.571429 36.571429 0 1 1 0 73.142858h-109.714286v109.714285a36.571429 36.571429 0 0 1-36.571429 36.571429z" p-id="22184"></path></svg>
|
||||
<div style="margin-top: 10px">常规作业</div>
|
||||
</div>
|
||||
</el-button>
|
||||
<el-button size="small" title="科学实验" text style="height: 54px;margin-left: 0" @click="showDialog('科学实验')">
|
||||
<div class="buttonDiv">
|
||||
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M901.705143 511.926857h-55.954286a8.045714 8.045714 0 0 1-8.045714-8.045714V183.881143H181.686857v656.091428H501.76c4.388571 0 8.045714 3.510857 8.045714 7.899429v56.027429c0 4.388571-3.657143 8.045714-8.045714 8.045714H141.750857a31.963429 31.963429 0 0 1-32.036571-32.036572V143.872c0-17.627429 14.336-31.963429 32.036571-31.963429H877.714286c17.700571 0 32.036571 14.336 32.036571 31.963429V503.954286c0 4.388571-3.657143 8.045714-8.045714 8.045714zM731.428571 911.945143a36.571429 36.571429 0 0 1-36.571428-36.571429v-109.714285H585.142857a36.571429 36.571429 0 0 1 0-73.142858h109.714286v-109.714285a36.571429 36.571429 0 0 1 73.142857 0v109.714285H877.714286a36.571429 36.571429 0 1 1 0 73.142858h-109.714286v109.714285a36.571429 36.571429 0 0 1-36.571429 36.571429z" p-id="22184"></path></svg>
|
||||
<div style="margin-top: 10px">科学实验</div>
|
||||
</div>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<!-- 作业列表 -->
|
||||
<div class="c-apt-r" v-loading='loadingActive'>
|
||||
<!-- 显示-作业内容 -->
|
||||
<template v-for="(item, index) in slidesStore.workList" :key="index">
|
||||
<div class="item">
|
||||
<div class="item-title">
|
||||
<el-tag :type="getTagType(item.worktype) || 'primary'">{{item.worktype}}</el-tag>
|
||||
<el-tooltip :content="item.uniquekey" placement="top">
|
||||
<div class="tt">{{item.uniquekey}}</div>
|
||||
</el-tooltip>
|
||||
<el-button class="btn-del" type="danger" link @click="removeWork(item, index)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- // 推送作业 -->
|
||||
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="85%" height="500">
|
||||
<el-scrollbar>
|
||||
<div style="height: 75vh;">
|
||||
<NewClassTsakAssign :currentCourse='currentCourse' @getData="getData" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-dialog>
|
||||
<!-- 活动引用 -->
|
||||
<el-dialog
|
||||
v-loading="tasklist_loading"
|
||||
v-model="activeVisible"
|
||||
append-to-body
|
||||
:show-close="false"
|
||||
width="40%"
|
||||
>
|
||||
<el-table :data="taskList" style="width: 100%" height="500" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" :selectable="selectable"/>
|
||||
<el-table-column prop="uniquekey" label="活动名称" width="150" />
|
||||
<el-table-column prop="worktype" label="活动类型" width="120" sortable>
|
||||
<template #default="scope">
|
||||
<el-tag :type="getTagType(scope.row.worktype) || 'primary'">{{ scope.row.worktype }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="timestamp" label="创建时间" sortable/>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="activeVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="savePPtData">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, nextTick, watch } from 'vue'
|
||||
import Divider from '../../../../../components/Divider.vue'
|
||||
import { ElMessageBox,ElMessage } from 'element-plus'
|
||||
import NewClassTsakAssign from '@/views/classTask/newClassTaskAssign/index.vue'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import { useGetHomework } from '@/hooks/useGetHomework'
|
||||
import { PPTApi } from '../../../../../api/index'
|
||||
import {useSlidesStore} from '../../../../../store'
|
||||
const slidesStore = useSlidesStore()
|
||||
interface CourseNode {
|
||||
rootid: number;
|
||||
parentNode: { id: number };
|
||||
id: number;
|
||||
itemtitle: string;
|
||||
}
|
||||
|
||||
interface CurrentCourse {
|
||||
textbookId: number;
|
||||
levelFirstId: number;
|
||||
levelSecondId: number;
|
||||
coursetitle: string;
|
||||
node: CourseNode;
|
||||
id: number;
|
||||
worktype: string;
|
||||
}
|
||||
|
||||
interface WorkType {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface WorkItem {
|
||||
status: string;
|
||||
activityContent?: string;
|
||||
worktype: string;
|
||||
quizlist?: { id: number }[];
|
||||
workcodes: string;
|
||||
base64?: string;
|
||||
prevData?: any;
|
||||
id: number;
|
||||
uniquekey?: string; // 添加 evaltitle 属性
|
||||
}
|
||||
|
||||
const currentCourse = reactive<CurrentCourse>({
|
||||
textbookId: 0,
|
||||
levelFirstId: 0,
|
||||
levelSecondId: 0,
|
||||
coursetitle: '',
|
||||
node: {} as CourseNode,
|
||||
id: 1,
|
||||
worktype: '',
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const tasklist_loading = ref<boolean>(false)
|
||||
|
||||
// 活动列表
|
||||
const taskList = ref<WorkItem[]>([])
|
||||
|
||||
// 活动引用的弹窗
|
||||
const activeVisible = ref<boolean>(false)
|
||||
|
||||
|
||||
const type = ref<WorkType[]>([
|
||||
{
|
||||
label: '习题训练',
|
||||
value: 'danger'
|
||||
},
|
||||
{
|
||||
label: '课堂展示',
|
||||
value: 'success'
|
||||
},
|
||||
{
|
||||
label: '常规作业',
|
||||
value: 'primary'
|
||||
},
|
||||
{
|
||||
label: '框架梳理',
|
||||
value: 'primary'
|
||||
},
|
||||
{
|
||||
label: '科学实验',
|
||||
value: 'primary'
|
||||
}
|
||||
])
|
||||
|
||||
// 获取所选择的作业列表
|
||||
const selectedWorkList = ref<WorkItem[]>([])
|
||||
// 活动页面的loading框
|
||||
const loadingActive = ref<boolean>(false)
|
||||
|
||||
// 选择活动列表
|
||||
const selectable = (row: WorkItem, index: number): boolean => {
|
||||
return row.status === '10';
|
||||
};
|
||||
|
||||
// 删除作业
|
||||
const removeWork = (item: WorkItem, index: number) => {
|
||||
ElMessageBox.confirm('是否确认删除?')
|
||||
.then(async() => {
|
||||
// 从workList中删除
|
||||
// slidesStore.workList.splice(index, 1)
|
||||
console.log('删除作业', item)
|
||||
await upDateData('del', [item.id])
|
||||
ElMessage.success('删除成功')
|
||||
})
|
||||
.catch(() => { });
|
||||
}
|
||||
|
||||
// 获取tag的样式
|
||||
const getTagType = (worktype: string): string => {
|
||||
return type.value.find(item => item.label === worktype)!.value
|
||||
}
|
||||
|
||||
// 获取活动引用的列表数据
|
||||
const initHomeWork = async () => {
|
||||
tasklist_loading.value = true;
|
||||
const { res, chapterId } = await useGetHomework(sessionStore.get('subject.curNode'));
|
||||
taskList.value = res;
|
||||
tasklist_loading.value = false;
|
||||
}
|
||||
|
||||
// 多选活动引用
|
||||
const handleSelectionChange = (val: WorkItem[]) => {
|
||||
selectedWorkList.value = [...val]
|
||||
}
|
||||
|
||||
// 打开添加作业活动
|
||||
const showDialog = (item: string) => {
|
||||
currentCourse.worktype = item
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 打开活动引用列表
|
||||
const openList = () => {
|
||||
activeVisible.value = true
|
||||
initHomeWork()
|
||||
}
|
||||
|
||||
// 添加活动引用列表作业
|
||||
const savePPtData = async () => {
|
||||
if (!selectedWorkList.value.length) {
|
||||
ElMessage.warning('请选择活动')
|
||||
return
|
||||
}
|
||||
const arr = selectedWorkList.value.map(item => item.id)
|
||||
await upDateData('add', arr)
|
||||
activeVisible.value = false
|
||||
}
|
||||
|
||||
// 接收习题训练的值
|
||||
const getData = async (data: WorkItem) => {
|
||||
await upDateData('add', [data.id])
|
||||
dialogVisible.value = false
|
||||
}
|
||||
// 更新数据-活动 type add/增加 | del/删除 ids 作业id
|
||||
const upDateData = (type: string, ids: number[]): Promise<any> => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
loadingActive.value = true
|
||||
// 更新本地数据
|
||||
let workIds = slidesStore.workList.map((o:any) => o.id)
|
||||
const id = slidesStore.currentSlide.id
|
||||
if (type === 'del') { // 删除
|
||||
workIds = workIds.filter(id => !ids.includes(id))
|
||||
} else { // 增加
|
||||
workIds = Array.from(new Set([...workIds, ...ids]))
|
||||
}
|
||||
// 保存到本地
|
||||
const wItem:any = slidesStore.workItem.find((o:any) => o.id === id)
|
||||
const workIdsStr = workIds.join(',') // 作业id->字符串
|
||||
if (!!wItem) wItem.activityContent = workIdsStr
|
||||
const data = { id, activityContent: workIdsStr }
|
||||
// 修改数据库
|
||||
await PPTApi.updateSlide(data, false)
|
||||
// 刷新活动列表
|
||||
await PPTApi.updateWorkList()
|
||||
loadingActive.value = false
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const curNode = sessionStore.get('subject.curNode') as CourseNode
|
||||
currentCourse.textbookId = curNode.rootid
|
||||
currentCourse.levelFirstId = curNode.parentNode.id
|
||||
currentCourse.levelSecondId = curNode.id
|
||||
currentCourse.coursetitle = curNode.itemtitle
|
||||
currentCourse.node = curNode
|
||||
})
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.buttonDiv{
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
}
|
||||
// (apt)编辑区-右侧
|
||||
.c-apt-r{
|
||||
flex:1;
|
||||
overflow: auto;
|
||||
.item{
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
.item-title{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.tt{
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
.btn-del{
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.item-body{
|
||||
position: relative;
|
||||
border: 1px solid silver;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
margin: 5px 10px;
|
||||
cursor: default;
|
||||
&:hover{
|
||||
border-color: var(--el-color-primary);
|
||||
.el-text{
|
||||
--el-text-color: var(--el-color-primary);
|
||||
}
|
||||
.el-tag{
|
||||
--el-tag-bg-color: var(--el-color-primary);
|
||||
--el-tag-border-color: var(--el-color-primary);
|
||||
--el-tag-text-color: var(--el-color-white);
|
||||
}
|
||||
}
|
||||
.c-icon-info{
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
}
|
||||
}
|
||||
.item-divider{
|
||||
margin: 5px 0;
|
||||
margin-left: 10px;
|
||||
width: calc(100% - 20px);
|
||||
--el-border-color: var(--current-color);
|
||||
}
|
||||
}
|
||||
.page .page-resource{
|
||||
height: 500px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -88,7 +88,9 @@ import Button from '../../../../components/Button.vue'
|
|||
import ButtonGroup from '../../../../components/ButtonGroup.vue'
|
||||
import Popover from '../../../../components/Popover.vue'
|
||||
import NumberInput from '../../../../components/NumberInput.vue'
|
||||
|
||||
import { PPTApi } from '../../../../api'
|
||||
import { Console } from 'node:console'
|
||||
import { x64 } from 'crypto-js'
|
||||
const shapeClipPathOptions = CLIPPATHS
|
||||
const ratioClipOptions = [
|
||||
{
|
||||
|
@ -221,10 +223,14 @@ const presetImageClip = (shape: string, ratio = 0) => {
|
|||
const replaceImage = (files: FileList) => {
|
||||
const imageFile = files[0]
|
||||
if (!imageFile) return
|
||||
getImageDataURL(imageFile).then(dataURL => {
|
||||
const props = { src: dataURL }
|
||||
updateImage(props)
|
||||
PPTApi.toRousrceUrl(imageFile).then(data=>{
|
||||
const props = { src: data }
|
||||
updateImage(props)
|
||||
})
|
||||
// getImageDataURL(imageFile).then(dataURL => {
|
||||
// const props = { src: dataURL }
|
||||
// updateImage(props)
|
||||
// })
|
||||
}
|
||||
|
||||
// 重置图片:清除全部样式
|
||||
|
|
|
@ -25,6 +25,8 @@ import SlideDesignPanel from './SlideDesignPanel.vue'
|
|||
import SlideAnimationPanel from './SlideAnimationPanel.vue'
|
||||
import MultiPositionPanel from './MultiPositionPanel.vue'
|
||||
import SymbolPanel from './SymbolPanel.vue'
|
||||
// 新增的活动页面
|
||||
import SymbolActivePanel from './ElementStylePanel/Active/index.vue'
|
||||
import Tabs from '../../../components/Tabs.vue'
|
||||
|
||||
interface ElementTabs {
|
||||
|
@ -54,6 +56,7 @@ const slideTabs = [
|
|||
{ label: '设计', key: ToolbarStates.SLIDE_DESIGN },
|
||||
{ label: '切换', key: ToolbarStates.SLIDE_ANIMATION },
|
||||
{ label: '动画', key: ToolbarStates.EL_ANIMATION },
|
||||
{ label: '活动', key: ToolbarStates.EL_ACTIVE },
|
||||
]
|
||||
const multiSelectTabs = [
|
||||
{ label: '样式', key: ToolbarStates.EL_STYLE },
|
||||
|
@ -86,6 +89,7 @@ const currentPanelComponent = computed(() => {
|
|||
[ToolbarStates.SLIDE_ANIMATION]: SlideAnimationPanel,
|
||||
[ToolbarStates.MULTI_POSITION]: MultiPositionPanel,
|
||||
[ToolbarStates.SYMBOL]: SymbolPanel,
|
||||
[ToolbarStates.EL_ACTIVE]: SymbolActivePanel,// 新增的活动页面
|
||||
}
|
||||
return panelMap[toolbarState.value] || null
|
||||
})
|
||||
|
|
|
@ -30,15 +30,14 @@
|
|||
@close="timerlVisible = false"
|
||||
/>
|
||||
|
||||
<div class="tools-left">
|
||||
<div class="tools-left" v-if="!classcourse">
|
||||
<IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" />
|
||||
<IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tools-right" :class="{ 'visible': rightToolsVisible }"
|
||||
@mouseleave="rightToolsVisible = false"
|
||||
@mouseenter="rightToolsVisible = true"
|
||||
@mouseleave="toolTrigger('leave')"
|
||||
@mouseenter="toolTrigger('enter')"
|
||||
>
|
||||
<div class="content">
|
||||
<div class="tool-btn page-number" @click="slideThumbnailModelVisible = true">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
|
||||
|
@ -49,15 +48,20 @@
|
|||
<IconOffScreenOne class="tool-btn" v-tooltip="'退出全屏'" v-if="fullscreenState" @click="manualExitFullscreen()" />
|
||||
<IconFullScreenOne class="tool-btn" v-tooltip="'进入全屏'" v-else @click="enterFullscreen()" />
|
||||
<IconPower class="tool-btn" v-tooltip="'结束放映'" @click="exitScreening()" />
|
||||
<IconPower class="tool-btn close" v-if="chat.groupid" v-tooltip="'结束课堂'" @click="exitCourse()" />
|
||||
</div>
|
||||
<div :class="['tools-icon',{opacity:iconHide}]" @click.stop="toolTrigger('icon')">
|
||||
<circle-double-down v-if="rightToolsVisible" theme="outline" size="30" fill="#409EFF"/>
|
||||
<circle-double-up v-else="!rightToolsVisible" theme="outline" size="30" fill="#E6A23C"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref , watchEffect, onMounted, onUnmounted} from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSlidesStore } from '../../store'
|
||||
import { useSlidesStore ,useScreenStore, useClasscourseStore} from '../../store'
|
||||
import type { ContextmenuItem } from '../../components/Contextmenu/types'
|
||||
import { enterFullscreen } from '../../utils/fullscreen'
|
||||
import useScreening from '../../hooks/useScreening'
|
||||
|
@ -69,12 +73,16 @@ import ScreenSlideList from './ScreenSlideList.vue'
|
|||
import SlideThumbnails from './SlideThumbnails.vue'
|
||||
import WritingBoardTool from './WritingBoardTool.vue'
|
||||
import CountdownTimer from './CountdownTimer.vue'
|
||||
import emitter from '@/utils/mitt';
|
||||
import Chat from '../../api/chat' // 聊天
|
||||
import { CircleDoubleDown, CircleDoubleUp } from '@icon-park/vue-next' // icon-park 图标库
|
||||
|
||||
const props = defineProps<{
|
||||
changeViewMode: (mode: 'base' | 'presenter') => void
|
||||
}>()
|
||||
|
||||
const { slides, slideIndex } = storeToRefs(useSlidesStore())
|
||||
const { classcourse, isEmit } = storeToRefs(useClasscourseStore()) // 课堂信息
|
||||
|
||||
const {
|
||||
autoPlayTimer,
|
||||
|
@ -95,17 +103,20 @@ const {
|
|||
execNext,
|
||||
animationIndex,
|
||||
} = useExecPlay()
|
||||
|
||||
const { slideWidth, slideHeight } = useSlideSize()
|
||||
const { exitScreening } = useScreening()
|
||||
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
||||
const chat:any = Chat() // 聊天室
|
||||
|
||||
const screenStore =useScreenStore()
|
||||
const rightToolsVisible = ref(false)
|
||||
const writingBoardToolVisible = ref(false)
|
||||
const timerlVisible = ref(false)
|
||||
const slideThumbnailModelVisible = ref(false)
|
||||
const laserPen = ref(false)
|
||||
|
||||
const timer = ref(0) // 记录操作时间
|
||||
const iconHide = ref(false) // 工具栏图标是否显示
|
||||
const timerId = ref(null) // 定时器id
|
||||
const contextmenus = (): ContextmenuItem[] => {
|
||||
return [
|
||||
{
|
||||
|
@ -187,6 +198,38 @@ const contextmenus = (): ContextmenuItem[] => {
|
|||
},
|
||||
]
|
||||
}
|
||||
|
||||
const toolTrigger = (type:string) => {
|
||||
const curT = Date.now()
|
||||
if (curT - timer.value < 200) return
|
||||
iconHide.value = false // 显示图标按钮
|
||||
if (timerId.value) clearTimeout(timerId.value) // 清除定时器
|
||||
switch (type) {
|
||||
case 'icon': // 点击图标
|
||||
timer.value = curT
|
||||
rightToolsVisible.value = !rightToolsVisible.value
|
||||
break
|
||||
case 'enter': // 移入
|
||||
timer.value = curT
|
||||
rightToolsVisible.value = true
|
||||
break
|
||||
case 'leave': // 移出
|
||||
rightToolsVisible.value = false
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
timerId.value = setTimeout(() => { // 定时器
|
||||
iconHide.value = true // 隐藏图标按钮
|
||||
}, 2000)
|
||||
}
|
||||
// 下课
|
||||
const exitCourse = async () => {
|
||||
// console.log('下课', chat)
|
||||
await chat.exitCourse() // 下课消息
|
||||
exitScreening() // 结束放映
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -242,6 +285,18 @@ const contextmenus = (): ContextmenuItem[] => {
|
|||
top: -66px;
|
||||
}
|
||||
|
||||
.tools-icon{
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: -35px;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
transition: opacity $transitionDelay;
|
||||
&.opacity{
|
||||
opacity: .35;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -267,6 +322,9 @@ const contextmenus = (): ContextmenuItem[] => {
|
|||
& + .tool-btn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&.close{
|
||||
color: #d14424;
|
||||
}
|
||||
}
|
||||
.page-number {
|
||||
font-size: 13px;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
</div>
|
||||
<Divider class="divider" />
|
||||
<div class="tool-btn" @click="exitScreening()"><IconPower class="tool-icon" /><span>结束放映</span></div>
|
||||
<div class="tool-btn close" @click="exitCourse()" v-if="chat.groupid"><IconPower class="tool-icon" /><span>结束课堂</span></div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
@ -55,7 +56,7 @@
|
|||
:class="{ 'active': index === slideIndex }"
|
||||
v-for="(slide, index) in slides"
|
||||
:key="slide.id"
|
||||
@click="turnSlideToIndex(index)"
|
||||
@click="turnSlideTo(index, $event)"
|
||||
>
|
||||
<ThumbnailSlide :slide="slide" :size="120 / viewportRatio" :visible="index < slidesLoadLimit" />
|
||||
</div>
|
||||
|
@ -77,9 +78,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSlidesStore } from '../../store'
|
||||
import { useSlidesStore, useClasscourseStore } from '../../store'
|
||||
import type { ContextmenuItem } from '../../components/Contextmenu/types'
|
||||
import { enterFullscreen } from '../../utils/fullscreen'
|
||||
import { parseText2Paragraphs } from '../../utils/textParser'
|
||||
|
@ -94,12 +95,15 @@ import ScreenSlideList from './ScreenSlideList.vue'
|
|||
import WritingBoardTool from './WritingBoardTool.vue'
|
||||
import CountdownTimer from './CountdownTimer.vue'
|
||||
import Divider from '../../components/Divider.vue'
|
||||
import emitter from '@/utils/mitt';
|
||||
import Chat from '../../api/chat' // 聊天
|
||||
|
||||
const props = defineProps<{
|
||||
changeViewMode: (mode: 'base' | 'presenter') => void
|
||||
}>()
|
||||
|
||||
const { slides, slideIndex, viewportRatio, currentSlide } = storeToRefs(useSlidesStore())
|
||||
const { classcourse, isEmit } = storeToRefs(useClasscourseStore()) // 课堂信息
|
||||
|
||||
const slideListWrapRef = ref<HTMLElement>()
|
||||
const thumbnailsRef = ref<HTMLElement>()
|
||||
|
@ -117,17 +121,31 @@ const {
|
|||
turnSlideToId,
|
||||
animationIndex,
|
||||
} = useExecPlay()
|
||||
|
||||
const { slideWidth, slideHeight } = useSlideSize(slideListWrapRef)
|
||||
const { exitScreening } = useScreening()
|
||||
const { slidesLoadLimit } = useLoadSlides()
|
||||
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
||||
const chat:any = Chat() // 聊天室
|
||||
|
||||
const remarkFontSize = ref(16)
|
||||
const currentSlideRemark = computed(() => {
|
||||
return parseText2Paragraphs(currentSlide.value.remark || '无备注')
|
||||
})
|
||||
|
||||
// 切换到指定的幻灯片
|
||||
const turnSlideTo = (index: number, e: PointerEvent) => {
|
||||
// 课堂信息存在时,不允许翻页
|
||||
console.log('课堂信息', classcourse, index)
|
||||
if (!!classcourse.value) return
|
||||
turnSlideToIndex(index)
|
||||
}
|
||||
// 下课
|
||||
const exitCourse = async () => {
|
||||
// console.log('下课', chat)
|
||||
await chat.exitCourse() // 下课消息
|
||||
exitScreening() // 结束放映
|
||||
}
|
||||
|
||||
const handleMousewheelThumbnails = (e: WheelEvent) => {
|
||||
if (!thumbnailsRef.value) return
|
||||
thumbnailsRef.value.scrollBy(e.deltaY, 0)
|
||||
|
@ -192,6 +210,7 @@ const contextmenus = (): ContextmenuItem[] => {
|
|||
},
|
||||
]
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -208,7 +227,7 @@ const contextmenus = (): ContextmenuItem[] => {
|
|||
background-color: #fff;
|
||||
border-right: solid 1px #eee;
|
||||
font-size: 12px;
|
||||
margin: 20px 0;
|
||||
padding: 20px 0;
|
||||
|
||||
.tool-btn {
|
||||
display: flex;
|
||||
|
@ -224,6 +243,9 @@ const contextmenus = (): ContextmenuItem[] => {
|
|||
&:hover, &.active {
|
||||
color: $themeColor;
|
||||
}
|
||||
&.close{
|
||||
color: #d14424;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { throttle } from 'lodash'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSlidesStore } from '../../../store'
|
||||
import { useSlidesStore, useClasscourseStore } from '../../../store'
|
||||
import { KEYS } from '../../../configs/hotkey'
|
||||
import { ANIMATION_CLASS_PREFIX } from '../../../configs/animation'
|
||||
import message from '../../../utils/message'
|
||||
import emitter from '@/utils/mitt';
|
||||
import Chat from '../../../api/chat' // 聊天封装
|
||||
// import ChatWs from '@/plugins/socket' // 聊天socket
|
||||
// import { MsgEnum } from '../../../api/types' // 消息枚举
|
||||
|
||||
export default () => {
|
||||
export default (isLoader?: boolean = true) => {
|
||||
// isLoader 是否执行 onMounted, onUnmounted
|
||||
const chatApi = Chat()
|
||||
const slidesStore = useSlidesStore()
|
||||
const { slides, slideIndex, formatedAnimations } = storeToRefs(slidesStore)
|
||||
const classcourseStore = useClasscourseStore() // 课堂信息-状态管理
|
||||
const { slides, slideIndex, formatedAnimations, animationIndex } = storeToRefs(slidesStore)
|
||||
|
||||
// 当前页的元素动画执行到的位置
|
||||
const animationIndex = ref(0)
|
||||
// const animationIndex = ref(0)
|
||||
|
||||
// 动画执行状态
|
||||
const inAnimation = ref(false)
|
||||
|
@ -19,10 +26,10 @@ export default () => {
|
|||
// 最小已播放页面索引
|
||||
const playedSlidesMinIndex = ref(slideIndex.value)
|
||||
|
||||
// 执行元素动画
|
||||
const runAnimation = () => {
|
||||
// 执行元素动画 isAsync 为 true 时,异步执行,否则同步执行
|
||||
const runAnimation = (isAsync: boolean) => {
|
||||
// 正在执行动画时,禁止其他新的动画开始
|
||||
if (inAnimation.value) return
|
||||
if (inAnimation.value && !isAsync) return
|
||||
|
||||
const { animations, autoNext } = formatedAnimations.value[animationIndex.value]
|
||||
animationIndex.value += 1
|
||||
|
@ -69,14 +76,15 @@ export default () => {
|
|||
elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const firstAnimations = formatedAnimations.value[0]
|
||||
if (firstAnimations && firstAnimations.animations.length) {
|
||||
const autoExecFirstAnimations = firstAnimations.animations.every(item => item.trigger === 'auto' || item.trigger === 'meantime')
|
||||
if (autoExecFirstAnimations) runAnimation()
|
||||
}
|
||||
})
|
||||
if (isLoader) { // 加载相关钩子
|
||||
onMounted(() => {
|
||||
const firstAnimations = formatedAnimations.value[0]
|
||||
if (firstAnimations && firstAnimations.animations.length) {
|
||||
const autoExecFirstAnimations = firstAnimations.animations.every(item => item.trigger === 'auto' || item.trigger === 'meantime')
|
||||
if (autoExecFirstAnimations) runAnimation()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 撤销元素动画,除了将索引前移外,还需要清除动画状态
|
||||
const revokeAnimation = () => {
|
||||
|
@ -121,9 +129,9 @@ export default () => {
|
|||
// 遇到元素动画时,优先执行动画播放,无动画则执行翻页
|
||||
// 向上播放遇到动画时,仅撤销到动画执行前的状态,不需要反向播放动画
|
||||
// 撤回到上一页时,若该页从未播放过(意味着不存在动画状态),需要将动画索引置为最小值(初始状态),否则置为最大值(最终状态)
|
||||
const execPrev = () => {
|
||||
const execPrev = (isAsync: boolean) => {
|
||||
if (formatedAnimations.value.length && animationIndex.value > 0) {
|
||||
revokeAnimation()
|
||||
revokeAnimation(isAsync)
|
||||
}
|
||||
else if (slideIndex.value > 0) {
|
||||
slidesStore.updateSlideIndex(slideIndex.value - 1)
|
||||
|
@ -139,9 +147,9 @@ export default () => {
|
|||
}
|
||||
inAnimation.value = false
|
||||
}
|
||||
const execNext = () => {
|
||||
const execNext = (isAsync: boolean) => {
|
||||
if (formatedAnimations.value.length && animationIndex.value < formatedAnimations.value.length) {
|
||||
runAnimation()
|
||||
runAnimation(isAsync)
|
||||
}
|
||||
else if (slideIndex.value < slides.value.length - 1) {
|
||||
slidesStore.updateSlideIndex(slideIndex.value + 1)
|
||||
|
@ -173,51 +181,69 @@ export default () => {
|
|||
}
|
||||
|
||||
// 鼠标滚动翻页
|
||||
const mousewheelListener = throttle(function(e: WheelEvent) {
|
||||
if (e.deltaY < 0) execPrev()
|
||||
else if (e.deltaY > 0) execNext()
|
||||
const mousewheelListener = (e: WheelEvent) => {
|
||||
// console.log('mousewheel', e)
|
||||
e.preventDefault() // 阻止默认事件
|
||||
mousewheelListenerThrottle(e)
|
||||
}
|
||||
const mousewheelListenerThrottle = throttle(function(e: WheelEvent) {
|
||||
if (e.deltaY < 0) turning(e, 'prev')
|
||||
else if (e.deltaY > 0) turning(e, 'next')
|
||||
}, 500, { leading: true, trailing: false })
|
||||
|
||||
// 触摸屏上下滑动翻页
|
||||
const touchInfo = ref<{ x: number; y: number; } | null>(null)
|
||||
|
||||
const touchStartListener = (e: TouchEvent) => {
|
||||
e.preventDefault() // 阻止默认事件
|
||||
touchInfo.value = {
|
||||
x: e.changedTouches[0].pageX,
|
||||
y: e.changedTouches[0].pageY,
|
||||
// x: e.changedTouches[0].pageX,
|
||||
// y: e.changedTouches[0].pageY,
|
||||
x: e.changedTouches[0].clientX,
|
||||
y: e.changedTouches[0].clientY,
|
||||
}
|
||||
}
|
||||
const touchEndListener = (e: TouchEvent) => {
|
||||
if (!touchInfo.value) return
|
||||
|
||||
const offsetX = Math.abs(touchInfo.value.x - e.changedTouches[0].pageX)
|
||||
const offsetY = e.changedTouches[0].pageY - touchInfo.value.y
|
||||
|
||||
const offsetX = Math.abs(touchInfo.value.x - e.changedTouches[0].clientX)
|
||||
const offsetY = e.changedTouches[0].clientY - touchInfo.value.y
|
||||
if ( Math.abs(offsetY) > offsetX && Math.abs(offsetY) > 50 ) {
|
||||
touchInfo.value = null
|
||||
|
||||
if (offsetY > 0) execPrev()
|
||||
else execNext()
|
||||
if (offsetY > 0) turning(e, 'prev')
|
||||
else turning(e, 'next')
|
||||
}
|
||||
}
|
||||
// 向上翻页/向下翻页
|
||||
const turning = async (e, type) => {
|
||||
e.preventDefault() // 阻止默认事件
|
||||
if (type === 'prev') execPrev()
|
||||
else if (type === 'next') execNext()
|
||||
if (classcourseStore.classcourse) { // 上课中
|
||||
const current = slideIndex.value
|
||||
const animationSteps = animationIndex.value
|
||||
const animation = type == 'next'?'Nextsteps':'Previoustep'
|
||||
const msg = { current, animation, animationSteps}
|
||||
chatApi.slideFlapping(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// 快捷键翻页
|
||||
const keydownListener = (e: KeyboardEvent) => {
|
||||
const key = e.key.toUpperCase()
|
||||
|
||||
if (key === KEYS.UP || key === KEYS.LEFT || key === KEYS.PAGEUP) execPrev()
|
||||
if (key === KEYS.UP || key === KEYS.LEFT || key === KEYS.PAGEUP) turning(e, 'prev')
|
||||
else if (
|
||||
key === KEYS.DOWN ||
|
||||
key === KEYS.RIGHT ||
|
||||
key === KEYS.SPACE ||
|
||||
key === KEYS.ENTER ||
|
||||
key === KEYS.PAGEDOWN
|
||||
) execNext()
|
||||
) turning(e, 'next')
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('keydown', keydownListener))
|
||||
onUnmounted(() => document.removeEventListener('keydown', keydownListener))
|
||||
|
||||
if (isLoader) { // 加载相关钩子
|
||||
onMounted(() => {document.addEventListener('keydown', keydownListener)})
|
||||
onUnmounted(() => {document.removeEventListener('keydown', keydownListener)})
|
||||
}
|
||||
|
||||
// 切换到上一张/上一张幻灯片(无视元素的入场动画)
|
||||
const turnPrevSlide = () => {
|
||||
slidesStore.updateSlideIndex(slideIndex.value - 1)
|
||||
|
|
|
@ -1,25 +1,47 @@
|
|||
<template>
|
||||
<div class="pptist-screen">
|
||||
<BaseView :changeViewMode="changeViewMode" v-if="viewMode === 'base'" />
|
||||
<PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
|
||||
<BaseView :changeViewMode="changeViewMode" v-if="viewMode === 'base'" />
|
||||
<PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
|
||||
<!-- 点赞组件 -->
|
||||
<upvote-vue ref="upvoteRef" type="2"></upvote-vue>
|
||||
<!-- <div style="z-index: 999;position: absolute;top:10px">
|
||||
</div> -->
|
||||
<!-- 推图上屏弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:fullscreen="true"
|
||||
class="gridPicRefdiv"
|
||||
style="overflow: hidden;"
|
||||
:show-close="false"
|
||||
>
|
||||
<grid-pic ref="gridPicRef" style="height:100%;" @clear="clearchidrenPic"></grid-pic>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { onMounted, onUnmounted, ref , nextTick} from 'vue'
|
||||
import { KEYS } from '../../configs/hotkey'
|
||||
import useScreening from '../../hooks/useScreening'
|
||||
import hooksUpvote from '../../api/upvote' // 点赞-工具
|
||||
|
||||
import BaseView from './BaseView.vue'
|
||||
import PresenterView from './PresenterView.vue'
|
||||
import upvoteVue from '@/views/tool/components/upvote.vue' // 点赞-子组件
|
||||
import gridPic from '@/components/grid-pic/index.vue' // 推图上屏弹子组件
|
||||
|
||||
import emitter from '@/utils/mitt' //mitt 事件总线
|
||||
|
||||
const viewMode = ref<'base' | 'presenter'>('base')
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const gridPicRef:any= ref(null)
|
||||
const changeViewMode = (mode: 'base' | 'presenter') => {
|
||||
viewMode.value = mode
|
||||
}
|
||||
|
||||
const { exitScreening } = useScreening()
|
||||
const upvoteRef = ref(null)
|
||||
hooksUpvote.init(upvoteRef) // 初始化点赞
|
||||
|
||||
// 快捷键退出放映
|
||||
const keydownListener = (e: KeyboardEvent) => {
|
||||
|
@ -27,6 +49,23 @@ const keydownListener = (e: KeyboardEvent) => {
|
|||
if (key === KEYS.ESC) exitScreening()
|
||||
}
|
||||
|
||||
const clearchidrenPic= ()=> {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
// 开启推图上屏幕
|
||||
emitter.on('opengridPic', async (data:object)=> {
|
||||
if(gridPicRef.value) gridPicRef.value.clearPic()
|
||||
dialogVisible.value = true
|
||||
await nextTick();
|
||||
gridPicRef.value.addPic(data.arr)
|
||||
});
|
||||
// 关闭推图
|
||||
emitter.on('closegridPic', ()=> {
|
||||
if(!gridPicRef.value) return
|
||||
gridPicRef.value.clearPic()
|
||||
dialogVisible.value = false
|
||||
});
|
||||
onMounted(() => document.addEventListener('keydown', keydownListener))
|
||||
onUnmounted(() => document.removeEventListener('keydown', keydownListener))
|
||||
</script>
|
||||
|
@ -38,4 +77,8 @@ onUnmounted(() => document.removeEventListener('keydown', keydownListener))
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.gridPicRefdiv .el-dialog__body){
|
||||
height: 100% !important;
|
||||
}
|
||||
</style>
|
|
@ -160,7 +160,7 @@ const topImgPositionStyle = computed(() => {
|
|||
return {
|
||||
left: -left * (100 / width) + '%',
|
||||
top: -top * (100 / height) + '%',
|
||||
width: bottomWidth / width * 100 + '%',
|
||||
width: bottomWidth / width * 100 + '%' ,
|
||||
height: bottomHeight / height * 100 + '%',
|
||||
}
|
||||
})
|
||||
|
@ -228,6 +228,7 @@ const updateRange = () => {
|
|||
width: parseInt(topImgPositionStyle.value.width),
|
||||
height: parseInt(topImgPositionStyle.value.height),
|
||||
}
|
||||
console.log('retPosition', retPosition)
|
||||
|
||||
const widthScale = 100 / retPosition.width
|
||||
const heightScale = 100 / retPosition.height
|
||||
|
@ -475,7 +476,7 @@ const scaleClipRange = (e: MouseEvent, type: OperateResizeHandlers) => {
|
|||
isMouseDown = false
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
|
||||
console.log('----------------------------------')
|
||||
updateRange()
|
||||
|
||||
setTimeout(() => isSettingClipRange.value = false, 0)
|
||||
|
@ -537,6 +538,7 @@ const edgePoints = [
|
|||
|
||||
img {
|
||||
position: absolute;
|
||||
max-width: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@ const handleClip = (data: ImageClipedEmitData | null) => {
|
|||
}
|
||||
img {
|
||||
position: absolute;
|
||||
max-width: none !important; // 防止图片被压缩
|
||||
}
|
||||
}
|
||||
.color-mask {
|
||||
|
|
|
@ -10,11 +10,10 @@ export const createChart = ({ headers, data }) => {
|
|||
})
|
||||
}
|
||||
// 大模型对话
|
||||
export const sendChart = ({ headers, data }) => {
|
||||
export const sendChart = (data) => {
|
||||
return request({
|
||||
url: '/qf/sendTalk',
|
||||
method: 'post',
|
||||
headers,
|
||||
data,
|
||||
})
|
||||
}
|
|
@ -56,4 +56,5 @@ export class Other {
|
|||
static baseUrl = "/common/upload"
|
||||
// 测试
|
||||
static uploadFile = data => ApiService.publicHttp(this.baseUrl, data, 'post', null, 'file')
|
||||
|
||||
}
|
|
@ -80,6 +80,15 @@ export function updateClassworkdata(data) {
|
|||
})
|
||||
}
|
||||
|
||||
// 批阅后, 待所有学生都批改完成后自动结束当前作业为[已完成]
|
||||
export function updateClassWorkDataAutoFinish(data) {
|
||||
return request({
|
||||
url: '/education/classworkdata/updAutoFinish',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改classwork
|
||||
export function updateClasswork(data) {
|
||||
return request({
|
||||
|
|
|
@ -71,3 +71,11 @@ export const addFileToKj = (id) => {
|
|||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export const getModelInfo = (params) => {
|
||||
return request({
|
||||
url: '/education/llmModel/getModelInfo',
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
|
|
@ -95,3 +95,11 @@ export function getCourseTeachingMsg(id) {
|
|||
})
|
||||
}
|
||||
|
||||
export function setPaging(data) {
|
||||
return request({
|
||||
url: '/education/classcourse/record/paging',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4723712 */
|
||||
src: url('iconfont.woff2?t=1732240267757') format('woff2'),
|
||||
url('iconfont.woff?t=1732240267757') format('woff'),
|
||||
url('iconfont.ttf?t=1732240267757') format('truetype');
|
||||
src: url('iconfont.woff2?t=1734337029245') format('woff2'),
|
||||
url('iconfont.woff?t=1734337029245') format('woff'),
|
||||
url('iconfont.ttf?t=1734337029245') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
@ -13,6 +13,30 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-yinle:before {
|
||||
content: "\e6c9";
|
||||
}
|
||||
|
||||
.icon-yuyin:before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
.icon-dianying:before {
|
||||
content: "\e693";
|
||||
}
|
||||
|
||||
.icon-jiqirenfushi:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-xiangmuicon_maobishufa:before {
|
||||
content: "\e651";
|
||||
}
|
||||
|
||||
.icon-meishu-F:before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
.icon-shangchuan:before {
|
||||
content: "\e61b";
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,48 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "11819186",
|
||||
"name": "音乐",
|
||||
"font_class": "yinle",
|
||||
"unicode": "e6c9",
|
||||
"unicode_decimal": 59081
|
||||
},
|
||||
{
|
||||
"icon_id": "6338162",
|
||||
"name": "语音生成",
|
||||
"font_class": "yuyin",
|
||||
"unicode": "e648",
|
||||
"unicode_decimal": 58952
|
||||
},
|
||||
{
|
||||
"icon_id": "6880941",
|
||||
"name": "视频生成",
|
||||
"font_class": "dianying",
|
||||
"unicode": "e693",
|
||||
"unicode_decimal": 59027
|
||||
},
|
||||
{
|
||||
"icon_id": "11532042",
|
||||
"name": "数字人生成",
|
||||
"font_class": "jiqirenfushi",
|
||||
"unicode": "e624",
|
||||
"unicode_decimal": 58916
|
||||
},
|
||||
{
|
||||
"icon_id": "13522843",
|
||||
"name": "文生图片",
|
||||
"font_class": "xiangmuicon_maobishufa",
|
||||
"unicode": "e651",
|
||||
"unicode_decimal": 58961
|
||||
},
|
||||
{
|
||||
"icon_id": "37635062",
|
||||
"name": "文生连环画",
|
||||
"font_class": "meishu-F",
|
||||
"unicode": "e638",
|
||||
"unicode_decimal": 58936
|
||||
},
|
||||
{
|
||||
"icon_id": "4942656",
|
||||
"name": "上传",
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
After Width: | Height: | Size: 520 KiB |
|
@ -144,8 +144,14 @@ import { convertTextToPicture, getQueue, getPromptId, getPicture, chattoprompt,
|
|||
import CryptoJS from 'crypto-js'
|
||||
import { useRoute } from 'vue-router'
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hasPPt: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
return {
|
||||
form: {
|
||||
ratio: "512",
|
||||
|
@ -384,7 +390,7 @@ export default {
|
|||
urls.push(url0)
|
||||
buttonState.push({
|
||||
disabled: false,
|
||||
text: "插入本课素材资源库",
|
||||
text: this.hasPPt ? '插入' : "插入本课素材资源库",
|
||||
})
|
||||
}
|
||||
this.skeletonNumber = 0
|
||||
|
@ -476,6 +482,7 @@ export default {
|
|||
|
||||
//保存图片到素材库
|
||||
async saveImage(resultIndex, index, url, resultItem) {
|
||||
|
||||
this.buttonStates[resultIndex][index].disabled = true;
|
||||
this.buttonStates[resultIndex][index].text = "正在保存...";
|
||||
const numberIndex = url.indexOf('filename=');
|
||||
|
@ -484,6 +491,7 @@ export default {
|
|||
const finalPath = path.substring(0, pngIndex + 4);
|
||||
try {
|
||||
const blob = await this.getImageBlob(`https://ai.ysaix.com:7853/view?filename=${finalPath}&type=temp`);
|
||||
|
||||
|
||||
const hash = CryptoJS.MD5(blob).toString();
|
||||
|
||||
|
@ -492,7 +500,10 @@ export default {
|
|||
let file = new File([blob], `${resultItem}.png`, {
|
||||
type: 'image/png'
|
||||
})
|
||||
|
||||
if(this.hasPPt){
|
||||
this.$emit('insertImg', file)
|
||||
return
|
||||
}
|
||||
// 添加参数
|
||||
formData.append('md5', hash);
|
||||
formData.append('file', file);
|
||||
|
|
|
@ -0,0 +1,333 @@
|
|||
<template>
|
||||
<div class="book-wrap">
|
||||
<el-scrollbar height="100%">
|
||||
<div class="book-name flex" @click="dialogVisible = true">
|
||||
<span>{{ curBook.data.itemtitle }}</span>
|
||||
<i class="iconfont icon-xiangyou"></i>
|
||||
</div>
|
||||
<div class="book-list" v-loading="treeLoading">
|
||||
<el-tree :data="treeData" accordion :props="defaultProps" node-key="id"
|
||||
:default-expanded-keys="defaultExpandedKeys" :current-node-key="curNode.data.id" highlight-current
|
||||
@node-click="handleNodeClick">
|
||||
<template #default="{ node }">
|
||||
<span :title="node.label" class="tree-label">{{ node.label }}</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<!--弹窗 选择教材-->
|
||||
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="550"
|
||||
style="border-radius: 10px; padding: 10px 15px;">
|
||||
<template #header>
|
||||
<div class="choose-book-header flex">
|
||||
<span>切换教材</span>
|
||||
<i class="iconfont icon-guanbi" @click="dialogVisible = false"></i>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="textbook-container">
|
||||
<el-scrollbar height="450px">
|
||||
<div class="textbook-item flex" v-for="item in subjectList" :class="curBook.data.id == item.id ? 'active-item' : ''"
|
||||
:key="item.id" @click="changeBook(item)">
|
||||
<img v-if="item.avartar" :src="item.avartar.indexOf('http') === 0 ? item.avartar : BaseUrl + item.avartar" class="textbook-img" alt="">
|
||||
<div v-else class="textbook-img">
|
||||
<i class="iconfont icon-jiaocaixuanze" style="font-size: 40px;"></i>
|
||||
</div>
|
||||
<span class="book-name">{{ item.itemtitle }}</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, nextTick, toRaw, reactive } from 'vue';
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { listEvaluation } from '@/api/subject'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
|
||||
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
// 定义要发送的emit事件
|
||||
const emit = defineEmits(['nodeClick', 'changeBook'])
|
||||
// 章节List
|
||||
const unitList = ref([])
|
||||
const subjectList = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
// 当前教材下面单元内容数据
|
||||
const treeData = ref([])
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'itemtitle',
|
||||
class: 'textbook-tree'
|
||||
}
|
||||
//查当前学科
|
||||
const subjectParams = reactive(
|
||||
{
|
||||
edusubject: '科学',
|
||||
edustage:'小学',
|
||||
itemkey: 'version',
|
||||
orderby: 'orderidx asc',
|
||||
pageSize: 10000
|
||||
}
|
||||
)
|
||||
// 查所有的学科
|
||||
const unitParams = reactive({
|
||||
edusubject:'科学',
|
||||
edustage:'小学',
|
||||
itemgroup: 'textbook',
|
||||
orderby: 'orderidx asc',
|
||||
pageSize: 10000
|
||||
})
|
||||
// 当前选中的教材
|
||||
const curBook = reactive({
|
||||
data: {}
|
||||
})
|
||||
// 当前节点
|
||||
const curNode = reactive({
|
||||
data:{}
|
||||
})
|
||||
const treeLoading = ref(false)
|
||||
// 默认展开的节点
|
||||
const defaultExpandedKeys = ref([])
|
||||
|
||||
//选择教材
|
||||
const changeBook = (data) => {
|
||||
curBook.data = data
|
||||
treeData.value = getTreeData(data.id)
|
||||
//切换教材后默认展开第一个并选中
|
||||
nextTick(() =>{
|
||||
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||
curNode.data = getLastLevelData(treeData.value)[0]
|
||||
handleNodeClick(curNode.data)
|
||||
})
|
||||
// 延迟关闭 视觉上选中
|
||||
setTimeout(() => {
|
||||
dialogVisible.value = false
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const getLastLevelData = (tree) => {
|
||||
let lastLevelData = [];
|
||||
// 递归函数遍历树形结构
|
||||
function traverseTree(nodes) {
|
||||
nodes.forEach((node) => {
|
||||
// 如果当前节点有子节点,继续遍历
|
||||
if (node.children && node.children.length > 0) {
|
||||
traverseTree(node.children);
|
||||
} else {
|
||||
// 如果没有子节点,说明是最后一层的节点
|
||||
lastLevelData.push(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 调用递归函数开始遍历
|
||||
traverseTree(tree);
|
||||
|
||||
// 返回最后一层的数据
|
||||
return lastLevelData;
|
||||
}
|
||||
// 根据id 拿到父节点数据
|
||||
const findParentByChildId = (treeData, targetNodeId) => {
|
||||
// 递归查找函数
|
||||
// 遍历树中的每个节点
|
||||
for (let node of treeData) {
|
||||
// 检查当前节点的子节点是否包含目标子节点 ID
|
||||
if (node.children && node.children.some(child => child.id === targetNodeId)) {
|
||||
// 如果当前节点的某个子节点的 ID 匹配目标子节点 ID,则当前节点即为父节点
|
||||
return node;
|
||||
}
|
||||
// 如果当前节点没有匹配的子节点,则递归检查当前节点的子节点
|
||||
if (node.children) {
|
||||
let parentNode = findParentByChildId(node.children, targetNodeId);
|
||||
if (parentNode) {
|
||||
return parentNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果未找到匹配的父节点,则返回 null 或者适当的默认值
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
const handleNodeClick = (data) => {
|
||||
/**
|
||||
* data : 当前节点数据
|
||||
*/
|
||||
let nodeData = cloneDeep(toRaw(data));
|
||||
|
||||
//增加一个label 之前取的label
|
||||
nodeData.label = nodeData.itemtitle
|
||||
// 父级节点 如果当前是一级节点 父级则为null
|
||||
let parent = {
|
||||
id: nodeData.parentid,
|
||||
label: nodeData.parenttitle,
|
||||
itemtitle: nodeData.parenttitle
|
||||
}
|
||||
const parentNode = nodeData.parentid ? parent : null
|
||||
nodeData.parentNode = parentNode
|
||||
let curData = {
|
||||
textBook: {
|
||||
curBookId: curBook.data.id,
|
||||
curBookName: curBook.data.itemtitle,
|
||||
curBookImg: BaseUrl + curBook.data.avartar,
|
||||
curBookPath: curBook.data.fileurl
|
||||
},
|
||||
node: nodeData
|
||||
}
|
||||
// 本地存储:electron-store
|
||||
emit('nodeClick', curData)
|
||||
}
|
||||
// 单元章节数据转为“树”结构
|
||||
const getTreeData = (bookId) =>{
|
||||
// 根据当前教材的id 查找出对应的章节
|
||||
let data = unitList.value.filter(item => item.rootid == bookId && item.level == 1)
|
||||
data.forEach( item => {
|
||||
item.children = unitList.value.filter( item2 => item2.parentid == item.id && item2.level == 2)
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
onMounted( async () => {
|
||||
treeLoading.value = true
|
||||
try{
|
||||
//获取学科列表
|
||||
const { rows } = await listEvaluation(subjectParams)
|
||||
// 获取所有的教材
|
||||
subjectList.value = rows
|
||||
|
||||
const res = await listEvaluation(unitParams)
|
||||
unitList.value = [...res.rows]
|
||||
// 当前教材
|
||||
curBook.data = rows[0]
|
||||
|
||||
// 章节"树"rows
|
||||
treeData.value = getTreeData(rows[0].id)
|
||||
|
||||
nextTick(() =>{
|
||||
// 默认展开 选中
|
||||
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||
curNode.data = getLastLevelData(treeData.value)[0]
|
||||
handleNodeClick(curNode.data)
|
||||
})
|
||||
|
||||
} finally{
|
||||
treeLoading.value = false
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.book-wrap {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 20px 0px rgba(99, 99, 99, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.book-name {
|
||||
background-color: #ffffff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
padding: 0 15px;
|
||||
z-index: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #3b3b3b;
|
||||
cursor: pointer;
|
||||
border-bottom: solid #f4f5f7 1px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.book-list {
|
||||
padding: 45px 10px 0 10px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.choose-dialog) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.choose-book-header {
|
||||
justify-content: space-between;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
|
||||
.icon-guanbi {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.textbook-container {
|
||||
.textbook-item {
|
||||
padding: 10px 20px;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
.book-name {
|
||||
margin-left: 20px;
|
||||
color: #3b3b3b;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f4f7f9;
|
||||
}
|
||||
}
|
||||
|
||||
.active-item {
|
||||
background-color: #f4f7f9;
|
||||
|
||||
.book-name {
|
||||
color: #368fff;
|
||||
font-weight: bold
|
||||
}
|
||||
}
|
||||
|
||||
.textbook-img {
|
||||
width: 55px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tree-node) {
|
||||
.el-tree-node__content {
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: #eaf3ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tree-label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content) {
|
||||
background-color: #eaf3ff !important;
|
||||
color: #409EFF
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div class="book-wrap">
|
||||
<el-scrollbar height="100%">
|
||||
<div class="book-name flex" @click="dialogVisible = true">
|
||||
<div class="book-name flex" v-if="isStadium() === true"> {{userStore.user.deptName}}</div>
|
||||
<div v-else class="book-name flex" @click="dialogVisible = true">
|
||||
<span>{{ curBook.data.itemtitle }}</span>
|
||||
<i class="iconfont icon-xiangyou"></i>
|
||||
</div>
|
||||
|
@ -46,8 +47,14 @@ import { onMounted, ref, nextTick, toRaw, reactive } from 'vue';
|
|||
import { cloneDeep } from 'lodash'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import { useGetSubject } from '@/hooks/useGetSubject'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
const userStore = useUserStore()
|
||||
|
||||
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
const isStadium = () => {
|
||||
let roles = userStore.user.roles
|
||||
return roles.some(item => item.roleKey === 'stadium')
|
||||
}
|
||||
// 定义要发送的emit事件
|
||||
const emit = defineEmits(['nodeClick', 'changeBook'])
|
||||
let useSubject = null
|
||||
|
@ -139,16 +146,22 @@ const handleNodeClick = (data) => {
|
|||
* data : 当前节点数据
|
||||
*/
|
||||
let nodeData = cloneDeep(toRaw(data));
|
||||
|
||||
|
||||
//增加一个label 之前取的label
|
||||
nodeData.label = nodeData.itemtitle
|
||||
// 父级节点 如果当前是一级节点 父级则为null
|
||||
let parent = {
|
||||
id: nodeData.parentid,
|
||||
label: nodeData.parenttitle,
|
||||
itemtitle: nodeData.parenttitle
|
||||
}
|
||||
const parentNode = nodeData.parentid ? parent : null
|
||||
let parentNode
|
||||
// 存在children 则为一级节点
|
||||
if(nodeData.children){
|
||||
// 为一级节点
|
||||
parentNode = null
|
||||
}
|
||||
else{
|
||||
parentNode = {
|
||||
id: nodeData.parentid,
|
||||
label: nodeData.parenttitle,
|
||||
itemtitle: nodeData.parenttitle
|
||||
}
|
||||
}
|
||||
nodeData.parentNode = parentNode
|
||||
let curData = {
|
||||
textBook: {
|
||||
|
@ -175,13 +188,17 @@ onMounted( async () => {
|
|||
curBook.data = sessionStore.get('subject.curBook')
|
||||
}
|
||||
else{
|
||||
curBook.data = subjectList.value[0]
|
||||
if (subjectList.value) {
|
||||
curBook.data = subjectList.value[0]
|
||||
}else {
|
||||
curBook.data = {}
|
||||
}
|
||||
}
|
||||
|
||||
// 章节"树"
|
||||
treeData.value = useSubject.getTreeData(curBook.data.id)
|
||||
sessionStore.set('subject.subjectTree',useSubject.getTreeData(curBook.data.id))
|
||||
|
||||
|
||||
nextTick(() =>{
|
||||
// 默认展开 选中
|
||||
if(sessionStore.get('subject.curNode')){
|
||||
|
@ -193,7 +210,7 @@ onMounted( async () => {
|
|||
}
|
||||
handleNodeClick(curNode.data)
|
||||
})
|
||||
|
||||
|
||||
} finally{
|
||||
treeLoading.value = false
|
||||
}
|
||||
|
|
|
@ -34,7 +34,14 @@ const getFileTypeIcon = () => {
|
|||
gif: 'icon-gif',
|
||||
txt: 'icon-txt',
|
||||
rar: 'icon-rar',
|
||||
apt: 'icon-A'
|
||||
apt: 'icon-A',
|
||||
aippt: 'icon-a-ziyuan91',
|
||||
aiyuyin: 'icon-yuyin', // 语音生成
|
||||
aivideo: 'icon-dianying', // 视频生成
|
||||
airobot: 'icon-jiqirenfushi', // 数字人生成
|
||||
aiimg: 'icon-xiangmuicon_maobishufa', // 文生图片
|
||||
aidraw: 'icon-meishu-F', // 文生连环画
|
||||
aiyinyue: 'icon-yinle' //文生音乐
|
||||
}
|
||||
if (iconObj[name]) {
|
||||
return '#' + iconObj[name]
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
<!--
|
||||
依赖: vuedraggable、v-viewer
|
||||
属性: showToolbar //是否显示工具栏 默认:false
|
||||
工具栏 添加图片:默认6个测试图片,不输入框添加则添加默认,输入图片链接,展示图片链接的图片
|
||||
清空图片:清空图片
|
||||
事件: clear 清空时触发
|
||||
outIndex 超出九个图片时触发
|
||||
方法: addPic //添加图片
|
||||
参数: src 图片链接
|
||||
clearPic //清空图片
|
||||
参数: 无
|
||||
使用方法: 加载组件后,通过ref调用addPic方法添加图片即可
|
||||
-->
|
||||
<template>
|
||||
<div style="position: relative;height: 100%;width: 100%;">
|
||||
<draggable handle=".header-btn" :draggable="false" item-key="backgroundColor" v-model="gridPicList" class="grid-pic-wrap" :style="getGrid">
|
||||
<template #item="{ element, index }">
|
||||
<div class="grid-pic-item" :key="element.backgroundColor" :style="getWH(element,index)">
|
||||
<div class="delete-btn" @click="()=>{gridPicList.splice(index,1);if(!gridPicList.length) emits('clear')} ">X</div>
|
||||
<div class="header-btn"></div>
|
||||
<ViewerItem :gridPicList="gridPicList" :index="index" :images="element"></ViewerItem>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<div class="grid-pic-toolbar">
|
||||
<el-input v-if="showToolbar" style="width: 500px" v-model="inputValue" type="text" />
|
||||
<el-button v-if="showToolbar" class="add-btn" @click="pushPic">
|
||||
添加
|
||||
</el-button>
|
||||
<el-button class="add-btn" @click="clearPic">
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- <el-button style="position:fixed;bottom: 20px;right: 80px;" @click="startPencil">
|
||||
画笔
|
||||
</el-button>-->
|
||||
<!-- <div class="modal-mode">
|
||||
<canvas id="canvas_pic_001" style="position: absolute;top: 0;left: 0;width: 100%;height: 100%;"></canvas>
|
||||
</div>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, computed, onMounted} from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
import ViewerItem from "./viewer-item.vue";
|
||||
// import Fabric from 'fabric';
|
||||
const gridPicList = ref([])
|
||||
const inputValue = ref('')
|
||||
const isShow = ref(false)
|
||||
const emits = defineEmits(['clear','outIndex']);
|
||||
|
||||
const props = defineProps({
|
||||
showToolbar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
// 获取图片样式
|
||||
const getWH = (item,index)=>{
|
||||
return {
|
||||
backgroundColor: item.backgroundColor,
|
||||
'grid-area': 'a' + index
|
||||
}
|
||||
}
|
||||
|
||||
const picList = [
|
||||
'https://prev.ysaix.com:7868/src/assets/images/homecard4.jpg',
|
||||
'https://prev.ysaix.com:7868/src/assets/images/homecard3.jpg',
|
||||
'https://prev.ysaix.com:7868/src/assets/images/homecard2.jpg',
|
||||
'https://prev.ysaix.com:7868/src/assets/images/homecard1.jpg',
|
||||
'https://prev.ysaix.com:7868/profile/avatar/2024/06/26/blob_20240626135106A001.png',
|
||||
'https://prev.ysaix.com:7868/assets/app_download.b3fb227b.png'
|
||||
]
|
||||
// 获取grid样式
|
||||
const getGrid = computed(() => {
|
||||
switch (gridPicList.value.length) {
|
||||
case 1:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0"`
|
||||
}
|
||||
case 2:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0 a1"`
|
||||
}
|
||||
case 3:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0 a1"
|
||||
"a0 a2"`
|
||||
}
|
||||
case 4:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0 a2"
|
||||
"a1 a3"`
|
||||
}
|
||||
case 5:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0 a2 a4"
|
||||
"a1 a3 a4"`
|
||||
}
|
||||
case 6:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0 a2 a4"
|
||||
"a1 a3 a5"`
|
||||
}
|
||||
case 7:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0 a2 a4"
|
||||
"a0 a2 a4"
|
||||
"a0 a2 a5"
|
||||
"a1 a3 a5"
|
||||
"a1 a3 a6"
|
||||
"a1 a3 a6"`
|
||||
}
|
||||
case 8:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0 a3 a6"
|
||||
"a0 a3 a6"
|
||||
"a1 a4 a6"
|
||||
"a1 a4 a7"
|
||||
"a2 a5 a7"
|
||||
"a2 a5 a7"`
|
||||
}
|
||||
case 9:
|
||||
return {
|
||||
'grid-template-areas':
|
||||
`"a0 a3 a6"
|
||||
"a1 a4 a7"
|
||||
"a2 a5 a8"`
|
||||
}
|
||||
default:
|
||||
return {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const pushPic = () => {
|
||||
let src = inputValue.value||picList[gridPicList.value.length]
|
||||
addPic(src)
|
||||
}
|
||||
// 添加图片
|
||||
const addPic = (data) => {
|
||||
let list = Array.isArray(data)?data:[data]
|
||||
if (gridPicList.value.length + list.length > 9) {
|
||||
console.log("超出九个图片")
|
||||
emits('outIndex')
|
||||
return
|
||||
}
|
||||
let listArr = [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let src = list[i]
|
||||
if (!src) {
|
||||
console.log("图片链接不能为空")
|
||||
return;
|
||||
}
|
||||
listArr.push({
|
||||
src: src,
|
||||
backgroundColor: getRandomColor()
|
||||
})
|
||||
}
|
||||
gridPicList.value.push(...listArr)
|
||||
inputValue.value = ''
|
||||
}
|
||||
// 清空图片
|
||||
const clearPic = () => {
|
||||
gridPicList.value = []
|
||||
emits('clear')
|
||||
}
|
||||
//开始画笔
|
||||
const startPencil = () => {
|
||||
isShow.value = !isShow.value
|
||||
}
|
||||
// 生成随机颜色
|
||||
function getRandomColor() {
|
||||
let r = Math.floor(Math.random() * 256).toString(16);
|
||||
let g = Math.floor(Math.random() * 256).toString(16);
|
||||
let b = Math.floor(Math.random() * 256).toString(16);
|
||||
// 如果生成的十六进制数字只有一位,前面补0
|
||||
r = r.length === 1? '0' + r : r;
|
||||
g = g.length === 1? '0' + g : g;
|
||||
b = b.length === 1? '0' + b : b;
|
||||
return `#${r}${g}${b}`;
|
||||
}
|
||||
/* //初始化画笔
|
||||
const initPend = () => {
|
||||
let canvas = new Fabric.fabric.Canvas('canvas_pic_001',{
|
||||
interactive: false,
|
||||
selection: true,
|
||||
backgroundColor: "rgba(15,15,15,0)"
|
||||
})
|
||||
canvas.defaultCursor = 'default'
|
||||
canvas.setHeight(300)
|
||||
canvas.setWidth(400)
|
||||
canvas.isDrawingMode = true;
|
||||
canvas.freeDrawingBrush = new Fabric.fabric.PencilBrush(canvas)
|
||||
canvas.freeDrawingBrush.width = 1//设置画笔粗细
|
||||
canvas.freeDrawingBrush.color = "red"//设置画笔颜色
|
||||
}*/
|
||||
/*onMounted(() => {
|
||||
initPend()
|
||||
})*/
|
||||
defineExpose({addPic,clearPic})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.modal-mode{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 1001;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.grid-pic-wrap{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
.grid-pic-item{
|
||||
//animation: fadeIn 0.5s ease-in-out forwards;
|
||||
background-color: #0a84ff;
|
||||
position: relative;
|
||||
.delete-btn{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
z-index: 999;
|
||||
&:hover{
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.header-btn{
|
||||
position: absolute;
|
||||
z-index: 998;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
border-bottom: 1px dotted #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
.grid-pic-toolbar{
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
display: flex;
|
||||
.add-btn{
|
||||
}
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<div class="viewer-item-wrap" :id="'viewer_id'+index">
|
||||
<Viewer @move="move" @moved="moved" @inited="inited" @zoomed="zoomed" :ref="collectRef('viewerRef'+index)" :options="optins" :images="[images.src]" class="images clearfix">
|
||||
<template #default="scope">
|
||||
<div class="viewer-img-box">
|
||||
<img v-for="src in scope.images" :key="index" :src="src" style="display: none">
|
||||
</div>
|
||||
</template>
|
||||
</Viewer>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref, watch, nextTick, onMounted} from "vue";
|
||||
import { component as Viewer } from 'v-viewer'
|
||||
// import Fabric from 'fabric';
|
||||
import 'viewerjs/dist/viewer.css'
|
||||
const props = defineProps({
|
||||
images: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
gridPicList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
let $viewer = null;
|
||||
const refs = ref([]);
|
||||
//初始化时
|
||||
const inited = (viewer) => {
|
||||
$viewer = viewer
|
||||
}
|
||||
//缩放时
|
||||
const zoomed = (e) => {
|
||||
// setImgStyle()
|
||||
}
|
||||
//移动时
|
||||
const moved = (e) => {
|
||||
// setImgStyle()
|
||||
}
|
||||
const move = (e) => {
|
||||
}
|
||||
const appendCanvasToShow = () => {
|
||||
initImgStyle()
|
||||
}
|
||||
|
||||
const setImgStyle = () => {
|
||||
let item = window.document.getElementById('viewer_id'+props.index)
|
||||
let canvas = item.querySelectorAll('.viewer-canvas')[0]
|
||||
let img = canvas.querySelectorAll('img')[0]
|
||||
let imgStyle = img.getAttribute('style')
|
||||
imgStyle = imgStyle.replace('relative', 'absolute') + 'z-index: 1002';
|
||||
img.style = imgStyle;
|
||||
let canvasNew = canvas.querySelectorAll('canvas')[0]
|
||||
canvasNew.style = imgStyle;
|
||||
}
|
||||
|
||||
const initImgStyle = () => {
|
||||
let item = window.document.getElementById('viewer_id'+props.index)
|
||||
let canvas = item.querySelectorAll('.viewer-canvas')[0]
|
||||
let img = canvas.querySelectorAll('img')[0];
|
||||
let imgStyle = img.getAttribute('style')
|
||||
imgStyle = imgStyle.replace('relative', 'absolute') + 'z-index: 1002';
|
||||
img.style = imgStyle;
|
||||
const canvasNew = document.createElement('canvas');
|
||||
canvasNew.style = imgStyle;
|
||||
canvasNew.id = 'canvas_pic_'+props.index
|
||||
canvas.appendChild(canvasNew);
|
||||
|
||||
initPend()
|
||||
}
|
||||
|
||||
const collectRef = (key) => {
|
||||
return (el) => {
|
||||
refs.value[key] = el;
|
||||
};
|
||||
};
|
||||
//viewer配置
|
||||
const optins = ref({
|
||||
"inline": true,
|
||||
"button": false,
|
||||
"navbar": false,
|
||||
"title": false,
|
||||
"toolbar": false,
|
||||
"tooltip": true,
|
||||
"zoomable": true,
|
||||
"rotatable": true,
|
||||
"movable": true,
|
||||
"scalable": true,
|
||||
"transition": true,
|
||||
"fullscreen": true,
|
||||
"keyboard": true
|
||||
})
|
||||
const initViewers = () => {
|
||||
refs.value['viewerRef'+props.index]?.rebuildViewer()
|
||||
/*setTimeout(()=>{
|
||||
initImgStyle()
|
||||
},300)*/
|
||||
}
|
||||
//初始化画笔
|
||||
const initPend = () => {
|
||||
let canvas = new Fabric.fabric.Canvas('canvas_pic_'+props.index,{
|
||||
interactive: false,
|
||||
selection: true,
|
||||
backgroundColor: "rgba(15,15,15,0)"
|
||||
})
|
||||
canvas.defaultCursor = 'default'
|
||||
canvas.setHeight(300)
|
||||
canvas.setWidth(400)
|
||||
canvas.isDrawingMode = true;
|
||||
canvas.freeDrawingBrush = new Fabric.fabric.PencilBrush(canvas)
|
||||
canvas.freeDrawingBrush.width = 1//设置画笔粗细
|
||||
canvas.freeDrawingBrush.color = "red"//设置画笔颜色
|
||||
}
|
||||
|
||||
watch(props.gridPicList, (newValue, oldValue) => {
|
||||
nextTick(()=>{
|
||||
initViewers()
|
||||
})
|
||||
});
|
||||
|
||||
/*
|
||||
watch(props.images, (newValue, oldValue) => {
|
||||
// optins.value.movable = newValue.dragable
|
||||
initPend()
|
||||
});
|
||||
*/
|
||||
|
||||
/*onMounted(()=>{
|
||||
setTimeout(()=>{
|
||||
appendCanvasToShow()
|
||||
}, 300)
|
||||
})*/
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.viewer-item-wrap{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
:deep(.viewer-canvas img) {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -136,7 +136,7 @@ const handleNodeClick = (data) => {
|
|||
id: nodeData.parentid,
|
||||
label: nodeData.parenttitle,
|
||||
itemtitle: nodeData.parenttitle
|
||||
}
|
||||
}
|
||||
const parentNode = nodeData.parentid ? parent : null
|
||||
nodeData.parentNode = parentNode
|
||||
|
||||
|
@ -204,7 +204,11 @@ onMounted(async () => {
|
|||
curBook.data = sessionStore.get('subject.curBook')
|
||||
}
|
||||
else{
|
||||
curBook.data = subjectList.value[0]
|
||||
if (subjectList.value) {
|
||||
curBook.data = subjectList.value[0]
|
||||
}else {
|
||||
curBook.data = {}
|
||||
}
|
||||
}
|
||||
|
||||
// 章节"树"
|
||||
|
@ -220,7 +224,7 @@ onMounted(async () => {
|
|||
}
|
||||
handleNodeClick(curNode.data)
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -29,6 +29,20 @@
|
|||
</template>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="file-list">
|
||||
<el-dropdown @command="changeFile" v-if="type == 3">
|
||||
<span class="el-dropdown-link">
|
||||
{{ curFile.fileName }}
|
||||
<i class="iconfont icon-xiangxia"></i>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="item in fileList" :command="item" :key="item.id">{{ item.fileName
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div class="input-box flex">
|
||||
<el-input v-model="textarea" @keyup.enter="send" :disabled="loaded"/>
|
||||
<div class="ipt-icon" @click="send">
|
||||
|
@ -40,13 +54,16 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { completion } from '@/api/mode/index'
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import { completion, docList } from '@/api/mode/index'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { dataSetJson } from '@/utils/comm.js'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { sendChart } from '@/api/ai/index'
|
||||
import emitter from '@/utils/mitt';
|
||||
|
||||
const userInfo = useUserStore().user
|
||||
|
||||
const textarea = ref('')
|
||||
|
||||
const isDialog = defineModel()
|
||||
|
@ -55,12 +72,20 @@ const props = defineProps({
|
|||
item: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return { name: '11' }
|
||||
return { name: '' }
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
curMode:{
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
conversation_id: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -84,7 +109,8 @@ const curNode = reactive({})
|
|||
const params = reactive(
|
||||
{
|
||||
prompt: '',
|
||||
dataset_id: ''
|
||||
dataset_id: '',
|
||||
template: ''
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -92,7 +118,24 @@ const params = reactive(
|
|||
const getCompletion = async (val) => {
|
||||
try {
|
||||
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value} 对${curNode.itemtitle}进行教学分析`
|
||||
const { data } = await completion(params)
|
||||
params.template = props.item.prompt
|
||||
|
||||
let data = null;
|
||||
// 教学大模型
|
||||
if(props.curMode == 1){
|
||||
const res = await sendChart({
|
||||
content: params.prompt,
|
||||
conversationId: props.conversation_id,
|
||||
stream: false
|
||||
})
|
||||
data = res.data
|
||||
}
|
||||
else{
|
||||
// 知识库模型
|
||||
const res = await completion(params)
|
||||
data = res.data
|
||||
}
|
||||
|
||||
let answer = data.answer
|
||||
msgList.value.push({
|
||||
type: 'robot',
|
||||
|
@ -106,31 +149,48 @@ const getCompletion = async (val) => {
|
|||
|
||||
const saveAdjust = (item) =>{
|
||||
isDialog.value = false
|
||||
ElMessage.success('操作成功')
|
||||
emitter.emit('onSaveAdjust', item.msg)
|
||||
}
|
||||
|
||||
const modeType = ref('课标')
|
||||
watch(() => props.type, (newVal) => {
|
||||
if (newVal == 1){
|
||||
modeType.value = '课标'
|
||||
}
|
||||
if (newVal == 2){
|
||||
modeType.value = '教材'
|
||||
}
|
||||
if (newVal == 2){
|
||||
modeType.value = '考试'
|
||||
}
|
||||
|
||||
}, { immediate: false })
|
||||
|
||||
const curFile = reactive({})
|
||||
const dataset_id = ref('')
|
||||
const fileList = ref([])
|
||||
const getList = () =>{
|
||||
docList({
|
||||
userId: userInfo.userId,
|
||||
dataset_id: dataset_id.value
|
||||
}).then( res =>{
|
||||
fileList.value = res.rows
|
||||
Object.assign(curFile, fileList.value[0])
|
||||
params.document_ids = fileList.value[0].docId
|
||||
})
|
||||
}
|
||||
emitter.on('changeCurFile', (item) =>{
|
||||
changeFile(item)
|
||||
})
|
||||
const changeFile = (val) =>{
|
||||
Object.assign(curFile, val);
|
||||
params.document_ids = val.docId
|
||||
}
|
||||
|
||||
const modeType = ref('')
|
||||
onMounted(() => {
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
|
||||
Object.assign(curNode, data);
|
||||
|
||||
modeType.value = props.type == 1 ? '课标' : props.type == 2 ? '教材' : '考试'
|
||||
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
|
||||
params.dataset_id = dataSetJson[jsonKey]
|
||||
if(props.type == 3){
|
||||
getList()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// 解绑
|
||||
onUnmounted(() => {
|
||||
emitter.off('changeCurFile');
|
||||
|
||||
})
|
||||
|
||||
|
@ -267,4 +327,9 @@ onMounted(() => {
|
|||
transform: scale(0.01);
|
||||
}
|
||||
}
|
||||
|
||||
.file-list{
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<el-dialog v-model="mode" :show-close="false" width="600" append-to-body destroy-on-close>
|
||||
<template #header>
|
||||
<div class="custom-header flex">
|
||||
<span>{{ item.ex3 == '1' ? '请输入新的模板名称' : isAdd ? '添加提示词' : '编辑提示词' }}</span>
|
||||
<span>{{ item.ex3 == '1' ? '请输入新的模板名称' : item.isAdd ? '添加提示词' : '编辑提示词' }}</span>
|
||||
<i class="iconfont icon-guanbi" @click="mode = false"></i>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -40,11 +40,7 @@ const props = defineProps({
|
|||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
isAdd: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
item: { // 子模板
|
||||
item: { // 当前操作的模板
|
||||
type: Object,
|
||||
default: () => {
|
||||
return { ex3: '' }
|
||||
|
@ -58,37 +54,45 @@ const form = reactive({
|
|||
prompt: '',
|
||||
})
|
||||
|
||||
watch(() => props.isAdd, (newVal) => {
|
||||
if (!newVal) {
|
||||
form.name = props.item?.name
|
||||
form.prompt = props.item?.prompt
|
||||
|
||||
watch(() => mode.value, (newVal) => {
|
||||
if(newVal){
|
||||
if (props.item.isAdd) {
|
||||
form.name = ''
|
||||
form.prompt = ''
|
||||
}
|
||||
else{
|
||||
form.name = props.item?.name
|
||||
form.prompt = props.item?.prompt
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}, { immediate: false })
|
||||
|
||||
},{ deep: true})
|
||||
|
||||
const loading = ref(false)
|
||||
const saveAdd = async () => {
|
||||
loading.value = true
|
||||
if (props.item.ex3 == '1') {
|
||||
|
||||
if (props.isAdd) {
|
||||
try {
|
||||
// 系统预设模板 copy一份
|
||||
const { msg } = await addKeyWords({ name: form.name, id: props.item.id })
|
||||
emitter.emit('onGetMain')
|
||||
ElMessage.success(msg)
|
||||
mode.value = false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
let id; // id 为主模板id
|
||||
if (props.item.isAdd) {
|
||||
id = props.item.id
|
||||
}
|
||||
else{
|
||||
onAddChildTemp(props.item.parentId)
|
||||
// 编辑状态下 item 为子模板 主模板则是item.parentId
|
||||
id = props.item.parentId
|
||||
}
|
||||
try {
|
||||
// 系统预设模板 copy一份
|
||||
const { msg } = await addKeyWords({ name: form.name, id })
|
||||
emitter.emit('onGetMain')
|
||||
ElMessage.success(msg)
|
||||
mode.value = false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (props.isAdd) {
|
||||
if (props.item.isAdd) {
|
||||
onAddChildTemp(props.item.id)
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<el-dialog v-model="isDialog" :show-close="false" width="900" destroy-on-close>
|
||||
<el-dialog v-model="isDialog" :show-close="false" width="900" append-to-body destroy-on-close>
|
||||
<template #header>
|
||||
<div class="custom-header flex">
|
||||
<span>选择{{ title }}</span>
|
||||
|
@ -7,25 +7,54 @@
|
|||
</div>
|
||||
</template>
|
||||
<div class="dialog-content">
|
||||
<div class="flex">
|
||||
<el-radio-group v-model="radio" @change="changeRadio">
|
||||
<el-radio :value="item.value" v-for="item in radioList">{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="content-list">
|
||||
<ul>
|
||||
<li v-for="(item, index) in list" :class="activeIndex == index ? 'li-active' : ''" @click="clickItem(index)">
|
||||
<el-image class="img" :src="item.url" />
|
||||
<span>{{ item.name }}</span>
|
||||
<li v-for="(item, index) in fileList" :class="activeIndex == index ? 'li-active' : ''"
|
||||
@click="clickItem(index, item)">
|
||||
<el-image class="img" :src="url" />
|
||||
<el-button type="primary" class="prev-btn" @click.stop="onPrevItem(item)">预览</el-button>
|
||||
<el-text truncated>{{ item.fileName }}</el-text>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="isDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="isDialog = false">
|
||||
确定
|
||||
<el-upload class="upload-demo" :action="uploadFileUrl" :limit="1" :show-file-list="false" :headers="headers"
|
||||
:on-success="onSuccess">
|
||||
<el-button type="primary">上传</el-button>
|
||||
</el-upload>
|
||||
<div>
|
||||
<el-button @click="isDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="isDialog = false">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="prevVisible" fullscreen :show-close="false" class="prev-dialog">
|
||||
<template #header>
|
||||
<div class="custom-header flex">
|
||||
<span>预览</span>
|
||||
<i class="iconfont icon-guanbi" @click="prevVisible = false"></i>
|
||||
</div>
|
||||
</template>
|
||||
<div style="height: calc(100vh - 120px);">
|
||||
<template v-if="getFileSuffix(prevItem.fileUrl) == 'pdf'">
|
||||
<iframe :src="prevItem.fileUrl"
|
||||
frameborder="0" width="100%" height="100%"></iframe>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-image :src="prevItem.fileUrl" style="height:100%"/>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<div></div>
|
||||
<el-button type="primary" @click="prevVisible = false">
|
||||
关闭
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -33,11 +62,24 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, reactive } from 'vue'
|
||||
import { completion, addDoc, docList } from '@/api/mode/index.js'
|
||||
import { getToken } from "@/utils/auth";
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import { dataSetJson } from '@/utils/comm.js'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { getFileSuffix } from '@/utils/ruoyi.js'
|
||||
import emitter from '@/utils/mitt';
|
||||
|
||||
const userInfo = useUserStore().user
|
||||
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload");
|
||||
const headers = ref({ Authorization: "Bearer " + getToken() });
|
||||
|
||||
const url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F11044b08-04c1-41a0-a453-1fd20b58a614%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1732953359&t=7ab1d1b3a903db85b1149914407aea35'
|
||||
|
||||
const isDialog = defineModel()
|
||||
const prevVisible = ref(false)
|
||||
|
||||
const props = defineProps({
|
||||
modeType: {
|
||||
|
@ -52,14 +94,13 @@ const title = computed(() => {
|
|||
if (props.modeType == 3) return '考试';
|
||||
})
|
||||
|
||||
|
||||
const radio = ref(1)
|
||||
const radioList = ref([
|
||||
{ label: '浏览研读', value: 1 },
|
||||
// { label: '跨学科研读', value: 2 },
|
||||
// { label: '跨学段研读', value: 3 },
|
||||
// { label: '课标修订研读', value: 4 },
|
||||
// { label: '自由研读', value: 5 },
|
||||
{ label: '跨学科研读', value: 2 },
|
||||
{ label: '跨学段研读', value: 3 },
|
||||
{ label: '课标修订研读', value: 4 },
|
||||
{ label: '自由研读', value: 5 },
|
||||
])
|
||||
const list = ref([
|
||||
{
|
||||
|
@ -76,15 +117,70 @@ const changeRadio = () => {
|
|||
})
|
||||
}
|
||||
}
|
||||
const activeIndex = ref(0)
|
||||
|
||||
const activeIndex = ref(-1)
|
||||
const dataset_id = ref('')
|
||||
|
||||
const clickItem = (index) => {
|
||||
activeIndex.value = index
|
||||
// 上传成功
|
||||
const onSuccess = async (response) => {
|
||||
let data = {
|
||||
url: response.url,
|
||||
dataset_id: dataset_id.value
|
||||
}
|
||||
const res = await completion(data)
|
||||
|
||||
if (res.data.code != 200) return
|
||||
let docData = {
|
||||
fileUrl: response.url,
|
||||
fileId: response.file.id,
|
||||
fileName: response.file.fileName,
|
||||
filesize: response.file.fileSize,
|
||||
datasetId: dataset_id.value,
|
||||
docId: res.data.document_id,
|
||||
edustage: curNode.edustage,
|
||||
edusubject: curNode.edusubject
|
||||
}
|
||||
const { msg } = await addDoc(docData)
|
||||
ElMessage.success(msg)
|
||||
getList()
|
||||
|
||||
}
|
||||
const curNode = reactive({})
|
||||
|
||||
const fileList = ref([])
|
||||
const curFile = reactive({})
|
||||
const getList = () => {
|
||||
docList({
|
||||
userId: userInfo.userId,
|
||||
dataset_id: dataset_id.value
|
||||
}).then(res => {
|
||||
fileList.value = [...res.rows]
|
||||
Object.assign(curFile, fileList.value[0])
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
const clickItem = (index, item) => {
|
||||
activeIndex.value = index
|
||||
Object.assign(curFile, item)
|
||||
emitter.emit('changeCurFile', item)
|
||||
}
|
||||
|
||||
const prevItem = reactive({})
|
||||
const onPrevItem = (item) => {
|
||||
Object.assign(prevItem, item)
|
||||
prevVisible.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data);
|
||||
// 暂时写死"考试-" 目前只有考试分析才会弹出来
|
||||
let jsonKey = `考试-${curNode.edustage}-${curNode.edusubject}`
|
||||
dataset_id.value = dataSetJson[jsonKey]
|
||||
getList()
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.custom-header {
|
||||
justify-content: space-between;
|
||||
|
@ -98,13 +194,16 @@ const clickItem = (index) => {
|
|||
|
||||
.dialog-content {
|
||||
padding-top: 10px;
|
||||
|
||||
.content-list {
|
||||
padding-top: 10px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
li {
|
||||
width: 130px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 13px;
|
||||
|
@ -114,9 +213,11 @@ const clickItem = (index) => {
|
|||
overflow: hidden;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.img {
|
||||
width: 100px;
|
||||
width: 100%;
|
||||
height: 130px;
|
||||
border: solid #ccc 1px;
|
||||
margin-bottom: 10px;
|
||||
|
@ -125,6 +226,10 @@ const clickItem = (index) => {
|
|||
&:hover {
|
||||
background: #E0EAFF;
|
||||
}
|
||||
|
||||
&:hover .prev-btn {
|
||||
transform: translate(-50%, -40px)
|
||||
}
|
||||
}
|
||||
|
||||
.li-active {
|
||||
|
@ -134,4 +239,20 @@ const clickItem = (index) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.prev-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) translateY(-110px);
|
||||
/* 按钮初始位置在容器外 */
|
||||
transition: transform 0.3s ease-in-out;
|
||||
/* 设置过渡效果 */
|
||||
}
|
||||
</style>
|
|
@ -2,12 +2,13 @@
|
|||
<div class="container-left-page flex">
|
||||
<div class="container-left-header flex">
|
||||
<el-button link @click="onClick">
|
||||
{{ curNode.edustage }}{{ curNode.edusubject }}{{ type == 1 ? '课标研读' : '教材分析' }}<i
|
||||
{{ curNode.edustage }}{{ curNode.edusubject }}{{ type == 1 ? '课标研读' : type == 2 ? '教材分析' : '考试分析' }}<i
|
||||
class="iconfont icon-xiangxia"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="container-left-pdf">
|
||||
<PDF :url="pdfUrl" :showCatalog="false" v-if="pdfUrl" />
|
||||
<el-empty v-else description="暂无数据" />
|
||||
</div>
|
||||
<!--弹窗-->
|
||||
<LeftDialog v-model="showDialog" :modeType="type" />
|
||||
|
@ -15,29 +16,36 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { ref, onMounted, nextTick, reactive } from 'vue'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import PDF from '@/components/PdfJs/index.vue'
|
||||
import LeftDialog from './left-dialog.vue'
|
||||
|
||||
const props = defineProps(['curNode', 'type'])
|
||||
const props = defineProps(['type'])
|
||||
|
||||
const showDialog = ref(false)
|
||||
const onClick = () => {
|
||||
if (props.type == 1) return
|
||||
if (props.type != 3) return
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
// 加载PDF
|
||||
const pdfUrl = ref('')
|
||||
const curNode = reactive({})
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
// 当前节点
|
||||
let nodeData = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, nodeData);
|
||||
|
||||
let data = sessionStore.get('subject.curBook')
|
||||
let fileurl = data.fileurl
|
||||
if(props.type == 1){
|
||||
fileurl = `${data.edustage}-${data.edusubject}-课标.txt`
|
||||
}
|
||||
if(fileurl == '') return
|
||||
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf')
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -14,11 +14,14 @@
|
|||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div>
|
||||
<el-button type="danger" link @click="removeItem(curTemplate, false)">
|
||||
<div class="flex">
|
||||
<el-select v-model="curMode" placeholder="Select" class="mr-4 w-30">
|
||||
<el-option v-for="item in modeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-button type="danger" link :disabled="!(templateList.length)" @click="removeItem(curTemplate, false)">
|
||||
删除
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="onAdd">
|
||||
<el-button type="primary" link :disabled="!(templateList.length)" @click="onAdd">
|
||||
<i class="iconfont icon-jiahao"></i>
|
||||
添加提示词
|
||||
</el-button>
|
||||
|
@ -26,75 +29,92 @@
|
|||
</div>
|
||||
</div>
|
||||
<!--List-->
|
||||
<div class="container-right-list">
|
||||
<template v-for="(item, index) in childTempList">
|
||||
<div class="template-item" v-loading="item.loading">
|
||||
<div class="item-header">
|
||||
<div>
|
||||
<span class="blue">#</span>{{ item.name }}
|
||||
</div>
|
||||
<el-popover placement="bottom-end" trigger="hover" popper-class="template-custom-popover">
|
||||
<template #reference>
|
||||
<el-button link type="primary">
|
||||
<i class="iconfont icon-shenglvehao"></i></el-button>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-button type="primary" link @click="editKeyWord(item)">编辑</el-button>
|
||||
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="item-text">
|
||||
{{ item.prompt }}
|
||||
</div>
|
||||
<div class="item-text text-answer" v-if="item.answer">
|
||||
<div class="item-icon">
|
||||
<i class="iconfont icon-ai"></i>
|
||||
</div>
|
||||
<div class="item-answer">
|
||||
<TypingEffect :text="item.oldAnswer" :delay="10" :aiShow="item.aiShow" @complete="onSaveTemp(item)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ai-btn" v-if="item.answer">
|
||||
<el-button type="primary" link @click="againResult(index, item)">
|
||||
<i class="iconfont icon-ai1"></i>
|
||||
重新研读
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="onAdjust(index, item)">
|
||||
<i class="iconfont icon-duihua"></i>
|
||||
AI对话调整
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="onEdit(index, item)">
|
||||
<i class="iconfont icon-bianji1"></i>
|
||||
手动编辑结果
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty v-if="!childTempList.length" description="暂无模板数据" />
|
||||
<div class="container-right-list" ref="listRef">
|
||||
<template v-for="(item, index) in childTempList">
|
||||
<div class="template-item" v-loading="item.loading">
|
||||
<div class="item-header">
|
||||
<div>
|
||||
<span class="blue">#</span>{{ item.name }}
|
||||
</div>
|
||||
<el-popover placement="bottom-end" trigger="hover" popper-class="template-custom-popover">
|
||||
<template #reference>
|
||||
<el-button link type="primary">
|
||||
<i class="iconfont icon-shenglvehao"></i></el-button>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-button type="primary" link @click="editKeyWord(item, false)">编辑</el-button>
|
||||
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="item-text">
|
||||
{{ item.prompt }}
|
||||
</div>
|
||||
<div class="item-text text-answer" v-if="item.answer">
|
||||
<div class="item-icon">
|
||||
<i class="iconfont icon-ai"></i>
|
||||
</div>
|
||||
<div class="item-answer">
|
||||
<TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow"
|
||||
@complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ai-btn" v-if="item.answer">
|
||||
<el-button type="primary" link @click="againResult(index, item)">
|
||||
<i class="iconfont icon-ai1"></i>
|
||||
重新研读
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="onAdjust(index, item)">
|
||||
<i class="iconfont icon-duihua"></i>
|
||||
AI对话调整
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="onEdit(index, item)">
|
||||
<i class="iconfont icon-bianji1"></i>
|
||||
手动编辑结果
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty v-if="!childTempList.length" description="暂无模板数据" />
|
||||
</div>
|
||||
</div>
|
||||
<!--编辑结果-->
|
||||
<EditDialog v-model="isEdit" :item="editItem" />
|
||||
<!--AI 对话调整-->
|
||||
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" />
|
||||
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" :curMode="curMode" :conversation_id="conversation_id"/>
|
||||
<!--添加、编辑提示词-->
|
||||
<keywordDialog v-model="isWordDialog" :isAdd="isAdd" :item="editItem" />
|
||||
<keywordDialog v-model="isWordDialog" :item="editItem" :modeType="type" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch, onUnmounted } from 'vue'
|
||||
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
|
||||
import { createChart, sendChart } from '@/api/ai/index'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import keywordDialog from './keyword-dialog.vue';
|
||||
import AdjustDialog from './adjust-dialog.vue'
|
||||
import EditDialog from './edit-dialog.vue'
|
||||
import TypingEffect from '@/components/typing-effect/index.vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import emitter from '@/utils/mitt';
|
||||
import { dataSetJson } from '@/utils/comm.js'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
const props = defineProps(['curNode', 'type'])
|
||||
const props = defineProps(['type'])
|
||||
const { user } = useUserStore()
|
||||
|
||||
const curMode = ref(2)
|
||||
const modeOptions = ref([
|
||||
{
|
||||
label: '教学大模型',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '知识库模型',
|
||||
value: 2
|
||||
}
|
||||
])
|
||||
|
||||
/*****************提示词相关****************/
|
||||
|
||||
|
@ -105,21 +125,22 @@ const props = defineProps(['curNode', 'type'])
|
|||
*/
|
||||
|
||||
const isWordDialog = ref(false)
|
||||
const isAdd = ref(false)
|
||||
const editItem = reactive({})
|
||||
const onAdd = () => {
|
||||
isAdd.value = true
|
||||
|
||||
Object.assign(editItem, curTemplate)
|
||||
editItem.isAdd = true
|
||||
isWordDialog.value = true
|
||||
}
|
||||
|
||||
const editKeyWord = (item, val) => {
|
||||
/**
|
||||
* isAdd: 字模板中的移除 为编辑 头部删除 添加提示词为新增
|
||||
* isAdd: 子模板中的移除 为编辑false 头部删除 添加提示词为新增 true
|
||||
*/
|
||||
isAdd.value = val
|
||||
Object.assign(editItem, item)
|
||||
editItem.isAdd = val
|
||||
isWordDialog.value = true
|
||||
|
||||
}
|
||||
|
||||
/*******************模板相关**********************/
|
||||
|
@ -136,39 +157,69 @@ const curTemplate = reactive({ name: '', id: '' })
|
|||
const templateList = ref([])
|
||||
const childTempList = ref([])
|
||||
const getTemplateList = () => {
|
||||
modelList({ model: props.type, type: 1, pageNum: 1, pageSize: 10000 }).then(res => {
|
||||
modelList({ createUser: user.userId, model: props.type, type: 1, pageNum: 1, pageSize: 10000, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
|
||||
templateList.value = res.rows
|
||||
Object.assign(curTemplate, res.rows[0]);
|
||||
getChildTemplate()
|
||||
if (res.rows.length > 0) {
|
||||
Object.assign(curTemplate, res.rows[0]);
|
||||
getChildTemplate()
|
||||
}
|
||||
})
|
||||
}
|
||||
const getChildTemplate = () => {
|
||||
tempLoading.value = true
|
||||
modelList({ model: props.type, type: 2, parentId: curTemplate.id }).then(res => {
|
||||
modelList({ model: props.type, type: 2, parentId: curTemplate.id, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
|
||||
childTempList.value = res.rows
|
||||
|
||||
if (childTempList.value.length) {
|
||||
childTempList.value.forEach(item => item.answer = '')
|
||||
}
|
||||
getTempResult()
|
||||
}).finally(() => {
|
||||
tempLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const isStarted = ref([]);
|
||||
const listRef = ref()
|
||||
// 查询模板结果
|
||||
const getTempResult = () => {
|
||||
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000 }).then(res => {
|
||||
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(res => {
|
||||
let rows = res.rows
|
||||
childTempList.value.forEach(item => {
|
||||
rows.forEach(el => {
|
||||
if (item.id == el.modelId) {
|
||||
item.answer = el.content
|
||||
item.answer = getResult(el.content)
|
||||
item.resultId = el.id
|
||||
}
|
||||
})
|
||||
})
|
||||
if (rows.length > 0) {
|
||||
isStarted.value = new Array(rows.length).fill(true)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
const scrollToBottom = (height, index) => {
|
||||
|
||||
if (listRef.value) {
|
||||
let sum = 0
|
||||
let listDom = listRef.value.children
|
||||
|
||||
if (index == 0) {
|
||||
// 220 去掉头部
|
||||
let screenHeight = window.innerHeight - 220
|
||||
if (height > screenHeight) {
|
||||
listRef.value.scrollTop = (height - screenHeight + 50)
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < index; i++) {
|
||||
sum += listDom[i].clientHeight
|
||||
}
|
||||
listRef.value.scrollTop = sum + height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 模板切换
|
||||
const changeTemplate = (val) => {
|
||||
ElMessageBox.confirm(
|
||||
|
@ -189,7 +240,7 @@ const changeTemplate = (val) => {
|
|||
const removeItem = async (item, isChild) => {
|
||||
/**
|
||||
* item: 当前操作的模板
|
||||
* isChild: 子模板中的移除为 true
|
||||
* isChild: 子模板中的移除为 true
|
||||
*/
|
||||
if (item.ex3 != '1') {
|
||||
ElMessageBox.confirm(
|
||||
|
@ -203,11 +254,11 @@ const removeItem = async (item, isChild) => {
|
|||
).then(() => {
|
||||
removeChildTemp(item.id).then(res => {
|
||||
ElMessage.success('操作成功')
|
||||
if(isChild){
|
||||
if (isChild) {
|
||||
// 获取子模板
|
||||
getChildTemplate()
|
||||
}
|
||||
else{
|
||||
else {
|
||||
// 获取主模板
|
||||
getTemplateList()
|
||||
}
|
||||
|
@ -215,11 +266,10 @@ const removeItem = async (item, isChild) => {
|
|||
})
|
||||
}
|
||||
else {
|
||||
editKeyWord(item,!isChild)
|
||||
editKeyWord(item, !isChild)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Ai对话调整
|
||||
const curIndex = ref(-1)
|
||||
const isAdjust = ref(false)
|
||||
|
@ -237,22 +287,6 @@ const onEdit = (index, item) => {
|
|||
isEdit.value = true
|
||||
}
|
||||
|
||||
|
||||
const modeType = ref('课标')
|
||||
watch(() => props.type, (newVal) => {
|
||||
if (newVal == 1){
|
||||
modeType.value = '课标'
|
||||
}
|
||||
if (newVal == 2){
|
||||
modeType.value = '教材'
|
||||
}
|
||||
if (newVal == 2){
|
||||
modeType.value = '考试'
|
||||
}
|
||||
|
||||
}, { immediate: false })
|
||||
|
||||
|
||||
// 重新研读
|
||||
const params = reactive(
|
||||
{
|
||||
|
@ -260,76 +294,147 @@ const params = reactive(
|
|||
dataset_id: ''
|
||||
}
|
||||
)
|
||||
const prompt = ref('')
|
||||
|
||||
// 重新研读
|
||||
const isAgain = ref(false)
|
||||
const againResult = async (index, item) => {
|
||||
isAgain.value = true
|
||||
isStarted.value[index] = false
|
||||
childTempList.value[index].answer = ''
|
||||
|
||||
if (index == 0) {
|
||||
listRef.value.scrollTop = 0
|
||||
|
||||
} else {
|
||||
scrollToBottom(50, index)
|
||||
}
|
||||
|
||||
try {
|
||||
await nextTick()
|
||||
childTempList.value[index].loading = true
|
||||
item.aiShow = true
|
||||
childTempList.value[index].oldAnswer = ''
|
||||
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value} 对${curNode.itemtitle}进行教学分析`
|
||||
const { data } = await completion(params)
|
||||
let answer = data.answer
|
||||
childTempList.value[index].oldAnswer = answer
|
||||
childTempList.value[index].answer = getResult(answer);
|
||||
// onEditSave(item)
|
||||
|
||||
let str = cloneDeep(prompt.value)
|
||||
str = str.replace('{模板标题}',item.name)
|
||||
str = str.replace('{模板内容}',item.prompt)
|
||||
params.prompt = str
|
||||
params.template = item.prompt
|
||||
|
||||
let data = null;
|
||||
// 教学大模型
|
||||
if (curMode.value == 1) {
|
||||
const res = await sendChart({
|
||||
content: params.prompt,
|
||||
conversationId: conversation_id.value,
|
||||
stream: false
|
||||
})
|
||||
data = res.data
|
||||
} else {
|
||||
// 知识库模型
|
||||
const res = await completion(params)
|
||||
data = res.data
|
||||
}
|
||||
|
||||
childTempList.value[index].answer = getResult(data.answer);
|
||||
isStarted.value[index] = true
|
||||
|
||||
} finally {
|
||||
childTempList.value[index].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
// 一键研读
|
||||
const getCompletion = async () => {
|
||||
isStarted.value = new Array(childTempList.length).fill(false)
|
||||
isStarted.value[0] = true
|
||||
|
||||
childTempList.value.forEach(item => {
|
||||
if (item.answer) {
|
||||
item.answer = ''
|
||||
}
|
||||
})
|
||||
|
||||
for (let item of childTempList.value) {
|
||||
try {
|
||||
item.loading = true
|
||||
item.aiShow = true
|
||||
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value} 对${curNode.itemtitle}进行教学分析`
|
||||
const { data } = await completion(params)
|
||||
let answer = data.answer
|
||||
item.oldAnswer = answer
|
||||
item.answer = getResult(answer);
|
||||
let str = cloneDeep(prompt.value)
|
||||
str = str.replace('{模板标题}',item.name)
|
||||
str = str.replace('{模板内容}',item.prompt)
|
||||
params.prompt = str
|
||||
params.template = item.prompt
|
||||
// 教学大模型
|
||||
let data = null
|
||||
if (curMode.value == 1) {
|
||||
const res = await sendChart({
|
||||
content: params.prompt,
|
||||
conversationId: conversation_id.value,
|
||||
stream: false
|
||||
})
|
||||
data = res.data
|
||||
}
|
||||
// 知识库模型
|
||||
else {
|
||||
const res = await completion(params)
|
||||
data = res.data
|
||||
}
|
||||
|
||||
item.answer = getResult(data.answer)
|
||||
onSaveTemp(item)
|
||||
} finally {
|
||||
item.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCompleteText = async (answer, index) => {
|
||||
if (index < childTempList.value.length - 1) {
|
||||
isStarted.value[index + 1] = true; // 开始显示下一个文本
|
||||
}
|
||||
if (isAgain.value) {
|
||||
try {
|
||||
await editTempResult({ id: childTempList.value[index].resultId, content: answer })
|
||||
} finally {
|
||||
isAgain.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 替换分析结果
|
||||
emitter.on('onSaveAdjust', (item) => {
|
||||
childTempList.value[curIndex.value].oldAnswer = item
|
||||
let answer = getResult(item);
|
||||
childTempList.value[curIndex.value].oldAnswer = item
|
||||
childTempList.value[curIndex.value].answer = answer
|
||||
childTempList.value[curIndex.value].answer = item
|
||||
onEditSave(childTempList.value[curIndex.value])
|
||||
})
|
||||
|
||||
|
||||
// 保存 重新研读后的结果
|
||||
const onEditSave = async (item) =>{
|
||||
const { res } = await editTempResult({id: item.resultId, content: item.oldAnswer})
|
||||
ElMessage.success(res)
|
||||
const onEditSave = async (item) => {
|
||||
const { msg } = await editTempResult({ id: item.resultId, content: item.answer })
|
||||
ElMessage.success(msg)
|
||||
getChildTemplate()
|
||||
}
|
||||
|
||||
// 保存模板
|
||||
const onSaveTemp = (item) => {
|
||||
if(item.oldAnswer == '') return
|
||||
const onSaveTemp = async (item) => {
|
||||
if (item.answer == '') return
|
||||
|
||||
const data = {
|
||||
mainModelId: curTemplate.id,
|
||||
modelId: item.id,
|
||||
examDocld: '',
|
||||
content: item.oldAnswer
|
||||
content: item.answer,
|
||||
ex1: curNode.id
|
||||
}
|
||||
const res = await tempSave(data)
|
||||
if(!item.resultId){
|
||||
item.resultId = res.data
|
||||
}
|
||||
tempSave(data).then(res => {})
|
||||
}
|
||||
|
||||
// 分析获取课标对话结果
|
||||
let getResult = (text) => {
|
||||
text = text.replace(/^\n\n(.*?)\n\n$/s, '<div>$1</div>');
|
||||
text = text.replace(/^\n(.*?)\n$/s, '<p>$1</p>');
|
||||
text = text.replace(/\*\*(.*?)\*\*/g, "<div class='text-tit'>$1</div>");
|
||||
text = text.replace(/(\d+\..*?)\n/g, "<div class='text-num'>$1</div>\n");
|
||||
return text
|
||||
// 去掉字符串中的 ### **
|
||||
let getResult = (str) => {
|
||||
let newStr = str.replace(/#+|(\*\*)/g, '');
|
||||
return newStr
|
||||
}
|
||||
|
||||
// 操作之后获取字模板
|
||||
|
@ -342,13 +447,49 @@ emitter.on('onGetMain', () => {
|
|||
})
|
||||
|
||||
|
||||
// 创建对话
|
||||
const conversation_id = ref('')
|
||||
const getChartId = () => {
|
||||
createChart({ app_id: '712ff0df-ed6b-470f-bf87-8cfbaf757be5' }).then(res => {
|
||||
localStorage.setItem("conversation_id", res.data.conversation_id);
|
||||
conversation_id.value = res.data.conversation_id;
|
||||
})
|
||||
}
|
||||
|
||||
// 查询prompt 替换
|
||||
const getPrompt = async () => {
|
||||
const { rows } = await modelList({ model: 5 })
|
||||
let str = rows.find(item => item.name.indexOf(modeType.value) != -1).prompt
|
||||
str = str.replace('{学段}', curNode.edustage)
|
||||
str = str.replace('{学科}', curNode.edusubject)
|
||||
let bookV = curNode.roottitle.split('-')[1] + '版本'
|
||||
str = str.replace('{教材版本}', bookV)
|
||||
str = str.replace('{课程名称}', `《${curNode.itemtitle}》`)
|
||||
if(modeType.value == '课标'){
|
||||
str = str.replace('{课标名称}', `${curNode.edustage}${curNode.edusubject}课标`)
|
||||
}
|
||||
prompt.value = str
|
||||
}
|
||||
|
||||
const curNode = reactive({})
|
||||
const modeType = ref('')
|
||||
onMounted(() => {
|
||||
getTemplateList()
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data);
|
||||
modeType.value = props.type == 1 ? '课标' : props.type == 2 ? '教材' : '考试'
|
||||
|
||||
getTemplateList()
|
||||
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
|
||||
params.dataset_id = dataSetJson[jsonKey]
|
||||
// 获取百度千帆会话ID
|
||||
conversation_id.value = localStorage.getItem('conversation_id')
|
||||
if (!conversation_id.value) {
|
||||
getChartId();
|
||||
}
|
||||
|
||||
// 获取prompt
|
||||
getPrompt()
|
||||
|
||||
})
|
||||
|
||||
// 解绑
|
||||
|
@ -381,87 +522,85 @@ onUnmounted(() => {
|
|||
padding: 5px 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
|
||||
.template-item {
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
border-radius: 5px;
|
||||
|
||||
.template-item {
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
border-radius: 5px;
|
||||
.item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
justify-content: space-between;
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
justify-content: space-between;
|
||||
|
||||
.blue {
|
||||
font-size: 22px;
|
||||
color: #409eff;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.item-text {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
color: #606266;
|
||||
|
||||
.item-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
background: #F6F6F6;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-answer {
|
||||
flex-direction: column;
|
||||
padding-top: 5px;
|
||||
width: 100%;
|
||||
|
||||
:deep(.text-tit) {
|
||||
font-weight: bold;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
:deep(.text-num) {
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.text-answer {
|
||||
.blue {
|
||||
font-size: 22px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.ai-btn {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.iconfont {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
:deep(.el-button) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.icon-ai1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.item-text {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
color: #606266;
|
||||
|
||||
.item-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
background: #F6F6F6;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-answer {
|
||||
flex-direction: column;
|
||||
padding-top: 5px;
|
||||
width: 100%;
|
||||
|
||||
:deep(.text-tit) {
|
||||
font-weight: bold;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
:deep(.text-num) {
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.text-answer {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.ai-btn {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.iconfont {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
:deep(.el-button) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.icon-ai1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -470,4 +609,4 @@ onUnmounted(() => {
|
|||
width: 110px !important;
|
||||
min-width: 110px !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
<div class="page-template flex">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<Left :curNode="curNode" :type="type" />
|
||||
<Left :type="type" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<Right :curNode="curNode" :type="type" />
|
||||
<Right :type="type" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, onMounted } from 'vue'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import Left from './container/left.vue'
|
||||
import Right from './container/right.vue'
|
||||
|
||||
|
@ -23,13 +21,6 @@ const props = defineProps({
|
|||
default: 1
|
||||
},
|
||||
})
|
||||
|
||||
const curNode = reactive({})
|
||||
onMounted(() =>{
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data);
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="typing-effect">
|
||||
<div class="typing-effect" ref="typingEffectRef">
|
||||
<!-- <span v-html="displayedText"></span> -->
|
||||
<el-input
|
||||
v-model="displayedText"
|
||||
|
@ -8,17 +8,17 @@
|
|||
readonly
|
||||
resize="none"
|
||||
style="width: 100%;"
|
||||
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:15px"
|
||||
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:14px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { ref, onMounted, watch, nextTick } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
type: [String, Object],
|
||||
required: true
|
||||
},
|
||||
delay: {
|
||||
|
@ -26,41 +26,51 @@ const props = defineProps({
|
|||
default: 100 // 默认每个字符出现的延迟时间,单位是毫秒
|
||||
},
|
||||
aiShow: {
|
||||
type: [Boolean]
|
||||
type: [Boolean] // 为true 只展示
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['complete']);
|
||||
const typingEffectRef = ref(null);
|
||||
const emit = defineEmits(['complete', 'updateScroll']);
|
||||
const displayedText = ref('');
|
||||
const index = ref(0);
|
||||
|
||||
const type = () => {
|
||||
if(!props.aiShow) return
|
||||
const type = async () => {
|
||||
await nextTick()
|
||||
if(!props.aiShow) {
|
||||
displayedText.value = props.text
|
||||
return
|
||||
}
|
||||
if (index.value <= props.text.length) {
|
||||
displayedText.value += props.text.charAt(index.value);
|
||||
index.value++;
|
||||
setTimeout(() => type(), props.delay);
|
||||
setTimeout(() => {
|
||||
type();
|
||||
emit('updateScroll', typingEffectRef.value.clientHeight); // 每次添加新字符后滚动到底部
|
||||
}, props.delay);
|
||||
} else {
|
||||
// 当所有字符都显示完毕时,触发 complete 事件
|
||||
emit('complete');
|
||||
emit('complete',displayedText.value);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
type();
|
||||
resetAndType();
|
||||
});
|
||||
|
||||
// 监听 props 的变化,以便当传入的 text 或 delay 发生改变时重新开始打字机效果
|
||||
watch([() => props.text, () => props.delay], () => {
|
||||
const resetAndType = () =>{
|
||||
displayedText.value = '';
|
||||
index.value = 0;
|
||||
type();
|
||||
});
|
||||
}
|
||||
|
||||
// 监听 props 的变化,以便当传入的 text 或 delay 发生改变时重新开始打字机效果
|
||||
watch([() => props.text, () => props.delay], resetAndType);
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.typing-effect {
|
||||
font-family: monospace;
|
||||
}
|
||||
:deep(.el-textarea__inner:hover){
|
||||
box-shadow: none;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</el-tooltip>
|
||||
</div>
|
||||
<div class="blockBox">
|
||||
<el-button @click="currentType = 'selection'"><el-image src="../../../src/assets/images/mouse-pointer.png"
|
||||
<el-button @click="currentType = 'selection'"><el-image :src="pointerImg"
|
||||
style="width: 14px; height: 14px; color: silver" /></el-button>
|
||||
</div>
|
||||
<template v-if="type == 'design'">
|
||||
|
@ -145,7 +145,7 @@
|
|||
<!-- 边框粗细 -->
|
||||
<div class="blockBox">
|
||||
<el-dropdown @command="updateStyle('lineWidth', $event)" placement="top">
|
||||
<el-button><el-image src="../../../src/assets/images/borderwidth.png"
|
||||
<el-button><el-image :src="borderImg"
|
||||
style="width: 14px; height: 14px"></el-image></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
|
@ -303,6 +303,9 @@ import {
|
|||
import Contextmenu from './components/Contextmenu.vue'
|
||||
import { fontFamilyList, fontSizeList } from './constants'
|
||||
|
||||
const borderImg = new URL('../../../src/assets/images/borderwidth.png', import.meta.url).href
|
||||
const pointerImg = new URL('../../../src/assets/images/mouse-pointer.png', import.meta.url).href
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
|
@ -504,6 +507,11 @@ const backToCenter = () => {
|
|||
app.scrollToCenter()
|
||||
}
|
||||
|
||||
const cancelActiveElement = () =>{
|
||||
app.cancelActiveElement()
|
||||
}
|
||||
|
||||
|
||||
// 显示全部
|
||||
const showFit = () => {
|
||||
let elementList = app.elements.elementList
|
||||
|
@ -697,8 +705,6 @@ const getCanvasBlob = async () =>{
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
watch(() => props.data, (newVal) => {
|
||||
if (newVal) {
|
||||
setCanvasData(newVal)
|
||||
|
@ -788,7 +794,8 @@ defineExpose({
|
|||
getCanvasJson,
|
||||
getCanvasBase64,
|
||||
setCanvasData,
|
||||
getCanvasBlob
|
||||
getCanvasBlob,
|
||||
cancelActiveElement
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ const closeWindow = () => {
|
|||
ElMessageBox.confirm('确认退出系统吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
customClass: 'login-close-tool',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
userStore.logOut().then(() => {
|
||||
|
@ -54,7 +55,11 @@ onMounted(() =>{
|
|||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.login-close-tool {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.header-tool {
|
||||
width: 100%;
|
||||
|
@ -81,4 +86,4 @@ onMounted(() =>{
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* 无限滚动
|
||||
*/
|
||||
import { nextTick } from 'vue'
|
||||
const mountedHook = async (el, binding) => {
|
||||
console.log(el, binding)
|
||||
const value = binding.value
|
||||
if (typeof value !== 'function') return console.error('v-scroll must be a function')
|
||||
await nextTick()
|
||||
}
|
||||
export default {
|
||||
// Hooks for Vue3
|
||||
mounted(el, binding) {
|
||||
mountedHook(el, binding)
|
||||
},
|
||||
// Hooks for Vue2
|
||||
inserted(el, binding) {
|
||||
mountedHook(el, binding)
|
||||
},
|
||||
|
||||
update(el, binding){
|
||||
},
|
||||
updated(el, binding){
|
||||
|
||||
},
|
||||
}
|
|
@ -32,7 +32,7 @@ export const editListItem = (row, courseObj) => {
|
|||
worktype: '', // 设计中的作业类型
|
||||
quizlist: [], // 设计中的试题列表
|
||||
chooseWorkLists: [],// 设计中的框架梳理list
|
||||
fileHomeworkList: [],// 设计中的常规作业list
|
||||
fileHomeworkList: [],//TODO 暂时共用这个字段(新增了 科学实验) 设计中的常规作业list
|
||||
whiteboardObj: '',// 设计中的课堂展示对象
|
||||
question: '', // 设计中的[课堂展示]的问题
|
||||
};
|
||||
|
@ -58,7 +58,7 @@ export const editListItem = (row, courseObj) => {
|
|||
|
||||
|
||||
if (row.worktype == '框架梳理') {
|
||||
// 框架梳理对应只有一个内容
|
||||
// 框架梳理对应只有一个内容
|
||||
getEvaluationclue(listCourseWork[0].id).then(res => {
|
||||
if ( res.data==null || res.data==undefined ) {
|
||||
return ;
|
||||
|
@ -112,6 +112,16 @@ export const editListItem = (row, courseObj) => {
|
|||
return resolve(classtaskObj);
|
||||
}
|
||||
}
|
||||
else if (row.worktype == '科学实验') {
|
||||
if(isJson(row.workcodes)){
|
||||
classtaskObj.fileHomeworkList = JSON.parse(row.workcodes);
|
||||
//
|
||||
// console.log('科学实验', classtaskObj);
|
||||
// 更新默认的科学实验( 学段 学科 以及实验科目)
|
||||
console.log('科学实验', classtaskObj);
|
||||
return resolve(classtaskObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -87,13 +87,15 @@ const getHomeWorkList = async () => {
|
|||
// } else
|
||||
// 课标研读 目标设定 教材研读 框架梳理 学科定位 TODO 后续接入在添加
|
||||
if (res.rows[i].worktype == '课堂展示') {
|
||||
res.rows[i].workclass = 'primary';
|
||||
res.rows[i].workclass = 'success';
|
||||
} else if (res.rows[i].worktype == '框架梳理') {
|
||||
res.rows[i].workclass = 'warning';
|
||||
} else if (res.rows[i].worktype == '常规作业') {
|
||||
res.rows[i].workclass = 'info';
|
||||
} else if (res.rows[i].worktype == '习题训练') {
|
||||
res.rows[i].workclass = 'danger';
|
||||
} else if (res.rows[i].worktype == '科学实验') {
|
||||
res.rows[i].workclass = 'primary';
|
||||
} else {
|
||||
res.rows[i].workclass = 'primary';
|
||||
}
|
||||
|
|
|
@ -15,24 +15,40 @@ export const useGetSubject = async () =>{
|
|||
// 单元章节树结构
|
||||
let treeData = null
|
||||
|
||||
// 根据学科 + 学段 获取所有单元章节
|
||||
// 根据学科 + 学段 获取所有单元章节
|
||||
const getSubjectUnit = async () =>{
|
||||
if(sessionStore.get('subject.unitList')){
|
||||
unitList.value = sessionStore.get('subject.unitList')
|
||||
}
|
||||
else{
|
||||
if(isStadium(userStore.user)) {
|
||||
//如果是基地人员,直接拿treeData
|
||||
const unitParams = {
|
||||
edusubject,
|
||||
edustage,
|
||||
itemgroup: 'textbook',
|
||||
itemgroup: '基地课程',
|
||||
orderby: 'orderidx asc',
|
||||
entpid: userStore.user.deptId,
|
||||
pageSize: 10000
|
||||
}
|
||||
const { rows } = await listEvaluation(unitParams)
|
||||
unitList.value = rows
|
||||
sessionStore.set('subject.unitList', rows)
|
||||
}else{
|
||||
if(sessionStore.get('subject.unitList')){
|
||||
unitList.value = sessionStore.get('subject.unitList')
|
||||
}
|
||||
else{
|
||||
const unitParams = {
|
||||
edusubject,
|
||||
edustage,
|
||||
itemgroup: 'textbook',
|
||||
orderby: 'orderidx asc',
|
||||
pageSize: 10000
|
||||
}
|
||||
const { rows } = await listEvaluation(unitParams)
|
||||
unitList.value = rows
|
||||
sessionStore.set('subject.unitList', rows)
|
||||
}
|
||||
await getSubject()
|
||||
}
|
||||
await getSubject()
|
||||
}
|
||||
const isStadium = (user) => {
|
||||
let roles = user.roles
|
||||
return roles.some(item => item.roleKey === 'stadium')
|
||||
}
|
||||
|
||||
// 根据学科 + 学段 获取教材
|
||||
|
@ -42,7 +58,7 @@ export const useGetSubject = async () =>{
|
|||
subjectList = sessionStore.get('subject.bookList')
|
||||
}
|
||||
else{
|
||||
const subjectParams = {
|
||||
const subjectParams = {
|
||||
itemkey: "version",
|
||||
edusubject,
|
||||
edustage,
|
||||
|
@ -68,11 +84,14 @@ export const useGetSubject = async () =>{
|
|||
|
||||
// 单元章节数据转为“树”结构
|
||||
const getTreeData = (bookId) =>{
|
||||
// 根据当前教材的id 查找出对应的章节
|
||||
let data = unitList.value.filter(item => item.rootid == bookId && item.level == 1)
|
||||
if (!bookId) {
|
||||
return unitList.value
|
||||
}
|
||||
// 根据当前教材的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
|
||||
}
|
||||
|
||||
|
@ -80,4 +99,4 @@ export const useGetSubject = async () =>{
|
|||
|
||||
return { subjectList, treeData, getTreeData }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,13 +36,13 @@ export const processList = (row, aloneOption=false) => {
|
|||
row[i].method = jjj.method
|
||||
row[i].discuss = jjj.discuss
|
||||
//row[i].discusscollapse = false;
|
||||
if (row[i].examdate !== null && row[i].examdate !== undefined) {
|
||||
if (row[i].examdate && row[i].examdate != "") {
|
||||
row[i].examdate = row[i].examdate.substring(0, 10)
|
||||
}
|
||||
|
||||
// 具体题型数据结构处理
|
||||
if (row[i].worktype == '复合题') {
|
||||
// 旧类型
|
||||
// 复合题 - 旧格式
|
||||
if (row[i].title.indexOf('!@#$%') !== -1) {
|
||||
// 1.选项解析替换
|
||||
const options = JSON.parse(row[i].workdesc)
|
||||
|
@ -129,7 +129,9 @@ export const processList = (row, aloneOption=false) => {
|
|||
|
||||
row[i].workanswerFormat = answer
|
||||
} else {
|
||||
// 处理[题干显示] - 不再需要处理
|
||||
// 复合题 - 现格式
|
||||
|
||||
// 处理[题干显示] - 不再需要处理(头部已处理)
|
||||
// row[i].titleFormat = row[i].title; // 仅占位提示
|
||||
|
||||
/**
|
||||
|
@ -222,6 +224,7 @@ export const processList = (row, aloneOption=false) => {
|
|||
row[i].workanswerFormat = workAnswerHtml
|
||||
}
|
||||
} else if (
|
||||
/** 主观题/非基础题(其中主要为第三方的各类解答题) */
|
||||
row[i].worktype == '主观题' ||
|
||||
(row[i].worktype !== '单选题' &&
|
||||
row[i].worktype !== '多选题' &&
|
||||
|
@ -236,7 +239,7 @@ export const processList = (row, aloneOption=false) => {
|
|||
row[i].workanswerFormat = JSON.parse(row[i].workanswer)
|
||||
}
|
||||
} else {
|
||||
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||
// 基础题: 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||
// 通用选项结构 ['ABC123','ABC123'] | ['ABC123','ABC123'] | [](填空题无选项) | [](判断题无选项)
|
||||
let workDescArr = []
|
||||
if (
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
<el-popover ref="popoverRef" placement="right" trigger="hover" popper-class="popoverStyle" :tabindex="999" >
|
||||
<template #reference>
|
||||
<div class="user-info">
|
||||
<el-image class="user-img" :src="dev_api + userStore.user.avatar" />
|
||||
<el-image class="user-img" :src="userStore.user.avatar ==='/img/avatar-default.jpg' || userStore.user.avatar ==='/images/img-avatar.png' ? defaultUserImg : dev_api + userStore.user.avatar" />
|
||||
<span>{{ userStore.user.nickName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="head-aside">
|
||||
<ul >
|
||||
<li :class="computedregistertype==1 || computedregistertype==3?'auth-li':'auth-li pointer-events'" @click="onUserTo('/schoolCertification')" >
|
||||
<li v-if="isStadium() !== true" :class="computedregistertype==1 || computedregistertype==3?'auth-li':'auth-li pointer-events'" @click="onUserTo('/schoolCertification')" >
|
||||
<i class="iconfont icon-renzheng-" :style="computedregistertype==4?'color:green;':''"></i>
|
||||
<span class="mlr-5" v-if="computedregistertype!=4">学校认证</span>
|
||||
<span class="mlr-5" v-else>{{ userStore.DeptInfo.register.schoolName }}</span>
|
||||
|
@ -19,8 +19,8 @@
|
|||
</li>
|
||||
<li v-if="computedregistertype!=4" :class="computedregistertype==1 || computedregistertype==2 ? '':'pointer-events'" @click="onUserTo('/joinSchool')">加入学校</li>
|
||||
<li @click="onUserTo('/profile')">个人中心</li>
|
||||
<li @click="onUserTo('/schoolManagement')">学校管理</li>
|
||||
<li @click="onUserTo('/class')">班级中心</li>
|
||||
<li v-if="isStadium() !== true" @click="onUserTo('/schoolManagement')">学校管理</li>
|
||||
<li v-if="isStadium() !== true" @click="onUserTo('/class')">班级中心</li>
|
||||
<li @click="logout">退出登录</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -48,17 +48,18 @@
|
|||
</div>
|
||||
<div class="verson">V{{ version }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch , reactive, onMounted,computed} from 'vue'
|
||||
import { ref, watch , reactive, onMounted, onBeforeMount, computed} from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import pkc from "../../../../../package.json"
|
||||
import defaultUserImg from '@/assets/images/img-avatar.png'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
|
||||
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
@ -74,18 +75,42 @@ const version = ref(pkc.version)
|
|||
|
||||
const popoverRef = ref('')
|
||||
|
||||
const headerMenus = [
|
||||
//是否是基地人员
|
||||
const isStadium = () => {
|
||||
let user = userStore.user
|
||||
let roles = user.roles
|
||||
return roles.some(item => item.roleKey === 'stadium')
|
||||
}
|
||||
|
||||
const headerMenus = isStadium() ?[{
|
||||
name: '教学实践',
|
||||
id: 6,
|
||||
icon: 'icon-jiaoxueshijian',
|
||||
path: '/prepare'
|
||||
},]:[
|
||||
{
|
||||
name: '教学大模型',
|
||||
id: 1,
|
||||
icon: 'icon-shouye',
|
||||
path: '/model/index'
|
||||
},
|
||||
// {
|
||||
// name: '教学工作台',
|
||||
// id: 2,
|
||||
// icon: 'icon-gongzuotai',
|
||||
// path: '/desktop'
|
||||
// },
|
||||
{
|
||||
name: '教学工作台',
|
||||
id: 2,
|
||||
icon: 'icon-gongzuotai',
|
||||
path: '/desktop'
|
||||
name: '教学实践',
|
||||
id: 4,
|
||||
icon: 'icon-jiaoxueshijian',
|
||||
path: '/prepare'
|
||||
},
|
||||
{
|
||||
name: '教学活动',
|
||||
id: 5,
|
||||
icon: 'icon-zuoyepigai',
|
||||
path: '/classTask'
|
||||
},
|
||||
{
|
||||
name: '资源中心',
|
||||
|
@ -126,12 +151,12 @@ const computedregistertype = computed(() => {
|
|||
if(type==3 && userStore.DeptInfo.register.auditStatus==0){
|
||||
return 2
|
||||
}
|
||||
|
||||
|
||||
// 创建学校 待审核
|
||||
if(type==4 && userStore.DeptInfo.register.auditStatus==0){
|
||||
return 3
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
const clickMenu = ({ id, disabled, path }) => {
|
||||
if (disabled) return
|
||||
|
@ -157,9 +182,9 @@ watch(
|
|||
|
||||
|
||||
const logout = () => {
|
||||
const hasClass = sessionStore.has('activeClass.id')
|
||||
const hasTool = sessionStore.get('isToolWin')
|
||||
if (hasClass || hasTool) return ElMessage.warning('当前正在上课,请先结束上课')
|
||||
|
||||
if(!!sessionStore.get('curr.classcourse'))return ElMessage.warning('当前正在上课,请先结束上课')
|
||||
|
||||
ElMessageBox.confirm('确认退出系统吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
|
@ -193,7 +218,7 @@ onMounted(() => {
|
|||
}
|
||||
.popoverStyle .head-aside{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
@ -246,7 +271,7 @@ onMounted(() => {
|
|||
align-items: center;
|
||||
font-size: 12px;
|
||||
.user-img{
|
||||
width: 56px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
@ -312,4 +337,4 @@ onMounted(() => {
|
|||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -44,9 +44,8 @@ const breadList = ref([])
|
|||
watch(
|
||||
() => router.currentRoute.value,
|
||||
(newValue) => {
|
||||
let path = newValue.path
|
||||
|
||||
if (path.includes('/model') && path !== ('/model/index')) {
|
||||
const { showBread } = newValue.meta
|
||||
if (showBread) {
|
||||
isShowBack.value = true
|
||||
breadList.value = newValue.matched.map(item => item.meta)
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
<el-header>
|
||||
<Header/>
|
||||
</el-header>
|
||||
<el-main :style="{ 'padding-top' : isShowBread ? 0 : '20px'}">
|
||||
<template v-if="isShowBread">
|
||||
<el-main style="padding-top : 20px">
|
||||
<!-- <template v-if="isShowBread">
|
||||
<div class="bread-row">
|
||||
<div class="back" @click="onBack">
|
||||
<i class="iconfont icon-fanhui"></i>
|
||||
|
@ -19,8 +19,8 @@
|
|||
<el-breadcrumb-item v-for="item in breadList"> {{ item.title }} </el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
<AppMain :style="{ height: isShowBread ? 'calc(100% - 45px)' : '100%' }" />
|
||||
</template> -->
|
||||
<AppMain />
|
||||
</el-main>
|
||||
</el-container>
|
||||
<Uploader v-if="uploaderStore.uploadList && uploaderStore.uploadList.length > 0" />
|
||||
|
|
|
@ -16,6 +16,7 @@ import router from './router'
|
|||
import log from 'electron-log/renderer' // 渲染进程日志-文件记录
|
||||
import customComponent from '@/components/common' // 自定义组件
|
||||
import plugins from './plugins' // plugins插件
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件
|
||||
Object.assign(console, log.functions) // 渲染进程日志-控制台替换
|
||||
|
@ -23,7 +24,6 @@ if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印
|
|||
|
||||
const app = createApp(App)
|
||||
|
||||
|
||||
//专为菁优网配置的请求转发
|
||||
app.config.globalProperties.$requestGetJYW = (url,config)=>{
|
||||
config.params = config.params?config.params:{}
|
||||
|
@ -47,3 +47,16 @@ app.use(router)
|
|||
.use(Icon)
|
||||
.use(Directive)
|
||||
.mount('#app')
|
||||
|
||||
const isStadium = (user) => {
|
||||
let roles = user.roles
|
||||
return roles.some(item => item.roleKey === 'stadium')
|
||||
}
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.path === '/model/index') {
|
||||
isStadium(useUserStore().user) === true ? next('/prepare') :next()
|
||||
}else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
|
|
@ -56,6 +56,8 @@ export class MsgEnum {
|
|||
*/
|
||||
static HEADS = {
|
||||
// === 旧定义-消息头(兼容以前) ===
|
||||
/** @desc: 开课 */
|
||||
MSG_open : 'open',
|
||||
/** @desc: 结束课程(下课) */
|
||||
MSG_closed : 'closed',
|
||||
/** @desc: 在线状态 */
|
||||
|
@ -78,6 +80,8 @@ export class MsgEnum {
|
|||
MSG_anmationclick : 'anmationclick',
|
||||
/** @desc: 群组创建成功 */
|
||||
MSG_classcourseopen : 'classcourseopen',
|
||||
/** @desc: 学生提交作业 */
|
||||
MSG_finishHomework : 'finishHomework',
|
||||
/** @desc: 学生的测练结果反馈 */
|
||||
MSG_classquizfeedback : 'classquizfeedback',
|
||||
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
|
||||
|
@ -92,7 +96,19 @@ export class MsgEnum {
|
|||
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
|
||||
/** @desc: 课堂讲授活动,选择不同的内容 */
|
||||
MSG_classlecturePagesrc : 'classlecturePagesrc',
|
||||
/** @desc: 课堂作业|活动 */
|
||||
MSG_homework : 'HOMEWORK',
|
||||
/** @desc: 公屏 - 课堂作业|活动 */
|
||||
MSG_pushSreen_work : 'pushSreen_work',
|
||||
/** @desc: 公屏 - 实验 */
|
||||
MSG_pushSreen_experiment : 'pushSreen_experiment',
|
||||
/** @desc: 点赞 */
|
||||
MSG_dz : 'dz',
|
||||
/** @desc: 疑惑 */
|
||||
MSG_yh : 'yh',
|
||||
// === 新定义-消息头 ===
|
||||
/** @desc: 课程创建-待开课 */
|
||||
MSG_0000: 0x0000,
|
||||
/** @desc: 点赞 */
|
||||
MSG_0001: 0x0001,
|
||||
/** @desc: 疑惑 */
|
||||
|
@ -134,4 +150,4 @@ export class MsgEnum {
|
|||
}
|
||||
}
|
||||
|
||||
export { MsgEnum as default }
|
||||
export default MsgEnum;
|
|
@ -5,51 +5,51 @@ let loadingInstance;
|
|||
export default {
|
||||
// 消息提示
|
||||
msg(content) {
|
||||
ElMessage.info(content)
|
||||
return ElMessage.info(content)
|
||||
},
|
||||
// 错误消息
|
||||
msgError(content) {
|
||||
ElMessage.error(content)
|
||||
return ElMessage.error(content)
|
||||
},
|
||||
// 成功消息
|
||||
msgSuccess(content) {
|
||||
ElMessage.success(content)
|
||||
return ElMessage.success(content)
|
||||
},
|
||||
// 警告消息
|
||||
msgWarning(content) {
|
||||
ElMessage.warning(content)
|
||||
return ElMessage.warning(content)
|
||||
},
|
||||
// 弹出提示
|
||||
alert(content) {
|
||||
ElMessageBox.alert(content, "系统提示")
|
||||
alert(content, option = {}) {
|
||||
return ElMessageBox.alert(content, "系统提示", option)
|
||||
},
|
||||
// 错误提示
|
||||
alertError(content) {
|
||||
ElMessageBox.alert(content, "系统提示", { type: 'error' })
|
||||
alertError(content, option = {}) {
|
||||
return ElMessageBox.alert(content, "系统提示", { type: 'error', ...option })
|
||||
},
|
||||
// 成功提示
|
||||
alertSuccess(content) {
|
||||
ElMessageBox.alert(content, "系统提示", { type: 'success' })
|
||||
alertSuccess(content, option = {}) {
|
||||
return ElMessageBox.alert(content, "系统提示", { type: 'success', ...option })
|
||||
},
|
||||
// 警告提示
|
||||
alertWarning(content) {
|
||||
ElMessageBox.alert(content, "系统提示", { type: 'warning' })
|
||||
alertWarning(content, option = {}) {
|
||||
return ElMessageBox.alert(content, "系统提示", { type: 'warning', ...option })
|
||||
},
|
||||
// 通知提示
|
||||
notify(content) {
|
||||
ElNotification.info(content)
|
||||
return ElNotification.info(content)
|
||||
},
|
||||
// 错误通知
|
||||
notifyError(content) {
|
||||
ElNotification.error(content);
|
||||
return ElNotification.error(content);
|
||||
},
|
||||
// 成功通知
|
||||
notifySuccess(content) {
|
||||
ElNotification.success(content)
|
||||
return ElNotification.success(content)
|
||||
},
|
||||
// 警告通知
|
||||
notifyWarning(content) {
|
||||
ElNotification.warning(content)
|
||||
return ElNotification.warning(content)
|
||||
},
|
||||
// 确认窗体
|
||||
confirm(content) {
|
||||
|
@ -78,5 +78,8 @@ export default {
|
|||
// 关闭遮罩层
|
||||
closeLoading() {
|
||||
loadingInstance.close();
|
||||
}
|
||||
},
|
||||
// messageBox: opt => ElMessageBox(opt),
|
||||
// 其他实例放出去,方便调用
|
||||
ElMessage, ElMessageBox, ElNotification, ElLoading
|
||||
}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* websocket 工具类(im 自己实现)
|
||||
* 单例模式: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
|
||||
* 实现的方法为先判断实例存在与否,如果存在则直接返回,不存在就创建了再返回,这就确保了一个类只有一个实例对象。
|
||||
*/
|
||||
import useUserStore from '@/store/modules/user' // 用户信息
|
||||
|
||||
export class ChatWs {
|
||||
instance = null; // 实例
|
||||
id = null; // 群聊id || 单聊id-用户id(userId)
|
||||
url = null; // ws地址
|
||||
closed = false; // 关闭状态
|
||||
onmessage = null; // 自定义处理
|
||||
errCount = 5; // 重连次数 (ms) 暂时不使用
|
||||
errTime = null; // 重连时间 (ms) 1秒内zhi间内不重连
|
||||
// 类型定义
|
||||
TYPES = {
|
||||
group: 'group', // 群发
|
||||
single: 'single', // 单发
|
||||
beat: 'heart_beat', // 心跳
|
||||
}
|
||||
static base = 'wss://file.ysaix.com:7868'
|
||||
|
||||
// 构造函数 是否自动连接
|
||||
constructor(bool = true) {
|
||||
if (!ChatWs.instance) {
|
||||
if (bool) { // 是否自动连接
|
||||
const userStore = useUserStore() // 用户信息
|
||||
const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址
|
||||
this.url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`;
|
||||
this.closed = false; // 关闭状态 防止重连失败
|
||||
// this.init(url);
|
||||
}
|
||||
ChatWs.instance = this;
|
||||
}
|
||||
return ChatWs.instance;
|
||||
}
|
||||
|
||||
// 初始化
|
||||
init(url) {
|
||||
!!url && (this.url = url);
|
||||
this.closed = false; // 关闭状态 防止重连失败
|
||||
this.ws = null;
|
||||
const _this = this
|
||||
this.heartCheck = {
|
||||
timeout: 1000 * 60, // 60s
|
||||
timeoutObj: null,
|
||||
serverTimeoutObj: null,
|
||||
reset() {
|
||||
clearTimeout(this.timeoutObj);
|
||||
clearTimeout(this.serverTimeoutObj);
|
||||
return this;
|
||||
},
|
||||
start() {
|
||||
const self = this;
|
||||
this.timeoutObj = setTimeout(function () {
|
||||
// 这里发送一个心跳,后端收到后,返回一个心跳消息,
|
||||
// onmessage拿到返回的心跳就说明连接正常
|
||||
console.log("websocket-发送心跳")
|
||||
_this.sendMsgBeat();
|
||||
self.serverTimeoutObj = setTimeout(function () {
|
||||
console.log("websocket-心跳响应超时")
|
||||
// 如果超过一定时间还没重置,说明后端主动断开了
|
||||
_this.ws.close(); // 如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
|
||||
}, self.timeout);
|
||||
}, this.timeout);
|
||||
},
|
||||
};
|
||||
return this.reconnect();
|
||||
}
|
||||
// 重连
|
||||
reconnect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const self = this;
|
||||
if (!!this.ws) { // 关闭之前的链接
|
||||
this.ws.close()
|
||||
this.ws = null
|
||||
}
|
||||
this.ws = new WebSocket(this.url);
|
||||
this.ws.onopen = function () {
|
||||
console.log("websocket-连接成功")
|
||||
self.heartCheck.reset().start();
|
||||
resolve()
|
||||
};
|
||||
this.ws.onmessage = function (e) {
|
||||
// console.log("websocket-收到消息", e)
|
||||
// 拿到任何消息都说明当前连接是正常的
|
||||
const isBeat = e.data == 'pong'
|
||||
isBeat && self.heartCheck.reset().start();
|
||||
const isEmpty = !e.data
|
||||
const isExts = e.data.includes('sessionId') || e.data == ('pong')
|
||||
if (isEmpty || isExts) return;
|
||||
// 自定义处理
|
||||
self.onmessage && self.onmessage(e.data, e);
|
||||
};
|
||||
this.ws.onerror = function (e) {
|
||||
console.log("websocket-连接异常", e)
|
||||
self.connectSocket() // 重连
|
||||
};
|
||||
this.ws.onclose = function (e) {
|
||||
console.log("websocket-连接断开", e)
|
||||
self.connectSocket() // 重连
|
||||
};
|
||||
})
|
||||
}
|
||||
connectSocket() {
|
||||
this.heartCheck.reset() // 重置心跳
|
||||
if (this.closed) return this.ws = null; // 关闭状态不重连
|
||||
// if(self.errCount <= 0) return; // 超过重连次数
|
||||
// self.errCount--; // 重连次数减1
|
||||
if (this.errTime) {
|
||||
const nowTime = Date.now();
|
||||
const bool = nowTime - this.errTime < 1000 // 1s内zhi间内不重连
|
||||
if (bool) return; // 1s内不重连
|
||||
}
|
||||
this.errTime = Date.now();
|
||||
// 延时5s 后重连
|
||||
console.log('重连中...')
|
||||
this.sleep(5000).then(_ => {this.reconnect()})
|
||||
}
|
||||
// 发送消息
|
||||
send(msg) {
|
||||
if (!msg) throw new Error("msg is not null")
|
||||
if (!this.ws) throw new Error("ws is not null")
|
||||
if (typeof msg === "object") msg = JSON.stringify(msg)
|
||||
if (!msg.includes('"msg":')) throw new Error("msg 格式错误请重试")
|
||||
this.ws.send(msg)
|
||||
}
|
||||
// 发送消息-带消息头(key)
|
||||
sendMsg(head, content, option = {}, ...arg) {
|
||||
if (!head && head!==0) throw new Error("head is not null")
|
||||
if (!content && content!==0) throw new Error("content is not null")
|
||||
let msg = { head, content, ...option }
|
||||
// 发送消息
|
||||
this.send(this.getMsgObj(msg, ...arg))
|
||||
}
|
||||
// 发送心跳
|
||||
sendMsgBeat() {
|
||||
// this.send(this.getMsgObj('ping', this.TYPES.beat))
|
||||
this.ws.send('ping')
|
||||
}
|
||||
/**
|
||||
* @description 获取消息对象
|
||||
* @param {*} msg 消息内容
|
||||
* @param {*} chatType 群发 group| 单发 single| 心态 heart_beat
|
||||
* @param {*} id 群聊id || 单聊id-用户id(userId)
|
||||
*/
|
||||
getMsgObj(msg, chatType = 'group', id) {
|
||||
// if (typeof msg === "object") msg = JSON.stringify(msg)
|
||||
const res = {msg, chatType}
|
||||
// if (!id) throw new Error(`${type=='group'?'群ID':'用户ID'} is not null`)
|
||||
if (chatType == 'group') res.groupId = id || this.id || ''
|
||||
else if (chatType == 'single') res.to = id || this.id || ''
|
||||
return res
|
||||
}
|
||||
// 监听
|
||||
watch(callback) {
|
||||
callback && (this.onmessage = callback);
|
||||
}
|
||||
// 关闭链接
|
||||
close() {
|
||||
this.closed = true;
|
||||
this.ws.close();
|
||||
}
|
||||
// 下课
|
||||
closedCourse(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendMsg('closed', '下课', null, 'group', id)
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
// 延时 ms 毫秒
|
||||
sleep(ms){
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
}
|
||||
// 连接socket
|
||||
export const connect = () => new ChatWs()
|
||||
export const getInstance = () => new ChatWs(false)
|
||||
// 默认实例
|
||||
export default new ChatWs()
|
|
@ -31,6 +31,11 @@ export const constantRoutes = [
|
|||
component: () => import('@/AixPPTist/src/App.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/gridPic',
|
||||
component: () => import('@/components/grid-pic/index.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/model',
|
||||
component: Layout,
|
||||
|
@ -47,19 +52,19 @@ export const constantRoutes = [
|
|||
path: 'curriculum',
|
||||
component: () => import('@/views/curriculum-standards/index.vue'),
|
||||
name: 'curriculum-standard',
|
||||
meta: { title: '课标研读' }
|
||||
meta: { title: '课标研读', showBread: true }
|
||||
},
|
||||
{
|
||||
path: 'teaching',
|
||||
component: () => import('@/views/teaching-material/index.vue'),
|
||||
name: 'teaching-material',
|
||||
meta: { title: '教材研读' }
|
||||
meta: { title: '教材研读', showBread: true }
|
||||
},
|
||||
{
|
||||
path: 'examination',
|
||||
component: () => import('@/views/examination-analysis/index.vue'),
|
||||
name: 'examination-analysis',
|
||||
meta: { title: '考试分析' }
|
||||
meta: { title: '考试分析', showBread: true }
|
||||
},
|
||||
{
|
||||
path: 'management',
|
||||
|
@ -71,25 +76,25 @@ export const constantRoutes = [
|
|||
path: 'design',
|
||||
component: () => import('@/views/teachingDesign/index.vue'),
|
||||
name: 'teaching-design',
|
||||
meta: { title: '教学框架设计' },
|
||||
meta: { title: '教学框架设计' , showBread: true},
|
||||
},
|
||||
{
|
||||
path: 'newClassTaskAssign',
|
||||
component: () => import('@/views/classTask/newClassTaskAssign/index.vue'),
|
||||
name: 'newClassTaskAssign',
|
||||
meta: { title: '作业管理' }
|
||||
meta: { title: '作业设计', showBread: true }
|
||||
},
|
||||
{
|
||||
path: 'questionUpload',
|
||||
component: () => import('@/views/classTask/newClassTaskAssign/questionUpload/index.vue'),
|
||||
name: 'questionUpload',
|
||||
meta: { title: '习题上传' }
|
||||
meta: { title: '习题上传', showBread: true }
|
||||
},
|
||||
{
|
||||
path: 'aiKolors',
|
||||
component: () => import('@/components/ai-kolors/index.vue'),
|
||||
name: 'aiKolors',
|
||||
meta: { title: '文生图片' }
|
||||
meta: { title: '文生图片', showBread: true }
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -101,7 +106,7 @@ const dynamicRoutes = [
|
|||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/desktop',
|
||||
redirect: '/model/index',
|
||||
meta: { title: '教学工作台' },
|
||||
children: [
|
||||
{
|
||||
|
@ -132,7 +137,7 @@ const dynamicRoutes = [
|
|||
path: 'prepare',
|
||||
component: () => import('@/views/prepare/index.vue'),
|
||||
name: 'prepare',
|
||||
meta: { title: '教学实践', showBread: true }
|
||||
meta: { title: '教学实践' }
|
||||
},
|
||||
{
|
||||
path: 'newClassTask',
|
||||
|
@ -152,13 +157,6 @@ const dynamicRoutes = [
|
|||
name: 'classCorrect',
|
||||
meta: { title: '作业批改', showBread: true }
|
||||
},
|
||||
{
|
||||
path: '/teach',
|
||||
component: () => import('@/views/teach/index.vue'),
|
||||
name: 'teach',
|
||||
meta: { title: '授课' }
|
||||
},
|
||||
|
||||
{
|
||||
path: '/profile',
|
||||
component: () => import('@/views/profile/index.vue'),
|
||||
|
|
|
@ -5,6 +5,12 @@ import { JYApiListCT, JYApiListOriginYear, JYApiListSO} from "@/utils/examQuesti
|
|||
|
||||
const useClassTaskStore = defineStore('classTask',{
|
||||
state: () => ({
|
||||
experimentObj:{
|
||||
edustage: '小学', // 教育阶段
|
||||
edusubject: '', // 学科
|
||||
experimentList: [], // 实验科目列表
|
||||
},
|
||||
isOpenQuestUploadView: false, // 是否打开习题上传的页面
|
||||
classListIds: [],
|
||||
entpCourseWorkTypeList: [
|
||||
{value: 0, label: "不限"},
|
||||
|
|
|
@ -49,10 +49,10 @@ export function blobToFile(blob, fileName, contentType) {
|
|||
/**
|
||||
* @description 计算两点直线距离 (获取直径)
|
||||
* (欧几里得距离公式): [ \text{distance} = \sqrt{(x2 - x1)^2 + (y2 - y1)^2} ]
|
||||
* @param {*} x1
|
||||
* @param {*} y1
|
||||
* @param {*} x2
|
||||
* @param {*} y2
|
||||
* @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))
|
||||
|
@ -62,10 +62,10 @@ 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。
|
||||
|
@ -76,7 +76,7 @@ export function getPercent(v, total, step=2) {
|
|||
!total && (total = 1)
|
||||
// 计算百分比,保留指定的小数位,并转换为数字类型
|
||||
let res = (v / total * 100).toFixed(step)-0
|
||||
|
||||
|
||||
// 确保百分比在0到100之间
|
||||
return res < 0 ? 0 : res > 100 ? 100 : res
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ export function getPercent(v, total, step=2) {
|
|||
* @param {*} start 前面保留几位 默认 3位
|
||||
* @param {*} end 后面保留几位 默认 3位
|
||||
* @param {*} rstr 替换字符 默认 ****
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export function phoneHideFormat(phone, start = 3, end = 4, rstr = '****') {
|
||||
// const reg = /^(\d{3})\d*(\d{4})$/
|
||||
|
@ -101,8 +101,8 @@ export function phoneHideFormat(phone, start = 3, end = 4, rstr = '****') {
|
|||
// ============= 习题工具--相关 ===================
|
||||
/**
|
||||
* @description 将字符串转换为数组
|
||||
* @param {*} str
|
||||
* @returns
|
||||
* @param {*} str
|
||||
* @returns
|
||||
*/
|
||||
export function quizStrToList(str = '') {
|
||||
if (!str) return []
|
||||
|
@ -175,8 +175,8 @@ export const validateUrl = (url) => {
|
|||
|
||||
/**
|
||||
* @description 时间 秒 转化 时分秒
|
||||
* @param {*} seconds
|
||||
* @returns
|
||||
* @param {*} seconds
|
||||
* @returns
|
||||
*/
|
||||
export function formatTime(seconds) {
|
||||
seconds = parseInt(seconds) // 转换整数
|
||||
|
@ -188,9 +188,9 @@ export function formatTime(seconds) {
|
|||
|
||||
/**
|
||||
* @description 时间格式化
|
||||
* @param {*} time
|
||||
* @param {*} fmt
|
||||
* @returns
|
||||
* @param {*} time
|
||||
* @param {*} fmt
|
||||
* @returns
|
||||
*/
|
||||
export function formatDate(time, fmt = 'yyyy-MM-dd') {
|
||||
let date
|
||||
|
@ -300,7 +300,7 @@ export function getDateStr1(num, fmt = 'yyyy-MM-dd hh:mm:ss') {
|
|||
}
|
||||
/**
|
||||
* 获取当前 0:0:0:0 时间
|
||||
* @param {*} [fmt] 格式 'yyyy-MM-dd hh:mm:ss'
|
||||
* @param {*} [fmt] 格式 'yyyy-MM-dd hh:mm:ss'
|
||||
*/
|
||||
export function getDateNow(fmt) {
|
||||
const date = new Date()
|
||||
|
@ -339,9 +339,9 @@ export function timeToStr(time,str = '时分秒', isPad = false) {
|
|||
* debounce(() => {
|
||||
console.log('Input event handled');
|
||||
}, 300);
|
||||
* @param {*} func
|
||||
* @param {*} wait
|
||||
* @returns
|
||||
* @param {*} func
|
||||
* @param {*} wait
|
||||
* @returns
|
||||
*/
|
||||
export function debounce(func, wait) {
|
||||
let timeout;
|
||||
|
@ -358,9 +358,9 @@ export function debounce(func, wait) {
|
|||
* throttle(() => {
|
||||
console.log('Scroll event handled');
|
||||
}, 300);
|
||||
* @param {*} func
|
||||
* @param {*} wait
|
||||
* @returns
|
||||
* @param {*} func
|
||||
* @param {*} wait
|
||||
* @returns
|
||||
*/
|
||||
export function throttle(func, wait) {
|
||||
let lastTime = 0;
|
||||
|
@ -376,7 +376,7 @@ export function throttle(func, wait) {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* 大模型对话dataset_id
|
||||
*/
|
||||
export const dataSetJson = {
|
||||
|
@ -396,5 +396,18 @@ export const dataSetJson = {
|
|||
"课标-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
|
||||
"课标-高中-地理": "270516829fd111efb13c0242ac140006",
|
||||
"课标-高中-政治": "a2f0b247b85d11ef84290242ac140005",
|
||||
"教材-高中-语文": "cee3062a9fcf11efa6910242ac140006",
|
||||
"教材-高中-生物": "fb5d01d59fd011ef9bb90242ac140006",
|
||||
"教材-高中-历史": "f2f6c1fb9fd011ef98740242ac140006",
|
||||
"教材-高中-英语": "e889fcac9fd011efb22a0242ac140006",
|
||||
"教材-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
|
||||
"教材-高中-地理": "270516829fd111efb13c0242ac140006",
|
||||
"教材-高中-政治": "a2f0b247b85d11ef84290242ac140005",
|
||||
"课标-小学-科学": "935cfec8bf6a11ef98950242ac140006",
|
||||
"课标-小学-数学": "3c4e298fbf7911ef8e8b0242ac140002",
|
||||
"课标-小学-语文": "f76f1aa5bf7111ef90c80242ac140002",
|
||||
"教材-小学-科学": "935cfec8bf6a11ef98950242ac140006",
|
||||
"教材-小学-数学": "3c4e298fbf7911ef8e8b0242ac140002",
|
||||
"教材-小学-语文": "f76f1aa5bf7111ef90c80242ac140002",
|
||||
"鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* 弹窗-函数
|
||||
*/
|
||||
import { h, render } from 'vue'
|
||||
import { ElDialog } from 'element-plus'
|
||||
|
||||
// 打开弹窗-函数
|
||||
export const openDialog = (option, content) => {
|
||||
let vNode
|
||||
const body = document.body
|
||||
const dOpts = {
|
||||
modelValue: true,
|
||||
width: 800,
|
||||
height: 600,
|
||||
title: '添加-超连接',
|
||||
draggable: true,
|
||||
'onUpdate:modelValue': val => {
|
||||
if (vNode && !val) render(null, body)
|
||||
},
|
||||
...option
|
||||
}
|
||||
vNode = h(ElDialog, dOpts, {
|
||||
default: typeof content == 'function' ? content(h) : content
|
||||
})
|
||||
render(vNode, body)
|
||||
}
|
||||
|
||||
// 打开链接
|
||||
export const openLink = (option, title) => {
|
||||
// https://phet.colorado.edu/sims/html/number-play/latest/number-play_zh_CN.html
|
||||
const isStr = typeof option == 'string'
|
||||
const opt = isStr ? {} : option
|
||||
const url = isStr ? option : option?.url || option?.src || option?.href
|
||||
const titleNew = isStr? title||'实验室' : option?.title || '添加-超连接'
|
||||
openDialog({
|
||||
title: titleNew,
|
||||
...opt
|
||||
}, (h) => {
|
||||
return h('iframe', {
|
||||
src: url,
|
||||
width: '100%',
|
||||
style: {
|
||||
height: 'calc(80vh - 75px)',
|
||||
},
|
||||
scrolling: 'no',
|
||||
frameborder: '0',
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,734 @@
|
|||
{
|
||||
"title": "实验",
|
||||
"data": {
|
||||
"primary":[
|
||||
{
|
||||
"label": "数量比较",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/number-compare/latest/number-compare_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "数字游戏",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/number-play/latest/number-play_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "数轴:距离",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/number-line-distance/latest/number-line-distance_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "比率和比例",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/ratio-and-proportion/latest/ratio-and-proportion_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "数轴:运算",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/number-line-operations/latest/number-line-operations_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "数轴:整数",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/number-line-integers/latest/number-line-integers_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "向量的和:等式",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/vector-addition-equations/latest/vector-addition-equations_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "向量相加",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/vector-addition/latest/vector-addition_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "曲线拟合",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/curve-fitting/latest/curve-fitting_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "分数:带分数",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/fractions-mixed-numbers/latest/fractions-mixed-numbers_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "分数:入门",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/fractions-intro/latest/fractions-intro_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "构建一个分数",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/build-a-fraction/latest/build-a-fraction_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "分数:等式",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/fractions-equality/latest/fractions-equality_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"label": "单位价格",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/unit-rates/latest/unit-rates_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "获得一个10",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/make-a-ten/latest/make-a-ten_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "木棒的计算问题",
|
||||
"fileurl": "https://www.netpad.net.cn/resource_web/course/?pack_id=6dc2ab05-cb06-4716-92ca-e00fb89ad1e6#/20808",
|
||||
"subject": "math"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"label": "几何光学",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/geometric-optics/latest/geometric-optics_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "密度",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/density/latest/density_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "能量滑板竞技场: 基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/energy-skate-park-basics/latest/energy-skate-park-basics_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "法拉第定律",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/faradays-law/latest/faradays-law_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "绳波",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/wave-on-a-string/latest/wave-on-a-string_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "光的混合",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/color-vision/latest/color-vision_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "平衡探究实验",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/balancing-act/latest/balancing-act_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "受到压力",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/under-pressure/latest/under-pressure_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "摩擦力",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/friction/latest/friction_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "力和运动:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/forces-and-motion-basics/latest/forces-and-motion-basics_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "静电电压",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/john-travoltage/latest/john-travoltage_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "万有引力实验",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gravity-force-lab/latest/gravity-force-lab_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "气球和静电(摩擦起电)",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/balloons-and-static-electricity/latest/balloons-and-static-electricity_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
"label": "密度",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/density/latest/density_zh_CN.html",
|
||||
"subject": "biology"
|
||||
},
|
||||
{
|
||||
"label": "基因表达基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gene-expression-essentials/latest/gene-expression-essentials_zh_CN.html",
|
||||
"subject": "biology"
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
"label": "密度",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/density/latest/density_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "PH值",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/ph-scale/latest/ph-scale_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"label": "密度",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/density/latest/density_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "创造一个分子",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/build-a-molecule/latest/build-a-molecule_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "扩散",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/diffusion/latest/diffusion_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
}
|
||||
],
|
||||
"junior": [
|
||||
{
|
||||
"label": "二项分布弹珠台几率",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/plinko-probability/latest/plinko-probability_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "建立方程",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/function-builder/latest/function-builder_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "三角函数之旅",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/trig-tour/latest/trig-tour_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "四则运算",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/arithmetic/latest/arithmetic_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "二次函数图像",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/graphing-quadratics/latest/graphing-quadratics_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "质量和弹簧",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/masses-and-springs/latest/masses-and-springs_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "等式探索:两个变量",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/equality-explorer-two-variables/latest/equality-explorer-two-variables_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "等式探索:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/equality-explorer-basics/latest/equality-explorer-basics_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "等式探索",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/equality-explorer/latest/equality-explorer_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "面积模型代数",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/area-model-algebra/latest/area-model-algebra_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "面积模型:小数",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/area-model-decimals/latest/area-model-decimals_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "面积模型乘法",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/area-model-multiplication/latest/area-model-multiplication_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "面积模型入门",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/area-model-introduction/latest/area-model-introduction_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "钟摆实验",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/pendulum-lab/latest/pendulum-lab_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "斜抛运动",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/projectile-motion/latest/projectile-motion_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "表达式变换",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/expression-exchange/latest/expression-exchange_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "电路建设工具包:交流",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/circuit-construction-kit-ac/latest/circuit-construction-kit-ac_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "交流虚拟实验室",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/circuit-construction-kit-ac-virtual-lab/latest/circuit-construction-kit-ac-virtual-lab_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "碰撞实验室",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/collision-lab/latest/collision-lab_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "能量滑板竞技场",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/energy-skate-park/latest/energy-skate-park_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "向量相加",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/vector-addition/latest/vector-addition_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "曲线拟合",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/curve-fitting/latest/curve-fitting_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "引力实验室:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gravity-force-lab-basics/latest/gravity-force-lab-basics_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "波动入门",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/waves-intro/latest/waves-intro_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "扩散",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/diffusion/latest/diffusion_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "气体基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gases-intro/latest/gases-intro_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "气体性质",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gas-properties/latest/gas-properties_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "质量与弹簧:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/masses-and-springs-basics/latest/masses-and-springs-basics_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "黑体辐射",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/blackbody-spectrum/latest/blackbody-spectrum_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "能量的形式和转换",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/energy-forms-and-changes/latest/energy-forms-and-changes_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "波的干涉",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/wave-interference/latest/wave-interference_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "库仑定律",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/coulombs-law/latest/coulombs-law_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "质量和弹簧",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/masses-and-springs/latest/masses-and-springs_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "电容器实验:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/capacitor-lab-basics/latest/capacitor-lab-basics_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "电路组建实验:直流虚拟实验室",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/circuit-construction-kit-dc-virtual-lab/latest/circuit-construction-kit-dc-virtual-lab_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "电路组建实验:直流",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/circuit-construction-kit-dc/latest/circuit-construction-kit-dc_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "钟摆实验",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/pendulum-lab/latest/pendulum-lab_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "斜抛运动",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/projectile-motion/latest/projectile-motion_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "物质状态:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/states-of-matter-basics/latest/states-of-matter-basics_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "物质状态",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/states-of-matter/latest/states-of-matter_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "重力和轨道",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gravity-and-orbits/latest/gravity-and-orbits_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "分子与光",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/molecules-and-light/latest/molecules-and-light_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "PH值",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/ph-scale/latest/ph-scale_zh_CN.html",
|
||||
"subject": "biology"
|
||||
},
|
||||
{
|
||||
"label": "光的混合",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/color-vision/latest/color-vision_zh_CN.html",
|
||||
"subject": "biology"
|
||||
},
|
||||
{
|
||||
"label": "自然选择",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/natural-selection/latest/natural-selection_zh_CN.html",
|
||||
"subject": "biology"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "受到压力",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/under-pressure/latest/under-pressure_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "万有引力实验",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gravity-force-lab/latest/gravity-force-lab_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "气球和静电(摩擦起电)",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/balloons-and-static-electricity/latest/balloons-and-static-electricity_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "气体基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gases-intro/latest/gases-intro_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "气体性质",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gas-properties/latest/gas-properties_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "黑体辐射",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/blackbody-spectrum/latest/blackbody-spectrum_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "能量的形式和转换",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/energy-forms-and-changes/latest/energy-forms-and-changes_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "库仑定律",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/coulombs-law/latest/coulombs-law_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "分子极性",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/molecule-polarity/latest/molecule-polarity_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "物质状态:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/states-of-matter-basics/latest/states-of-matter-basics_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "物质状态",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/states-of-matter/latest/states-of-matter_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "原子的相互作用",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/atomic-interactions/latest/atomic-interactions_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "卢瑟福散射",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/rutherford-scattering/latest/rutherford-scattering_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "原子的相互作用",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/atomic-interactions/latest/atomic-interactions_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
}
|
||||
],
|
||||
"senior": [
|
||||
|
||||
{
|
||||
"label": "一次线性函数的拟合",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/least-squares-regression/latest/least-squares-regression_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "区域建造者",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/area-builder/latest/area-builder_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "绳波",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/wave-on-a-string/latest/wave-on-a-string_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "直线图形",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/graphing-lines/latest/graphing-lines_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "分数配对",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/fraction-matcher/latest/fraction-matcher_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "平衡探究实验",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/balancing-act/latest/balancing-act_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "绘图:斜率与截距",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/graphing-slope-intercept/latest/graphing-slope-intercept_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "函数构造器:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/function-builder-basics/latest/function-builder-basics_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
{
|
||||
"label": "比例游乐场",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/proportion-playground/latest/proportion-playground_zh_CN.html",
|
||||
"subject": "math"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "二项分布弹珠台几率",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/plinko-probability/latest/plinko-probability_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "原子的相互作用",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/atomic-interactions/latest/atomic-interactions_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "电荷与电场",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/charges-and-fields/latest/charges-and-fields_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "卢瑟福散射",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/rutherford-scattering/latest/rutherford-scattering_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "光的折射",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/bending-light/latest/bending-light_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "胡克定律",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/hookes-law/latest/hookes-law_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "部分电路欧姆定律",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/ohms-law/latest/ohms-law_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "电线的电阻",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/resistance-in-a-wire/latest/resistance-in-a-wire_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
{
|
||||
"label": "原子模型",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/build-an-atom/latest/build-an-atom_zh_CN.html",
|
||||
"subject": "physics"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "分子极性",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/molecule-polarity/latest/molecule-polarity_zh_CN.html",
|
||||
"subject": "biology"
|
||||
},
|
||||
{
|
||||
"label": "神经元",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/neuron/latest/neuron_zh_CN.html",
|
||||
"subject": "biology"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "引力实验室:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gravity-force-lab-basics/latest/gravity-force-lab-basics_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "波动入门",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/waves-intro/latest/waves-intro_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "扩散",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/diffusion/latest/diffusion_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "气体基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gases-intro/latest/gases-intro_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "气体性质",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gas-properties/latest/gas-properties_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "分子与光",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/molecules-and-light/latest/molecules-and-light_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "绳波",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/wave-on-a-string/latest/wave-on-a-string_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "黑体辐射",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/blackbody-spectrum/latest/blackbody-spectrum_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "波的干涉",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/wave-interference/latest/wave-interference_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
{
|
||||
"label": "重力和轨道",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/gravity-and-orbits/latest/gravity-and-orbits_zh_CN.html",
|
||||
"subject": "sciences"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "同位素和原子的质量",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/isotopes-and-atomic-mass/latest/isotopes-and-atomic-mass_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "分子与光",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/molecules-and-light/latest/molecules-and-light_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "分子形状",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/molecule-shapes/latest/molecule-shapes_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "分子形状:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/molecule-shapes-basics/latest/molecule-shapes-basics_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "反应物,生成物及未反应物",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/reactants-products-and-leftovers/latest/reactants-products-and-leftovers_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "pH值:基础",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/ph-scale-basics/latest/ph-scale-basics_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "绳波",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/wave-on-a-string/latest/wave-on-a-string_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "PH值",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/ph-scale/latest/ph-scale_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "配平化学方程式",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/balancing-chemical-equations/latest/balancing-chemical-equations_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "酸碱溶度",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/acid-base-solutions/latest/acid-base-solutions_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "浓度",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/concentration/latest/concentration_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "气球和静电(摩擦起电)",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/balloons-and-static-electricity/latest/balloons-and-static-electricity_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "比尔定律实验",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/beers-law-lab/latest/beers-law-lab_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},{
|
||||
"label": "摩尔浓度",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/molarity/latest/molarity_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
},
|
||||
{
|
||||
"label": "原子模型",
|
||||
"fileurl": "https://phet.colorado.edu/sims/html/build-an-atom/latest/build-an-atom_zh_CN.html",
|
||||
"subject": "chemistry"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* ppt 转换为图片
|
||||
*/
|
||||
import { h, render, getCurrentInstance } from 'vue'
|
||||
import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
|
||||
import { PPTXFileToJson } from "@/AixPPTist/src/hooks/useImport"
|
||||
import ThumbnailSlide from '@/AixPPTist/src/views/components/ThumbnailSlide/index.vue'
|
||||
import { useSlidesStore } from '@/AixPPTist/src/store'
|
||||
// 延时
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
/**
|
||||
* @description: 渲染组件
|
||||
* @param {*} node 节点或属性
|
||||
* @param {*} props 属性
|
||||
* @param {*} children 子元素
|
||||
* @param {*} container 容器
|
||||
*/
|
||||
const renderComponent = (node, props = {}, children, container) => {
|
||||
let vNode, body
|
||||
if (!node) throw new Error('vNode is required')
|
||||
if (typeof container == 'string') {
|
||||
if (node == 'slide') {
|
||||
vNode = h(ThumbnailSlide, props, children)
|
||||
} else throw new Error('vNode has no corresponding component')
|
||||
} else {
|
||||
vNode = h(node, props, children)
|
||||
}
|
||||
if (!container) body = document.body // 默认为body
|
||||
else { // 判断是否为字符串
|
||||
if (typeof container == 'string') {
|
||||
body = document.querySelector(container)
|
||||
} else {
|
||||
body = container
|
||||
}
|
||||
}
|
||||
return render(vNode, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 幻灯片转换为图片
|
||||
* 提示:icon组件找不到是应为 h() 渲染是底层创建的虚拟节点,找不到全局组件
|
||||
* @param {*} slides 幻灯片数据
|
||||
* @param {*} options 配置 number 为幻灯片宽度 | object 为配置项
|
||||
* @returns
|
||||
*/
|
||||
export const slidesToImg = (slides = [], options) => {
|
||||
let width, option, ispng = true
|
||||
if (typeof options =='number'){width = options; option = {}}
|
||||
else {const { width: w, isPng, ...opt } = options;width = w; ispng=isPng; option = opt}
|
||||
const slidesStore = useSlidesStore()
|
||||
!!width && slidesStore.setViewportSize(width) // 设置幻灯片宽度
|
||||
return new Promise(async(resolve) => {
|
||||
const instance = getCurrentInstance()
|
||||
console.log('instance', instance)
|
||||
const slidesDom = []
|
||||
for(const slide of slides) {
|
||||
const props = { class: 'c-thumbnail', slide, size: 120, ...option }
|
||||
const node = h(ThumbnailSlide, props)
|
||||
slidesDom.push(node)
|
||||
}
|
||||
// 渲染组件到body
|
||||
const props = { class: 'c-thumbnails', style:{position:'absolute',top:0,left:'-200vw'}}
|
||||
renderComponent('div', props, slidesDom)
|
||||
let imgs = []
|
||||
const toImag = ispng? toPng : toJpeg
|
||||
for(const slide of slidesDom) {
|
||||
const img = await toImag(slide.el)
|
||||
imgs.push(img)
|
||||
}
|
||||
// console.log('ppt生成图片: ', imgs)
|
||||
// console.log('图片已生成,正在卸载组件')
|
||||
!!width && slidesStore.setViewportSize(1000) // 设置幻灯片宽度-恢复
|
||||
render(null, document.body) // 卸载组件
|
||||
resolve(imgs)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* description: ppt 文件转换为图片
|
||||
* @param {*} file file 为文件对象| arrayBuffer 为数组
|
||||
* @param {*} options 配置 number 为幻灯片宽度 | object 为配置项
|
||||
* @returns
|
||||
*/
|
||||
export const pptToImg = async(file, options) => {
|
||||
const { slides } = await PPTXFileToJson(file)
|
||||
return slidesToImg(slides, options)
|
||||
}
|
|
@ -57,24 +57,31 @@ export const resourceFormat = [
|
|||
// 资源类型
|
||||
export const resourceType = [
|
||||
{
|
||||
id:1,
|
||||
label: '课例库',
|
||||
value: "'apt','课件','教案'"
|
||||
},
|
||||
{
|
||||
label: '作业库',
|
||||
value: '作业',
|
||||
disabled: true
|
||||
},
|
||||
// {
|
||||
// label: '作业库',
|
||||
// value: '作业',
|
||||
// disabled: true
|
||||
// },
|
||||
|
||||
{
|
||||
id:2,
|
||||
label: '素材库',
|
||||
value: "'素材'"
|
||||
},
|
||||
{
|
||||
label: '习题库',
|
||||
value: '习题',
|
||||
disabled: true
|
||||
}
|
||||
id:3,
|
||||
label: '实验室',
|
||||
value: "'素材'"
|
||||
},
|
||||
// {
|
||||
// label: '习题库',
|
||||
// value: '习题',
|
||||
// disabled: true
|
||||
// }
|
||||
]
|
||||
// 年级划分
|
||||
export const gradeList = [
|
||||
|
|
|
@ -258,16 +258,45 @@ export const getFileName = (filename) => {
|
|||
return filename.replace(/\.[^/.]+$/, "");
|
||||
}
|
||||
|
||||
// 清除当前选中的教材 章节 相关信息
|
||||
export const clearBookInfo = () =>{
|
||||
//当前选中的教材
|
||||
localStorage.removeItem('curBook')
|
||||
// 当前选中的节点
|
||||
localStorage.removeItem('curNode')
|
||||
// 所有章节单元数据
|
||||
localStorage.removeItem('unitList')
|
||||
// 所有教材数据
|
||||
localStorage.removeItem('subjectList')
|
||||
// 展开的节点
|
||||
localStorage.removeItem('defaultExpandedKeys')
|
||||
|
||||
/**
|
||||
* 根据图片的url转换对应的base64值
|
||||
* @param { String } url 如:http://xxxx/xxx.png
|
||||
* @returns base64取值
|
||||
*/
|
||||
export const urlToBase64 = (url) => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!url) {
|
||||
reject('请传入url内容')
|
||||
}
|
||||
if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(url)) {
|
||||
// 图片地址
|
||||
let image = new Image()
|
||||
// 设置跨域问题
|
||||
image.setAttribute('crossOrigin', 'anonymous')
|
||||
// 图片地址
|
||||
image.src = url +"?v=" + Math.random(); // 处理缓存,fix缓存bug,有缓存,浏览器会报错;
|
||||
image.onload = () => {
|
||||
let canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
ctx.drawImage(image, 0, 0, image.width, image.height)
|
||||
|
||||
// 获取图片后缀
|
||||
const ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase()
|
||||
// 转base64
|
||||
const dataUrl = canvas.toDataURL(`image/${ext}`)
|
||||
resolve(dataUrl || '')
|
||||
canvas = null // 清除canvas元素
|
||||
image = null // 清除img元素
|
||||
}
|
||||
} else {
|
||||
// 非图片地址
|
||||
reject('非(png/jpe?g/gif/svg等)图片地址');
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
|
@ -9,7 +9,7 @@ export const asyncLocalFile = (item) => {
|
|||
if (isAsync === true) {
|
||||
item.async = 'on'
|
||||
if (type === 'down') {
|
||||
console.log(item)
|
||||
// console.log(item)
|
||||
ipcRenderer.send('download-file-default', {
|
||||
url: item.fileFullPath,
|
||||
fileName: item.fileNewName
|
||||
|
|
|
@ -208,6 +208,10 @@ export const createWindow = async (type, data) => {
|
|||
autoHideMenuBar: true,
|
||||
maximizable: false,
|
||||
}
|
||||
// pptlist的时候可以选择最大化
|
||||
if (data.url == '/pptist'){
|
||||
defOption.maximizable = true;
|
||||
}
|
||||
data.isConsole = true // 是否开启控制台
|
||||
data.option = {...defOption, ...option}
|
||||
const win = await toolWindow(type, data)
|
||||
|
@ -221,7 +225,7 @@ export const createWindow = async (type, data) => {
|
|||
.filter(k => typeof data[k] === 'function')
|
||||
.forEach(k => events[k] = data[k])
|
||||
eventHandles(type, win, events) // 事件监听处理
|
||||
break
|
||||
return win
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
@ -244,10 +248,11 @@ export function toolWindow(type, {url, isConsole, isWeb=true, option={}}) {
|
|||
const devUrl = `${BaseUrl}${url}`
|
||||
const buildUrl = path.join(__dirname, 'index.html')
|
||||
const urlAll = isDev ? devUrl : buildUrl
|
||||
let logoIco = import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?'/resources/yc-logo.png':'/resources/logo2.ico'
|
||||
return new Promise(async(resolve) => {
|
||||
const config = {
|
||||
width, height,
|
||||
icon: path.join(appPath, '/resources/logo2.ico'),
|
||||
icon: path.join(appPath, logoIco),
|
||||
webPreferences: {
|
||||
preload: path.join(API.preloadPath, '/index.js'),
|
||||
sandbox: false,
|
||||
|
@ -418,6 +423,19 @@ export const toLinkWeb = (path) => {
|
|||
cookieData: { ...config }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 外部跳转-web网页
|
||||
* @param {*} url
|
||||
*/
|
||||
export const toLinkLeftWeb = (url) => {
|
||||
// 通知主进程
|
||||
ipcRenderer.send('openWindow', {
|
||||
key: `win-${Date.now()}`,
|
||||
fullPath: url
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 外部跳转-web网页
|
||||
* @param {*} path
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
<!-- <div class="class-reserv-tabs">
|
||||
<el-segmented v-model="tabActive" block :options="tabOptions" size="large" />
|
||||
</div>-->
|
||||
<div class="class-reserv-body">
|
||||
<div class="class-reserv-body" v-infinite-scroll="load">
|
||||
<template v-for="(item, index) in dataList" :key="index">
|
||||
<reserv-item
|
||||
<!-- <reserv-item
|
||||
:style="{'background-color': index%2==0?'#f5f5f5':''}"
|
||||
:item="item"
|
||||
v-if="item.bookImg"
|
||||
@open-edit="reservDialog.openDialog(item)"
|
||||
@delete-reserv="deleteReserv(item)"
|
||||
@change="(...o) => emit('change', ...o)"
|
||||
></reserv-item>
|
||||
></reserv-item> -->
|
||||
<reserv-item-apt
|
||||
v-if="!item.bookImg"
|
||||
:style="{'background-color': index%2==0?'#f5f5f5':''}"
|
||||
|
@ -22,13 +22,14 @@
|
|||
@change="(...o) => emit('change', ...o)"
|
||||
></reserv-item-apt>
|
||||
</template>
|
||||
<el-divider v-if="page.isEnd">到底了,没了</el-divider>
|
||||
</div>
|
||||
<reserv ref="reservDialog"></reserv>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { ref, onMounted, computed, watch, reactive } from 'vue'
|
||||
import { getSelfReserv } from '@/api/classManage'
|
||||
import { listClasscourseNew } from '@/api/teaching/classcourse' // api接口
|
||||
import ReservItem from '@/views/classManage/reserv-item.vue'
|
||||
|
@ -36,6 +37,7 @@ import Reserv from '@/views/prepare/container/reserv.vue'
|
|||
import { useToolState } from '@/store/modules/tool'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import ReservItemApt from '@/views/classManage/reserv-item-apt.vue'
|
||||
import vScroll from '@/directive/scroll' // 指令--滚动
|
||||
// import Chat from '@/utils/chat' // im 登录初始化
|
||||
// if (!Chat.imChat) Chat.init()
|
||||
|
||||
|
@ -44,6 +46,12 @@ const reservDialog = ref(null)
|
|||
const tabOptions = ref(['进行中', '已结束'])
|
||||
const tabActive = ref('进行中')
|
||||
const dataList = ref([])
|
||||
const page = reactive({
|
||||
pageNum: 0, // 页码
|
||||
pageSize: 10, // 每页条数
|
||||
total: 0, // 总条数
|
||||
isEnd: false // 是否加载完
|
||||
})
|
||||
|
||||
const toolStore = useToolState()
|
||||
const userStore = useUserStore()
|
||||
|
@ -72,21 +80,42 @@ const deleteReserv = (item) => {
|
|||
})*/
|
||||
// 获取数据
|
||||
const getData = () => {
|
||||
Promise.all([listClasscourseNew({teacherid: userStore.id,evalid: props.curNode.id,pageSize:1000}), getSelfReserv({ex2:props.curNode.id})]).then(([res1,res2])=>{
|
||||
let list = res2.data || []
|
||||
let list2 = res1.rows || []
|
||||
// list.sort((a,b) => { if(a.status=='上课中') return -1; else return 0 })
|
||||
list = list.concat(list2)
|
||||
const { pageNum, pageSize } = page
|
||||
const params = {
|
||||
evalid: props.curNode.id,
|
||||
teacherid: userStore.id,
|
||||
pageNum, pageSize
|
||||
}
|
||||
listClasscourseNew(params)
|
||||
.then((res) => {
|
||||
const list = res.rows || []
|
||||
const total = res.total || 0
|
||||
list.sort((a,b) => { return new Date(b.createTime) - new Date(a.createTime) })
|
||||
dataList.value = list
|
||||
dataList.value.push(...list)
|
||||
page.total = total // 总条数
|
||||
page.isEnd = dataList.value.length == total // 是否结束
|
||||
})
|
||||
// aippt+ppt 获取数据
|
||||
// Promise.all([listClasscourseNew({teacherid: userStore.id,evalid: props.curNode.id,pageSize:1000}), getSelfReserv({ex2:props.curNode.id})]).then(([res1,res2])=>{
|
||||
// let list = res2.data || []
|
||||
// let list2 = res1.rows || []
|
||||
// // list.sort((a,b) => { if(a.status=='上课中') return -1; else return 0 })
|
||||
// list = list.concat(list2)
|
||||
// list.sort((a,b) => { return new Date(b.createTime) - new Date(a.createTime) })
|
||||
// dataList.value = list
|
||||
// })
|
||||
/*getSelfReserv().then((res) => {
|
||||
const list = res.data || []
|
||||
list.sort((a,b) => { if(a.status=='上课中') return -1; else return 0 })
|
||||
dataList.value = list
|
||||
})*/
|
||||
}
|
||||
|
||||
// 列表加载更多
|
||||
const load = () => {
|
||||
if(page.isEnd) return console.log('已加载完-所有') // 结束
|
||||
page.pageNum++
|
||||
getData()
|
||||
}
|
||||
watch(
|
||||
() => [dataList,toolStore.isToolWin,props.curNode],
|
||||
() => {
|
||||
|
@ -96,13 +125,14 @@ watch(
|
|||
}
|
||||
)
|
||||
onMounted(() => {
|
||||
getData() // 加载数据
|
||||
// getData() // 加载数据
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.class-reserv-wrap {
|
||||
height: 100%;
|
||||
// height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
//padding: 15px 10px;
|
||||
|
|
|
@ -7,28 +7,35 @@
|
|||
<span>{{item.caption}}</span>
|
||||
</div>
|
||||
<div class="class-reserv-item-tool" style="width: 200px;max-width: 300px">
|
||||
<el-tag v-if="item.status === 'closed'" style="margin-right: 5px" type="success">已结束</el-tag>
|
||||
<el-tag v-if="item.status === 'open'" style="margin-right: 5px" type="danger">上课中</el-tag>
|
||||
<el-button v-if="item.status === 'open'" :disabled="toolStore.isToolWin" size="small" type="primary" @click="startClassR(item)"
|
||||
>继续上课</el-button
|
||||
>
|
||||
<!-- <el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>-->
|
||||
<el-button v-if="item.status === 'open'" :loading="loading" size="small" type="info" @click="endClassR(item)"
|
||||
>下课{{ loading?'中...':'' }}</el-button
|
||||
>
|
||||
<el-tag v-if="item.status === ''" style="margin-right: 5px" type="warning">待开课</el-tag>
|
||||
<el-tag v-else-if="item.status === 'closed'" style="margin-right: 5px" type="success">已结束</el-tag>
|
||||
<el-tag v-else-if="item.status === 'open'" style="margin-right: 5px" type="danger">上课中</el-tag>
|
||||
<template v-if="!item.status">
|
||||
<el-button size="small" type="primary" :icon="ChatDotRound" @click="chatSend()">上课(APP)</el-button>
|
||||
</template>
|
||||
<template v-else-if="item.status === 'open'">
|
||||
<el-button :disabled="toolStore.isToolWin" size="small" type="primary" @click="startClassR(item)"
|
||||
>继续上课</el-button>
|
||||
<!--<el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>-->
|
||||
<el-button :loading="loading" size="small" type="info" @click="endClassR(item)"
|
||||
>下课{{ loading?'中...':'' }}</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="class-reserv-item-tool" style="width: 50px;">
|
||||
<!-- <el-button v-if="item.status!='open'" size="small" type="danger" @click="deleteReserv">删除</el-button>-->
|
||||
<el-tag>APT</el-tag>
|
||||
<!-- <el-tag>APT</el-tag> -->
|
||||
<el-tag>AIPPT</el-tag>
|
||||
</div>
|
||||
<div style="min-width: 150px;"><span> 浏览:25955 点赞:26605</span></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useToolState } from '@/store/modules/tool'
|
||||
import { deleteSmartReserv } from '@/api/classManage'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ChatDotRound } from '@element-plus/icons-vue'
|
||||
|
||||
const emit = defineEmits(['openEdit', 'deleteReserv', 'change'])
|
||||
const props = defineProps({
|
||||
item: {
|
||||
|
@ -39,6 +46,7 @@ const props = defineProps({
|
|||
const basePath = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
const toolStore = useToolState() // 获取状态管理-tool
|
||||
const loading = ref(false) // 下课中的loading
|
||||
const msg = reactive({ id: null, time: null }) // 发送消息的对象
|
||||
const openEdit = () => {
|
||||
emit('openEdit', props.item)
|
||||
}
|
||||
|
@ -61,11 +69,24 @@ const startClassR = (item) => {
|
|||
const endClassR = (item) => {
|
||||
emit('change', 'close', item, { type: 2, loading })
|
||||
}
|
||||
// 发送上课消息
|
||||
const chatSend = () => {
|
||||
const time = Date.now()
|
||||
if(msg.id) { // 同一个人30秒内不能重复发送
|
||||
const isEq = msg.id == props.item.id && (time - msg.time) < 30 * 1000
|
||||
if(isEq) return ElMessage.warning('请勿重复发送(30秒内)!')
|
||||
} else { // 记录发送信息
|
||||
msg.id = props.item.id
|
||||
msg.time = time
|
||||
}
|
||||
emit('change', 'wsApp', props.item)
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.class-reserv-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
|
@ -91,7 +112,7 @@ const endClassR = (item) => {
|
|||
}
|
||||
}
|
||||
.class-reserv-item-tool {
|
||||
margin-left: 15px;
|
||||
margin: 0 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue