Compare commits

..

242 Commits
2.5.5 ... main

Author SHA1 Message Date
zhangxuelin d0cbb5cbcd Merge pull request 'zxl' (#182) from zxl into main
Reviewed-on: #182
2024-12-25 17:30:13 +08:00
zhangxuelin 5686669563 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-25 17:29:43 +08:00
zhangxuelin eb7d6df355 添加推图片上屏 2024-12-25 17:29:34 +08:00
lyc 960ab581c3 Merge pull request 'edit' (#181) from lyc-dev into main 2024-12-25 17:25:11 +08:00
lyc 0d6767170d edit 2024-12-25 17:24:48 +08:00
zhangxuelin ae839825d4 Merge pull request 'zxl' (#180) from zxl into main
Reviewed-on: #180
2024-12-25 16:56:38 +08:00
zhangxuelin ef670dee99 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-25 16:55:47 +08:00
朱浩 c81f393715 宫格插件 2024-12-25 14:38:29 +08:00
zhangxuelin 06bb6284c4 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-25 13:39:31 +08:00
朱浩 b5caa7ba80 Merge remote-tracking branch 'origin/main' 2024-12-25 12:04:41 +08:00
朱浩 53ff508635 宫格插件 2024-12-25 12:04:27 +08:00
zouyf 9cce9c224c Merge pull request '[作业批改] - 替换自动批改完成' (#179) from zouyf_tmp into main
Reviewed-on: #179
2024-12-25 10:42:58 +08:00
“zouyf” 171dc72ee7 [作业批改] - 替换自动批改完成 2024-12-25 10:33:20 +08:00
zhangxuelin c2750e3da6 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-25 10:27:02 +08:00
zhangxuelin 5550ca570c no message 2024-12-25 10:25:07 +08:00
lyc 62a419356c Merge pull request 'edit' (#178) from lyc-dev into main 2024-12-24 17:32:25 +08:00
lyc 4b2d13db8e edit 2024-12-24 17:31:56 +08:00
zhangxuelin ab9bad467a Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl
# Conflicts:
#	src/renderer/src/AixPPTist/src/views/Editor/EditorHeader/index.vue
2024-12-24 16:08:48 +08:00
zhengdegang f63bb69919 Merge pull request 'zdg_dev' (#177) from zdg_dev into main
Reviewed-on: #177
2024-12-24 16:08:38 +08:00
zdg b3ffa9bdaa Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-24 16:07:00 +08:00
zdg 5510021566 上课允许不选班级开课 2024-12-24 16:06:53 +08:00
lyc 89e0b2f97c Merge pull request 'edit 框架设计' (#176) from lyc-dev into main 2024-12-24 14:48:25 +08:00
lyc 406e581182 edit 框架设计 2024-12-24 14:47:58 +08:00
lyc ea0e1c91cf Merge pull request 'lyc-dev' (#175) from lyc-dev into main 2024-12-24 14:38:20 +08:00
lyc d4dce61555 Merge branch 'main' into lyc-dev 2024-12-24 14:37:06 +08:00
lyc d6d5d13232 pptList 插入素材 增加loading 2024-12-24 14:32:59 +08:00
zhengdegang 33e38f8992 Merge pull request 'zdg_dev' (#174) from zdg_dev into main
Reviewed-on: #174
2024-12-24 12:05:02 +08:00
zdg 11968879d8 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev
# Conflicts:
#	src/renderer/src/AixPPTist/src/views/Editor/EditorHeader/index.vue
2024-12-24 11:33:49 +08:00
zdg db86299523 ppt缩略图生成 2024-12-24 11:32:52 +08:00
zdg c152a3d3ee aippt生成-缩略图 2024-12-24 11:24:53 +08:00
zhangxuelin a366d5d4d6 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-24 11:24:01 +08:00
zhangxuelin e0ec22c3ac 替换照片改为线上url 2024-12-24 11:23:52 +08:00
lyc 9c8872579f Merge pull request 'edit 框架设计' (#173) from lyc-dev into main 2024-12-24 10:21:42 +08:00
lyc 4f32927b49 edit 框架设计 2024-12-24 10:21:13 +08:00
zhangxuelin e132f3927b Merge pull request 'zxl' (#172) from zxl into main
Reviewed-on: #172
2024-12-24 10:07:31 +08:00
朱浩 c290c170d3 报错后及时回转loading按钮 2024-12-24 10:01:34 +08:00
朱浩 3867a9603f 报错后及时回转loading按钮 2024-12-24 09:58:31 +08:00
zhangxuelin f732520d8d Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-24 09:51:31 +08:00
lyc 01fbb9d05c Merge pull request 'lyc-dev' (#171) from lyc-dev into main 2024-12-23 17:02:30 +08:00
lyc 1334becb54 Merge branch 'main' into lyc-dev 2024-12-23 17:01:53 +08:00
lyc c95508ed59 edit 模板 2024-12-23 17:01:40 +08:00
zhangxuelin cf540d73df Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-23 15:26:47 +08:00
baigl 35bf246975 Merge pull request 'baigl' (#170) from baigl into main
Reviewed-on: #170
2024-12-23 14:04:52 +08:00
白了个白 e3c4706880 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-23 14:02:24 +08:00
白了个白 c507331fb5 作业设计:作业类型 tag颜色样式修改 2024-12-23 14:01:04 +08:00
lyc abd7be0698 Merge pull request '模板edit' (#169) from lyc-dev into main 2024-12-23 10:45:55 +08:00
lyc 92e8aa64d7 模板edit 2024-12-23 10:45:27 +08:00
zhangxuelin 83d3cd5df8 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-23 09:41:19 +08:00
朱浩 b1407706a8 永川个性化打包 2024-12-22 12:15:01 +08:00
朱浩 996b4006a2 永川个性化打包 2024-12-22 11:46:10 +08:00
朱浩 4531533382 大模型顺序问题修改 2024-12-21 21:42:07 +08:00
朱浩 a601a9f8dd Merge remote-tracking branch 'origin/main' 2024-12-21 21:40:07 +08:00
zhengdegang 086836e016 Merge pull request '1.pptList 实验室功能' (#168) from zdg_dev into main
Reviewed-on: #168
2024-12-21 12:17:04 +08:00
zdg acb0231304 1.pptList 实验室功能
2.pplList 批量更新缩略图
2024-12-21 12:15:58 +08:00
baigl 94e23c3d69 Merge pull request 'baigl' (#167) from baigl into main
Reviewed-on: #167
2024-12-20 17:19:17 +08:00
白了个白 ea5ed4b6ac Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-20 17:18:32 +08:00
白了个白 6b45fa61dd 1 2024-12-20 17:17:35 +08:00
白了个白 7aa75794dc 科学实验:批改优化 2024-12-20 17:16:57 +08:00
朱浩 a2c2c4eec7 Merge remote-tracking branch 'origin/main' 2024-12-20 17:14:40 +08:00
CYS 4095390f45 Merge pull request '插入素材' (#166) from cys_dev into main
Reviewed-on: #166
2024-12-20 17:12:50 +08:00
cys d75af2636d 插入素材 2024-12-20 17:12:07 +08:00
朱浩 1983dafbba Merge remote-tracking branch 'origin/main' 2024-12-20 16:52:18 +08:00
朱浩 6c7284383a 宫格图片插件 2024-12-20 16:52:02 +08:00
白了个白 f7a3aa6e53 科学实验:学科写死 2024-12-20 16:37:08 +08:00
baigl 28f79f7ab0 Merge pull request 'baigl' (#165) from baigl into main
Reviewed-on: #165
2024-12-20 16:07:29 +08:00
白了个白 1488d7cc03 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-20 16:06:55 +08:00
白了个白 045b44b833 1 2024-12-20 16:06:41 +08:00
baigl b47a3bb63a Merge pull request 'baigl' (#164) from baigl into main
Reviewed-on: #164
2024-12-20 16:02:49 +08:00
白了个白 5e954bab0b Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-20 16:02:07 +08:00
白了个白 7322056dad 1 2024-12-20 16:01:46 +08:00
zhengdegang 18370129df Merge pull request 'zdg_dev' (#163) from zdg_dev into main
Reviewed-on: #163
2024-12-20 16:00:48 +08:00
zdg 0bf94af5a3 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-20 16:00:14 +08:00
zdg 7b042fd001 ppt上课 工具栏 2024-12-20 16:00:09 +08:00
CYS 3bf381f5c5 Merge pull request '插入素材' (#162) from cys_dev into main
Reviewed-on: #162
2024-12-20 15:57:22 +08:00
cys fb67b16258 插入素材 2024-12-20 15:52:22 +08:00
baigl 016e87277b Merge pull request 'baigl' (#161) from baigl into main
Reviewed-on: #161
2024-12-20 15:45:01 +08:00
白了个白 502c772b46 1 2024-12-20 15:42:19 +08:00
白了个白 57cd8ea45a 1 2024-12-20 15:24:27 +08:00
白了个白 8bbd01e2b2 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-20 15:24:02 +08:00
白了个白 622a71253f 作业设计:科学实验 2024-12-20 15:23:34 +08:00
zouyf a2591c60bc Merge pull request '更新菁优网参数' (#160) from zouyf_tmp into main
Reviewed-on: #160
2024-12-20 14:45:40 +08:00
“zouyf” da8d6c4b32 更新菁优网参数 2024-12-20 14:44:59 +08:00
朱浩 795c4d82c7 Merge remote-tracking branch 'origin/main' 2024-12-20 11:18:00 +08:00
朱浩 8e0b9f72d5 宫格图片插件 2024-12-20 11:16:44 +08:00
白了个白 4ad59a8b4e Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-20 11:15:32 +08:00
白了个白 3de2ddb094 作业设计:新增 科学实验作业类型 2024-12-20 11:13:37 +08:00
zouyf 6dbd140f80 Merge pull request '[作业批改] - 增加科学实验的批改(同常规作业)' (#159) from zouyf_tmp into main
Reviewed-on: #159
2024-12-20 11:06:37 +08:00
“zouyf” 7bca2fdc2d [作业批改] - 增加科学实验的批改(同常规作业) 2024-12-20 11:05:22 +08:00
lyc ee96250af2 Merge pull request '隐藏教学工作台' (#158) from lyc-dev into main 2024-12-20 10:57:49 +08:00
lyc 54be3cad4f 隐藏教学工作台 2024-12-20 10:56:55 +08:00
zouyf 90161e0396 Merge pull request '暂屏蔽-[校本题库, 智能推荐, AI设计作业]' (#157) from zouyf_tmp into main
Reviewed-on: #157
2024-12-20 10:27:05 +08:00
“zouyf” 3a6c7dfa47 暂屏蔽-[校本题库, 智能推荐, AI设计作业] 2024-12-20 10:25:30 +08:00
zhengdegang c0e82cd329 Merge pull request 'zdg_dev' (#156) from zdg_dev into main
Reviewed-on: #156
2024-12-20 09:45:45 +08:00
zdg e5667fac2d Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-20 09:44:02 +08:00
zdg f9bb7ed672 去掉 无用代码 2024-12-20 09:43:56 +08:00
朱浩 0ab34e7aa3 退出登录问题 2024-12-20 09:42:28 +08:00
朱浩 7751692061 新增宫格查看插件 2024-12-20 09:39:20 +08:00
朱浩 c69ae97a48 Merge remote-tracking branch 'origin/main' 2024-12-19 18:42:54 +08:00
朱浩 051dbdf4a2 新增宫格查看插件 2024-12-19 18:42:35 +08:00
zhengdegang d9b4b5c886 Merge pull request 'pplist' (#155) from zdg_dev into main
Reviewed-on: #155
2024-12-19 17:34:00 +08:00
zdg bc195e1f51 pplist 2024-12-19 17:33:19 +08:00
yangws ba35677947 Merge pull request 'add:添加实验室;' (#154) from yws_dev into main
Reviewed-on: #154
2024-12-19 17:27:53 +08:00
lyc 4b393ecec9 add:添加实验室; 2024-12-19 17:26:53 +08:00
lyc 2565481306 Merge pull request 'lyc-dev' (#153) from lyc-dev into main 2024-12-19 16:56:31 +08:00
lyc f708e2f741 Merge branch 'main' into lyc-dev 2024-12-19 16:55:52 +08:00
lyc 94276db542 edit 2024-12-19 16:55:37 +08:00
zhengdegang 6ef5d4575e Merge pull request 'zdg_dev' (#152) from zdg_dev into main
Reviewed-on: #152
2024-12-19 16:49:25 +08:00
zdg dfc10aebbe pptlist公屏上课 2024-12-19 16:48:43 +08:00
zdg ff6fab0bcc Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-19 11:14:19 +08:00
zdg 45abab7a41 公屏上课 2024-12-19 11:14:14 +08:00
朱浩 6128f267e1 Merge remote-tracking branch 'origin/main' 2024-12-19 11:07:27 +08:00
朱浩 e0a406c497 新增宫格查看插件 2024-12-19 11:07:12 +08:00
lyc cebf864b82 Merge pull request '模板修改' (#151) from lyc-dev into main 2024-12-18 17:31:44 +08:00
lyc 0ed69394ef 模板修改 2024-12-18 17:31:10 +08:00
lyc 0b6039f9d8 Merge pull request '白板按钮图标不显示' (#150) from lyc-dev into main 2024-12-18 17:17:15 +08:00
lyc 627c70d0e1 白板按钮图标不显示 2024-12-18 17:16:23 +08:00
zhangxuelin 64272467a2 ppist里面不显示导入 2024-12-18 16:08:19 +08:00
zhengdegang 792ab3284f Merge pull request 'zdg_dev' (#149) from zdg_dev into main
Reviewed-on: #149
2024-12-18 16:01:31 +08:00
zdg 38a50d18bf 注释 2024-12-18 16:00:31 +08:00
zdg 6fa0aa6e5f Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-18 15:59:51 +08:00
zdg b0fca4ad9b ppt上课-全屏演讲者
点赞修复
2024-12-18 15:59:45 +08:00
lyc 9381785991 Merge pull request 'lyc-dev' (#148) from lyc-dev into main 2024-12-18 11:17:53 +08:00
lyc 28561b5016 Merge branch 'main' into lyc-dev 2024-12-18 11:17:25 +08:00
lyc 2e2ebbd47f 模板新增 模型选择 2024-12-18 11:17:09 +08:00
baigl 2b5b365e2f Merge pull request 'baigl' (#147) from baigl into main
Reviewed-on: #147
2024-12-18 09:54:29 +08:00
白了个白 90ac7a49c7 1 2024-12-18 09:53:39 +08:00
白了个白 75ddbf6f26 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-18 09:52:08 +08:00
白了个白 de9235751f 作业管理:习题上传后,返回进入 个人题库界面 2024-12-18 09:51:44 +08:00
zhengdegang c33c0d923e Merge pull request 'zdg_dev' (#146) from zdg_dev into main
Reviewed-on: #146
2024-12-17 17:36:33 +08:00
zdg 94fa1a2457 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-17 17:35:57 +08:00
zdg abce344d42 ppt 动画 2024-12-17 17:35:48 +08:00
zdg 0d38a12094 ppt 2024-12-17 17:34:47 +08:00
白了个白 dfd53637be 作业管理:习题上传页面加上返回 2024-12-17 17:20:53 +08:00
白了个白 c01a29bf3f 习题上传:扫描识别url修改 2024-12-17 17:20:30 +08:00
zhangxuelin 436bdfe7c8 Merge pull request 'zxl' (#145) from zxl into main
Reviewed-on: #145
2024-12-17 10:28:58 +08:00
zhangxuelin 2d0be935bf Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-17 10:26:51 +08:00
zhangxuelin d6dfe966c0 pptist 比例修改 2024-12-17 10:26:41 +08:00
zdg cf7f985020 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-16 17:49:09 +08:00
zdg 5d6c946e08 更新 2024-12-16 17:49:04 +08:00
yangws 55efc7f3e1 Merge pull request 'fix:修复头像问题;' (#144) from yws_dev into main
Reviewed-on: #144
2024-12-16 17:06:28 +08:00
小杨 122c5341e9 fix:修复头像问题; 2024-12-16 17:06:01 +08:00
朱浩 c2a4f2c708 Merge remote-tracking branch 'origin/main' 2024-12-16 16:46:22 +08:00
yangws eb18344b6b Merge pull request 'yws_dev' (#143) from yws_dev into main
Reviewed-on: #143
2024-12-16 16:41:13 +08:00
小杨 e080e39e1c Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-16 16:40:43 +08:00
小杨 25f09cb4e0 fix:头像类型修改; 2024-12-16 16:40:33 +08:00
朱浩 3b67c34ccb Merge remote-tracking branch 'origin/main' 2024-12-16 16:20:53 +08:00
朱浩 187c222fe5 大模型页面补全 2024-12-16 16:20:30 +08:00
lyc a255b1af71 Merge pull request 'edit 模板' (#142) from lyc-dev into main 2024-12-16 16:18:02 +08:00
lyc 546c8ad72f edit 模板 2024-12-16 16:17:32 +08:00
zhengdegang e58cb334e4 Merge pull request 'ppt -优化活动列表' (#141) from zdg_dev into main
Reviewed-on: #141
2024-12-16 15:03:14 +08:00
zdg c298e1c0a2 ppt -优化活动列表 2024-12-16 15:01:07 +08:00
朱浩 ed8051d0e0 Merge remote-tracking branch 'origin/main' 2024-12-16 14:24:16 +08:00
朱浩 628055fb58 大模型页面补全 2024-12-16 14:24:00 +08:00
zhengdegang 1698ce30dd Merge pull request '更新ppt活动' (#140) from zdg_dev into main
Reviewed-on: #140
2024-12-16 12:18:57 +08:00
zdg a973d296fe 更新ppt活动 2024-12-16 12:18:19 +08:00
zhangxuelin bf4b857eb7 Merge pull request 'zxl' (#139) from zxl into main
Reviewed-on: #139
2024-12-16 10:19:55 +08:00
zhangxuelin 79839458f0 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-16 10:19:14 +08:00
zhangxuelin d018a28200 添加注册验证码倒计时 2024-12-16 10:19:04 +08:00
zhengdegang 2214c5d805 Merge pull request 'ppt上课 拖动更新排序 动画播放等' (#138) from zdg_dev into main
Reviewed-on: #138
2024-12-16 10:16:14 +08:00
zdg 9ad959dfb7 ppt上课 拖动更新排序 动画播放等 2024-12-16 10:15:08 +08:00
lyc 0b9fce21f3 Merge pull request 'edit' (#137) from lyc-dev into main 2024-12-16 10:06:40 +08:00
lyc 37d0592dcc edit 2024-12-16 10:06:14 +08:00
yangws a421ca94bc Merge pull request 'fix:修改监听活动的方式;' (#136) from yws_dev into main
Reviewed-on: #136
2024-12-16 09:56:18 +08:00
小杨 6eda37c8c9 fix:修改监听活动的方式; 2024-12-16 09:55:48 +08:00
lyc 2a11173874 Merge pull request 'lyc-dev' (#135) from lyc-dev into main 2024-12-16 09:50:59 +08:00
lyc 88869e9a8a Merge branch 'main' into lyc-dev 2024-12-16 09:50:30 +08:00
lyc 08db70a3f2 edit 2024-12-16 09:50:17 +08:00
yangws f9efb76282 Merge pull request 'fix:修改头像是取消头像消失的问题;' (#134) from yws_dev into main
Reviewed-on: #134
2024-12-13 14:01:33 +08:00
小杨 ec3d728896 fix:修改头像是取消头像消失的问题; 2024-12-13 14:01:13 +08:00
yangws cb0ab7f2e9 Merge pull request 'fix:ppt活动类型判断;' (#133) from yws_dev into main
Reviewed-on: #133
2024-12-13 13:48:19 +08:00
小杨 43e9c9f44e fix:ppt活动类型判断; 2024-12-13 13:47:56 +08:00
yangws 809c639ce9 Merge pull request 'fix:头像问题;' (#132) from yws_dev into main
Reviewed-on: #132
2024-12-13 13:46:49 +08:00
小杨 243561dfd8 fix:头像问题; 2024-12-13 13:45:06 +08:00
zhengdegang e5bc693f66 Merge pull request 'zdg_dev' (#131) from zdg_dev into main
Reviewed-on: #131
2024-12-13 13:18:09 +08:00
zdg 29888b0674 ppt播放 已经翻页异常情况 2024-12-13 13:17:18 +08:00
zdg 645f9ab120 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-13 13:16:31 +08:00
朱浩 74767b44df Merge remote-tracking branch 'origin/main' 2024-12-13 13:01:34 +08:00
朱浩 ef4ec36bbe 大模型页面补全 2024-12-13 13:00:02 +08:00
zdg c4a15e9c53 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-13 12:46:51 +08:00
yangws 15b9f38810 Merge pull request 'yws_dev' (#130) from yws_dev into main
Reviewed-on: #130
2024-12-13 11:19:23 +08:00
小杨 a34883abbb Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-13 11:18:48 +08:00
小杨 8f8016cb0b fix:添加默认头像以及pptlist的bug; 2024-12-13 11:18:33 +08:00
lyc a3079a87d0 edit 模板 2024-12-13 10:58:01 +08:00
lyc e43573bae2 Merge pull request 'edit' (#129) from lyc-dev into main 2024-12-13 10:34:04 +08:00
lyc 83d885bcbe 查询模板增加user 2024-12-13 10:33:35 +08:00
lyc b5669a97f1 模板查询增加user 2024-12-13 10:28:42 +08:00
lyc be2afadd43 edit 2024-12-13 10:10:36 +08:00
朱浩 a62dde1b93 Merge remote-tracking branch 'origin/main' 2024-12-13 10:06:23 +08:00
lyc b4864bc109 Merge pull request 'lyc-dev' (#128) from lyc-dev into main 2024-12-13 09:47:56 +08:00
lyc a379176496 edti 插入素材 2024-12-13 09:47:15 +08:00
zdg 6a9392d498 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-12 17:33:47 +08:00
zdg e8ecc27441 全屏 2024-12-12 17:33:39 +08:00
zhengdegang c2d5f2e99a Merge pull request 'zdg_dev' (#127) from zdg_dev into main
Reviewed-on: #127
2024-12-12 17:29:42 +08:00
lyc eaa14b1666 Merge branch 'main' into lyc-dev 2024-12-12 17:14:55 +08:00
lyc 99c26145d8 edit 2024-12-12 17:14:39 +08:00
zdg e7990dc175 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-12 17:02:31 +08:00
zdg 31de0da3c5 优化 2024-12-12 17:02:22 +08:00
朱浩 82548ce937 Merge remote-tracking branch 'origin/main' 2024-12-12 16:55:19 +08:00
朱浩 aa3be39721 研学相关权限问题 2024-12-12 16:55:08 +08:00
baigl c3b975fee9 Merge pull request 'baigl' (#126) from baigl into main
Reviewed-on: #126
2024-12-12 16:45:29 +08:00
白了个白 ea2a8ebfda Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-12 16:44:36 +08:00
白了个白 308465adb4 ppts: 64转file 提取到utils image中 2024-12-12 16:44:03 +08:00
yangws 469116701b Merge pull request 'fix:修改pptlist活动的bug;' (#125) from yws_dev into main
Reviewed-on: #125
2024-12-12 16:39:46 +08:00
小杨 7861727e92 fix:修改pptlist活动的bug; 2024-12-12 16:39:01 +08:00
zdg 80b66ffb28 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-12 16:38:44 +08:00
zhangxuelin c5e71e6e7b Merge pull request '修改作业弹窗' (#122) from zxl into main
Reviewed-on: #122
2024-12-12 16:38:33 +08:00
zhangxuelin c7ebc6de65 1 2024-12-12 16:37:51 +08:00
zhangxuelin b906a1e688 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-12 16:37:22 +08:00
baigl f75ab621d2 Merge pull request 'baigl' (#124) from baigl into main
Reviewed-on: #124
2024-12-12 16:35:47 +08:00
白了个白 a711eb2745 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-12 16:35:04 +08:00
白了个白 363b284b7c 1 2024-12-12 16:34:47 +08:00
zdg 75b5884210 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-12 16:24:53 +08:00
zdg 03f160464c ppt上课 2024-12-12 16:24:47 +08:00
baigl dc4c0fd334 Merge pull request 'baigl' (#123) from baigl into main
Reviewed-on: #123
2024-12-12 16:24:36 +08:00
白了个白 fb3ffd5dda Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-12 16:23:51 +08:00
白了个白 5771533039 ppts: 插入试题 图片路径存储修改 2024-12-12 16:23:28 +08:00
zhangxuelin 75ea11e171 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-12 16:15:55 +08:00
zhangxuelin 851a39897b 修改作业弹窗 2024-12-12 16:15:16 +08:00
zhangxuelin 69bfc68b28 Merge pull request '作业弹窗' (#121) from zxl into main
Reviewed-on: #121
2024-12-12 16:12:53 +08:00
zhangxuelin 590b318757 作业弹窗 2024-12-12 16:12:32 +08:00
CYS 4421ee7fea Merge pull request '政治课标dataset_id修改' (#120) from cys_dev into main
Reviewed-on: #120
2024-12-12 16:05:55 +08:00
cys 19ef95447c 1 2024-12-12 16:04:43 +08:00
baigl 25b6da7f26 Merge pull request '教程分析:pdf页面展示修改' (#119) from baigl into main
Reviewed-on: #119
2024-12-12 15:27:00 +08:00
白了个白 892b4ee3b0 教程分析:pdf页面展示修改 2024-12-12 15:26:14 +08:00
zdg 354e0b44c9 生产环境地址 2024-12-12 12:27:01 +08:00
zdg 5e04a4c2c2 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-12 12:21:53 +08:00
zdg ca93eb72ac 更新消息 2024-12-12 12:21:47 +08:00
yangws 3d8f7c2a3a Merge pull request 'fix:修改pptlist添加活动的bug;' (#118) from yws_dev into main
Reviewed-on: #118
2024-12-12 11:27:19 +08:00
小杨 78cc5edd39 fix:修改pptlist添加活动的bug; 2024-12-12 11:26:26 +08:00
朱浩 9f4e232d6f Merge remote-tracking branch 'origin/main' 2024-12-12 11:20:12 +08:00
朱浩 afc9fe89d1 解决按钮层级问题 2024-12-12 11:19:38 +08:00
朱浩 894ed269ce 解决生产无法关闭PPTIST页面问题 2024-12-12 11:17:12 +08:00
zdg b15fa29ffa Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-12 11:02:22 +08:00
zdg 9ef4812acb Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-12 10:31:00 +08:00
zdg d313f901a3 去掉注释 2024-12-11 17:32:11 +08:00
zdg 8990ffdc58 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-11 17:17:01 +08:00
zdg 13b1a895a4 上课 socket 2024-12-11 17:16:55 +08:00
朱浩 9c7aeb1a27 Merge remote-tracking branch 'origin/main' 2024-12-11 17:11:22 +08:00
朱浩 a9faeea3fd Merge remote-tracking branch 'origin/main' 2024-12-11 17:03:04 +08:00
朱浩 5493de7a61 版本升级 2024-12-11 17:02:44 +08:00
朱浩 02ab1ffa8e 霞客 2024-12-11 16:42:46 +08:00
朱浩 10e76add35 霞客 2024-12-11 16:35:52 +08:00
119 changed files with 6223 additions and 1500 deletions

View File

@ -17,6 +17,7 @@ VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktx
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/' VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'
# websocket 地址 # websocket 地址
# VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
VITE_APP_WS_URL = 'wss://file.ysaix.com:7868' VITE_APP_WS_URL = 'wss://file.ysaix.com:7868'
# VITE_APP_WS_URL = 'ws://192.168.2.16:7865' # VITE_APP_WS_URL = 'ws://192.168.2.16:7865'

View File

@ -19,7 +19,7 @@ VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktx
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/' VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
# websocket 地址 # websocket 地址
VITE_APP_WS_URL = 'wss://file.ysaix.com:7868' VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
# 是否显示开发工具 # 是否显示开发工具
VITE_SHOW_DEV_TOOLS = 'false' VITE_SHOW_DEV_TOOLS = 'false'

25
.env.yc Normal file
View File

@ -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'

25
.env.yc2 Normal file
View File

@ -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'

54
electron-builder-yc.yml Normal file
View File

@ -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:
- '**/*'

54
electron-builder-yc2.yml Normal file
View File

@ -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:
- '**/*'

View File

@ -33,6 +33,7 @@ export default defineConfig({
proxy: { proxy: {
'/dev-api': { '/dev-api': {
target: 'http://27.128.240.72:7865', target: 'http://27.128.240.72:7865',
// target: 'https://prev.ysaix.com:7868/prod-api/',
// target: 'http://36.134.181.164:7863', // target: 'http://36.134.181.164:7863',
// target: 'http://192.168.0.102:7865', // target: 'http://192.168.0.102:7865',
changeOrigin: true, changeOrigin: true,

View File

@ -1,6 +1,6 @@
{ {
"name": "aix-win-ws", "name": "aix-win-ws",
"version": "2.5.4", "version": "2.5.9",
"description": "", "description": "",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "上海交大重庆人工智能研究院", "author": "上海交大重庆人工智能研究院",
@ -16,6 +16,8 @@
"build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml", "build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
"build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml", "build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml",
"build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml", "build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml",
"build:yc": "electron-vite build --mode yc && electron-builder --win --config ./electron-builder-yc.yml",
"build:yc2": "electron-vite build --mode yc2 && electron-builder --win --config ./electron-builder-yc2.yml",
"build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml", "build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml",
"build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml", "build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml",
"build:linux": "npm run build && electron-builder --linux" "build:linux": "npm run build && electron-builder --linux"
@ -92,6 +94,8 @@
"tinycolor2": "^1.6.0", "tinycolor2": "^1.6.0",
"tinymce": "6.8.3", "tinymce": "6.8.3",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"v-viewer": "^3.0.11",
"viewerjs": "^1.11.7",
"vite-plugin-electron": "^0.28.8", "vite-plugin-electron": "^0.28.8",
"vue": "^3.4.34", "vue": "^3.4.34",
"vue-cropper": "1.0.3", "vue-cropper": "1.0.3",

BIN
resources/yc-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -7,6 +7,7 @@ import Logger from './logger' // 日志封装
import chat from './chat' // chat封装 import chat from './chat' // chat封装
import Store from './store' // Store封装 import Store from './store' // Store封装
import updateInit from './update' import updateInit from './update'
// 代理 electron/remote // 代理 electron/remote
// 第一步引入remote // 第一步引入remote
import remote from '@electron/remote/main' import remote from '@electron/remote/main'
@ -41,19 +42,19 @@ if(!gotTheLock){
} }
}) })
} }
let logoIco = import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?'../../resources/yc-logo.png':'../../resources/logo2.ico'
//登录窗口 //登录窗口
function createLoginWindow() { function createLoginWindow() {
if (loginWindow) return if (loginWindow) return
loginWindow = new BrowserWindow({ loginWindow = new BrowserWindow({
width: 888, width: import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?1060:888,
height: 520, height: 520,
show: false, show: false,
frame: false, frame: false,
autoHideMenuBar: true, autoHideMenuBar: true,
maximizable: false, maximizable: false,
resizable: false, resizable: false,
icon: join(__dirname, '../../resources/logo2.ico'), icon: join(__dirname, logoIco),
...(process.platform === 'linux' ? { icon } : {}), ...(process.platform === 'linux' ? { icon } : {}),
webPreferences: { webPreferences: {
defaultEncoding: 'utf-8', defaultEncoding: 'utf-8',
@ -95,7 +96,7 @@ function createMainWindow() {
frame: false, // 无边框 frame: false, // 无边框
autoHideMenuBar: true, autoHideMenuBar: true,
maximizable: false, maximizable: false,
icon: join(__dirname, '../../resources/logo2.ico'), icon: join(__dirname, logoIco),
...(process.platform === 'linux' ? { icon } : {}), ...(process.platform === 'linux' ? { icon } : {}),
webPreferences: { webPreferences: {
defaultEncoding: 'utf-8', defaultEncoding: 'utf-8',

View File

@ -8,7 +8,7 @@
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/> --> /> -->
<meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; default-src 'self' 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;" /> <meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; frame-src 'self' *; default-src 'self' https://wzyzoss.eos-chongqing-3.cmecloud.cn/; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net;" />
</head> </head>

View File

@ -13097,15 +13097,15 @@ const PDFViewerApplication = {
} }
if (isValidSpreadMode(spread)) { if (isValidSpreadMode(spread)) {
//默认双页 //默认双页
// this.pdfViewer.spreadMode = spread; this.pdfViewer.spreadMode = spread;
this.pdfViewer.spreadMode = 1; // this.pdfViewer.spreadMode = 1;
} }
}; };
this.isInitialViewSet = true; this.isInitialViewSet = true;
this.pdfSidebar?.setInitialView(sidebarView); this.pdfSidebar?.setInitialView(sidebarView);
//默认双页 //默认双页
// setViewerModes(scrollMode, spreadMode); setViewerModes(scrollMode, spreadMode);
setViewerModes(scrollMode, 1); // setViewerModes(scrollMode, 1);
if (this.initialBookmark) { if (this.initialBookmark) {
setRotation(this.initialRotation); setRotation(this.initialRotation);
delete this.initialRotation; delete this.initialRotation;

View File

@ -28,8 +28,9 @@ import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api
import { PPTApi } from './api' import { PPTApi } from './api'
import { sessionStore } from '@/utils/store' // electron-store import { sessionStore } from '@/utils/store' // electron-store
import './api/watcher' // import watcher from './api/watcher' //
import emitter from '@/utils/mitt' //mitt 线
watcher() //
const loading = ref(true) const loading = ref(true)
const _isPC = isPC() const _isPC = isPC()
@ -39,8 +40,8 @@ const slidesStore = useSlidesStore()
const { databaseId } = storeToRefs(mainStore) const { databaseId } = storeToRefs(mainStore)
const { screening } = storeToRefs(useScreenStore()) const { screening } = storeToRefs(useScreenStore())
if (import.meta.env.MODE !== 'development') { if (import.meta.env.MODE === 'development') {
window.onbeforeunload = () => false // window.onbeforeunload = () => false
} }
onMounted(async () => { onMounted(async () => {

View File

@ -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,
}
}

View File

@ -7,36 +7,60 @@ import { sessionStore } from '@/utils/store' // electron-store 状态管理
import * as useStore from '../store' // pptist-状态管理 import * as useStore from '../store' // pptist-状态管理
import ChatWs from '@/plugins/socket' // 聊天socket import ChatWs from '@/plugins/socket' // 聊天socket
import msgUtils from '@/plugins/modal' // 消息工具 import msgUtils from '@/plugins/modal' // 消息工具
import emitter from '@/utils/mitt' //mitt 事件总线
import { nextTick } from 'vue'
const slidesStore = useStore.useSlidesStore() // 幻灯片-状态管理
const screenStore = useStore.useScreenStore() // 全屏-状态管理 const screenStore = useStore.useScreenStore() // 全屏-状态管理
const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理 const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息 const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
const isPublic = sessionStore.get('curr.isPublic') // 是否公屏开课
export class Classcourse { export class Classcourse {
msgObj:ElMessageBox = null // 提示消息对象 msgObj:ElMessageBox = null // 提示消息对象
classcourse:any = null // 课堂信息
id: number|string = null // 课堂id
constructor() { constructor() {
this.load() this.load()
} }
// 延时
sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
/** /**
* @description * @description
*/ */
load() { async load() {
console.log('classcourse-load', classcourse)
// 打开全屏 // 打开全屏
screenStore.setScreening(!!classcourse) const isCourse = !!classcourse
screenStore.setScreening(isCourse)
// 如果课堂信息有值则连接socket // 如果课堂信息有值则连接socket
if (!!classcourse) { if (isCourse) {
// 连接socket // 连接socket
if (!ChatWs.ws) ChatWs.init()
ChatWs.id = classcourse.timgroupid // 群组id ChatWs.id = classcourse.timgroupid // 群组id
console.log('ws- ', classcourse) 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) classcourseStore.setClasscourse(classcourse)
// 待上课提示 // 待上课提示
if (!classcourse.status) { if (!classcourse.status) {
this.msgObj = { this.msgObj = {
type: 'success', type: 'success',
title: '系统提示', title: '系统提示',
message: '公屏课堂已准备完毕,请等待老师开启课堂!', message: '公屏课堂已准备完毕,等待老师开启课堂...',
center: true, center: true,
showClose: false, showClose: false,
showCancelButton: false, showCancelButton: false,

View File

@ -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;
}
}

View File

@ -3,16 +3,18 @@
* @author zdg * @author zdg
* @date 2024-11-26 * @date 2024-11-26
*/ */
import { toRaw } from 'vue' import { toRaw, nextTick } from 'vue'
import type { Result } from './types' // 接口类型 import type { Result } from './types' // 接口类型
import msgUtils from '@/plugins/modal' // 消息工具 import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
import * as API_smarttalk from '@/api/file' // 相关api import * as API_smarttalk from '@/api/file' // 相关api
import * as API_classwork from '@/api/teaching/classwork' // 相关api
import * as useStore from '../store' // pptist-状态管理 import * as useStore from '../store' // pptist-状态管理
import { sessionStore } from '@/utils/store' // electron-store 状态管理 import { sessionStore } from '@/utils/store' // electron-store 状态管理
import useUserStore from '@/store/modules/user' // 外部-用户信息 import useUserStore from '@/store/modules/user' // 外部-用户信息
import * as Api_server from '@/api/apiService' // 相关api import * as Api_server from '@/api/apiService' // 相关api
import * as commUtils from '@/utils/comm.js' import * as commUtils from '@/utils/comm.js' // 工具
import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
const slidesStore = useStore.useSlidesStore() const slidesStore = useStore.useSlidesStore()
const userStore = useUserStore() const userStore = useUserStore()
@ -48,6 +50,8 @@ export class Utils {
}, delay) }, delay)
} }
} }
// 延时
static sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
} }
/** ppt相关后端接口处理 */ /** ppt相关后端接口处理 */
@ -55,31 +59,39 @@ export class PPTApi {
// 变量 // 变量
static isUpdate = true // 是否更新数据 static isUpdate = true // 是否更新数据
// 获取所有幻灯片列表 // 获取所有幻灯片列表 isUpdate为true不更新
static getSlideList(parentid: (Number | String)): Promise<Boolean> { static getSlideList(parentid: (Number | String),isUpdate?:Boolean): Promise<Boolean> {
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 } const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params) const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
if (res.code === 200) { if (res.code === 200) {
const slides = (res.rows || []).map(o => { if(!isUpdate){
if (!!o.datacontent) { const slides = (res.rows || []).map(o => {
const json = JSON.parse(o.datacontent) if (!!o.datacontent) {
!!json && (json.id = o.id) const json = JSON.parse(o.datacontent)
return json !!json && (json.id = o.id)
} return json
// 如果没有数据,默认空白页 }
return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}} // 如果没有数据,默认空白页
}) return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}}
})
// slidesStore.updateSlideIndex(0) // 下标0 为第一页
slidesStore.setSlides(slides) // 写入数据
}
// 活动列表处理 // 活动列表处理
const workList = (res.rows || []).map(o => o.activityContent) // const workList = (res.rows || []).map(o => o.activityContent)
const workItem = [...res.rows] const workItem = res.rows ? [...res.rows] : []
// 加入活动后刷新ppt数据内容不跟换为第一页
// slidesStore.updateSlideIndex(0) // 下标0 为第一页
slidesStore.setSlides(slides) // 写入数据
// 写入作业列表数据
slidesStore.setWorkList(workList)
// 获取所有的pptlist的数据 // 获取所有的pptlist的数据
slidesStore.setWorkItem(workItem) slidesStore.setWorkItem(workItem)
// 没有上课时调用-作业列表
if(!classcourse) this.updateWorkList()
// 没有上课时调用-批量更新缩略图
if(!classcourse) {
Utils.sleep(1500).then(() => {
this.batchUpdateThumUrl()
})
}
resolve(true) resolve(true)
} else msgUtils.msgError(res.msg || '获取数据失败');resolve(false) } else msgUtils.msgError(res.msg || '获取数据失败');resolve(false)
}) })
@ -132,24 +144,35 @@ export class PPTApi {
const currentSlide = toRaw(slidesStore.currentSlide) const currentSlide = toRaw(slidesStore.currentSlide)
const isAdd = !/^\d+$/.test(currentSlide.id) // 是否新增 const isAdd = !/^\d+$/.test(currentSlide.id) // 是否新增
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 为非数字,说明是新增的幻灯片) if (isAdd) { // 新增的幻灯片(id 为非数字,说明是新增的幻灯片)
const bool = await this.addSlide(currentSlide) 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 { // 防抖-更新 } else { // 防抖-更新
if (!this.isUpdate) return this.isUpdate = true // 下次更新数据 if (!this.isUpdate) return this.isUpdate = true // 下次更新数据
const params = { if (isBatch) { // 批量更新-排序
id: currentSlide.id, this.batchUpdateSlides(newData, true)
datacontent: JSON.stringify(currentSlide), } else { // 更新当前页幻灯片
const params = {
id: currentSlide.id,
datacontent: JSON.stringify(currentSlide),
}
Utils.mxThrottle(() => {this.updateSlide(params)}, 200, 2)
} }
Utils.mxThrottle(() => {this.updateSlide(params)}, 200, 2)
} }
} }
// 更新幻灯片 // 更新幻灯片 isThum 是否更新缩略图
static updateSlide(data: object): Promise<Boolean> { static updateSlide(data: object, isThum = true): Promise<Boolean> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (isThum) { // 更新缩略图
const thumUrl = await this.getSlideThumUrl()
data.base64Code = thumUrl // 更新缩略图
}
const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data) const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data)
console.log(data,'data');
console.log(res,'dresata');
if (res.code === 200) { if (res.code === 200) {
resolve(true) resolve(true)
} else msgUtils.msgError(res.msg || '更新失败');resolve(false) } else msgUtils.msgError(res.msg || '更新失败');resolve(false)
@ -192,21 +215,77 @@ export class PPTApi {
}) })
} }
// 更新-活动列表
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) => { static toRousrceUrl =async (o:any) => {
const formData = new FormData() const formData = new FormData()
formData.append('file', o) formData.append('file', o)
formData.append('ral', true)
const res = await Api_server.Other.uploadFile(formData) const res = await Api_server.Other.uploadFile(formData)
if (res && res.code == 200){ if (res && res.code == 200){
const url = res?.url const url = res?.url
url &&(o.src = url) url &&(o.src = url)
return url return url
} }
}
}
} }
export class Homework{ export class Homework{
static win: null // 作业弹窗
// 作业弹窗 // 作业弹窗
static async showHomework(id: any) { static async showHomework(id: any) {
let result = await getClassWorkList(id) let result = await getClassWorkList(id)
@ -214,7 +293,14 @@ export class Homework{
  localStorage.setItem('teachClassWorkItem', JSON.stringify(result[0]));   localStorage.setItem('teachClassWorkItem', JSON.stringify(result[0]));
  toolStore.isTaskWin=true; // 设置打开批改窗口   toolStore.isTaskWin=true; // 设置打开批改窗口
//   emit('closeActive') //   emit('closeActive')
  createWindow('open-taskwin',{url:'/teachClassTask'}); // 重复打开,先关闭弹窗
// if (this.win) this.win?.close?.()
this.win = await createWindow('open-taskwin',{url:'/teachClassTask'})
 return this.win;
}
static closeHomework() {
if (this.win) this.win?.close?.()
this.win = null
} }
} }
export default PPTApi export default PPTApi

View File

@ -7,13 +7,13 @@ import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
export default class { export default class {
// 删除幻灯片 // 删除幻灯片
static delSlide(id: string): Promise<Boolean> { static delSlide(id: string): Promise<Boolean> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const res: Result = await API_entpcoursefile.delEntpcoursefile(id) const res: Result = await API_entpcoursefile.delEntpcoursefile(id)
if (res.code === 200) { if (res.code === 200) {
resolve(true) resolve(true)
} else msgUtils.msgError(res.msg || '删除失败');resolve(false) } else msgUtils.msgError(res.msg || '删除失败');resolve(false)
}) })
} }
} }

View File

@ -106,6 +106,8 @@ export class MsgEnum {
MSG_anmationclick : 'anmationclick', MSG_anmationclick : 'anmationclick',
/** @desc: 群组创建成功 */ /** @desc: 群组创建成功 */
MSG_classcourseopen : 'classcourseopen', MSG_classcourseopen : 'classcourseopen',
/** @desc: 学生提交作业 */
MSG_finishHomework : 'finishHomework',
/** @desc: 学生的测练结果反馈 */ /** @desc: 学生的测练结果反馈 */
MSG_classquizfeedback : 'classquizfeedback', MSG_classquizfeedback : 'classquizfeedback',
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */ /** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
@ -120,6 +122,18 @@ export class MsgEnum {
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate', MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
/** @desc: 课堂讲授活动,选择不同的内容 */ /** @desc: 课堂讲授活动,选择不同的内容 */
MSG_classlecturePagesrc : 'classlecturePagesrc', MSG_classlecturePagesrc : 'classlecturePagesrc',
/** @desc: 课堂作业|活动 */
MSG_homework : 'HOMEWORK',
/** @desc: 公屏 - 课堂作业|活动 */
MSG_pushSreen_work : 'pushSreen_work',
/** @desc: 公屏 - 实验 */
MSG_pushSreen_experiment : 'pushSreen_experiment',
/** @desc: 点赞 */
MSG_dz : 'dz',
/** @desc: 疑惑 */
MSG_yh : 'yh',
/** @desc: 推图片上屏 */
MSG_pushSreen_ImgList : 'pushSreen_ImgList',
// === 新定义-消息头 === // === 新定义-消息头 ===
/** @desc: 课程创建-待开课 */ /** @desc: 课程创建-待开课 */
MSG_0000: 0x0000, MSG_0000: 0x0000,

View File

@ -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)
}
}

View File

@ -10,85 +10,152 @@ import { MsgEnum } from './types' // 消息枚举
import ChatWs from '@/plugins/socket' // 聊天socket import ChatWs from '@/plugins/socket' // 聊天socket
import Classcourse from './classcourse' // 课程相关 import Classcourse from './classcourse' // 课程相关
import msgUtils from '@/plugins/modal' // 消息工具 import msgUtils from '@/plugins/modal' // 消息工具
const slidesStore = store.useSlidesStore() import * as dialogUtils from '@/utils/dialog' // 弹窗-函数
const classcourseStore = store.useClasscourseStore() // 课堂信息-状态管理 import { Homework } from './index' // api-作业相关
const resource = sessionStore.get('curr.resource') // apt 资源 import emitter from '@/utils/mitt' //mitt 事件总线
const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源 import useExecPlay from '../views/Screen/hooks/useExecPlay' // 播放控制
import hooksUpvote from './upvote' // 点赞-工具
import gridPic from './gridPic' // 上屏-工具
/** /**
* @description * @description
*/ */
// 监听幻灯片内容变化 export default () => {
watch(() => slidesStore.slides, (newVal, oldVal) => { const slidesStore = store.useSlidesStore()
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容 const classcourseStore = store.useClasscourseStore() // 课堂信息-状态管理
},{ deep: true }) const resource = sessionStore.get('curr.resource') // apt 资源
const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源
// 监听标题变化 const { execNext, turnPrevSlide } = useExecPlay(false) // 不加载钩子
watch(() => slidesStore.title, (newVal, oldVal) => { // 监听幻灯片内容变化
if (oldVal == '未命名演示文稿') return // 初始加载,不需要更新数据 watch(() => slidesStore.slides, (newVal, oldVal) => {
updatePPT({title: newVal}) PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
}) },{ deep: true })
// 消息监听ws // 监听标题变化
console.log('监听器已开启', ChatWs) watch(() => slidesStore.title, (newVal, oldVal) => {
if (ChatWs.ws) { if (oldVal == '未命名演示文稿') return // 初始加载,不需要更新数据
ChatWs.watch((msg, e) => { updatePPT({title: newVal})
try { })
handleMessage(JSON.parse(msg))
} catch (error) { // 监听幻灯片下标变化
console.error('socket 解析异常 ', error, e) watch(() => slidesStore.slideIndex, (newVal, oldVal) => {
handleMessage(msg) 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)
}) })
}
// 更新ppt内容 // 消息监听ws
const updatePPT = async (data) => { // console.log('监听器已开启', ChatWs)
if (!resource) return if (!!ChatWs.ws) {
data.id = resource.id ChatWs.watch((data, e) => {
await PPTApi.updateSlide(data) // 更新ppt内容 try {
sessionStore.set('curr.resource.title', data.title) handleMessage(JSON.parse(data)?.msg)
// 更新smarttalk内容 } catch (error) {
if (!!smarttalk && !!data.title) { console.error('socket 解析异常 ', error, e)
const {id, fileFlag} = smarttalk handleMessage(data)
const params = { id, fileShowName: `${data.title}.${fileFlag}` } }
await PPTApi.updateSmarttalk(params) // 更新ppt内容 })
sessionStore.set('curr.smarttalk.fileShowName', params.fileShowName)
} }
}
// 更新ppt内容
// ws消息处理 const updatePPT = async (data) => {
const handleMessage = (msg) => { if (!resource) return
if (typeof msg === 'object'){ data.id = resource.id
const { head, content, ...other } = msg await PPTApi.updateSlide(data) // 更新ppt内容
switch (head) { sessionStore.set('curr.resource.title', data.title)
case MsgEnum.HEADS.MSG_open: // 开课 // 更新smarttalk内容
// 课堂信息不一致 if (!!smarttalk && !!data.title) {
if (Classcourse.id !== content.id) { const {id, fileFlag} = smarttalk
msgUtils.alertError('老师开课信息异常,请重新进入公屏!') const params = { id, fileShowName: `${data.title}.${fileFlag}` }
.then(() => { // 点击确定按钮,关闭窗口 await PPTApi.updateSmarttalk(params) // 更新ppt内容
window.close() sessionStore.set('curr.smarttalk.fileShowName', params.fileShowName)
})
} else { // 正常更新数据
classcourseStore.classcourse.status = 'open'
// 更新课堂信息-关闭警告框
Classcourse?.msgObj?.onVanish()
}
break
case MsgEnum.HEADS.MSG_slideFlapping: // 幻灯片翻页
const slideIndex = content.current
slidesStore.updateSlideIndex(slideIndex) // 更新幻灯片下标
break
case MsgEnum.HEADS.MSG_closed: // 下课:
window.close() // 关闭窗口
break
default:
break
} }
} }
}
// console.log('监听器已开启', Classcourse) // ws消息处理
// setTimeout(() => { const handleMessage = (msg) => {
// console.log('关闭弹窗') console.log('ws消息处理', msg)
// // Classcourse.msgObj?.close() if (typeof msg === 'object'){
// Classcourse?.msgObj?.onVanish() const { head, content, ...other } = msg
// }, 10 * 1000) 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)
// 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)
}

View File

@ -1,5 +1,7 @@
import { useScreenStore, useSlidesStore, useClasscourseStore } from '../store' import { useScreenStore, useSlidesStore, useClasscourseStore } from '../store'
import { enterFullscreen, exitFullscreen, isFullscreen } from '../utils/fullscreen' import { enterFullscreen, exitFullscreen, isFullscreen } from '../utils/fullscreen'
import { sessionStore } from '@/utils/store' // electron-store 状态管理
import ChatWs from '@/plugins/socket' // 聊天socket
export default () => { export default () => {
const screenStore = useScreenStore() const screenStore = useScreenStore()
@ -23,7 +25,13 @@ export default () => {
const classcourse = classcourseStore.classcourse const classcourse = classcourseStore.classcourse
if (!!classcourse) { //DOTO 有课堂,执行退相关操作 if (!!classcourse) { //DOTO 有课堂,执行退相关操作
console.log('退出放映状态') console.log('退出放映状态')
window.close() ChatWs?.close() // 关闭ws
sessionStore.delete('curr.classcourse') // 清除课堂信息
sessionStore.delete('curr.resource') // 清除课件信息
sessionStore.delete('curr.isPublic') // 清除公屏状态
setTimeout(() => {
window.close() // 关闭窗口
}, 1000)
} else screenStore.setScreening(false) } else screenStore.setScreening(false)
if (isFullscreen()) exitFullscreen() if (isFullscreen()) exitFullscreen()
} }

View File

@ -3,16 +3,21 @@ import type { Classcourse } from '../api/types'
export interface ClasscourseState { export interface ClasscourseState {
classcourse: Classcourse | any, // 课堂信息 classcourse: Classcourse | any, // 课堂信息
isEmit: boolean, // 是否加载监听事件(动画播放)
} }
export const useClasscourseStore = defineStore('classcourse', { export const useClasscourseStore = defineStore('classcourse', {
state: (): ClasscourseState => ({ state: (): ClasscourseState => ({
classcourse: null, // 课堂信息 classcourse: null, // 课堂信息
isEmit: false, // 是否加载监听事件(动画播放)
}), }),
actions: { actions: {
setClasscourse(classcourse: Classcourse) { setClasscourse(classcourse: Classcourse) {
this.classcourse = classcourse this.classcourse = classcourse
}, },
setIsEmit(isEmit: boolean) {
this.isEmit = isEmit
},
}, },
}) })

View File

@ -33,7 +33,8 @@ export interface SlidesState {
slides: Slide[] slides: Slide[]
slideIndex: number slideIndex: number
viewportSize: number viewportSize: number
viewportRatio: number viewportRatio: number,
animationIndex: number, // 不是从0开始
workList:Object[], workList:Object[],
workItem:Object[], workItem:Object[],
} }
@ -46,6 +47,7 @@ export const useSlidesStore = defineStore('slides', {
slideIndex: 0, // 当前页面索引 slideIndex: 0, // 当前页面索引
viewportSize: 1000, // 可视区域宽度基数 viewportSize: 1000, // 可视区域宽度基数
viewportRatio: 0.5625, // 可视区域比例默认16:9 viewportRatio: 0.5625, // 可视区域比例默认16:9
animationIndex: 0, // 不是从0开始
workList:[],// 活动的列表 workList:[],// 活动的列表
workItem:[],// 获取到的所有pptlist workItem:[],// 获取到的所有pptlist
}), }),
@ -145,48 +147,16 @@ export const useSlidesStore = defineStore('slides', {
setWorkItem(list: Object[]) { setWorkItem(list: Object[]) {
this.workItem = list this.workItem = list
}, },
addWorkItem(data: any) {
const id = data?.parentid
if (!id) return
this.workItem.splice(this.slideIndex, 0, data)
},
addSlide(slide: Slide | Slide[]) { addSlide(slide: Slide | Slide[]) {
const resource = sessionStore.get('curr.resource')||{}
const enpt = sessionStore.get('curr.entp')||{}
const {id, ...content} = slide
const params = {
parentid: resource.id,
activityContent:null,
entpid: userStore.user.deptId,
entpcourseid: enpt.id,
ppttype: 'file',
title: '',
fileurl: '',
filetype: 'slide',
datacontent: JSON.stringify(content),
filekey: '',
filetag: '',
fileidx: 0,
dflag: 0,
status: '',
edituserid: userStore.id,
}
const slides = Array.isArray(slide) ? slide : [slide] const slides = Array.isArray(slide) ? slide : [slide]
for (const slide of slides) { for (const slide of slides) {
if (slide.sectionTag) delete slide.sectionTag if (slide.sectionTag) delete slide.sectionTag
} }
API_entpcoursefile.addEntpcoursefileReturnId(params).then((rid:number) => { const addIndex = this.slideIndex + 1
if(!!rid){ this.slides.splice(addIndex, 0, ...slides)
params.id = rid this.slideIndex = addIndex
this.addWorkItem(params)
const addIndex = this.slideIndex + 1
this.slides.splice(addIndex, 0, ...slides)
this.slideIndex = addIndex
}
})
}, },
updateSlide(props: Partial<Slide>, slideId?: string) { updateSlide(props: Partial<Slide>, slideId?: string) {
const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex
this.slides[slideIndex] = { ...this.slides[slideIndex], ...props } this.slides[slideIndex] = { ...this.slides[slideIndex], ...props }
@ -238,6 +208,9 @@ export const useSlidesStore = defineStore('slides', {
updateSlideIndex(index: number) { updateSlideIndex(index: number) {
this.slideIndex = index this.slideIndex = index
}, },
updateAnimationIndex(index: number) {
this.animationIndex = index
},
addElement(element: PPTElement | PPTElement[]) { addElement(element: PPTElement | PPTElement[]) {
const elements = Array.isArray(element) ? element : [element] const elements = Array.isArray(element) ? element : [element]

View File

@ -72,4 +72,55 @@ export const isSVGString = (text: string): boolean => {
export const svg2File = (svg: string): File => { export const svg2File = (svg: string): File => {
const blob = new Blob([svg], { type: 'image/svg+xml' }) const blob = new Blob([svg], { type: 'image/svg+xml' })
return new File([blob], `${Date.now()}.svg`, { type: 'image/svg+xml' }) return new File([blob], `${Date.now()}.svg`, { type: 'image/svg+xml' })
}
/**
*
* @returns
*/
export const getTime=()=>{
const now = new Date();
const year = now.getFullYear();
const month = ('0' + (now.getMonth() + 1)).slice(-2);
const day = ('0' + now.getDate()).slice(-2);
const hours = ('0' + now.getHours()).slice(-2);
const minutes = ('0' + now.getMinutes()).slice(-2);
const seconds = ('0' + now.getSeconds()).slice(-2);
return `${year}-${month}-${day}_${hours}:${minutes}:${seconds}`;
};
/**
* base64转图片File
* @param {String} base64 base64
* @param {String} fileName | myimg
* @returns File file数据类型
*/
export const base64ToFile = (base64: string, fileName = '试题图片') => {
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
let data = base64.split(','),
// 利用正则表达式 从前缀中获取图片的类型信息image/png、image/jpeg、image/webp等
type = data[0].match(/:(.*?);/)[1],
// 从图片的类型信息中 获取具体的文件格式后缀png、jpeg、webp
suffix = type.split('/')[1],
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
bstr = window.atob(data[1]),
// 获取解码结果字符串的长度
n = bstr.length,
// 根据解码结果字符串的长度创建一个等长的整形数字数组
// 但在创建时 所有元素初始值都为 0
u8arr = new Uint8Array(n)
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
while (n--) {
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
u8arr[n] = bstr.charCodeAt(n)
}
const filename = fileName+getTime()
// 利用构造函数创建File文件对象
// new File(bits, name, options)
const file = new File([u8arr], `${filename}.${suffix}`, {
type: type
})
// 返回file
return file
} }

View File

@ -19,7 +19,9 @@
import { ref, reactive, onMounted, computed } from 'vue'; import { ref, reactive, onMounted, computed } from 'vue';
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { getSmarttalkPage } from '@/api/file' import { getSmarttalkPage } from '@/api/file'
import { getFileSuffix, urlToBase64 } from '@/utils/ruoyi.js' import * as commUtils from '@/utils/comm.js'
import { getFileSuffix } from '@/utils/ruoyi.js'
import { PPTApi } from '../../../api'
const emit = defineEmits(['insertMaterial', 'close']) const emit = defineEmits(['insertMaterial', 'close'])
@ -59,70 +61,31 @@ const fileUrl = computed(() => (item) =>{
} }
}) })
// //
const onInsert = async (item) =>{ const onInsert = async (item) =>{
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){ loading.value = true
emit('insertMaterial',{ type: 'video', data: item.fileFullPath }) const res = await fetch(item.fileFullPath)
} const bolb = await res.blob()
else{ const file = commUtils.blobToFile(bolb, item.fileShowName)
const base64 = await urlToBase64(item.fileFullPath)
emit('insertMaterial',{ type: 'img', data: base64 })
}
}
try {
const data = await PPTApi.toRousrceUrl(file)
const GetUrlParameters = (parameters) => { if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
let resData = ""; emit('insertMaterial',{ type: 'video', data })
let url = document.location.toString();
let arrUrl = url.split("?");
//
if (arrUrl.length > 1) {
//
let parametersArr = arrUrl[1].split("&");
//
for (let i = 0; i <= parametersArr.length; i++) {
if (parametersArr[i]) {
//
let parameterStr = parametersArr[i].split("=");
if (parameters == parameterStr[0]) {
resData = parameterStr[1];
break;
}
}
} }
else{
emit('insertMaterial',{ type: 'img', data })
}
} finally {
loading.value = false
} }
return resData;
} }
const proxyToBase64 = (url)=> {
const dourl = GetUrlParameters(url)
console.log(dourl,'dourl')
return
axios({
url: "/api/logo.png",
method: "get",
responseType: "blob",
}).then((res) => {
const reader = new FileReader();
reader.readAsDataURL(res.data);
reader.onload = () => {
console.log(reader.result);
};
});
}
// //
const onClose = () =>{ const onClose = () =>{
emit('close') emit('close')
} }
onMounted(() => { onMounted(() => {
let data = sessionStore.get('subject.curNode') let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data); Object.assign(curNode, data);

View File

@ -114,7 +114,7 @@
/> />
</Modal> </Modal>
<!--插入试题-->
<el-dialog v-model="classWorkTaskVisible" append-to-body :show-close="false" width="70%"> <el-dialog v-model="classWorkTaskVisible" append-to-body :show-close="false" width="70%">
<QuestToPPTist <QuestToPPTist
class="class-work-task-modal" class="class-work-task-modal"
@ -132,7 +132,7 @@
<Modal <Modal
v-model:visible="imgVisible" v-model:visible="imgVisible"
:width="1300"> :width="1300">
<TextCreateImg hasPPt @insertImg="(url: string) => { createImageElement(url); imgVisible = false }" /> <TextCreateImg hasPPt @insertImg="insertImg" />
</Modal> </Modal>
</div> </div>
</template> </template>
@ -141,7 +141,7 @@
import { ref } from 'vue' import { ref } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore, useSnapshotStore } from '../../../store' import { useMainStore, useSnapshotStore } from '../../../store'
import { getImageDataURL } from '../../../utils/image' import { getImageDataURL, base64ToFile } from '../../../utils/image'
import type { ShapePoolItem } from '../../../configs/shapes' import type { ShapePoolItem } from '../../../configs/shapes'
import type { LinePoolItem } from '../../../configs/lines' import type { LinePoolItem } from '../../../configs/lines'
import useScaleCanvas from '../../../hooks/useScaleCanvas' import useScaleCanvas from '../../../hooks/useScaleCanvas'
@ -165,6 +165,7 @@ import { PPTApi } from '../../../api'
import TextCreateImg from '@/components/ai-kolors/index.vue' import TextCreateImg from '@/components/ai-kolors/index.vue'
import { toPng } from 'html-to-image' // html-to-image import { toPng } from 'html-to-image' // html-to-image
const mainStore = useMainStore() const mainStore = useMainStore()
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore) const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
const { canUndo, canRedo } = storeToRefs(useSnapshotStore()) const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
@ -209,8 +210,14 @@ const insertImageElement = (files: FileList) => {
} }
const onhtml2canvas = async (html: HTMLElement) => { const onhtml2canvas = async (html: HTMLElement) => {
const ele = await toPng(html); const base64Dta = await toPng(html);
createImageElement(ele); // base64File
const toFile = base64ToFile(base64Dta)
// 线
PPTApi.toRousrceUrl(toFile).then(data=>{
createImageElement(data)
})
// createImageElement(ele);
} }
const shapePoolVisible = ref(false) const shapePoolVisible = ref(false)
@ -276,7 +283,7 @@ interface MaterialParams {
type: string, type: string,
data: string data: string
} }
const insertMaterial = (item: MaterialParams) =>{ const insertMaterial = async (item: MaterialParams) =>{
const { type, data } = item const { type, data } = item
if(type == 'video'){ if(type == 'video'){
createVideoElement(data) createVideoElement(data)
@ -289,6 +296,13 @@ const insertMaterial = (item: MaterialParams) =>{
// //
const imgVisible = ref(false) const imgVisible = ref(false)
const insertImg = async (file: any) =>{
PPTApi.toRousrceUrl(file).then(data=>{
createImageElement(data)
imgVisible.value = false
})
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -3,7 +3,7 @@
<div class="left"> <div class="left">
<Popover trigger="click" placement="bottom-start" v-model:value="mainMenuVisible"> <Popover trigger="click" placement="bottom-start" v-model:value="mainMenuVisible">
<template #content> <template #content>
<FileInput accept=".pptist" @change="files => { <!-- <FileInput accept=".pptist" @change="files => {
importSpecificFile(files) importSpecificFile(files)
mainMenuVisible = false mainMenuVisible = false
}"> }">
@ -15,8 +15,8 @@
}"> }">
<PopoverMenuItem>导入 pptx 文件</PopoverMenuItem> <PopoverMenuItem>导入 pptx 文件</PopoverMenuItem>
</FileInput> </FileInput>
<PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem> <PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem> -->
<PopoverMenuItem @click="resetSlides(); mainMenuVisible = false">重置幻灯片</PopoverMenuItem> <!-- <PopoverMenuItem @click="resetSlides(); mainMenuVisible = false">重置幻灯片</PopoverMenuItem> -->
<!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/issues')">意见反馈</PopoverMenuItem> --> <!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/issues')">意见反馈</PopoverMenuItem> -->
<!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/blob/master/doc/Q&A.md')">常见问题</PopoverMenuItem> --> <!-- <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/blob/master/doc/Q&A.md')">常见问题</PopoverMenuItem> -->
<PopoverMenuItem @click="mainMenuVisible = false; hotkeyDrawerVisible = true">快捷操作</PopoverMenuItem> <PopoverMenuItem @click="mainMenuVisible = false; hotkeyDrawerVisible = true">快捷操作</PopoverMenuItem>
@ -54,9 +54,9 @@
<div class="arrow-btn"><IconDown class="arrow" /></div> <div class="arrow-btn"><IconDown class="arrow" /></div>
</Popover> </Popover>
</div> </div>
<div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')"> <!-- <div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
<IconDownload class="icon" /> <IconDownload class="icon" />
</div> </div> -->
<div class="menu-item" v-tooltip="`${userStore.user.parentDeptName}-${userStore.user.nickName}`"> <div class="menu-item" v-tooltip="`${userStore.user.parentDeptName}-${userStore.user.nickName}`">
<el-avatar size="small" :src="avatar" /> <el-avatar size="small" :src="avatar" />
</div> </div>

View File

@ -25,6 +25,12 @@
<div style="margin-top: 10px">常规作业</div> <div style="margin-top: 10px">常规作业</div>
</div> </div>
</el-button> </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> </div>
<Divider /> <Divider />
@ -32,22 +38,22 @@
<!-- 作业列表 --> <!-- 作业列表 -->
<div class="c-apt-r" v-loading='loadingActive'> <div class="c-apt-r" v-loading='loadingActive'>
<!-- 显示-作业内容 --> <!-- 显示-作业内容 -->
<template v-for="(item, index) in workList" :key="index"> <template v-for="(item, index) in slidesStore.workList" :key="index">
<div class="item"> <div class="item">
<div class="item-title"> <div class="item-title">
<el-tag :type="getTagType(item.worktype) || 'primary'">{{item.worktype}}</el-tag> <el-tag :type="getTagType(item.worktype) || 'primary'">{{item.worktype}}</el-tag>
<el-tooltip :content="item.uniquekey" placement="top"> <el-tooltip :content="item.uniquekey" placement="top">
<div class="tt">{{item.uniquekey}}</div> <div class="tt">{{item.uniquekey}}</div>
</el-tooltip> </el-tooltip>
<el-button class="btn-del" type="danger" link @click="handleRemoveDemoActivityClassWork(item)">删除</el-button> <el-button class="btn-del" type="danger" link @click="removeWork(item, index)">删除</el-button>
</div> </div>
</div> </div>
</template> </template>
</div> </div>
<!-- // --> <!-- // -->
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="90%" height="500"> <el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="85%" height="500">
<el-scrollbar height="550"> <el-scrollbar>
<div style="height: 550px;"> <div style="height: 75vh;">
<NewClassTsakAssign :currentCourse='currentCourse' @getData="getData" /> <NewClassTsakAssign :currentCourse='currentCourse' @getData="getData" />
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -80,20 +86,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, nextTick, watch } from 'vue' import { ref, reactive, onMounted, nextTick, watch } from 'vue'
import Divider from '../../../../../components/Divider.vue' import Divider from '../../../../../components/Divider.vue'
import { listEntpcoursefile } from '@/api/education/entpcoursefile'
import { homeworklist } from '@/api/teaching/classwork'
import { processList } from "@/hooks/useProcessList";
import { listEntpcoursework } from "@/api/classTask/index";
import { ElMessageBox,ElMessage } from 'element-plus' import { ElMessageBox,ElMessage } from 'element-plus'
import NewClassTsakAssign from '@/views/classTask/newClassTaskAssign/index.vue' import NewClassTsakAssign from '@/views/classTask/newClassTaskAssign/index.vue'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { useGetHomework } from '@/hooks/useGetHomework' import { useGetHomework } from '@/hooks/useGetHomework'
import { PPTApi } from '../../../../../api/index' import { PPTApi } from '../../../../../api/index'
import { storeToRefs } from 'pinia' import {useSlidesStore} from '../../../../../store'
import {useSlidesStore} from '../../../../../store'
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { slides, slideIndex, currentSlide, workItem } = storeToRefs(slidesStore)
interface CourseNode { interface CourseNode {
rootid: number; rootid: number;
parentNode: { id: number }; parentNode: { id: number };
@ -111,12 +110,6 @@ interface CurrentCourse {
worktype: string; worktype: string;
} }
interface Params {
parentid: number;
pageSize: number;
orderby: string;
}
interface WorkType { interface WorkType {
label: string; label: string;
value: string; value: string;
@ -167,65 +160,37 @@ const type = ref<WorkType[]>([
label: '常规作业', label: '常规作业',
value: 'primary' value: 'primary'
}, },
{
label: '框架梳理',
value: 'primary'
},
{
label: '科学实验',
value: 'primary'
}
]) ])
const objItem = ref<any>({})
//
const workList = ref<WorkItem[]>([])
// //
const selectedWorkList = ref<WorkItem[]>([]) const selectedWorkList = ref<WorkItem[]>([])
// loading // loading
const loadingActive = ref<boolean>(false) const loadingActive = ref<boolean>(false)
//
const paramData = ref<{ id: number, activityContent: string }>({} as { id: number, activityContent: string })
const selectable = (row: WorkItem, index: number): boolean => { const selectable = (row: WorkItem, index: number): boolean => {
return row.status === '10'; return row.status === '10';
}; };
//
const formatClassWorkFile = async (postData: WorkItem[]): Promise<void> => { const removeWork = (item: WorkItem, index: number) => {
for (let i = 0; i < postData.length; i++) {
let item = postData[i];
switch (item.worktype) {
case '框架梳理': {
}
break;
case '习题训练': {
// let workIds = item.quizlist!.map(items => items.id).join(',');
// let ress = await listEntpcoursework({ ids: workIds });
// const arr = ress.rows.map((item:{id:number}) => {
// return item.id
// })
// processList(ress.rows);
}
break;
case '课堂展示': {
// item.base64 = JSON.parse(item.workcodes).base64;
}
break;
case '常规作业': {
// item.prevData = JSON.parse(item.workcodes);
}
}
workList.value.push(item)
loadingActive.value = false
}
await nextTick();
}//
const handleRemoveDemoActivityClassWork = (item: WorkItem) => {
ElMessageBox.confirm('是否确认删除?') ElMessageBox.confirm('是否确认删除?')
.then(() => { .then(async() => {
workList.value = [] // workList
const arr = paramData.value.activityContent.split(',') // slidesStore.workList.splice(index, 1)
const filterArr = arr.filter(itemId => itemId!== item.id.toString()) console.log('删除作业', item)
paramData.value.activityContent = filterArr.join(',') await upDateData('del', [item.id])
upDateData() ElMessage.success('删除成功')
}) })
.catch(() => { }); .catch(() => { });
} }
// tag // tag
@ -252,6 +217,7 @@ const showDialog = (item: string) => {
dialogVisible.value = true dialogVisible.value = true
} }
//
const openList = () => { const openList = () => {
activeVisible.value = true activeVisible.value = true
initHomeWork() initHomeWork()
@ -259,65 +225,44 @@ const openList = () => {
// //
const savePPtData = async () => { const savePPtData = async () => {
if (selectedWorkList.value.length === 0) { if (!selectedWorkList.value.length) {
ElMessage.warning('请选择活动') ElMessage.warning('请选择活动')
return return
} }
workList.value = [] const arr = selectedWorkList.value.map(item => item.id)
const arr = selectedWorkList.value.map(item => item.id) await upDateData('add', arr)
// activeVisible.value = false
const existingIds = paramData.value.activityContent ? paramData.value.activityContent.split(',') : []
paramData.value.activityContent = Array.from(new Set([...existingIds, ...arr])).join(',')
upDateData()
activeVisible.value = false
}
// ppt
const getCurrentPPtData = async () => {
workList.value = []
objItem.value = workItem.value[slideIndex.value]
paramData.value.id = objItem.value.id
console.log(paramData.value.id,'paramData.value.id');
paramData.value.activityContent = objItem.value?.activityContent
if (objItem.value?.activityContent) {
loadingActive.value = true
const res = await homeworklist({ ids: objItem.value?.activityContent, pageSize: 100 })
await formatClassWorkFile(res.rows)
}
} }
// //
const getData = async (data: WorkItem) => { const getData = async (data: WorkItem) => {
workList.value = [] await upDateData('add', [data.id])
if(paramData.value.activityContent){ dialogVisible.value = false
const arr = paramData.value.activityContent.split(',')
arr.push(data.id.toString())
const unitArr = Array.from(new Set(arr))
paramData.value.activityContent = unitArr.join(',')
}else{
paramData.value.activityContent = data.id.toString()
}
upDateData()
dialogVisible.value = false
} }
const upDateData = async () => { // - type add/ | del/ ids id
await PPTApi.updateSlide(paramData.value) const upDateData = (type: string, ids: number[]): Promise<any> => {
return new Promise(async (resolve, reject) => {
loadingActive.value = true loadingActive.value = true
const res = await homeworklist({ ids: paramData.value.activityContent, pageSize: 100 }) //
await formatClassWorkFile(res.rows) let workIds = slidesStore.workList.map((o:any) => o.id)
const resource = sessionStore.get('curr.resource') const id = slidesStore.currentSlide.id
await PPTApi.getSlideList(resource.id) if (type === 'del') { //
} workIds = workIds.filter(id => !ids.includes(id))
// } else { //
const objectsAreEqual = (obj1: any[], obj2: any[]) => { workIds = Array.from(new Set([...workIds, ...ids]))
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (obj1[key] !== obj2[key]) return false;
} }
return true; //
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(() => { onMounted(() => {
@ -327,16 +272,8 @@ onMounted(() => {
currentCourse.levelSecondId = curNode.id currentCourse.levelSecondId = curNode.id
currentCourse.coursetitle = curNode.itemtitle currentCourse.coursetitle = curNode.itemtitle
currentCourse.node = curNode currentCourse.node = curNode
objItem.value = workItem.value[slideIndex.value]
getCurrentPPtData()
}) })
watch(() => [workItem.value.length,slideIndex.value], (newVal,oldVal) => {
if(!objectsAreEqual(newVal,oldVal))
getCurrentPPtData()
})
// watch(() => workItem.value.length, () => {
// getCurrentPPtData()
// })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.buttonDiv{ .buttonDiv{

View File

@ -88,7 +88,9 @@ import Button from '../../../../components/Button.vue'
import ButtonGroup from '../../../../components/ButtonGroup.vue' import ButtonGroup from '../../../../components/ButtonGroup.vue'
import Popover from '../../../../components/Popover.vue' import Popover from '../../../../components/Popover.vue'
import NumberInput from '../../../../components/NumberInput.vue' import NumberInput from '../../../../components/NumberInput.vue'
import { PPTApi } from '../../../../api'
import { Console } from 'node:console'
import { x64 } from 'crypto-js'
const shapeClipPathOptions = CLIPPATHS const shapeClipPathOptions = CLIPPATHS
const ratioClipOptions = [ const ratioClipOptions = [
{ {
@ -221,10 +223,14 @@ const presetImageClip = (shape: string, ratio = 0) => {
const replaceImage = (files: FileList) => { const replaceImage = (files: FileList) => {
const imageFile = files[0] const imageFile = files[0]
if (!imageFile) return if (!imageFile) return
getImageDataURL(imageFile).then(dataURL => { PPTApi.toRousrceUrl(imageFile).then(data=>{
const props = { src: dataURL } const props = { src: data }
updateImage(props) updateImage(props)
}) })
// getImageDataURL(imageFile).then(dataURL => {
// const props = { src: dataURL }
// updateImage(props)
// })
} }
// //

View File

@ -30,18 +30,14 @@
@close="timerlVisible = false" @close="timerlVisible = false"
/> />
<div class="tools-left"> <div class="tools-left" v-if="!classcourse">
<IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" /> <IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" />
<IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" /> <IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" />
</div> </div>
<!-- 点赞组件 -->
<div style="z-index: 999;position: absolute;top:10px">
<upvote-vue ref="upvoteRef" type="2"></upvote-vue>
</div>
<div <div
class="tools-right" :class="{ 'visible': rightToolsVisible }" class="tools-right" :class="{ 'visible': rightToolsVisible }"
@mouseleave="rightToolsVisible = false" @mouseleave="toolTrigger('leave')"
@mouseenter="rightToolsVisible = true" @mouseenter="toolTrigger('enter')"
> >
<div class="content"> <div class="content">
<div class="tool-btn page-number" @click="slideThumbnailModelVisible = true">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div> <div class="tool-btn page-number" @click="slideThumbnailModelVisible = true">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
@ -52,15 +48,20 @@
<IconOffScreenOne class="tool-btn" v-tooltip="'退出全屏'" v-if="fullscreenState" @click="manualExitFullscreen()" /> <IconOffScreenOne class="tool-btn" v-tooltip="'退出全屏'" v-if="fullscreenState" @click="manualExitFullscreen()" />
<IconFullScreenOne class="tool-btn" v-tooltip="'进入全屏'" v-else @click="enterFullscreen()" /> <IconFullScreenOne class="tool-btn" v-tooltip="'进入全屏'" v-else @click="enterFullscreen()" />
<IconPower class="tool-btn" v-tooltip="'结束放映'" @click="exitScreening()" /> <IconPower class="tool-btn" v-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> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref , watchEffect} from 'vue' import { ref , watchEffect, onMounted, onUnmounted} from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useSlidesStore ,useScreenStore} from '../../store' import { useSlidesStore ,useScreenStore, useClasscourseStore} from '../../store'
import type { ContextmenuItem } from '../../components/Contextmenu/types' import type { ContextmenuItem } from '../../components/Contextmenu/types'
import { enterFullscreen } from '../../utils/fullscreen' import { enterFullscreen } from '../../utils/fullscreen'
import useScreening from '../../hooks/useScreening' import useScreening from '../../hooks/useScreening'
@ -72,14 +73,16 @@ import ScreenSlideList from './ScreenSlideList.vue'
import SlideThumbnails from './SlideThumbnails.vue' import SlideThumbnails from './SlideThumbnails.vue'
import WritingBoardTool from './WritingBoardTool.vue' import WritingBoardTool from './WritingBoardTool.vue'
import CountdownTimer from './CountdownTimer.vue' import CountdownTimer from './CountdownTimer.vue'
import upvoteVue from '@/views/tool/components/upvote.vue' // -
import emitter from '@/utils/mitt'; import emitter from '@/utils/mitt';
import Chat from '../../api/chat' //
import { CircleDoubleDown, CircleDoubleUp } from '@icon-park/vue-next' // icon-park
const props = defineProps<{ const props = defineProps<{
changeViewMode: (mode: 'base' | 'presenter') => void changeViewMode: (mode: 'base' | 'presenter') => void
}>() }>()
const { slides, slideIndex } = storeToRefs(useSlidesStore()) const { slides, slideIndex } = storeToRefs(useSlidesStore())
const { classcourse, isEmit } = storeToRefs(useClasscourseStore()) //
const { const {
autoPlayTimer, autoPlayTimer,
@ -100,18 +103,20 @@ const {
execNext, execNext,
animationIndex, animationIndex,
} = useExecPlay() } = useExecPlay()
const { slideWidth, slideHeight } = useSlideSize() const { slideWidth, slideHeight } = useSlideSize()
const { exitScreening } = useScreening() const { exitScreening } = useScreening()
const { fullscreenState, manualExitFullscreen } = useFullscreen() const { fullscreenState, manualExitFullscreen } = useFullscreen()
const chat:any = Chat() //
const screenStore =useScreenStore()
const rightToolsVisible = ref(false) const rightToolsVisible = ref(false)
const writingBoardToolVisible = ref(false) const writingBoardToolVisible = ref(false)
const timerlVisible = ref(false) const timerlVisible = ref(false)
const slideThumbnailModelVisible = ref(false) const slideThumbnailModelVisible = ref(false)
const laserPen = ref(false) const laserPen = ref(false)
const upvoteRef = ref(null) const timer = ref(0) //
const screenStore =useScreenStore() const iconHide = ref(false) //
const timerId = ref(null) // id
const contextmenus = (): ContextmenuItem[] => { const contextmenus = (): ContextmenuItem[] => {
return [ return [
{ {
@ -193,11 +198,37 @@ const contextmenus = (): ContextmenuItem[] => {
}, },
] ]
} }
// 1 2
emitter.on('upvoteTrigger', (type) => {
upvoteRef.value?.trigger(type)
});
const toolTrigger = (type:string) => {
const curT = Date.now()
if (curT - timer.value < 200) return
iconHide.value = false //
if (timerId.value) clearTimeout(timerId.value) //
switch (type) {
case 'icon': //
timer.value = curT
rightToolsVisible.value = !rightToolsVisible.value
break
case 'enter': //
timer.value = curT
rightToolsVisible.value = true
break
case 'leave': //
rightToolsVisible.value = false
break
default:
break
}
timerId.value = setTimeout(() => { //
iconHide.value = true //
}, 2000)
}
//
const exitCourse = async () => {
// console.log('', chat)
await chat.exitCourse() //
exitScreening() //
}
</script> </script>
@ -254,6 +285,18 @@ emitter.on('upvoteTrigger', (type) => {
top: -66px; top: -66px;
} }
.tools-icon{
position: absolute;
right: 8px;
top: -35px;
z-index: 1;
cursor: pointer;
transition: opacity $transitionDelay;
&.opacity{
opacity: .35;
}
}
.content { .content {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -279,6 +322,9 @@ emitter.on('upvoteTrigger', (type) => {
& + .tool-btn { & + .tool-btn {
margin-left: 15px; margin-left: 15px;
} }
&.close{
color: #d14424;
}
} }
.page-number { .page-number {
font-size: 13px; font-size: 13px;

View File

@ -12,6 +12,7 @@
</div> </div>
<Divider class="divider" /> <Divider class="divider" />
<div class="tool-btn" @click="exitScreening()"><IconPower class="tool-icon" /><span>结束放映</span></div> <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>
<div class="content"> <div class="content">
@ -55,7 +56,7 @@
:class="{ 'active': index === slideIndex }" :class="{ 'active': index === slideIndex }"
v-for="(slide, index) in slides" v-for="(slide, index) in slides"
:key="slide.id" :key="slide.id"
@click="turnSlideToIndex(index)" @click="turnSlideTo(index, $event)"
> >
<ThumbnailSlide :slide="slide" :size="120 / viewportRatio" :visible="index < slidesLoadLimit" /> <ThumbnailSlide :slide="slide" :size="120 / viewportRatio" :visible="index < slidesLoadLimit" />
</div> </div>
@ -77,9 +78,9 @@
</template> </template>
<script lang="ts" setup> <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 { storeToRefs } from 'pinia'
import { useSlidesStore } from '../../store' import { useSlidesStore, useClasscourseStore } from '../../store'
import type { ContextmenuItem } from '../../components/Contextmenu/types' import type { ContextmenuItem } from '../../components/Contextmenu/types'
import { enterFullscreen } from '../../utils/fullscreen' import { enterFullscreen } from '../../utils/fullscreen'
import { parseText2Paragraphs } from '../../utils/textParser' import { parseText2Paragraphs } from '../../utils/textParser'
@ -94,12 +95,15 @@ import ScreenSlideList from './ScreenSlideList.vue'
import WritingBoardTool from './WritingBoardTool.vue' import WritingBoardTool from './WritingBoardTool.vue'
import CountdownTimer from './CountdownTimer.vue' import CountdownTimer from './CountdownTimer.vue'
import Divider from '../../components/Divider.vue' import Divider from '../../components/Divider.vue'
import emitter from '@/utils/mitt';
import Chat from '../../api/chat' //
const props = defineProps<{ const props = defineProps<{
changeViewMode: (mode: 'base' | 'presenter') => void changeViewMode: (mode: 'base' | 'presenter') => void
}>() }>()
const { slides, slideIndex, viewportRatio, currentSlide } = storeToRefs(useSlidesStore()) const { slides, slideIndex, viewportRatio, currentSlide } = storeToRefs(useSlidesStore())
const { classcourse, isEmit } = storeToRefs(useClasscourseStore()) //
const slideListWrapRef = ref<HTMLElement>() const slideListWrapRef = ref<HTMLElement>()
const thumbnailsRef = ref<HTMLElement>() const thumbnailsRef = ref<HTMLElement>()
@ -117,17 +121,31 @@ const {
turnSlideToId, turnSlideToId,
animationIndex, animationIndex,
} = useExecPlay() } = useExecPlay()
const { slideWidth, slideHeight } = useSlideSize(slideListWrapRef) const { slideWidth, slideHeight } = useSlideSize(slideListWrapRef)
const { exitScreening } = useScreening() const { exitScreening } = useScreening()
const { slidesLoadLimit } = useLoadSlides() const { slidesLoadLimit } = useLoadSlides()
const { fullscreenState, manualExitFullscreen } = useFullscreen() const { fullscreenState, manualExitFullscreen } = useFullscreen()
const chat:any = Chat() //
const remarkFontSize = ref(16) const remarkFontSize = ref(16)
const currentSlideRemark = computed(() => { const currentSlideRemark = computed(() => {
return parseText2Paragraphs(currentSlide.value.remark || '无备注') return parseText2Paragraphs(currentSlide.value.remark || '无备注')
}) })
//
const turnSlideTo = (index: number, e: PointerEvent) => {
//
console.log('课堂信息', classcourse, index)
if (!!classcourse.value) return
turnSlideToIndex(index)
}
//
const exitCourse = async () => {
// console.log('', chat)
await chat.exitCourse() //
exitScreening() //
}
const handleMousewheelThumbnails = (e: WheelEvent) => { const handleMousewheelThumbnails = (e: WheelEvent) => {
if (!thumbnailsRef.value) return if (!thumbnailsRef.value) return
thumbnailsRef.value.scrollBy(e.deltaY, 0) thumbnailsRef.value.scrollBy(e.deltaY, 0)
@ -192,6 +210,7 @@ const contextmenus = (): ContextmenuItem[] => {
}, },
] ]
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -208,7 +227,7 @@ const contextmenus = (): ContextmenuItem[] => {
background-color: #fff; background-color: #fff;
border-right: solid 1px #eee; border-right: solid 1px #eee;
font-size: 12px; font-size: 12px;
margin: 20px 0; padding: 20px 0;
.tool-btn { .tool-btn {
display: flex; display: flex;
@ -224,6 +243,9 @@ const contextmenus = (): ContextmenuItem[] => {
&:hover, &.active { &:hover, &.active {
color: $themeColor; color: $themeColor;
} }
&.close{
color: #d14424;
}
} }
.divider { .divider {

View File

@ -1,17 +1,24 @@
import { onMounted, onUnmounted, ref } from 'vue' import { onMounted, onUnmounted, ref } from 'vue'
import { throttle } from 'lodash' import { throttle } from 'lodash'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useSlidesStore } from '../../../store' import { useSlidesStore, useClasscourseStore } from '../../../store'
import { KEYS } from '../../../configs/hotkey' import { KEYS } from '../../../configs/hotkey'
import { ANIMATION_CLASS_PREFIX } from '../../../configs/animation' import { ANIMATION_CLASS_PREFIX } from '../../../configs/animation'
import message from '../../../utils/message' import message from '../../../utils/message'
import emitter from '@/utils/mitt';
import Chat from '../../../api/chat' // 聊天封装
// import ChatWs from '@/plugins/socket' // 聊天socket
// import { MsgEnum } from '../../../api/types' // 消息枚举
export default () => { export default (isLoader?: boolean = true) => {
// isLoader 是否执行 onMounted, onUnmounted
const chatApi = Chat()
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { slides, slideIndex, formatedAnimations } = storeToRefs(slidesStore) const classcourseStore = useClasscourseStore() // 课堂信息-状态管理
const { slides, slideIndex, formatedAnimations, animationIndex } = storeToRefs(slidesStore)
// 当前页的元素动画执行到的位置 // 当前页的元素动画执行到的位置
const animationIndex = ref(0) // const animationIndex = ref(0)
// 动画执行状态 // 动画执行状态
const inAnimation = ref(false) const inAnimation = ref(false)
@ -19,10 +26,10 @@ export default () => {
// 最小已播放页面索引 // 最小已播放页面索引
const playedSlidesMinIndex = ref(slideIndex.value) const playedSlidesMinIndex = ref(slideIndex.value)
// 执行元素动画 // 执行元素动画 isAsync 为 true 时,异步执行,否则同步执行
const runAnimation = () => { const runAnimation = (isAsync: boolean) => {
// 正在执行动画时,禁止其他新的动画开始 // 正在执行动画时,禁止其他新的动画开始
if (inAnimation.value) return if (inAnimation.value && !isAsync) return
const { animations, autoNext } = formatedAnimations.value[animationIndex.value] const { animations, autoNext } = formatedAnimations.value[animationIndex.value]
animationIndex.value += 1 animationIndex.value += 1
@ -69,14 +76,15 @@ export default () => {
elRef.addEventListener('animationend', handleAnimationEnd, { once: true }) elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
} }
} }
if (isLoader) { // 加载相关钩子
onMounted(() => { onMounted(() => {
const firstAnimations = formatedAnimations.value[0] const firstAnimations = formatedAnimations.value[0]
if (firstAnimations && firstAnimations.animations.length) { if (firstAnimations && firstAnimations.animations.length) {
const autoExecFirstAnimations = firstAnimations.animations.every(item => item.trigger === 'auto' || item.trigger === 'meantime') const autoExecFirstAnimations = firstAnimations.animations.every(item => item.trigger === 'auto' || item.trigger === 'meantime')
if (autoExecFirstAnimations) runAnimation() if (autoExecFirstAnimations) runAnimation()
} }
}) })
}
// 撤销元素动画,除了将索引前移外,还需要清除动画状态 // 撤销元素动画,除了将索引前移外,还需要清除动画状态
const revokeAnimation = () => { const revokeAnimation = () => {
@ -121,9 +129,9 @@ export default () => {
// 遇到元素动画时,优先执行动画播放,无动画则执行翻页 // 遇到元素动画时,优先执行动画播放,无动画则执行翻页
// 向上播放遇到动画时,仅撤销到动画执行前的状态,不需要反向播放动画 // 向上播放遇到动画时,仅撤销到动画执行前的状态,不需要反向播放动画
// 撤回到上一页时,若该页从未播放过(意味着不存在动画状态),需要将动画索引置为最小值(初始状态),否则置为最大值(最终状态) // 撤回到上一页时,若该页从未播放过(意味着不存在动画状态),需要将动画索引置为最小值(初始状态),否则置为最大值(最终状态)
const execPrev = () => { const execPrev = (isAsync: boolean) => {
if (formatedAnimations.value.length && animationIndex.value > 0) { if (formatedAnimations.value.length && animationIndex.value > 0) {
revokeAnimation() revokeAnimation(isAsync)
} }
else if (slideIndex.value > 0) { else if (slideIndex.value > 0) {
slidesStore.updateSlideIndex(slideIndex.value - 1) slidesStore.updateSlideIndex(slideIndex.value - 1)
@ -139,9 +147,9 @@ export default () => {
} }
inAnimation.value = false inAnimation.value = false
} }
const execNext = () => { const execNext = (isAsync: boolean) => {
if (formatedAnimations.value.length && animationIndex.value < formatedAnimations.value.length) { if (formatedAnimations.value.length && animationIndex.value < formatedAnimations.value.length) {
runAnimation() runAnimation(isAsync)
} }
else if (slideIndex.value < slides.value.length - 1) { else if (slideIndex.value < slides.value.length - 1) {
slidesStore.updateSlideIndex(slideIndex.value + 1) slidesStore.updateSlideIndex(slideIndex.value + 1)
@ -173,51 +181,69 @@ export default () => {
} }
// 鼠标滚动翻页 // 鼠标滚动翻页
const mousewheelListener = throttle(function(e: WheelEvent) { const mousewheelListener = (e: WheelEvent) => {
if (e.deltaY < 0) execPrev() // console.log('mousewheel', e)
else if (e.deltaY > 0) execNext() e.preventDefault() // 阻止默认事件
mousewheelListenerThrottle(e)
}
const mousewheelListenerThrottle = throttle(function(e: WheelEvent) {
if (e.deltaY < 0) turning(e, 'prev')
else if (e.deltaY > 0) turning(e, 'next')
}, 500, { leading: true, trailing: false }) }, 500, { leading: true, trailing: false })
// 触摸屏上下滑动翻页 // 触摸屏上下滑动翻页
const touchInfo = ref<{ x: number; y: number; } | null>(null) const touchInfo = ref<{ x: number; y: number; } | null>(null)
const touchStartListener = (e: TouchEvent) => { const touchStartListener = (e: TouchEvent) => {
e.preventDefault() // 阻止默认事件
touchInfo.value = { touchInfo.value = {
x: e.changedTouches[0].pageX, // x: e.changedTouches[0].pageX,
y: e.changedTouches[0].pageY, // y: e.changedTouches[0].pageY,
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY,
} }
} }
const touchEndListener = (e: TouchEvent) => { const touchEndListener = (e: TouchEvent) => {
if (!touchInfo.value) return if (!touchInfo.value) return
const offsetX = Math.abs(touchInfo.value.x - e.changedTouches[0].clientX)
const offsetX = Math.abs(touchInfo.value.x - e.changedTouches[0].pageX) const offsetY = e.changedTouches[0].clientY - touchInfo.value.y
const offsetY = e.changedTouches[0].pageY - touchInfo.value.y
if ( Math.abs(offsetY) > offsetX && Math.abs(offsetY) > 50 ) { if ( Math.abs(offsetY) > offsetX && Math.abs(offsetY) > 50 ) {
touchInfo.value = null touchInfo.value = null
if (offsetY > 0) turning(e, 'prev')
if (offsetY > 0) execPrev() else turning(e, 'next')
else execNext() }
}
// 向上翻页/向下翻页
const turning = async (e, type) => {
e.preventDefault() // 阻止默认事件
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 keydownListener = (e: KeyboardEvent) => {
const key = e.key.toUpperCase() const key = e.key.toUpperCase()
if (key === KEYS.UP || key === KEYS.LEFT || key === KEYS.PAGEUP) execPrev() if (key === KEYS.UP || key === KEYS.LEFT || key === KEYS.PAGEUP) turning(e, 'prev')
else if ( else if (
key === KEYS.DOWN || key === KEYS.DOWN ||
key === KEYS.RIGHT || key === KEYS.RIGHT ||
key === KEYS.SPACE || key === KEYS.SPACE ||
key === KEYS.ENTER || key === KEYS.ENTER ||
key === KEYS.PAGEDOWN key === KEYS.PAGEDOWN
) execNext() ) turning(e, 'next')
} }
if (isLoader) { // 加载相关钩子
onMounted(() => document.addEventListener('keydown', keydownListener)) onMounted(() => {document.addEventListener('keydown', keydownListener)})
onUnmounted(() => document.removeEventListener('keydown', keydownListener)) onUnmounted(() => {document.removeEventListener('keydown', keydownListener)})
}
// 切换到上一张/上一张幻灯片(无视元素的入场动画) // 切换到上一张/上一张幻灯片(无视元素的入场动画)
const turnPrevSlide = () => { const turnPrevSlide = () => {
slidesStore.updateSlideIndex(slideIndex.value - 1) slidesStore.updateSlideIndex(slideIndex.value - 1)

View File

@ -1,25 +1,47 @@
<template> <template>
<div class="pptist-screen"> <div class="pptist-screen">
<BaseView :changeViewMode="changeViewMode" v-if="viewMode === 'base'" /> <BaseView :changeViewMode="changeViewMode" v-if="viewMode === 'base'" />
<PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" /> <PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
<!-- 点赞组件 -->
<upvote-vue ref="upvoteRef" type="2"></upvote-vue>
<!-- <div style="z-index: 999;position: absolute;top:10px">
</div> -->
<!-- 推图上屏弹窗 -->
<el-dialog
v-model="dialogVisible"
:fullscreen="true"
class="gridPicRefdiv"
style="overflow: hidden;"
:show-close="false"
>
<grid-pic ref="gridPicRef" style="height:100%;" @clear="clearchidrenPic"></grid-pic>
</el-dialog>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue' import { onMounted, onUnmounted, ref , nextTick} from 'vue'
import { KEYS } from '../../configs/hotkey' import { KEYS } from '../../configs/hotkey'
import useScreening from '../../hooks/useScreening' import useScreening from '../../hooks/useScreening'
import hooksUpvote from '../../api/upvote' // -
import BaseView from './BaseView.vue' import BaseView from './BaseView.vue'
import PresenterView from './PresenterView.vue' import PresenterView from './PresenterView.vue'
import upvoteVue from '@/views/tool/components/upvote.vue' // -
import gridPic from '@/components/grid-pic/index.vue' //
import emitter from '@/utils/mitt' //mitt 线
const viewMode = ref<'base' | 'presenter'>('base') const viewMode = ref<'base' | 'presenter'>('base')
const dialogVisible = ref(false)
const gridPicRef:any= ref(null)
const changeViewMode = (mode: 'base' | 'presenter') => { const changeViewMode = (mode: 'base' | 'presenter') => {
viewMode.value = mode viewMode.value = mode
} }
const { exitScreening } = useScreening() const { exitScreening } = useScreening()
const upvoteRef = ref(null)
hooksUpvote.init(upvoteRef) //
// 退 // 退
const keydownListener = (e: KeyboardEvent) => { const keydownListener = (e: KeyboardEvent) => {
@ -27,6 +49,23 @@ const keydownListener = (e: KeyboardEvent) => {
if (key === KEYS.ESC) exitScreening() if (key === KEYS.ESC) exitScreening()
} }
const clearchidrenPic= ()=> {
dialogVisible.value = false
}
//
emitter.on('opengridPic', async (data:object)=> {
if(gridPicRef.value) gridPicRef.value.clearPic()
dialogVisible.value = true
await nextTick();
gridPicRef.value.addPic(data.arr)
});
//
emitter.on('closegridPic', ()=> {
if(!gridPicRef.value) return
gridPicRef.value.clearPic()
dialogVisible.value = false
});
onMounted(() => document.addEventListener('keydown', keydownListener)) onMounted(() => document.addEventListener('keydown', keydownListener))
onUnmounted(() => document.removeEventListener('keydown', keydownListener)) onUnmounted(() => document.removeEventListener('keydown', keydownListener))
</script> </script>
@ -38,4 +77,8 @@ onUnmounted(() => document.removeEventListener('keydown', keydownListener))
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
:deep(.gridPicRefdiv .el-dialog__body){
height: 100% !important;
}
</style> </style>

View File

@ -10,11 +10,10 @@ export const createChart = ({ headers, data }) => {
}) })
} }
// 大模型对话 // 大模型对话
export const sendChart = ({ headers, data }) => { export const sendChart = (data) => {
return request({ return request({
url: '/qf/sendTalk', url: '/qf/sendTalk',
method: 'post', method: 'post',
headers,
data, data,
}) })
} }

View File

@ -2,6 +2,8 @@ import axios from 'axios'
import request from '@/utils/request' import request from '@/utils/request'
import { getToken } from "@/utils/auth"; import { getToken } from "@/utils/auth";
let rootPath = import.meta.env.VITE_APP_ENV === 'production' ? 'https://ai.ysaix.com:7864' : ''
// 文生图片 // 文生图片
export function convertTextToPicture(data) { export function convertTextToPicture(data) {
return axios({ return axios({
@ -42,7 +44,7 @@ export function getPicture(data) {
// 大模型对话生成prompt模板 // 大模型对话生成prompt模板
export function chattoprompt(dataset_id,prompt) { export function chattoprompt(dataset_id,prompt) {
return axios({ return axios({
url: '/api/v1/parse/docs', url: rootPath + '/api/v1/parse/docs',
method: 'post', method: 'post',
headers: { headers: {
'Authorization': 'Bearer ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm', 'Authorization': 'Bearer ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm',
@ -68,7 +70,7 @@ export function textSensitiveWord(data) {
// 图片上传资源库 // 图片上传资源库
export function uploadPicture(data) { export function uploadPicture(data) {
return axios({ return axios({
url: '/dev-api/smarttalk/file/upload', url: import.meta.env.VITE_APP_BASE_API + '/smarttalk/file/upload',
method: 'post', method: 'post',
headers: { headers: {
'Accept': '*/*', 'Accept': '*/*',

View File

@ -80,6 +80,15 @@ export function updateClassworkdata(data) {
}) })
} }
// 批阅后, 待所有学生都批改完成后自动结束当前作业为[已完成]
export function updateClassWorkDataAutoFinish(data) {
return request({
url: '/education/classworkdata/updAutoFinish',
method: 'put',
data: data
})
}
// 修改classwork // 修改classwork
export function updateClasswork(data) { export function updateClasswork(data) {
return request({ return request({

View File

@ -71,3 +71,11 @@ export const addFileToKj = (id) => {
method: 'get' method: 'get'
}) })
} }
export const getModelInfo = (params) => {
return request({
url: '/education/llmModel/getModelInfo',
method: 'post',
params
})
}

View File

@ -95,3 +95,11 @@ export function getCourseTeachingMsg(id) {
}) })
} }
export function setPaging(data) {
return request({
url: '/education/classcourse/record/paging',
method: 'post',
data
})
}

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4723712 */ font-family: "iconfont"; /* Project id 4723712 */
src: url('iconfont.woff2?t=1733880548695') format('woff2'), src: url('iconfont.woff2?t=1734337029245') format('woff2'),
url('iconfont.woff?t=1733880548695') format('woff'), url('iconfont.woff?t=1734337029245') format('woff'),
url('iconfont.ttf?t=1733880548695') format('truetype'); url('iconfont.ttf?t=1734337029245') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-yinle:before {
content: "\e6c9";
}
.icon-yuyin:before { .icon-yuyin:before {
content: "\e648"; content: "\e648";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,13 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "11819186",
"name": "音乐",
"font_class": "yinle",
"unicode": "e6c9",
"unicode_decimal": 59081
},
{ {
"icon_id": "6338162", "icon_id": "6338162",
"name": "语音生成", "name": "语音生成",

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

View File

@ -482,10 +482,7 @@ export default {
// //
async saveImage(resultIndex, index, url, resultItem) { async saveImage(resultIndex, index, url, resultItem) {
if(this.hasPPt){
this.$emit('insertImg', url)
return
}
this.buttonStates[resultIndex][index].disabled = true; this.buttonStates[resultIndex][index].disabled = true;
this.buttonStates[resultIndex][index].text = "正在保存..."; this.buttonStates[resultIndex][index].text = "正在保存...";
const numberIndex = url.indexOf('filename='); const numberIndex = url.indexOf('filename=');
@ -494,6 +491,7 @@ export default {
const finalPath = path.substring(0, pngIndex + 4); const finalPath = path.substring(0, pngIndex + 4);
try { try {
const blob = await this.getImageBlob(`https://ai.ysaix.com:7853/view?filename=${finalPath}&type=temp`); const blob = await this.getImageBlob(`https://ai.ysaix.com:7853/view?filename=${finalPath}&type=temp`);
const hash = CryptoJS.MD5(blob).toString(); const hash = CryptoJS.MD5(blob).toString();
@ -502,7 +500,10 @@ export default {
let file = new File([blob], `${resultItem}.png`, { let file = new File([blob], `${resultItem}.png`, {
type: 'image/png' type: 'image/png'
}) })
if(this.hasPPt){
this.$emit('insertImg', file)
return
}
// //
formData.append('md5', hash); formData.append('md5', hash);
formData.append('file', file); formData.append('file', file);

View 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>

View File

@ -1,7 +1,8 @@
<template> <template>
<div class="book-wrap"> <div class="book-wrap">
<el-scrollbar height="100%"> <el-scrollbar height="100%">
<div class="book-name flex" @click="dialogVisible = true"> <div class="book-name flex" v-if="isStadium() === true"> {{userStore.user.deptName}}</div>
<div v-else class="book-name flex" @click="dialogVisible = true">
<span>{{ curBook.data.itemtitle }}</span> <span>{{ curBook.data.itemtitle }}</span>
<i class="iconfont icon-xiangyou"></i> <i class="iconfont icon-xiangyou"></i>
</div> </div>
@ -46,8 +47,14 @@ import { onMounted, ref, nextTick, toRaw, reactive } from 'vue';
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { useGetSubject } from '@/hooks/useGetSubject' import { useGetSubject } from '@/hooks/useGetSubject'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
const isStadium = () => {
let roles = userStore.user.roles
return roles.some(item => item.roleKey === 'stadium')
}
// emit // emit
const emit = defineEmits(['nodeClick', 'changeBook']) const emit = defineEmits(['nodeClick', 'changeBook'])
let useSubject = null let useSubject = null
@ -139,7 +146,7 @@ const handleNodeClick = (data) => {
* data : 当前节点数据 * data : 当前节点数据
*/ */
let nodeData = cloneDeep(toRaw(data)); let nodeData = cloneDeep(toRaw(data));
//label label //label label
nodeData.label = nodeData.itemtitle nodeData.label = nodeData.itemtitle
let parentNode let parentNode
@ -181,13 +188,17 @@ onMounted( async () => {
curBook.data = sessionStore.get('subject.curBook') curBook.data = sessionStore.get('subject.curBook')
} }
else{ else{
curBook.data = subjectList.value[0] if (subjectList.value) {
curBook.data = subjectList.value[0]
}else {
curBook.data = {}
}
} }
// "" // ""
treeData.value = useSubject.getTreeData(curBook.data.id) treeData.value = useSubject.getTreeData(curBook.data.id)
sessionStore.set('subject.subjectTree',useSubject.getTreeData(curBook.data.id)) sessionStore.set('subject.subjectTree',useSubject.getTreeData(curBook.data.id))
nextTick(() =>{ nextTick(() =>{
// //
if(sessionStore.get('subject.curNode')){ if(sessionStore.get('subject.curNode')){
@ -199,7 +210,7 @@ onMounted( async () => {
} }
handleNodeClick(curNode.data) handleNodeClick(curNode.data)
}) })
} finally{ } finally{
treeLoading.value = false treeLoading.value = false
} }

View File

@ -41,6 +41,7 @@ const getFileTypeIcon = () => {
airobot: 'icon-jiqirenfushi', // airobot: 'icon-jiqirenfushi', //
aiimg: 'icon-xiangmuicon_maobishufa', // aiimg: 'icon-xiangmuicon_maobishufa', //
aidraw: 'icon-meishu-F', // aidraw: 'icon-meishu-F', //
aiyinyue: 'icon-yinle' //
} }
if (iconObj[name]) { if (iconObj[name]) {
return '#' + iconObj[name] return '#' + iconObj[name]

View File

@ -0,0 +1,269 @@
<!--
依赖 vuedraggablev-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>

View File

@ -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>

View File

@ -136,7 +136,7 @@ const handleNodeClick = (data) => {
id: nodeData.parentid, id: nodeData.parentid,
label: nodeData.parenttitle, label: nodeData.parenttitle,
itemtitle: nodeData.parenttitle itemtitle: nodeData.parenttitle
} }
const parentNode = nodeData.parentid ? parent : null const parentNode = nodeData.parentid ? parent : null
nodeData.parentNode = parentNode nodeData.parentNode = parentNode
@ -204,7 +204,11 @@ onMounted(async () => {
curBook.data = sessionStore.get('subject.curBook') curBook.data = sessionStore.get('subject.curBook')
} }
else{ 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) handleNodeClick(curNode.data)
}) })
}) })
</script> </script>

View File

@ -30,7 +30,7 @@
</div> </div>
</el-scrollbar> </el-scrollbar>
<div class="file-list"> <div class="file-list">
<el-dropdown @command="changeFile"> <el-dropdown @command="changeFile" v-if="type == 3">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
{{ curFile.fileName }} {{ curFile.fileName }}
<i class="iconfont icon-xiangxia"></i> <i class="iconfont icon-xiangxia"></i>
@ -54,11 +54,12 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue' import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { completion, docList } from '@/api/mode/index' import { completion, docList } from '@/api/mode/index'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { dataSetJson } from '@/utils/comm.js' import { dataSetJson } from '@/utils/comm.js'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { sendChart } from '@/api/ai/index'
import emitter from '@/utils/mitt'; import emitter from '@/utils/mitt';
const userInfo = useUserStore().user const userInfo = useUserStore().user
@ -71,12 +72,20 @@ const props = defineProps({
item: { item: {
type: Object, type: Object,
default: () => { default: () => {
return { name: '11' } return { name: '' }
} }
}, },
type: { type: {
type: Number, type: Number,
default: 1 default: 1
},
curMode:{
type: Number,
default: 1
},
conversation_id: {
type: [Number, String],
default: ''
} }
}) })
@ -100,7 +109,8 @@ const curNode = reactive({})
const params = reactive( const params = reactive(
{ {
prompt: '', prompt: '',
dataset_id: '' dataset_id: '',
template: ''
} }
) )
@ -108,7 +118,24 @@ const params = reactive(
const getCompletion = async (val) => { const getCompletion = async (val) => {
try { try {
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析` params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params) params.template = props.item.prompt
let data = null;
//
if(props.curMode == 1){
const res = await sendChart({
content: params.prompt,
conversationId: props.conversation_id,
stream: false
})
data = res.data
}
else{
//
const res = await completion(params)
data = res.data
}
let answer = data.answer let answer = data.answer
msgList.value.push({ msgList.value.push({
type: 'robot', type: 'robot',
@ -125,19 +152,6 @@ const saveAdjust = (item) =>{
emitter.emit('onSaveAdjust', item.msg) 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 curFile = reactive({})
const dataset_id = ref('') const dataset_id = ref('')
@ -160,11 +174,12 @@ const changeFile = (val) =>{
params.document_ids = val.docId params.document_ids = val.docId
} }
const modeType = ref('')
onMounted(() => { onMounted(() => {
let data = sessionStore.get('subject.curNode') let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data); Object.assign(curNode, data);
modeType.value = props.type == 1 ? '课标' : props.type == 2 ? '教材' : '考试'
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}` let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey] params.dataset_id = dataSetJson[jsonKey]
if(props.type == 3){ if(props.type == 3){

View File

@ -40,7 +40,7 @@ const props = defineProps({
type: Number, type: Number,
default: 1 default: 1
}, },
item: { // item: { //
type: Object, type: Object,
default: () => { default: () => {
return { ex3: '' } return { ex3: '' }
@ -73,23 +73,25 @@ const loading = ref(false)
const saveAdd = async () => { const saveAdd = async () => {
loading.value = true loading.value = true
if (props.item.ex3 == '1') { if (props.item.ex3 == '1') {
let id; // id id
if (props.item.isAdd) { if (props.item.isAdd) {
try { id = props.item.id
// 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
}
} }
else{ 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 { } else {
if (props.item.isAdd) { if (props.item.isAdd) {
onAddChildTemp(props.item.id) onAddChildTemp(props.item.id)
} }

View File

@ -16,12 +16,12 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick } from 'vue' import { ref, onMounted, nextTick, reactive } from 'vue'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import PDF from '@/components/PdfJs/index.vue' import PDF from '@/components/PdfJs/index.vue'
import LeftDialog from './left-dialog.vue' import LeftDialog from './left-dialog.vue'
const props = defineProps(['curNode', 'type']) const props = defineProps(['type'])
const showDialog = ref(false) const showDialog = ref(false)
const onClick = () => { const onClick = () => {
@ -31,8 +31,13 @@ const onClick = () => {
// PDF // PDF
const pdfUrl = ref('') const pdfUrl = ref('')
const curNode = reactive({})
onMounted(async () => { onMounted(async () => {
await nextTick() await nextTick()
//
let nodeData = sessionStore.get('subject.curNode')
Object.assign(curNode, nodeData);
let data = sessionStore.get('subject.curBook') let data = sessionStore.get('subject.curBook')
let fileurl = data.fileurl let fileurl = data.fileurl
if(props.type == 1){ if(props.type == 1){

View File

@ -14,7 +14,10 @@
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<div> <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 type="danger" link :disabled="!(templateList.length)" @click="removeItem(curTemplate, false)">
删除 删除
</el-button> </el-button>
@ -52,7 +55,8 @@
<i class="iconfont icon-ai"></i> <i class="iconfont icon-ai"></i>
</div> </div>
<div class="item-answer"> <div class="item-answer">
<TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow" @complete="handleCompleteText($event,index)" @updateScroll="scrollToBottom($event,index)" /> <TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow"
@complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" />
</div> </div>
</div> </div>
<div class="ai-btn" v-if="item.answer"> <div class="ai-btn" v-if="item.answer">
@ -77,24 +81,40 @@
<!--编辑结果--> <!--编辑结果-->
<EditDialog v-model="isEdit" :item="editItem" /> <EditDialog v-model="isEdit" :item="editItem" />
<!--AI 对话调整--> <!--AI 对话调整-->
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" /> <AdjustDialog v-model="isAdjust" :type="type" :item="editItem" :curMode="curMode" :conversation_id="conversation_id"/>
<!--添加编辑提示词--> <!--添加编辑提示词-->
<keywordDialog v-model="isWordDialog" :item="editItem" /> <keywordDialog v-model="isWordDialog" :item="editItem" :modeType="type" />
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, watch, onUnmounted, nextTick } from 'vue' import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index' import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
import { createChart, sendChart } from '@/api/ai/index'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import keywordDialog from './keyword-dialog.vue'; import keywordDialog from './keyword-dialog.vue';
import AdjustDialog from './adjust-dialog.vue' import AdjustDialog from './adjust-dialog.vue'
import EditDialog from './edit-dialog.vue' import EditDialog from './edit-dialog.vue'
import TypingEffect from '@/components/typing-effect/index.vue' import TypingEffect from '@/components/typing-effect/index.vue'
import useUserStore from '@/store/modules/user'
import emitter from '@/utils/mitt'; import emitter from '@/utils/mitt';
import { dataSetJson } from '@/utils/comm.js' import { dataSetJson } from '@/utils/comm.js'
import { cloneDeep } from 'lodash'
const props = defineProps(['curNode', 'type']) const props = defineProps(['type'])
const { user } = useUserStore()
const curMode = ref(2)
const modeOptions = ref([
{
label: '教学大模型',
value: 1
},
{
label: '知识库模型',
value: 2
}
])
/*****************提示词相关****************/ /*****************提示词相关****************/
@ -120,7 +140,7 @@ const editKeyWord = (item, val) => {
Object.assign(editItem, item) Object.assign(editItem, item)
editItem.isAdd = val editItem.isAdd = val
isWordDialog.value = true isWordDialog.value = true
} }
/*******************模板相关**********************/ /*******************模板相关**********************/
@ -137,9 +157,9 @@ const curTemplate = reactive({ name: '', id: '' })
const templateList = ref([]) const templateList = ref([])
const childTempList = ref([]) const childTempList = ref([])
const getTemplateList = () => { const getTemplateList = () => {
modelList({ model: props.type, type: 1, pageNum: 1, pageSize: 10000, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => { modelList({ createUser: user.userId, model: props.type, type: 1, pageNum: 1, pageSize: 10000, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
templateList.value = res.rows templateList.value = res.rows
if(res.rows.length > 0){ if (res.rows.length > 0) {
Object.assign(curTemplate, res.rows[0]); Object.assign(curTemplate, res.rows[0]);
getChildTemplate() getChildTemplate()
} }
@ -149,7 +169,7 @@ const getChildTemplate = () => {
tempLoading.value = true tempLoading.value = true
modelList({ model: props.type, type: 2, parentId: curTemplate.id, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => { modelList({ model: props.type, type: 2, parentId: curTemplate.id, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
childTempList.value = res.rows childTempList.value = res.rows
if(childTempList.value.length){ if (childTempList.value.length) {
childTempList.value.forEach(item => item.answer = '') childTempList.value.forEach(item => item.answer = '')
} }
getTempResult() getTempResult()
@ -161,7 +181,7 @@ const isStarted = ref([]);
const listRef = ref() const listRef = ref()
// //
const getTempResult = () => { const getTempResult = () => {
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000 }).then(res => { tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(res => {
let rows = res.rows let rows = res.rows
childTempList.value.forEach(item => { childTempList.value.forEach(item => {
rows.forEach(el => { rows.forEach(el => {
@ -171,28 +191,28 @@ const getTempResult = () => {
} }
}) })
}) })
if(rows.length > 0){ if (rows.length > 0) {
isStarted.value = new Array(rows.length).fill(true) isStarted.value = new Array(rows.length).fill(true)
} }
}) })
} }
const scrollToBottom = (height,index) =>{ const scrollToBottom = (height, index) => {
if (listRef.value) { if (listRef.value) {
let sum = 0 let sum = 0
let listDom = listRef.value.children let listDom = listRef.value.children
if(index == 0){ if (index == 0) {
// 220 // 220
let screenHeight = window.innerHeight - 220 let screenHeight = window.innerHeight - 220
if(height > screenHeight){ if (height > screenHeight) {
listRef.value.scrollTop = (height - screenHeight + 50) listRef.value.scrollTop = (height - screenHeight + 50)
} }
} }
else{ else {
for(let i = 0; i < index; i++){ for (let i = 0; i < index; i++) {
sum += listDom[i].clientHeight sum += listDom[i].clientHeight
} }
listRef.value.scrollTop = sum + height listRef.value.scrollTop = sum + height
@ -220,7 +240,7 @@ const changeTemplate = (val) => {
const removeItem = async (item, isChild) => { const removeItem = async (item, isChild) => {
/** /**
* item: 当前操作的模板 * item: 当前操作的模板
* isChild: 子模板中的移除为 true * isChild: 子模板中的移除为 true
*/ */
if (item.ex3 != '1') { if (item.ex3 != '1') {
ElMessageBox.confirm( ElMessageBox.confirm(
@ -250,7 +270,6 @@ const removeItem = async (item, isChild) => {
} }
} }
// Ai // Ai
const curIndex = ref(-1) const curIndex = ref(-1)
const isAdjust = ref(false) const isAdjust = ref(false)
@ -268,21 +287,6 @@ const onEdit = (index, item) => {
isEdit.value = true isEdit.value = true
} }
const modeType = ref('课标')
watch(() => props.type, (newVal) => {
if (newVal == 1) {
modeType.value = '课标'
}
if (newVal == 2) {
modeType.value = '教材'
}
if (newVal == 3) {
modeType.value = '考试'
}
}, { immediate: false })
// //
const params = reactive( const params = reactive(
{ {
@ -290,6 +294,7 @@ const params = reactive(
dataset_id: '' dataset_id: ''
} }
) )
const prompt = ref('')
// //
const isAgain = ref(false) const isAgain = ref(false)
@ -298,10 +303,10 @@ const againResult = async (index, item) => {
isStarted.value[index] = false isStarted.value[index] = false
childTempList.value[index].answer = '' childTempList.value[index].answer = ''
if(index == 0){ if (index == 0) {
listRef.value.scrollTop = 0 listRef.value.scrollTop = 0
}else{ } else {
scrollToBottom(50, index) scrollToBottom(50, index)
} }
@ -309,8 +314,28 @@ const againResult = async (index, item) => {
await nextTick() await nextTick()
childTempList.value[index].loading = true childTempList.value[index].loading = true
item.aiShow = true item.aiShow = true
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params) 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); childTempList.value[index].answer = getResult(data.answer);
isStarted.value[index] = true isStarted.value[index] = true
@ -318,13 +343,14 @@ const againResult = async (index, item) => {
childTempList.value[index].loading = false childTempList.value[index].loading = false
} }
} }
// //
const getCompletion = async () => { const getCompletion = async () => {
isStarted.value = new Array(childTempList.length).fill(false) isStarted.value = new Array(childTempList.length).fill(false)
isStarted.value[0] = true isStarted.value[0] = true
childTempList.value.forEach(item =>{ childTempList.value.forEach(item => {
if(item.answer){ if (item.answer) {
item.answer = '' item.answer = ''
} }
}) })
@ -333,8 +359,27 @@ const getCompletion = async () => {
try { try {
item.loading = true item.loading = true
item.aiShow = true item.aiShow = true
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析` let str = cloneDeep(prompt.value)
const { data } = await completion(params) str = str.replace('{模板标题}',item.name)
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) item.answer = getResult(data.answer)
onSaveTemp(item) onSaveTemp(item)
} finally { } finally {
@ -343,14 +388,14 @@ const getCompletion = async () => {
} }
} }
const handleCompleteText = async (answer, index) =>{ const handleCompleteText = async (answer, index) => {
if (index < childTempList.value.length - 1) { if (index < childTempList.value.length - 1) {
isStarted.value[index + 1] = true; // isStarted.value[index + 1] = true; //
} }
if(isAgain.value){ if (isAgain.value) {
try{ try {
await editTempResult({ id: childTempList.value[index].resultId, content: answer }) await editTempResult({ id: childTempList.value[index].resultId, content: answer })
}finally{ } finally {
isAgain.value = false isAgain.value = false
} }
} }
@ -370,16 +415,20 @@ const onEditSave = async (item) => {
} }
// //
const onSaveTemp = (item) => { const onSaveTemp = async (item) => {
if (item.answer == '') return if (item.answer == '') return
const data = { const data = {
mainModelId: curTemplate.id, mainModelId: curTemplate.id,
modelId: item.id, modelId: item.id,
examDocld: '', examDocld: '',
content: item.answer content: item.answer,
ex1: curNode.id
}
const res = await tempSave(data)
if(!item.resultId){
item.resultId = res.data
} }
tempSave(data).then(res => { })
} }
// ### ** // ### **
@ -398,13 +447,49 @@ emitter.on('onGetMain', () => {
}) })
//
const conversation_id = ref('')
const getChartId = () => {
createChart({ app_id: '712ff0df-ed6b-470f-bf87-8cfbaf757be5' }).then(res => {
localStorage.setItem("conversation_id", res.data.conversation_id);
conversation_id.value = res.data.conversation_id;
})
}
// prompt
const getPrompt = async () => {
const { rows } = await modelList({ model: 5 })
let str = rows.find(item => item.name.indexOf(modeType.value) != -1).prompt
str = str.replace('{学段}', curNode.edustage)
str = str.replace('{学科}', curNode.edusubject)
let bookV = curNode.roottitle.split('-')[1] + '版本'
str = str.replace('{教材版本}', bookV)
str = str.replace('{课程名称}', `${curNode.itemtitle}`)
if(modeType.value == '课标'){
str = str.replace('{课标名称}', `${curNode.edustage}${curNode.edusubject}课标`)
}
prompt.value = str
}
const curNode = reactive({}) const curNode = reactive({})
const modeType = ref('')
onMounted(() => { onMounted(() => {
let data = sessionStore.get('subject.curNode') let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data); Object.assign(curNode, data);
modeType.value = props.type == 1 ? '课标' : props.type == 2 ? '教材' : '考试'
getTemplateList() getTemplateList()
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}` let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey] params.dataset_id = dataSetJson[jsonKey]
// ID
conversation_id.value = localStorage.getItem('conversation_id')
if (!conversation_id.value) {
getChartId();
}
// prompt
getPrompt()
}) })
// //
@ -524,4 +609,4 @@ onUnmounted(() => {
width: 110px !important; width: 110px !important;
min-width: 110px !important; min-width: 110px !important;
} }
</style> </style>

View File

@ -2,18 +2,16 @@
<div class="page-template flex"> <div class="page-template flex">
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<Left :curNode="curNode" :type="type" /> <Left :type="type" />
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<Right :curNode="curNode" :type="type" /> <Right :type="type" />
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script setup> <script setup>
import { reactive, onMounted } from 'vue'
import { sessionStore } from '@/utils/store'
import Left from './container/left.vue' import Left from './container/left.vue'
import Right from './container/right.vue' import Right from './container/right.vue'
@ -23,13 +21,6 @@ const props = defineProps({
default: 1 default: 1
}, },
}) })
const curNode = reactive({})
onMounted(() =>{
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -15,7 +15,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<div class="blockBox"> <div class="blockBox">
<el-button @click="currentType = 'selection'"><el-image src="../../../src/assets/images/mouse-pointer.png" <el-button @click="currentType = 'selection'"><el-image :src="pointerImg"
style="width: 14px; height: 14px; color: silver" /></el-button> style="width: 14px; height: 14px; color: silver" /></el-button>
</div> </div>
<template v-if="type == 'design'"> <template v-if="type == 'design'">
@ -145,7 +145,7 @@
<!-- 边框粗细 --> <!-- 边框粗细 -->
<div class="blockBox"> <div class="blockBox">
<el-dropdown @command="updateStyle('lineWidth', $event)" placement="top"> <el-dropdown @command="updateStyle('lineWidth', $event)" placement="top">
<el-button><el-image src="../../../src/assets/images/borderwidth.png" <el-button><el-image :src="borderImg"
style="width: 14px; height: 14px"></el-image></el-button> style="width: 14px; height: 14px"></el-image></el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
@ -303,6 +303,9 @@ import {
import Contextmenu from './components/Contextmenu.vue' import Contextmenu from './components/Contextmenu.vue'
import { fontFamilyList, fontSizeList } from './constants' import { fontFamilyList, fontSizeList } from './constants'
const borderImg = new URL('../../../src/assets/images/borderwidth.png', import.meta.url).href
const pointerImg = new URL('../../../src/assets/images/mouse-pointer.png', import.meta.url).href
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Boolean, type: Boolean,
@ -504,6 +507,11 @@ const backToCenter = () => {
app.scrollToCenter() app.scrollToCenter()
} }
const cancelActiveElement = () =>{
app.cancelActiveElement()
}
// //
const showFit = () => { const showFit = () => {
let elementList = app.elements.elementList let elementList = app.elements.elementList
@ -697,8 +705,6 @@ const getCanvasBlob = async () =>{
}) })
} }
watch(() => props.data, (newVal) => { watch(() => props.data, (newVal) => {
if (newVal) { if (newVal) {
setCanvasData(newVal) setCanvasData(newVal)
@ -788,7 +794,8 @@ defineExpose({
getCanvasJson, getCanvasJson,
getCanvasBase64, getCanvasBase64,
setCanvasData, setCanvasData,
getCanvasBlob getCanvasBlob,
cancelActiveElement
}) })
</script> </script>

View File

@ -38,6 +38,7 @@ const closeWindow = () => {
ElMessageBox.confirm('确认退出系统吗?', '提示', { ElMessageBox.confirm('确认退出系统吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
customClass: 'login-close-tool',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
userStore.logOut().then(() => { userStore.logOut().then(() => {
@ -54,7 +55,11 @@ onMounted(() =>{
}) })
</script> </script>
<style>
.login-close-tool {
-webkit-app-region: no-drag;
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.header-tool { .header-tool {
width: 100%; width: 100%;
@ -81,4 +86,4 @@ onMounted(() =>{
} }
} }
} }
</style> </style>

View File

@ -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){
},
}

View File

@ -32,7 +32,7 @@ export const editListItem = (row, courseObj) => {
worktype: '', // 设计中的作业类型 worktype: '', // 设计中的作业类型
quizlist: [], // 设计中的试题列表 quizlist: [], // 设计中的试题列表
chooseWorkLists: [],// 设计中的框架梳理list chooseWorkLists: [],// 设计中的框架梳理list
fileHomeworkList: [],// 设计中的常规作业list fileHomeworkList: [],//TODO 暂时共用这个字段(新增了 科学实验) 设计中的常规作业list
whiteboardObj: '',// 设计中的课堂展示对象 whiteboardObj: '',// 设计中的课堂展示对象
question: '', // 设计中的[课堂展示]的问题 question: '', // 设计中的[课堂展示]的问题
}; };
@ -58,7 +58,7 @@ export const editListItem = (row, courseObj) => {
if (row.worktype == '框架梳理') { if (row.worktype == '框架梳理') {
// 框架梳理对应只有一个内容 // 框架梳理对应只有一个内容
getEvaluationclue(listCourseWork[0].id).then(res => { getEvaluationclue(listCourseWork[0].id).then(res => {
if ( res.data==null || res.data==undefined ) { if ( res.data==null || res.data==undefined ) {
return ; return ;
@ -112,6 +112,16 @@ export const editListItem = (row, courseObj) => {
return resolve(classtaskObj); 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);
}
}
} }
}); });
} }

View File

@ -87,13 +87,15 @@ const getHomeWorkList = async () => {
// } else // } else
// 课标研读 目标设定 教材研读 框架梳理 学科定位 TODO 后续接入在添加 // 课标研读 目标设定 教材研读 框架梳理 学科定位 TODO 后续接入在添加
if (res.rows[i].worktype == '课堂展示') { if (res.rows[i].worktype == '课堂展示') {
res.rows[i].workclass = 'primary'; res.rows[i].workclass = 'success';
} else if (res.rows[i].worktype == '框架梳理') { } else if (res.rows[i].worktype == '框架梳理') {
res.rows[i].workclass = 'warning'; res.rows[i].workclass = 'warning';
} else if (res.rows[i].worktype == '常规作业') { } else if (res.rows[i].worktype == '常规作业') {
res.rows[i].workclass = 'info'; res.rows[i].workclass = 'info';
} else if (res.rows[i].worktype == '习题训练') { } else if (res.rows[i].worktype == '习题训练') {
res.rows[i].workclass = 'danger'; res.rows[i].workclass = 'danger';
} else if (res.rows[i].worktype == '科学实验') {
res.rows[i].workclass = 'primary';
} else { } else {
res.rows[i].workclass = 'primary'; res.rows[i].workclass = 'primary';
} }

View File

@ -15,24 +15,40 @@ export const useGetSubject = async () =>{
// 单元章节树结构 // 单元章节树结构
let treeData = null let treeData = null
// 根据学科 + 学段 获取所有单元章节 // 根据学科 + 学段 获取所有单元章节
const getSubjectUnit = async () =>{ const getSubjectUnit = async () =>{
if(sessionStore.get('subject.unitList')){ if(isStadium(userStore.user)) {
unitList.value = sessionStore.get('subject.unitList') //如果是基地人员直接拿treeData
}
else{
const unitParams = { const unitParams = {
edusubject, itemgroup: '基地课程',
edustage,
itemgroup: 'textbook',
orderby: 'orderidx asc', orderby: 'orderidx asc',
entpid: userStore.user.deptId,
pageSize: 10000 pageSize: 10000
} }
const { rows } = await listEvaluation(unitParams) const { rows } = await listEvaluation(unitParams)
unitList.value = rows 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') subjectList = sessionStore.get('subject.bookList')
} }
else{ else{
const subjectParams = { const subjectParams = {
itemkey: "version", itemkey: "version",
edusubject, edusubject,
edustage, edustage,
@ -68,11 +84,14 @@ export const useGetSubject = async () =>{
// 单元章节数据转为“树”结构 // 单元章节数据转为“树”结构
const getTreeData = (bookId) =>{ const getTreeData = (bookId) =>{
// 根据当前教材的id 查找出对应的章节 if (!bookId) {
let data = unitList.value.filter(item => item.rootid == bookId && item.level == 1) return unitList.value
}
// 根据当前教材的id 查找出对应的章节
let data = unitList.value.filter(item => item.rootid == bookId && item.level == 1)
data.forEach( item => { data.forEach( item => {
item.children = unitList.value.filter( item2 => item2.parentid == item.id && item2.level == 2) item.children = unitList.value.filter( item2 => item2.parentid == item.id && item2.level == 2)
}) })
return data return data
} }
@ -80,4 +99,4 @@ export const useGetSubject = async () =>{
return { subjectList, treeData, getTreeData } return { subjectList, treeData, getTreeData }
} }

View File

@ -5,13 +5,13 @@
<el-popover ref="popoverRef" placement="right" trigger="hover" popper-class="popoverStyle" :tabindex="999" > <el-popover ref="popoverRef" placement="right" trigger="hover" popper-class="popoverStyle" :tabindex="999" >
<template #reference> <template #reference>
<div class="user-info"> <div class="user-info">
<el-image class="user-img" :src="dev_api + userStore.user.avatar" /> <el-image class="user-img" :src="userStore.user.avatar ==='/img/avatar-default.jpg' || userStore.user.avatar ==='/images/img-avatar.png' ? defaultUserImg : dev_api + userStore.user.avatar" />
<span>{{ userStore.user.nickName }}</span> <span>{{ userStore.user.nickName }}</span>
</div> </div>
</template> </template>
<div class="head-aside"> <div class="head-aside">
<ul > <ul >
<li :class="computedregistertype==1 || computedregistertype==3?'auth-li':'auth-li pointer-events'" @click="onUserTo('/schoolCertification')" > <li v-if="isStadium() !== true" :class="computedregistertype==1 || computedregistertype==3?'auth-li':'auth-li pointer-events'" @click="onUserTo('/schoolCertification')" >
<i class="iconfont icon-renzheng-" :style="computedregistertype==4?'color:green;':''"></i> <i class="iconfont icon-renzheng-" :style="computedregistertype==4?'color:green;':''"></i>
<span class="mlr-5" v-if="computedregistertype!=4">学校认证</span> <span class="mlr-5" v-if="computedregistertype!=4">学校认证</span>
<span class="mlr-5" v-else>{{ userStore.DeptInfo.register.schoolName }}</span> <span class="mlr-5" v-else>{{ userStore.DeptInfo.register.schoolName }}</span>
@ -19,8 +19,8 @@
</li> </li>
<li v-if="computedregistertype!=4" :class="computedregistertype==1 || computedregistertype==2 ? '':'pointer-events'" @click="onUserTo('/joinSchool')">加入学校</li> <li v-if="computedregistertype!=4" :class="computedregistertype==1 || computedregistertype==2 ? '':'pointer-events'" @click="onUserTo('/joinSchool')">加入学校</li>
<li @click="onUserTo('/profile')">个人中心</li> <li @click="onUserTo('/profile')">个人中心</li>
<li @click="onUserTo('/schoolManagement')">学校管理</li> <li v-if="isStadium() !== true" @click="onUserTo('/schoolManagement')">学校管理</li>
<li @click="onUserTo('/class')">班级中心</li> <li v-if="isStadium() !== true" @click="onUserTo('/class')">班级中心</li>
<li @click="logout">退出登录</li> <li @click="logout">退出登录</li>
</ul> </ul>
</div> </div>
@ -53,12 +53,13 @@
</template> </template>
<script setup> <script setup>
import { ref, watch , reactive, onMounted,computed} from 'vue' import { ref, watch , reactive, onMounted, onBeforeMount, computed} from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { sessionStore } from '@/utils/store'
import pkc from "../../../../../package.json" import pkc from "../../../../../package.json"
import defaultUserImg from '@/assets/images/img-avatar.png'
import { sessionStore } from '@/utils/store'
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
@ -74,19 +75,31 @@ const version = ref(pkc.version)
const popoverRef = ref('') const popoverRef = ref('')
const headerMenus = [ //
const isStadium = () => {
let user = userStore.user
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')
}
const headerMenus = isStadium() ?[{
name: '教学实践',
id: 6,
icon: 'icon-jiaoxueshijian',
path: '/prepare'
},]:[
{ {
name: '教学大模型', name: '教学大模型',
id: 1, id: 1,
icon: 'icon-shouye', icon: 'icon-shouye',
path: '/model/index' path: '/model/index'
}, },
{ // {
name: '教学工作台', // name: '',
id: 2, // id: 2,
icon: 'icon-gongzuotai', // icon: 'icon-gongzuotai',
path: '/desktop' // path: '/desktop'
}, // },
{ {
name: '教学实践', name: '教学实践',
id: 4, id: 4,
@ -169,9 +182,9 @@ watch(
const logout = () => { const logout = () => {
const hasClass = sessionStore.has('activeClass.id')
const hasTool = sessionStore.get('isToolWin') if(!!sessionStore.get('curr.classcourse'))return ElMessage.warning('当前正在上课,请先结束上课')
if (hasClass || hasTool) return ElMessage.warning('当前正在上课,请先结束上课')
ElMessageBox.confirm('确认退出系统吗?', '提示', { ElMessageBox.confirm('确认退出系统吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',

View File

@ -44,7 +44,6 @@ const breadList = ref([])
watch( watch(
() => router.currentRoute.value, () => router.currentRoute.value,
(newValue) => { (newValue) => {
console.log(newValue)
const { showBread } = newValue.meta const { showBread } = newValue.meta
if (showBread) { if (showBread) {
isShowBack.value = true isShowBack.value = true

View File

@ -16,6 +16,7 @@ import router from './router'
import log from 'electron-log/renderer' // 渲染进程日志-文件记录 import log from 'electron-log/renderer' // 渲染进程日志-文件记录
import customComponent from '@/components/common' // 自定义组件 import customComponent from '@/components/common' // 自定义组件
import plugins from './plugins' // plugins插件 import plugins from './plugins' // plugins插件
import useUserStore from '@/store/modules/user'
if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件 if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件
Object.assign(console, log.functions) // 渲染进程日志-控制台替换 Object.assign(console, log.functions) // 渲染进程日志-控制台替换
@ -23,7 +24,6 @@ if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印
const app = createApp(App) const app = createApp(App)
//专为菁优网配置的请求转发 //专为菁优网配置的请求转发
app.config.globalProperties.$requestGetJYW = (url,config)=>{ app.config.globalProperties.$requestGetJYW = (url,config)=>{
config.params = config.params?config.params:{} config.params = config.params?config.params:{}
@ -47,3 +47,16 @@ app.use(router)
.use(Icon) .use(Icon)
.use(Directive) .use(Directive)
.mount('#app') .mount('#app')
const isStadium = (user) => {
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')
}
router.beforeEach((to, from, next) => {
if (to.path === '/model/index') {
isStadium(useUserStore().user) === true ? next('/prepare') :next()
}else {
next()
}
})

View File

@ -80,6 +80,8 @@ export class MsgEnum {
MSG_anmationclick : 'anmationclick', MSG_anmationclick : 'anmationclick',
/** @desc: 群组创建成功 */ /** @desc: 群组创建成功 */
MSG_classcourseopen : 'classcourseopen', MSG_classcourseopen : 'classcourseopen',
/** @desc: 学生提交作业 */
MSG_finishHomework : 'finishHomework',
/** @desc: 学生的测练结果反馈 */ /** @desc: 学生的测练结果反馈 */
MSG_classquizfeedback : 'classquizfeedback', MSG_classquizfeedback : 'classquizfeedback',
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */ /** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
@ -94,6 +96,16 @@ export class MsgEnum {
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate', MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
/** @desc: 课堂讲授活动,选择不同的内容 */ /** @desc: 课堂讲授活动,选择不同的内容 */
MSG_classlecturePagesrc : 'classlecturePagesrc', MSG_classlecturePagesrc : 'classlecturePagesrc',
/** @desc: 课堂作业|活动 */
MSG_homework : 'HOMEWORK',
/** @desc: 公屏 - 课堂作业|活动 */
MSG_pushSreen_work : 'pushSreen_work',
/** @desc: 公屏 - 实验 */
MSG_pushSreen_experiment : 'pushSreen_experiment',
/** @desc: 点赞 */
MSG_dz : 'dz',
/** @desc: 疑惑 */
MSG_yh : 'yh',
// === 新定义-消息头 === // === 新定义-消息头 ===
/** @desc: 课程创建-待开课 */ /** @desc: 课程创建-待开课 */
MSG_0000: 0x0000, MSG_0000: 0x0000,
@ -138,4 +150,4 @@ export class MsgEnum {
} }
} }
export { MsgEnum as default } export default MsgEnum;

View File

@ -28,6 +28,7 @@ export class ChatWs {
const userStore = useUserStore() // 用户信息 const userStore = useUserStore() // 用户信息
const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址 const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址
this.url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`; this.url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`;
this.closed = false; // 关闭状态 防止重连失败
// this.init(url); // this.init(url);
} }
ChatWs.instance = this; ChatWs.instance = this;
@ -38,6 +39,7 @@ export class ChatWs {
// 初始化 // 初始化
init(url) { init(url) {
!!url && (this.url = url); !!url && (this.url = url);
this.closed = false; // 关闭状态 防止重连失败
this.ws = null; this.ws = null;
const _this = this const _this = this
this.heartCheck = { this.heartCheck = {
@ -64,43 +66,46 @@ export class ChatWs {
}, this.timeout); }, this.timeout);
}, },
}; };
this.reconnect(); return this.reconnect();
} }
// 重连 // 重连
reconnect() { reconnect() {
const self = this; return new Promise((resolve, reject) => {
if (!!this.ws) { // 关闭之前的链接 const self = this;
this.ws.close() if (!!this.ws) { // 关闭之前的链接
this.ws = null this.ws.close()
} this.ws = null
this.ws = new WebSocket(this.url); }
this.ws.onopen = function () { this.ws = new WebSocket(this.url);
console.log("websocket-连接成功") this.ws.onopen = function () {
self.heartCheck.reset().start(); console.log("websocket-连接成功")
}; self.heartCheck.reset().start();
this.ws.onmessage = function (e) { resolve()
// console.log("websocket-收到消息", e) };
// 拿到任何消息都说明当前连接是正常的 this.ws.onmessage = function (e) {
const isBeat = e.data == 'pong' // console.log("websocket-收到消息", e)
isBeat && self.heartCheck.reset().start(); // 拿到任何消息都说明当前连接是正常的
const isEmpty = !e.data const isBeat = e.data == 'pong'
const isExts = e.data.includes('sessionId') || e.data == ('pong') isBeat && self.heartCheck.reset().start();
if (isEmpty || isExts) return; const isEmpty = !e.data
// 自定义处理 const isExts = e.data.includes('sessionId') || e.data == ('pong')
self.onmessage && self.onmessage(e.data, e); if (isEmpty || isExts) return;
}; // 自定义处理
this.ws.onerror = function (e) { self.onmessage && self.onmessage(e.data, e);
console.log("websocket-连接异常", e) };
self.connectSocket() // 重连 this.ws.onerror = function (e) {
}; console.log("websocket-连接异常", e)
this.ws.onclose = function (e) { self.connectSocket() // 重连
console.log("websocket-连接断开", e) };
self.connectSocket() // 重连 this.ws.onclose = function (e) {
}; console.log("websocket-连接断开", e)
self.connectSocket() // 重连
};
})
} }
connectSocket() { connectSocket() {
this.heartCheck.reset() // 重置心跳 this.heartCheck.reset() // 重置心跳
if (self.closed) return; // 关闭状态不重连 if (this.closed) return this.ws = null; // 关闭状态不重连
// if(self.errCount <= 0) return; // 超过重连次数 // if(self.errCount <= 0) return; // 超过重连次数
// self.errCount--; // 重连次数减1 // self.errCount--; // 重连次数减1
if (this.errTime) { if (this.errTime) {
@ -157,6 +162,13 @@ export class ChatWs {
this.closed = true; this.closed = true;
this.ws.close(); this.ws.close();
} }
// 下课
closedCourse(id) {
return new Promise((resolve, reject) => {
this.sendMsg('closed', '下课', null, 'group', id)
resolve()
})
}
// 延时 ms 毫秒 // 延时 ms 毫秒
sleep(ms){ sleep(ms){
return new Promise(resolve => setTimeout(resolve, ms)) return new Promise(resolve => setTimeout(resolve, ms))

View File

@ -31,6 +31,11 @@ export const constantRoutes = [
component: () => import('@/AixPPTist/src/App.vue'), component: () => import('@/AixPPTist/src/App.vue'),
hidden: true hidden: true
}, },
{
path: '/gridPic',
component: () => import('@/components/grid-pic/index.vue'),
hidden: true
},
{ {
path: '/model', path: '/model',
component: Layout, component: Layout,
@ -83,7 +88,7 @@ export const constantRoutes = [
path: 'questionUpload', path: 'questionUpload',
component: () => import('@/views/classTask/newClassTaskAssign/questionUpload/index.vue'), component: () => import('@/views/classTask/newClassTaskAssign/questionUpload/index.vue'),
name: 'questionUpload', name: 'questionUpload',
meta: { title: '习题上传' } meta: { title: '习题上传', showBread: true }
}, },
{ {
path: 'aiKolors', path: 'aiKolors',
@ -101,7 +106,7 @@ const dynamicRoutes = [
{ {
path: '/', path: '/',
component: Layout, component: Layout,
redirect: '/desktop', redirect: '/model/index',
meta: { title: '教学工作台' }, meta: { title: '教学工作台' },
children: [ children: [
{ {

View File

@ -5,6 +5,12 @@ import { JYApiListCT, JYApiListOriginYear, JYApiListSO} from "@/utils/examQuesti
const useClassTaskStore = defineStore('classTask',{ const useClassTaskStore = defineStore('classTask',{
state: () => ({ state: () => ({
experimentObj:{
edustage: '小学', // 教育阶段
edusubject: '', // 学科
experimentList: [], // 实验科目列表
},
isOpenQuestUploadView: false, // 是否打开习题上传的页面
classListIds: [], classListIds: [],
entpCourseWorkTypeList: [ entpCourseWorkTypeList: [
{value: 0, label: "不限"}, {value: 0, label: "不限"},

View File

@ -49,10 +49,10 @@ export function blobToFile(blob, fileName, contentType) {
/** /**
* @description 计算两点直线距离 (获取直径) * @description 计算两点直线距离 (获取直径)
* (欧几里得距离公式): [ \text{distance} = \sqrt{(x2 - x1)^2 + (y2 - y1)^2} ] * (欧几里得距离公式): [ \text{distance} = \sqrt{(x2 - x1)^2 + (y2 - y1)^2} ]
* @param {*} x1 * @param {*} x1
* @param {*} y1 * @param {*} y1
* @param {*} x2 * @param {*} x2
* @param {*} y2 * @param {*} y2
*/ */
export function getDistance(x1,y1,x2,y2) { export function getDistance(x1,y1,x2,y2) {
return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2)) 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以确保百分比的合理范围 * 如果计算结果小于0则返回0如果大于100则返回100以确保百分比的合理范围
* *
* @param {number} v - 待计算的值 * @param {number} v - 待计算的值
* @param {number} total - 总数 * @param {number} total - 总数
* @param {number} [step=2] - 百分比的小数位数默认为2 * @param {number} [step=2] - 百分比的小数位数默认为2
@ -76,7 +76,7 @@ export function getPercent(v, total, step=2) {
!total && (total = 1) !total && (total = 1)
// 计算百分比,保留指定的小数位,并转换为数字类型 // 计算百分比,保留指定的小数位,并转换为数字类型
let res = (v / total * 100).toFixed(step)-0 let res = (v / total * 100).toFixed(step)-0
// 确保百分比在0到100之间 // 确保百分比在0到100之间
return res < 0 ? 0 : res > 100 ? 100 : res return res < 0 ? 0 : res > 100 ? 100 : res
} }
@ -89,7 +89,7 @@ export function getPercent(v, total, step=2) {
* @param {*} start 前面保留几位 默认 3 * @param {*} start 前面保留几位 默认 3
* @param {*} end 后面保留几位 默认 3 * @param {*} end 后面保留几位 默认 3
* @param {*} rstr 替换字符 默认 **** * @param {*} rstr 替换字符 默认 ****
* @returns * @returns
*/ */
export function phoneHideFormat(phone, start = 3, end = 4, rstr = '****') { export function phoneHideFormat(phone, start = 3, end = 4, rstr = '****') {
// const reg = /^(\d{3})\d*(\d{4})$/ // const reg = /^(\d{3})\d*(\d{4})$/
@ -101,8 +101,8 @@ export function phoneHideFormat(phone, start = 3, end = 4, rstr = '****') {
// ============= 习题工具--相关 =================== // ============= 习题工具--相关 ===================
/** /**
* @description 将字符串转换为数组 * @description 将字符串转换为数组
* @param {*} str * @param {*} str
* @returns * @returns
*/ */
export function quizStrToList(str = '') { export function quizStrToList(str = '') {
if (!str) return [] if (!str) return []
@ -175,8 +175,8 @@ export const validateUrl = (url) => {
/** /**
* @description 时间 转化 时分秒 * @description 时间 转化 时分秒
* @param {*} seconds * @param {*} seconds
* @returns * @returns
*/ */
export function formatTime(seconds) { export function formatTime(seconds) {
seconds = parseInt(seconds) // 转换整数 seconds = parseInt(seconds) // 转换整数
@ -188,9 +188,9 @@ export function formatTime(seconds) {
/** /**
* @description 时间格式化 * @description 时间格式化
* @param {*} time * @param {*} time
* @param {*} fmt * @param {*} fmt
* @returns * @returns
*/ */
export function formatDate(time, fmt = 'yyyy-MM-dd') { export function formatDate(time, fmt = 'yyyy-MM-dd') {
let date let date
@ -300,7 +300,7 @@ export function getDateStr1(num, fmt = 'yyyy-MM-dd hh:mm:ss') {
} }
/** /**
* 获取当前 0:0:0:0 时间 * 获取当前 0:0:0:0 时间
* @param {*} [fmt] 格式 'yyyy-MM-dd hh:mm:ss' * @param {*} [fmt] 格式 'yyyy-MM-dd hh:mm:ss'
*/ */
export function getDateNow(fmt) { export function getDateNow(fmt) {
const date = new Date() const date = new Date()
@ -339,9 +339,9 @@ export function timeToStr(time,str = '时分秒', isPad = false) {
* debounce(() => { * debounce(() => {
console.log('Input event handled'); console.log('Input event handled');
}, 300); }, 300);
* @param {*} func * @param {*} func
* @param {*} wait * @param {*} wait
* @returns * @returns
*/ */
export function debounce(func, wait) { export function debounce(func, wait) {
let timeout; let timeout;
@ -358,9 +358,9 @@ export function debounce(func, wait) {
* throttle(() => { * throttle(() => {
console.log('Scroll event handled'); console.log('Scroll event handled');
}, 300); }, 300);
* @param {*} func * @param {*} func
* @param {*} wait * @param {*} wait
* @returns * @returns
*/ */
export function throttle(func, wait) { export function throttle(func, wait) {
let lastTime = 0; let lastTime = 0;
@ -376,7 +376,7 @@ export function throttle(func, wait) {
} }
/** /**
* *
* 大模型对话dataset_id * 大模型对话dataset_id
*/ */
export const dataSetJson = { export const dataSetJson = {
@ -395,6 +395,19 @@ export const dataSetJson = {
"课标-高中-英语": "e889fcac9fd011efb22a0242ac140006", "课标-高中-英语": "e889fcac9fd011efb22a0242ac140006",
"课标-高中-数学": "e03aa4fe9fd011ef91270242ac140006", "课标-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
"课标-高中-地理": "270516829fd111efb13c0242ac140006", "课标-高中-地理": "270516829fd111efb13c0242ac140006",
"课标-高中-政治": "a7df2b01aafd11ef8bb40242ac140002", "课标-高中-政治": "a2f0b247b85d11ef84290242ac140005",
"教材-高中-语文": "cee3062a9fcf11efa6910242ac140006",
"教材-高中-生物": "fb5d01d59fd011ef9bb90242ac140006",
"教材-高中-历史": "f2f6c1fb9fd011ef98740242ac140006",
"教材-高中-英语": "e889fcac9fd011efb22a0242ac140006",
"教材-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
"教材-高中-地理": "270516829fd111efb13c0242ac140006",
"教材-高中-政治": "a2f0b247b85d11ef84290242ac140005",
"课标-小学-科学": "935cfec8bf6a11ef98950242ac140006",
"课标-小学-数学": "3c4e298fbf7911ef8e8b0242ac140002",
"课标-小学-语文": "f76f1aa5bf7111ef90c80242ac140002",
"教材-小学-科学": "935cfec8bf6a11ef98950242ac140006",
"教材-小学-数学": "3c4e298fbf7911ef8e8b0242ac140002",
"教材-小学-语文": "f76f1aa5bf7111ef90c80242ac140002",
"鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm" "鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm"
} }

View File

@ -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',
})
})
}

View File

@ -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"
}
]
}
}

View File

@ -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)
}

View File

@ -57,24 +57,31 @@ export const resourceFormat = [
// 资源类型 // 资源类型
export const resourceType = [ export const resourceType = [
{ {
id:1,
label: '课例库', label: '课例库',
value: "'apt','课件','教案'" value: "'apt','课件','教案'"
}, },
{ // {
label: '作业库', // label: '作业库',
value: '作业', // value: '作业',
disabled: true // disabled: true
}, // },
{ {
id:2,
label: '素材库', label: '素材库',
value: "'素材'" value: "'素材'"
}, },
{ {
label: '习题库', id:3,
value: '习题', label: '实验室',
disabled: true value: "'素材'"
} },
// {
// label: '习题库',
// value: '习题',
// disabled: true
// }
] ]
// 年级划分 // 年级划分
export const gradeList = [ export const gradeList = [

View File

@ -9,7 +9,7 @@ export const asyncLocalFile = (item) => {
if (isAsync === true) { if (isAsync === true) {
item.async = 'on' item.async = 'on'
if (type === 'down') { if (type === 'down') {
console.log(item) // console.log(item)
ipcRenderer.send('download-file-default', { ipcRenderer.send('download-file-default', {
url: item.fileFullPath, url: item.fileFullPath,
fileName: item.fileNewName fileName: item.fileNewName

View File

@ -225,7 +225,7 @@ export const createWindow = async (type, data) => {
.filter(k => typeof data[k] === 'function') .filter(k => typeof data[k] === 'function')
.forEach(k => events[k] = data[k]) .forEach(k => events[k] = data[k])
eventHandles(type, win, events) // 事件监听处理 eventHandles(type, win, events) // 事件监听处理
break return win
} }
default: default:
break break
@ -248,10 +248,11 @@ export function toolWindow(type, {url, isConsole, isWeb=true, option={}}) {
const devUrl = `${BaseUrl}${url}` const devUrl = `${BaseUrl}${url}`
const buildUrl = path.join(__dirname, 'index.html') const buildUrl = path.join(__dirname, 'index.html')
const urlAll = isDev ? devUrl : buildUrl 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) => { return new Promise(async(resolve) => {
const config = { const config = {
width, height, width, height,
icon: path.join(appPath, '/resources/logo2.ico'), icon: path.join(appPath, logoIco),
webPreferences: { webPreferences: {
preload: path.join(API.preloadPath, '/index.js'), preload: path.join(API.preloadPath, '/index.js'),
sandbox: false, sandbox: false,
@ -422,6 +423,19 @@ export const toLinkWeb = (path) => {
cookieData: { ...config } cookieData: { ...config }
}) })
} }
/**
* @description 外部跳转-web网页
* @param {*} url
*/
export const toLinkLeftWeb = (url) => {
// 通知主进程
ipcRenderer.send('openWindow', {
key: `win-${Date.now()}`,
fullPath: url
})
}
/** /**
* @description 外部跳转-web网页 * @description 外部跳转-web网页
* @param {*} path * @param {*} path

View File

@ -3,16 +3,16 @@
<!-- <div class="class-reserv-tabs"> <!-- <div class="class-reserv-tabs">
<el-segmented v-model="tabActive" block :options="tabOptions" size="large" /> <el-segmented v-model="tabActive" block :options="tabOptions" size="large" />
</div>--> </div>-->
<div class="class-reserv-body"> <div class="class-reserv-body" v-infinite-scroll="load">
<template v-for="(item, index) in dataList" :key="index"> <template v-for="(item, index) in dataList" :key="index">
<reserv-item <!-- <reserv-item
:style="{'background-color': index%2==0?'#f5f5f5':''}" :style="{'background-color': index%2==0?'#f5f5f5':''}"
:item="item" :item="item"
v-if="item.bookImg" v-if="item.bookImg"
@open-edit="reservDialog.openDialog(item)" @open-edit="reservDialog.openDialog(item)"
@delete-reserv="deleteReserv(item)" @delete-reserv="deleteReserv(item)"
@change="(...o) => emit('change', ...o)" @change="(...o) => emit('change', ...o)"
></reserv-item> ></reserv-item> -->
<reserv-item-apt <reserv-item-apt
v-if="!item.bookImg" v-if="!item.bookImg"
:style="{'background-color': index%2==0?'#f5f5f5':''}" :style="{'background-color': index%2==0?'#f5f5f5':''}"
@ -22,13 +22,14 @@
@change="(...o) => emit('change', ...o)" @change="(...o) => emit('change', ...o)"
></reserv-item-apt> ></reserv-item-apt>
</template> </template>
<el-divider v-if="page.isEnd">到底了没了</el-divider>
</div> </div>
<reserv ref="reservDialog"></reserv> <reserv ref="reservDialog"></reserv>
</el-container> </el-container>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, computed, watch } from 'vue' import { ref, onMounted, computed, watch, reactive } from 'vue'
import { getSelfReserv } from '@/api/classManage' import { getSelfReserv } from '@/api/classManage'
import { listClasscourseNew } from '@/api/teaching/classcourse' // api import { listClasscourseNew } from '@/api/teaching/classcourse' // api
import ReservItem from '@/views/classManage/reserv-item.vue' 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 { useToolState } from '@/store/modules/tool'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import ReservItemApt from '@/views/classManage/reserv-item-apt.vue' import ReservItemApt from '@/views/classManage/reserv-item-apt.vue'
import vScroll from '@/directive/scroll' // --
// import Chat from '@/utils/chat' // im // import Chat from '@/utils/chat' // im
// if (!Chat.imChat) Chat.init() // if (!Chat.imChat) Chat.init()
@ -44,6 +46,12 @@ const reservDialog = ref(null)
const tabOptions = ref(['进行中', '已结束']) const tabOptions = ref(['进行中', '已结束'])
const tabActive = ref('进行中') const tabActive = ref('进行中')
const dataList = ref([]) const dataList = ref([])
const page = reactive({
pageNum: 0, //
pageSize: 10, //
total: 0, //
isEnd: false //
})
const toolStore = useToolState() const toolStore = useToolState()
const userStore = useUserStore() const userStore = useUserStore()
@ -72,21 +80,42 @@ const deleteReserv = (item) => {
})*/ })*/
// //
const getData = () => { const getData = () => {
Promise.all([listClasscourseNew({teacherid: userStore.id,evalid: props.curNode.id,pageSize:1000}), getSelfReserv({ex2:props.curNode.id})]).then(([res1,res2])=>{ const { pageNum, pageSize } = page
let list = res2.data || [] const params = {
let list2 = res1.rows || [] evalid: props.curNode.id,
// list.sort((a,b) => { if(a.status=='') return -1; else return 0 }) teacherid: userStore.id,
list = list.concat(list2) 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) }) 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) => { /*getSelfReserv().then((res) => {
const list = res.data || [] const list = res.data || []
list.sort((a,b) => { if(a.status=='上课中') return -1; else return 0 }) list.sort((a,b) => { if(a.status=='上课中') return -1; else return 0 })
dataList.value = list dataList.value = list
})*/ })*/
} }
//
const load = () => {
if(page.isEnd) return console.log('已加载完-所有') //
page.pageNum++
getData()
}
watch( watch(
() => [dataList,toolStore.isToolWin,props.curNode], () => [dataList,toolStore.isToolWin,props.curNode],
() => { () => {
@ -96,13 +125,14 @@ watch(
} }
) )
onMounted(() => { onMounted(() => {
getData() // // getData() //
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.class-reserv-wrap { .class-reserv-wrap {
height: 100%; height: 100%;
// height: 300px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
//padding: 15px 10px; //padding: 15px 10px;

View File

@ -15,8 +15,7 @@
</template> </template>
<template v-else-if="item.status === 'open'"> <template v-else-if="item.status === 'open'">
<el-button :disabled="toolStore.isToolWin" size="small" type="primary" @click="startClassR(item)" <el-button :disabled="toolStore.isToolWin" size="small" type="primary" @click="startClassR(item)"
>继续上课</el-button >继续上课</el-button>
>
<!--<el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>--> <!--<el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>-->
<el-button :loading="loading" size="small" type="info" @click="endClassR(item)" <el-button :loading="loading" size="small" type="info" @click="endClassR(item)"
>下课{{ loading?'中...':'' }}</el-button> >下课{{ loading?'中...':'' }}</el-button>
@ -24,7 +23,8 @@
</div> </div>
<div class="class-reserv-item-tool" style="width: 50px;"> <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-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>
<div style="min-width: 150px;"><span> 浏览25955 点赞26605</span></div> <div style="min-width: 150px;"><span> 浏览25955 点赞26605</span></div>
</div> </div>
@ -86,6 +86,7 @@ const chatSend = () => {
<style scoped lang="scss"> <style scoped lang="scss">
.class-reserv-item { .class-reserv-item {
display: flex; display: flex;
align-items: center;
background-color: white; background-color: white;
border-radius: 10px; border-radius: 10px;
padding: 5px; padding: 5px;
@ -111,7 +112,7 @@ const chatSend = () => {
} }
} }
.class-reserv-item-tool { .class-reserv-item-tool {
margin-left: 15px; margin: 0 7px;
display: flex; display: flex;
align-items: center; align-items: center;
} }

View File

@ -154,14 +154,14 @@ const getClassWorkList = async () => {
// ---------------------------------------------- // ----------------------------------------------
// UI // UI
if (list[i].worktype == '学习目标定位') { if (list[i].worktype == '课堂展示') {
list[i].workclass = 'success' list[i].workclass = 'success'
list[i].workcodesList = JSON.parse(list[i].workcodes) list[i].workcodesList = JSON.parse(list[i].workcodes)
} else if (list[i].worktype == '教材研读') { } else if (list[i].worktype == '科学实验') {
list[i].workclass = 'primary' list[i].workclass = 'primary'
} else if (list[i].worktype == '框架梳理') { } else if (list[i].worktype == '框架梳理') {
list[i].workclass = 'warning' list[i].workclass = 'warning'
} else if (list[i].worktype == '学科定位') { } else if (list[i].worktype == '常规作业') {
list[i].workclass = 'info' list[i].workclass = 'info'
} else if (list[i].worktype == '习题训练') { } else if (list[i].worktype == '习题训练') {
list[i].workclass = 'danger' list[i].workclass = 'danger'

View File

@ -437,13 +437,15 @@ const queryPushRecords = (row) => {
// } else // } else
// TODO // TODO
if (res.rows[i].worktype == '课堂展示') { if (res.rows[i].worktype == '课堂展示') {
res.rows[i].workclass = 'primary'; res.rows[i].workclass = 'success';
} else if (res.rows[i].worktype == '框架梳理') { } else if (res.rows[i].worktype == '框架梳理') {
res.rows[i].workclass = 'warning'; res.rows[i].workclass = 'warning';
} else if (res.rows[i].worktype == '常规作业') { } else if (res.rows[i].worktype == '常规作业') {
res.rows[i].workclass = 'info'; res.rows[i].workclass = 'info';
} else if (res.rows[i].worktype == '习题训练') { } else if (res.rows[i].worktype == '习题训练') {
res.rows[i].workclass = 'danger'; res.rows[i].workclass = 'danger';
} else if (res.rows[i].worktype == '科学实验') {
res.rows[i].workclass = 'primary';
} else { } else {
res.rows[i].workclass = 'primary'; res.rows[i].workclass = 'primary';
} }

View File

@ -141,7 +141,8 @@
v-if=" v-if="
dialogProps.studentObj.worktype == '常规作业' || dialogProps.studentObj.worktype == '常规作业' ||
dialogProps.studentObj.worktype == '课堂展示' || dialogProps.studentObj.worktype == '课堂展示' ||
dialogProps.studentObj.worktype == '框架梳理' dialogProps.studentObj.worktype == '框架梳理' ||
dialogProps.studentObj.worktype == '科学实验'
" "
> >
<div v-for="(stuItem, sIndex) in dialogProps.studentQuizAllList" :key="stuItem.id"> <div v-for="(stuItem, sIndex) in dialogProps.studentQuizAllList" :key="stuItem.id">
@ -157,7 +158,8 @@
v-if=" v-if="
dialogProps.studentObj.worktype == '常规作业' || dialogProps.studentObj.worktype == '常规作业' ||
dialogProps.studentObj.worktype == '课堂展示' || dialogProps.studentObj.worktype == '课堂展示' ||
dialogProps.studentObj.worktype == '框架梳理' dialogProps.studentObj.worktype == '框架梳理' ||
dialogProps.studentObj.worktype == '科学实验'
" "
> >
<!-- 文件内容格式mp3/mp4/doc/docx/excel/pdf/ppt/pptx/jpg/jpeg/gif/png/txt -> <!-- 文件内容格式mp3/mp4/doc/docx/excel/pdf/ppt/pptx/jpg/jpeg/gif/png/txt ->
@ -210,7 +212,13 @@
<!-- 学生答题展示 --> <!-- 学生答题展示 -->
<div v-if="feedContentList.length > 0"> <div v-if="feedContentList.length > 0">
<div v-if="dialogProps.studentObj.worktype == '常规作业' && stuItem.rightanswer != ''&& stuItem.rightanswer != null"> <div
v-if="
(dialogProps.studentObj.worktype == '常规作业' || dialogProps.studentObj.worktype == '科学实验') &&
stuItem.rightanswer != '' &&
stuItem.rightanswer != null
"
>
<!-- 常规作业学生有的会答复 --> <!-- 常规作业学生有的会答复 -->
<p style="padding: 10px 0;text-align: left;">学生答复内容</p> <p style="padding: 10px 0;text-align: left;">学生答复内容</p>
<div style="padding: 0 20px;text-align: left;">{{stuItem.rightanswer}}</div> <div style="padding: 0 20px;text-align: left;">{{stuItem.rightanswer}}</div>
@ -427,7 +435,7 @@ import useUserStore from '@/store/modules/user'
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
// import { Plus } from '@element-plus/icons-vue' // import { Plus } from '@element-plus/icons-vue'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import { updateClassworkeval, updateClassworkdata, getClassworkdata } from '@/api/classTask' import { updateClassworkeval, getClassworkdata, updateClassWorkDataAutoFinish } from '@/api/classTask'
import { getTimeDate } from '@/utils/date' import { getTimeDate } from '@/utils/date'
import ReFilePreview from '@/components/refile-preview/index.vue' import ReFilePreview from '@/components/refile-preview/index.vue'
import { quizStrToList } from '@/utils/comm'; import { quizStrToList } from '@/utils/comm';
@ -700,30 +708,34 @@ const acceptParams = async (params) => {
}) })
} else { } else {
// //
if (params.studentObj.worktype == '常规作业') { if (params.studentObj.worktype == '常规作业' || params.studentObj.worktype == '科学实验') {
try { if(params.studentObj.worktype == '常规作业'){
// datacontent TODO try {
const res = await getClassworkdata(params.studentObj.id); // datacontent TODO
if(res.data.datacontent != ''){ const res = await getClassworkdata(params.studentObj.id);
const teachWorkFileList = JSON.parse(res.data.datacontent); if(res.data.datacontent != ''){
console.log(teachWorkFileList, '老师filelist-------------') const teachWorkFileList = JSON.parse(res.data.datacontent);
teachWorkFileList && console.log(teachWorkFileList, '老师filelist-------------')
teachWorkFileList.forEach((item) => { teachWorkFileList &&
if ( teachWorkFileList.forEach((item) => {
item.name.indexOf('jpg') > -1 || if (
item.name.indexOf('jpeg') > -1 || item.name.indexOf('jpg') > -1 ||
item.name.indexOf('png') > -1 item.name.indexOf('jpeg') > -1 ||
) { item.name.indexOf('png') > -1
teachImageList.value.push(item) ) {
} else { teachImageList.value.push(item)
teachFileList.value.push(item) } else {
} teachFileList.value.push(item)
}) }
teacherFeedContentList.value.push(teachWorkFileList) })
} teacherFeedContentList.value.push(teachWorkFileList)
}
} catch (error) { } catch (error) {
console.error('Invalid JSON:', error) console.error('Invalid JSON:', error)
}
}else{
// TODO 2024-12-20
} }
params.studentQuizAllList.forEach((item) => { params.studentQuizAllList.forEach((item) => {
@ -878,7 +890,7 @@ const onSubmit = () => {
updatedate: getTimeDate(),// = year+'-'+month+'-'+day+' '+hh+':'+mm; updatedate: getTimeDate(),// = year+'-'+month+'-'+day+' '+hh+':'+mm;
}; };
// //
updateClassworkdata(formd).then(res => { updateClassWorkDataAutoFinish(formd).then(res => {
}) })
// //

View File

@ -29,14 +29,15 @@ import { ElMessage } from 'element-plus'
const emit = defineEmits(['itemClick']) const emit = defineEmits(['itemClick'])
const items = shallowRef([ const items = shallowRef([
{ title: '自主搜题', description: '上千万高质量习题资源,历届考试真题,每道题均有习题解析', icon: '#icon-soutibao-',type:'primary' }, { title: '自主搜题', description: '上千万高质量习题资源,历届考试真题,每道题均有习题解析', icon: '#icon-soutibao-',type:'danger' },
{ title: '校本题库', description: '本校公共题库资源。', icon: '#icon-soutibao-',type:'primary' }, // { title: '', description: '', icon: '#icon-soutibao-',type:'danger' },
{ title: '个人题库', description: '老师上传维护自己的个人题库。', icon: '#icon-soutibao-',type:'primary' }, { title: '个人题库', description: '老师上传维护自己的个人题库。', icon: '#icon-soutibao-',type:'danger' },
{ title: '智能推荐', description: '通过对学生的薄弱知识点分析,推送不同难度的习题进行强化训练。', icon: '#icon-tubiao_wuxing-',type:'primary' }, // { title: '', description: '', icon: '#icon-tubiao_wuxing-',type:'primary' },
{ title: '课堂展示', description: '通过课堂白板绘制作业,提升学生的创作思维能力。', icon: '#icon-huaban',type:'danger' }, { title: '课堂展示', description: '通过课堂白板绘制作业,提升学生的创作思维能力。', icon: '#icon-huaban',type:'success' },
{ title: '常规作业', description: '推送pdf、视频、音频、图片学生可以拍照上传。', icon: '#icon-zhaoxiangji',type:'danger' }, { title: '常规作业', description: '推送pdf、视频、音频、图片学生可以拍照上传。', icon: '#icon-zhaoxiangji',type:'info' },
{ title: 'AI设计作业', description: '通过AI助手根据课标、教材、考试等分析结果智能创建作业。', icon: '#icon-jiqiren_o',type:'danger' }, // { title: 'AI', description: 'AI', icon: '#icon-jiqiren_o',type:'danger' },
{ title: '习题上传', description: '自己上传个人题库。', icon: '#icon-shangchuan',type:'danger' }, { title: '习题上传', description: '自己上传个人题库。', icon: '#icon-shangchuan',type:'danger' },
{ title: '科学实验', description: '学生完成虚拟仿真实验,并提交实验结果。', icon: '#icon-shangchuan',type:'primary' },
]); ]);
const handleClick = (item) => { const handleClick = (item) => {

View File

@ -0,0 +1,54 @@
<template>
<div style="display: flex;">
<el-select
v-model="value"
placeholder="请选择实验课程"
size="large"
style="width: 240px"
@change="onSelectOption"
>
<el-option
v-for="item in classTaskStore.experimentList"
:key="item.value"
:label="item.label"
:value="item.label"
/>
</el-select>
</div>
</template>
<script setup>
import useUserStore from '@/store/modules/user'
import {ArrowDown} from '@element-plus/icons-vue'
import { onMounted,ref } from 'vue';
import useClassTaskStore from '@/store/modules/classTask'
const userStore = useUserStore().user
const subjectList = ref([])
const classTaskStore = useClassTaskStore().experimentObj
// emit
let emit = defineEmits(['selectItem'])
const props = defineProps({
list: {
type: Array,
default: () => ([])
},
})
const value = ref('')
onMounted(() => {
})
const onSelectOption = (option) => {
console.log(option,'选择的实验课-------')
emit('selectItem', classTaskStore.experimentList.filter(item => item.label === option)[0])
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,199 @@
<template>
<div style="display: flex;">
<div style="margin-left: 15px">
<el-dropdown @command="handleUserEduStage">
<span class="el-dropdown-link">
<el-button class="custom-button" type="default" round >{{ useClassTaskStore().experimentObj.edustage }}
<el-icon><ArrowDown /></el-icon>
</el-button>
</span>
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item command="幼儿园">幼儿园</el-dropdown-item> -->
<el-dropdown-item command="小学">小学</el-dropdown-item>
<el-dropdown-item command="初中">初中</el-dropdown-item>
<el-dropdown-item command="高中">高中</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div style="margin-left: 15px">
<el-dropdown @command="handleUserEduSubject">
<span class="el-dropdown-link">
<el-button class="custom-button" type="default" round>{{ useClassTaskStore().experimentObj.edusubject }}
<el-icon><ArrowDown /></el-icon>
</el-button>
</span>
<template #dropdown>
<el-dropdown-menu>
<template v-for="(item, index) in subjectList">
<el-dropdown-item v-if="item.edustage == useClassTaskStore().experimentObj.edustage && isExpList(item.itemtitle)" :command="item.itemtitle">{{
item.itemtitle }}</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup>
import useUserStore from '@/store/modules/user'
import {ArrowDown} from '@element-plus/icons-vue'
import { onMounted,ref } from 'vue';
import { listEvaluation } from '@/api/subject/index'
import jsonData from "@/utils/phetData.json";
import useClassTaskStore from '@/store/modules/classTask'
const userStore = useUserStore().user
// emit
// let emit = defineEmits(['experlist'])
// const expObj = ref({
// edustage: useClassTaskStore().experimentObj.edustage,
// edusubject: useClassTaskStore().experimentObj.edusubject,
// })
const subjectList = ref([])
const chooseGrade= ref({})
const expList = ref([]) //
const checkList = ref([])//
//
const getSubject = () => {
//
// if(!userStore.subject) return
// listEvaluation({ itemkey: 'subject', pageSize: 500 }).then((res) => {
// const arr = userStore.subject.split(',')
// subjectList.value = res.rows.filter(item => arr.includes(String(item.id))).map(items => items)
// console.log(subjectList,'subjectList');
// })
// list
const edustageList = ['小学','初中','高中'];
const subList = ['数学','物理','化学','生物','科学'];
edustageList.forEach((item) => {
subList.forEach((subItems) => {
subjectList.value.push({
edustage: item,
edusubject: subItems,
itemtitle: subItems
})
})
})
console.log(subjectList,'subjectList');
//
handleUserEduStage(useClassTaskStore().experimentObj.edustage)
}
//
const isExpList = (edusubject) => {
let list = [];
switch (edusubject){
case '数学':
list = expList.value.filter(item => item.subject === 'math')
break;
case '物理':
list = expList.value.filter(item => item.subject === 'physics')
break;
case '化学':
checkList.value = expList.value.filter(item => item.subject === 'chemistry')
break;
case '生物':
list = expList.value.filter(item => item.subject === 'biology')
break;
case '科学':
list = expList.value.filter(item => item.subject === 'sciences')
break;
}
return list.length > 0
}
//
const handleUserEduStage = (item) => {
// userStore.edustage = item
useClassTaskStore().experimentObj.edustage = item
//
expList.value = []
if(item === '小学'){
chooseGrade.value = jsonData.data.primary
expList.value = chooseGrade.value
const newSubjectList = subjectList.value.filter(item => item.edustage === '小学');
for(let i in newSubjectList){
const name = newSubjectList[i].itemtitle
if(isExpList(name)){
useClassTaskStore().experimentObj.edusubject = name;
//
handleUserEduSubject(name)
}
}
}else if(item === '初中'){
chooseGrade.value = jsonData.data.junior
expList.value = chooseGrade.value
const newSubjectList = subjectList.value.filter(item => item.edustage === '初中');
for(let i in newSubjectList){
const name = newSubjectList[i].itemtitle
if(isExpList(name)){
useClassTaskStore().experimentObj.edusubject = name;
//
handleUserEduSubject(name)
}
}
}else if(item === '高中'){
chooseGrade.value = jsonData.data.senior
expList.value = chooseGrade.value
const newSubjectList = subjectList.value.filter(item => item.edustage === '高中');
for(let i in newSubjectList){
const name = newSubjectList[i].itemtitle
if(isExpList(name)){
useClassTaskStore().experimentObj.edusubject = name;
//
handleUserEduSubject(name)
}
}
}
}
//
const handleUserEduSubject = (item) => {
// userStore.edusubject = item;
useClassTaskStore().experimentObj.edusubject = item;
console.log(item,'选择的学科-------')
checkList.value = []
switch (item){
case '数学':
checkList.value = expList.value.filter(item => item.subject === 'math')
break;
case '物理':
checkList.value = expList.value.filter(item => item.subject === 'physics')
break;
case '化学':
checkList.value = expList.value.filter(item => item.subject === 'chemistry')
break;
case '生物':
checkList.value = expList.value.filter(item => item.subject === 'biology')
break;
case '科学':
checkList.value = expList.value.filter(item => item.subject === 'sciences')
break;
}
console.log(checkList.value,'checkList')
useClassTaskStore().experimentObj.experimentList = checkList.value;
// emit('experlist',checkList.value)
}
onMounted(() => {
getSubject()
})
</script>
<style scoped>
.custom-button {
width: auto;
border: 1px solid rgb(59, 130, 246);
outline: none;
outline-offset: none;
padding: 0 24px;
}
.custom-button i {
margin-left: 8px; /* 调整图标与文字之间的间距 */
}
</style>

View File

@ -0,0 +1,105 @@
<template>
<div class="experiment-page">
<div class="activeExp-header">
<div class="infomation" v-if="isStadium() !== true" >
<!-- <selectClass v-if="!isSubject" @experlist="getExperimentList" /> -->
<selectClass v-if="!isSubject" />
</div>
<div>
<selectExperiment @selectItem="getExperimentListItem" />
</div>
</div>
<div ref="mainDiv" class="activeExp-main" style="overflow: auto">
<div v-if="!activeExp.fileurl"><el-empty description="暂无学科实验"></el-empty></div>
<iframe v-else :src="activeExp.fileurl" ref="myuunity" width="100%" height="100%" scrolling="no" frameborder="0"></iframe>
<!-- <phet/>-->
</div>
</div>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
//import html2canvas from 'html2canvas';
import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue'
import useUserStore from '@/store/modules/user'
import useClassTaskStore from '@/store/modules/classTask'
import selectClass from './components/selectClass.vue' //
import selectExperiment from './components/experimentList.vue' //
// emit
let emit = defineEmits(['clickExpObj'])
const { proxy } = getCurrentInstance()
const userStore = useUserStore().user
const props = defineProps({
bookobj: {
type: Object,
default: () => ({})
},
expObj: {
type: Object,
default: () => ({})
}
})
//
// const experimentList = ref([]);
const activeExp = ref({});
const isStadium = () => {
let roles = userStore.roles
return roles.some(item => item.roleKey === 'stadium')
}
// const mainLeftBarHeight = ref(0);
onMounted(() => {
// var mainLeftBar = document.getElementById("mainLeftBar");
// mainLeftBarHeight.value = document.documentElement.clientHeight-50-mainLeftBar.offsetParent.offsetTop - 10;
// getDivHeight()
// window.addEventListener('resize', getDivHeight)
})
// const getDivHeight = () => {
// const screenheight = window.innerHeight;
// proxy.$refs.mainDiv.style.height = screenheight-140 > 320 ? screenheight-140+'px' : 320+'px';
// console.log("height", proxy.$refs.mainDiv.style.height);
// // 704 +
// // mainDiv ref="mainDiv"
// }
//
// const getExperimentList = (val) => {
// console.log(val,'list')
// // experimentList.value = val;
// }
//
const getExperimentListItem = (val) => {
console.log(val,'选择的实验课程信息')
activeExp.value = val;
emit('clickExpObj', val)
}
//
watch(() => props.expObj.fileurl, (newVal, oldVal) => {
console.log(props.expObj,'科学实验科目')
activeExp.value = props.expObj;
})
</script>
<style scoped lang="scss">
.experiment-page {
height: 100%;
display: flex;
flex-direction: column;
.activeExp-header {
display: flex;
justify-content: space-between;
}
.activeExp-main{
flex: auto;
}
}
</style>

View File

@ -58,9 +58,9 @@
<el-tab-pane label="自主搜题" name="自主搜题" class="prepare-center-zzst"> <el-tab-pane label="自主搜题" name="自主搜题" class="prepare-center-zzst">
<SearchQuestion :bookobj="courseObj" @addQuiz="handleClassWorkQuizAdd" /> <SearchQuestion :bookobj="courseObj" @addQuiz="handleClassWorkQuizAdd" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="校本题库" name="校本题库" class="prepare-center-xbtk"> <!-- <el-tab-pane label="校本题库" name="校本题库" class="prepare-center-xbtk">
<SchoolQuestion /> <SchoolQuestion />
</el-tab-pane> </el-tab-pane> -->
<el-tab-pane label="个人题库" name="个人题库" class="prepare-center-grst"> <el-tab-pane label="个人题库" name="个人题库" class="prepare-center-grst">
<MyQuestion :bookobj="courseObj" @addQuiz="handleClassWorkQuizAdd"/> <MyQuestion :bookobj="courseObj" @addQuiz="handleClassWorkQuizAdd"/>
</el-tab-pane> </el-tab-pane>
@ -77,6 +77,11 @@
<FileUpload v-model="classWorkForm.fileHomeworkList" :fileSize="800" :fileType="['mp3','mp4','doc','docx','xlsx','xls','pdf','ppt','pptx','jpg','jpeg','gif','png','txt']"/> <FileUpload v-model="classWorkForm.fileHomeworkList" :fileSize="800" :fileType="['mp3','mp4','doc','docx','xlsx','xls','pdf','ppt','pptx','jpg','jpeg','gif','png','txt']"/>
</div> </div>
</div> </div>
<div v-if="(currentRow.worktype == '科学实验' || classWorkForm.worktype == '科学实验')&& currentRow.id>0" class="page-center">
<div class="experiment-homework">
<ExperimentQuestion :expObj="classWorkForm.fileHomeworkList&&classWorkForm.fileHomeworkList[0]" @clickExpObj="getExpObj" />
</div>
</div>
<div v-if="currentRow.id>0 " class="page-right"> <div v-if="currentRow.id>0 " class="page-right">
<div class="prepare-top" > <div class="prepare-top" >
@ -140,6 +145,7 @@ import { editListItem } from '@/hooks/useClassTask'
import MyQuestion from '@/views/classTask/newClassTaskAssign/myQuestion/index.vue' import MyQuestion from '@/views/classTask/newClassTaskAssign/myQuestion/index.vue'
import SchoolQuestion from '@/views/classTask/newClassTaskAssign/schoolQuestion/index.vue' import SchoolQuestion from '@/views/classTask/newClassTaskAssign/schoolQuestion/index.vue'
import SearchQuestion from '@/views/classTask/newClassTaskAssign/searchQuestion/index.vue' import SearchQuestion from '@/views/classTask/newClassTaskAssign/searchQuestion/index.vue'
import ExperimentQuestion from "@/views/classTask/newClassTaskAssign/experimentQuestion/index.vue";
import whiteboard from '@/components/whiteboard/whiteboard.vue' import whiteboard from '@/components/whiteboard/whiteboard.vue'
import FileUpload from "@/components/FileUpload/index.vue"; import FileUpload from "@/components/FileUpload/index.vue";
import Right from './Right/index.vue' import Right from './Right/index.vue'
@ -149,10 +155,14 @@ import { useGetHomework } from '@/hooks/useGetHomework'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import useClassTaskStore from '@/store/modules/classTask'
const userStore = useUserStore().user const userStore = useUserStore().user
const route = useRoute(); const route = useRoute();
const router = useRouter() const router = useRouter()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const useClassTaskStores = useClassTaskStore();
const props = defineProps({ const props = defineProps({
currentCourse: Object, currentCourse: Object,
}) })
@ -189,7 +199,8 @@ const boardLoading = ref(false);
const fileLoading = ref(false); // loading const fileLoading = ref(false); // loading
onMounted(() => { onMounted(() => {
currentRow.value = {id:0}; console.log("----onMounted-------")
currentRow.value.id = 0
if(propsQueryCourseObj){ if(propsQueryCourseObj){
if(JSON.parse(propsQueryCourseObj)){ if(JSON.parse(propsQueryCourseObj)){
courseObj.textbookId = JSON.parse(propsQueryCourseObj).bookObj // courseObj.textbookId = JSON.parse(propsQueryCourseObj).bookObj //
@ -216,7 +227,28 @@ onMounted(() => {
} }
} }
initHomeWork(); initHomeWork();
isInToMyQuestion(); //
}) })
//
const isInToMyQuestion = () => {
console.log('isOpenQuestUploadView',useClassTaskStores.isOpenQuestUploadView);
if(useClassTaskStores.isOpenQuestUploadView){
useClassTaskStores.isOpenQuestUploadView = false;
currentRow.value.id = 1; //
activeAptTab.value = "个人题库";
//
classWorkForm.id = 0;
classWorkForm.uniquekey = ""; //
classWorkForm.worktype = "习题训练"; //
classWorkForm.title = ""; //
classWorkForm.quizlist = []; //
classWorkForm.chooseWorkLists = []; // list
classWorkForm.fileHomeworkList = []; //
classWorkForm.whiteboardObj = ""; // -
classWorkForm.question = ""; // -
}
}
watch(() => props.currentCourse, (newVal, oldVal) => { watch(() => props.currentCourse, (newVal, oldVal) => {
if(newVal){ if(newVal){
courseObj.textbookId = newVal.textbookId // courseObj.textbookId = newVal.textbookId //
@ -232,6 +264,16 @@ watch(() => props.currentCourse, (newVal, oldVal) => {
} }
console.log(newVal,'newval'); console.log(newVal,'newval');
},{deep:true}) },{deep:true})
// ------------
const getExpObj = (obj)=>{
// obj:{
// fileurl: "https://phet.colorado.edu/sims/html/number-compare/latest/number-compare_zh_CN.html"
// label: ""
// subject: "math"
// }
classWorkForm.fileHomeworkList = [obj];
}
//------------ //------------
const handleItemClick = (itemName) => { const handleItemClick = (itemName) => {
console.log('itemName', itemName); console.log('itemName', itemName);
@ -240,7 +282,7 @@ const handleItemClick = (itemName) => {
return; return;
} }
currentRow.value = {id:1}; // currentRow.value.id = 1; //
/** /**
* 智能推荐AI设计作业 * 智能推荐AI设计作业
* 习题训练 自主搜题 校本题库 个人题库 * 习题训练 自主搜题 校本题库 个人题库
@ -283,7 +325,7 @@ const initHomeWork = async()=> {
const handleNewAllClass = () => { const handleNewAllClass = () => {
taskTable.value.setCurrentRow({});// taskTable.value.setCurrentRow({});//
currentRow.value = {id:0}; // currentRow.value.id = 0; //
//-------- //--------
classWorkForm.id = 0; classWorkForm.id = 0;
classWorkForm.uniquekey = ""; // classWorkForm.uniquekey = ""; //
@ -310,7 +352,7 @@ const handleDelete =() => {
return delClasswork(ids.join(',')); return delClasswork(ids.join(','));
}).then(() => { }).then(() => {
taskTable.value.setCurrentRow({});// taskTable.value.setCurrentRow({});//
currentRow.value = {id:0}; // currentRow.value.id = 0; //
taskList.value = []; taskList.value = [];
// initHomeWork(); // initHomeWork();
setTimeout(() => { setTimeout(() => {
@ -404,7 +446,7 @@ const handleCurrentChange = (val) => {
console.log(val,'???????????') console.log(val,'???????????')
if(val && val.id >0 ) { if(val && val.id >0 ) {
currentRow.value = val; currentRow.value.id = 1;
classWorkForm.worktype = val.worktype; // classWorkForm.worktype = val.worktype; //
editListItem(val, courseObj).then((obj) => { editListItem(val, courseObj).then((obj) => {
if(obj){ if(obj){
@ -556,6 +598,32 @@ const handleClassWorkFormQuizRemove = (index) =>{
fileLoading.value = false fileLoading.value = false
} }
} }
else if(classWorkForm.worktype === "科学实验"){
if (classWorkForm.fileHomeworkList.length == 0) return ElMessage({ type: 'warning', message: '请选择科学实验的课程!'});
cform.workcodes = JSON.stringify(classWorkForm.fileHomeworkList);
cform.entpcourseworklist = JSON.stringify([{'id':-3, 'score': '10'}]);
try {
console.log(cform,'科学实验')
addClassworkReturnId(cform).then((res) => {
ElMessage({ type: 'success', message: '作业设计成功!'});
//
classWorkForm.worktype = "科学实验";
classWorkForm.uniquekey = ''; // props.propsformobj.uniquekey, //
classWorkForm.title = "";
classWorkForm.quizlist = [], //
//
classWorkForm.chooseWorkLists = []; // list
classWorkForm.whiteboardObj = ''; // ? //
classWorkForm.fileHomeworkList = []; // list
classWorkForm.id = res
emits('getData',classWorkForm)
// TODO
})
} finally{
//
}
}
else { else {
// //
var ll = []; var ll = [];
@ -602,9 +670,9 @@ const handleClassWorkFormQuizRemove = (index) =>{
console.log('该清空左侧列表数据了'); console.log('该清空左侧列表数据了');
// //
if(isShow.value){ if(isShow.value){
currentRow.value = {id:1}; currentRow.value.id = 1;
}else{ }else{
currentRow.value = {id:0}; currentRow.value.id = 0;
} }
initHomeWork(); initHomeWork();
@ -641,7 +709,10 @@ const editWork = async (cform) =>{
}else{ }else{
if (classWorkForm.fileHomeworkList.length == 0) return ElMessage({ type: 'warning', message: '请上传常规作业附件!'}); if (classWorkForm.fileHomeworkList.length == 0) return ElMessage({ type: 'warning', message: '请上传常规作业附件!'});
} }
}else { }else if( classWorkForm.worktype == '科学实验') {
if (classWorkForm.fileHomeworkList.length == 0) return ElMessage({ type: 'warning', message: '请选择科学实验科目!'});
}
else {
if (classWorkForm.chooseWorkLists.length == 0) { if (classWorkForm.chooseWorkLists.length == 0) {
// //
ElMessage.error('请先添加作业资源!'); ElMessage.error('请先添加作业资源!');
@ -733,13 +804,21 @@ const editWork = async (cform) =>{
// 1. (, ) // 1. (, )
cform.workcodes = JSON.stringify(classWorkForm.fileHomeworkList); cform.workcodes = JSON.stringify(classWorkForm.fileHomeworkList);
} }
else if (classWorkForm.worktype=='科学实验') { //TODO fileHomeworkList
// 1. (, )
cform.workcodes = JSON.stringify(classWorkForm.fileHomeworkList);
}
// 3. // 3.
let res = await updateClasswork(cform); let res = await updateClasswork(cform);
if (res.code == 200) { if (res.code == 200) {
ElMessage.success('更新成功'); ElMessage.success('更新成功');
// //
currentRow.value = {id:0}; if(isShow.value){
currentRow.value.id = 1;
}else{
currentRow.value.id = 0;
}
initHomeWork(); initHomeWork();
// // // //
// router.back() // router.back()
@ -839,6 +918,11 @@ const editWork = async (cform) =>{
padding: 20px; padding: 20px;
box-sizing: border-box; box-sizing: border-box;
} }
.experiment-homework{
padding: 15px;
height: 100%;
}
} }
.page-right { .page-right {
overflow: hidden; overflow: hidden;

View File

@ -6,9 +6,9 @@
<el-tab-pane label="自主搜题" name="自主搜题" class="prepare-center-zzst"> <el-tab-pane label="自主搜题" name="自主搜题" class="prepare-center-zzst">
<SearchQuestion :bookobj="courseObj" :isHtml2canvas="true" @addQuizImgBs64="handleaddQuizImgBs64" /> <SearchQuestion :bookobj="courseObj" :isHtml2canvas="true" @addQuizImgBs64="handleaddQuizImgBs64" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="校本题库" name="校本题库" class="prepare-center-xbtk"> <!-- <el-tab-pane label="校本题库" name="校本题库" class="prepare-center-xbtk">
<SchoolQuestion /> <SchoolQuestion />
</el-tab-pane> </el-tab-pane> -->
<el-tab-pane label="个人题库" name="个人题库" class="prepare-center-grst"> <el-tab-pane label="个人题库" name="个人题库" class="prepare-center-grst">
<MyQuestion :bookobj="courseObj" :isHtml2canvas="true" @addQuizImgBs64="handleaddQuizImgBs64"/> <MyQuestion :bookobj="courseObj" :isHtml2canvas="true" @addQuizImgBs64="handleaddQuizImgBs64"/>
</el-tab-pane> </el-tab-pane>

View File

@ -74,7 +74,7 @@
<script setup> <script setup>
import "vue-cropper/dist/index.css"; import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper"; import { VueCropper } from "vue-cropper";
import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue' import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
@ -88,6 +88,8 @@ import { useRouter, useRoute } from 'vue-router'
import { ocrImg2ExamByManualUpl, ocrImg2ItemByManualUpl } from "@/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues"; import { ocrImg2ExamByManualUpl, ocrImg2ItemByManualUpl } from "@/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues";
import QuesItem from "@/views/classTask/newClassTaskAssign/questionUpload/quesItem/index.vue"; import QuesItem from "@/views/classTask/newClassTaskAssign/questionUpload/quesItem/index.vue";
import useClassTaskStore from '@/store/modules/classTask'
// const Remote = require('@electron/remote') // const Remote = require('@electron/remote')
// const fs = require('fs'); // const fs = require('fs');
@ -96,7 +98,9 @@ import useUserStore from '@/store/modules/user'
const userStore = useUserStore().user const userStore = useUserStore().user
const route = useRoute(); const route = useRoute();
const router = useRouter() const router = useRouter()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance();
const useClassTaskStores = useClassTaskStore();
const props = defineProps({ const props = defineProps({
}) })
@ -151,6 +155,7 @@ const cropOption = reactive({
onMounted(() => { onMounted(() => {
useClassTaskStores.isOpenQuestUploadView = true; //
console.log('propsQueryCourseObj', JSON.parse(propsQueryCourseObj)); console.log('propsQueryCourseObj', JSON.parse(propsQueryCourseObj));
if(propsQueryCourseObj&&JSON.parse(propsQueryCourseObj)){ if(propsQueryCourseObj&&JSON.parse(propsQueryCourseObj)){
courseObj.textbookId = JSON.parse(propsQueryCourseObj).bookObj // courseObj.textbookId = JSON.parse(propsQueryCourseObj).bookObj //
@ -161,7 +166,13 @@ onMounted(() => {
} }
initHomeWork(); initHomeWork();
}) })
onUnmounted(()=>{
// 1s isOpenQuestUploadView
setTimeout(()=>{
useClassTaskStores.isOpenQuestUploadView = false; //
console.log('onUnmounted 习题上传');
}, 1000)
})
/** /**
* 获取 entpcourseid 获取作业列表 * 获取 entpcourseid 获取作业列表

View File

@ -1,6 +1,7 @@
import { ElMessageBox, ElMessage } from "element-plus"; import { ElMessageBox, ElMessage } from "element-plus";
import qs from "qs"; import qs from "qs";
import axios from 'axios' import axios from 'axios'
import request from '@/utils/request'
import { pyOCRAPI } from "@/api/education/entpcoursework"; import { pyOCRAPI } from "@/api/education/entpcoursework";
@ -9,13 +10,20 @@ const baidubceConfig = {
// Header // Header
'Content-Type': "application/x-www-form-urlencoded", 'Content-Type': "application/x-www-form-urlencoded",
// 格式 // 格式
'Accept' : 'application/json', 'Accept': 'application/json',
// id(临时测试) // id(临时测试)
'client_id': "U0DrGBE6X92IXgV6cJMNON8F", 'client_id': "U0DrGBE6X92IXgV6cJMNON8F",
// 密钥(临时测试) // 密钥(临时测试)
'client_secret': 'oWb0M0YWMmZPMQIhIUkJX99ddr7h61qf', 'client_secret': 'oWb0M0YWMmZPMQIhIUkJX99ddr7h61qf',
}; };
export function getOcrContent(data) {
return request({
url: '/ocr/exam',
method: 'post',
data: data
})
}
/** /**
@ -34,8 +42,8 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = ''
let regex = null; let regex = null;
// 识别内容拼接 // 识别内容拼接
let ocrTxt = '' let ocrTxt = ''
if(isLocalTest) { if (isLocalTest) {
// 临时本地测试json格式跟百度ocr一致 // 临时本地测试json格式跟百度ocr一致
const response = await fetch('/cropImgTest/single.json'); const response = await fetch('/cropImgTest/single.json');
const resOcr = await response.json(); const resOcr = await response.json();
@ -59,7 +67,7 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = ''
} }
else { else {
const tmp = await ocrImg2Json(imgBase64); const tmp = await ocrImg2Json(imgBase64);
if(!tmp?.data) { if (!tmp?.data) {
return examItem; return examItem;
} }
ocrJson = tmp.data.results; ocrJson = tmp.data.results;
@ -69,12 +77,12 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = ''
}); });
} }
if(ocrJson == '') { if (ocrJson == '') {
ElMessage.error('[人工录入-单项]识别的图片为空, 识别失败, 请检查重试!'); ElMessage.error('[人工录入-单项]识别的图片为空, 识别失败, 请检查重试!');
return examItem; return examItem;
} }
if(ocrTxt == '') { if (ocrTxt == '') {
ElMessage.error('[人工录入-单项]识别内容拼接失败, 请检查重试!'); ElMessage.error('[人工录入-单项]识别内容拼接失败, 请检查重试!');
return examItem; return examItem;
} }
@ -87,7 +95,7 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = ''
ocrTxt = ocrTxt.replace(regex, '').replace(/<br \/>/g, ''); ocrTxt = ocrTxt.replace(regex, '').replace(/<br \/>/g, '');
examItem = ocrTxt; examItem = ocrTxt;
} }
else if (curItem === 'workdesc') { else if (curItem === 'workdesc') {
// 该类型下无需[判断题]和[主观题]处理 // 该类型下无需[判断题]和[主观题]处理
if (examType.includes('复合题')) { if (examType.includes('复合题')) {
// 因[题目+选项]分离正则匹配需要, 故需开头手动拼一个<br /> // 因[题目+选项]分离正则匹配需要, 故需开头手动拼一个<br />
@ -96,13 +104,13 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = ''
worktype: '单选题', worktype: '单选题',
params: [], params: [],
} }
mutiParams.arrWorkDesc.forEach( item => { mutiParams.arrWorkDesc.forEach(item => {
const obj = { const obj = {
title: item.title, title: item.title,
workanswer: '', workanswer: '',
checkAnswer: [], checkAnswer: [],
type: item.type, type: item.type,
options: item.options.map(element => {return {text: element.replace(/<br \/>/g, '')}}), options: item.options.map(element => { return { text: element.replace(/<br \/>/g, '') } }),
} }
examItem.params.push(obj); examItem.params.push(obj);
}); });
@ -114,7 +122,7 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = ''
// 先判断是否存在选项标识, 且存在2个及以上(A.---1.---(1)---1) // 先判断是否存在选项标识, 且存在2个及以上(A.---1.---(1)---1)
regex = /\s*[A-H][..。]/g; regex = /\s*[A-H][..。]/g;
const matches = ocrTxt.match(regex); const matches = ocrTxt.match(regex);
if (matches==null || matches.length < 2){ if (matches == null || matches.length < 2) {
ElMessage.error('[人工录入-单项]识别[选项]失败, 请检查重试!'); ElMessage.error('[人工录入-单项]识别[选项]失败, 请检查重试!');
return examItem; return examItem;
} }
@ -138,7 +146,7 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = ''
examItem.push(obj); examItem.push(obj);
return examItem; return examItem;
} }
} }
else if (curItem === 'workanswer') { else if (curItem === 'workanswer') {
// 该类型下只做[主观题]和[复合题]的处理 // 该类型下只做[主观题]和[复合题]的处理
@ -163,8 +171,8 @@ export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = ''
let ocrJson = ''; let ocrJson = '';
// 识别内容拼接 // 识别内容拼接
let ocrTxt = ''; let ocrTxt = '';
if(isLocalTest) { if (isLocalTest) {
// 临时本地测试json格式跟百度ocr一致 // 临时本地测试json格式跟百度ocr一致
const response = await fetch('/cropImgTest/single.json'); const response = await fetch('/cropImgTest/single.json');
const resOcr = await response.json(); const resOcr = await response.json();
@ -173,7 +181,7 @@ export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = ''
ocrJson.forEach(ele => { ocrJson.forEach(ele => {
ocrTxt += `${ele.words.word}<br />`; ocrTxt += `${ele.words.word}<br />`;
}); });
//-------------------------------------------------------------- //--------------------------------------------------------------
// 备用ocr识别服务 python的一个识别服务 // 备用ocr识别服务 python的一个识别服务
// const response = await ocrImgPyJson(imgBase64); // const response = await ocrImgPyJson(imgBase64);
@ -186,7 +194,7 @@ export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = ''
// }); // });
} else { } else {
const tmp = await ocrImg2Json(imgBase64); const tmp = await ocrImg2Json(imgBase64);
if(!tmp?.data) { if (!tmp?.data) {
return examQues; return examQues;
} }
ocrJson = tmp.data.results; ocrJson = tmp.data.results;
@ -195,20 +203,20 @@ export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = ''
}); });
} }
if(ocrJson == '') { if (ocrJson == '') {
ElMessage.error('[人工录入-整题]图片识别内容为空, 识别失败, 请重试!'); ElMessage.error('[人工录入-整题]图片识别内容为空, 识别失败, 请重试!');
return examQues; return examQues;
} }
if(ocrTxt == '') {
if (ocrTxt == '') {
ElMessage.error('[人工录入-整题]识别内容拼接失败, 请重试!'); ElMessage.error('[人工录入-整题]识别内容拼接失败, 请重试!');
return examQues; return examQues;
} }
// 识别内容转为试题结构 // 识别内容转为试题结构
examQues = assembleExam(ocrTxt); examQues = assembleExam(ocrTxt);
if(examQues.err != '') { if (examQues.err != '') {
ElMessage.error(`[人工录入-整题]${examQues.err}, 请重试!`); ElMessage.error(`[人工录入-整题]${examQues.err}, 请重试!`);
examQues = {}; examQues = {};
} }
@ -226,30 +234,36 @@ const ocrImg2Json = async (urlBase64) => {
ElMessage.error("未检测到截图图片, 请截取图片后再识别"); ElMessage.error("未检测到截图图片, 请截取图片后再识别");
return null; return null;
} }
const resToken = await bdyAPI_getToken();
if (resToken.status !== 200) {
ElMessage.error("百度智能云用户标识有误");
return null;
}
const token = resToken.data?.access_token;
let base64Code = urlBase64.split(",")[1]; let base64Code = urlBase64.split(",")[1];
const query = { const resOcr = await getOcrContent({ base64Code: base64Code });
image: base64Code, //图片地址(base64) if (resOcr.code !== 200) {
line_probability: false, //是否返回每行识别结果的置信度。默认为false ElMessage.error("图片识别错误");
disp_line_poly: false, //是否返回每行的四角点坐标。默认为false
words_type: 'handprint_mix', //文字类型。 默认:印刷文字识别 = handwring_only手写文字识别 = handprint_mix 手写印刷混排识别
layout_analysis: false, //是否分析文档版面包括layout图、表、标题、段落、目录attribute栏、页眉、页脚、页码、脚注的分析输出
recg_long_division: false, //是否检测并识别手写竖式
recg_formula: true, //控制是否检测并识别公式默认为false
}
const resOcr = await bdyAPI_getOcrContent(token, base64Code, query);
if (resOcr.status !== 200) {
ElMessage.error("百度智能云图片识别错误");
return null; return null;
} }
// const resToken = await bdyAPI_getToken();
// if (resToken.status !== 200) {
// ElMessage.error("百度智能云用户标识有误");
// return null;
// }
// const token = resToken.data?.access_token;
// let base64Code = urlBase64.split(",")[1];
// const query = {
// image: base64Code, //图片地址(base64)
// line_probability: false, //是否返回每行识别结果的置信度。默认为false
// disp_line_poly: false, //是否返回每行的四角点坐标。默认为false
// words_type: 'handprint_mix', //文字类型。 默认:印刷文字识别 = handwring_only手写文字识别 = handprint_mix 手写印刷混排识别
// layout_analysis: false, //是否分析文档版面包括layout图、表、标题、段落、目录attribute栏、页眉、页脚、页码、脚注的分析输出
// recg_long_division: false, //是否检测并识别手写竖式
// recg_formula: true, //控制是否检测并识别公式默认为false
// }
// const resOcr = await bdyAPI_getOcrContent(token, base64Code, query);
// if (resOcr.status !== 200) {
// ElMessage.error("百度智能云图片识别错误");
// return null;
// }
return resOcr; return resOcr;
} }
@ -264,7 +278,7 @@ const ocrImgPyJson = async (urlBase64) => {
ElMessage.error("未检测到截图图片, 请截取图片后再识别"); ElMessage.error("未检测到截图图片, 请截取图片后再识别");
return null; return null;
} }
const resOcr = await pyOCRAPI(urlBase64); const resOcr = await pyOCRAPI(urlBase64);
if (resOcr.status !== 200) { if (resOcr.status !== 200) {
ElMessage.error("图片识别错误"); ElMessage.error("图片识别错误");
@ -351,7 +365,7 @@ const assembleExam = (eachSub) => {
let regex = null; let regex = null;
let titleAndWorkDesc = '', let titleAndWorkDesc = '',
answer = ''; answer = '';
// 获取[题源] - 格式化 // 获取[题源] - 格式化
@ -361,7 +375,7 @@ const assembleExam = (eachSub) => {
subObj.worktag = workTag[0].replace(/^\d*[..。]/g, ''); subObj.worktag = workTag[0].replace(/^\d*[..。]/g, '');
subObj.worktag = subObj.worktag.replace('(', '').replace(')', ''); subObj.worktag = subObj.worktag.replace('(', '').replace(')', '');
} }
// 去掉开头的序号和题源 // 去掉开头的序号和题源
eachSub = eachSub.replace(regex, ''); eachSub = eachSub.replace(regex, '');
// 先判断是否存在答案 // 先判断是否存在答案
@ -370,7 +384,7 @@ const assembleExam = (eachSub) => {
if (!hasAnswer) { if (!hasAnswer) {
// 不存在答案, 仅处理[题干+选项] // 不存在答案, 仅处理[题干+选项]
titleAndWorkDesc = eachSub; titleAndWorkDesc = eachSub;
}else { } else {
// 存在答案, 需处理[题干+选项]和[答案+解析] // 存在答案, 需处理[题干+选项]和[答案+解析]
regex = /(<br \/>?\s*[【\[].*?[】\]])/g; regex = /(<br \/>?\s*[【\[].*?[】\]])/g;
let tmpList = eachSub.split(regex); let tmpList = eachSub.split(regex);
@ -386,10 +400,10 @@ const assembleExam = (eachSub) => {
// 第二部分[分析-答案] 处理 // 第二部分[分析-答案] 处理
let answerAndAnswer = {}; let answerAndAnswer = {};
// 将第二部分的内容做key-value绑定 - 键为【分析】、【讨论】、【方法】等. 值为随之分隔的内容 // 将第二部分的内容做key-value绑定 - 键为【分析】、【讨论】、【方法】等. 值为随之分隔的内容
for (let i=1; i<tmpList.length-1; i=i+2){ for (let i = 1; i < tmpList.length - 1; i = i + 2) {
let key = tmpList[i]; let key = tmpList[i];
key = key.replace(/<br \/>|【|】|\[|\]/g, ''); key = key.replace(/<br \/>|【|】|\[|\]/g, '');
let value = tmpList[i+1]; let value = tmpList[i + 1];
value = value.replace(/^<br \/>+|<br \/>+$/g, ''); value = value.replace(/^<br \/>+|<br \/>+$/g, '');
answerAndAnswer[key] = value; answerAndAnswer[key] = value;
} }
@ -430,12 +444,12 @@ const assembleExam = (eachSub) => {
// [答案] - 初步初始化 --- 根据答案判断试题大分类: 复合题(实际为大题) 或 其他基础题型(单选,多选,填空,判断) // [答案] - 初步初始化 --- 根据答案判断试题大分类: 复合题(实际为大题) 或 其他基础题型(单选,多选,填空,判断)
answer = answerAndAnswer['答案'].trim(); answer = answerAndAnswer['答案'].trim();
if(!answer) { if (!answer) {
answer = answerAndAnswer['答案及评分参考'].trim(); answer = answerAndAnswer['答案及评分参考'].trim();
answer = answer.replace(/^\d+[\u4e00-\u9fa5][..。]\s*<br \/>/, ''); // 去掉 - 有些开头会有[xx分。] answer = answer.replace(/^\d+[\u4e00-\u9fa5][..。]\s*<br \/>/, ''); // 去掉 - 有些开头会有[xx分。]
} }
// 将多余的空格替换为固定的4个空格 // 将多余的空格替换为固定的4个空格
answer = answer.replaceAll("\\s{3,}"," "); answer = answer.replaceAll("\\s{3,}", " ");
if (answer == null | answer == '') { if (answer == null | answer == '') {
subObj.err = '题目缺少[答案]'; subObj.err = '题目缺少[答案]';
return subObj; return subObj;
@ -455,7 +469,7 @@ const assembleExam = (eachSub) => {
let answerFind = regex.test(answer); let answerFind = regex.test(answer);
regex = /(\d+[..。]|\(\d+\)|\d+)/; regex = /(\d+[..。]|\(\d+\)|\d+)/;
let titleFind = regex.test(titleAndWorkDesc); let titleFind = regex.test(titleAndWorkDesc);
if(titleFind && answerFind){ if (titleFind && answerFind) {
/** /**
* [复合题] - 处理逻辑 * [复合题] - 处理逻辑
*/ */
@ -471,7 +485,7 @@ const assembleExam = (eachSub) => {
if (tmpExam) { if (tmpExam) {
// 错误信息 // 错误信息
if(tmpExam.errMsg !== '') { if (tmpExam.errMsg !== '') {
subObj.err = tmpExam.err; subObj.err = tmpExam.err;
return subObj; return subObj;
} }
@ -484,7 +498,7 @@ const assembleExam = (eachSub) => {
subObj.workanswer = JSON.stringify(tmpExam.arrWorkAnswer); subObj.workanswer = JSON.stringify(tmpExam.arrWorkAnswer);
} }
} }
return subObj; return subObj;
} }
@ -508,7 +522,7 @@ const processExamSingle = function (titleAndWorkDesc, answer) {
let matcher = null; let matcher = null;
/** [判断题]的处理逻辑, resp: -1-未找到 0-*为对应匹配的index */ /** [判断题]的处理逻辑, resp: -1-未找到 0-*为对应匹配的index */
let judgedStatus = answer!=='' ? containsExactMatch(answer) : -1; let judgedStatus = answer !== '' ? containsExactMatch(answer) : -1;
/** 其他基础题型(单选,多选,填空,判断)的处理逻辑 */ /** 其他基础题型(单选,多选,填空,判断)的处理逻辑 */
// 先去掉开头的试题序号 // 先去掉开头的试题序号
@ -524,7 +538,7 @@ const processExamSingle = function (titleAndWorkDesc, answer) {
answer = answer.replace("<br />", "").trim(); answer = answer.replace("<br />", "").trim();
// [题型] - 格式化 - 根据答案字符个数区分[单选]或[多选] // [题型] - 格式化 - 根据答案字符个数区分[单选]或[多选]
examSingle.workType = answer==='' ? '单选题' : answer.length == 1 ? "单选题" : "多选题"; examSingle.workType = answer === '' ? '单选题' : answer.length == 1 ? "单选题" : "多选题";
// 切分题干+选项 // 切分题干+选项
regex = /<br \/>*\s*[A-H][..。]/g; regex = /<br \/>*\s*[A-H][..。]/g;
@ -535,12 +549,12 @@ const processExamSingle = function (titleAndWorkDesc, answer) {
// [选项]-处理 --- ['ABC123','ABC123'] // [选项]-处理 --- ['ABC123','ABC123']
for (let i = 1; i < tmpSplit.length; i++) { for (let i = 1; i < tmpSplit.length; i++) {
let option = tmpSplit[i].replace("<br />", "").trim(); let option = tmpSplit[i].replace("<br />", "").trim();
//option = option.replace("_", ""); //option = option.replace("_", "");
// [选项] - 格式化 // [选项] - 格式化
examSingle.arrWorkDesc.push(option); examSingle.arrWorkDesc.push(option);
} }
// [题目答案] --- ['0'] | ['0','1'] // [题目答案] --- ['0'] | ['0','1']
if (answer !== '') { if (answer !== '') {
// 答案为空时, 置空后直接返回 // 答案为空时, 置空后直接返回
@ -570,7 +584,7 @@ const processExamSingle = function (titleAndWorkDesc, answer) {
examSingle.arrWorkAnswer = answer.split(" "); examSingle.arrWorkAnswer = answer.split(" ");
} }
} }
else if( judgedStatus != -1 ) { else if (judgedStatus != -1) {
/** /**
* 判断题 * 判断题
*/ */
@ -635,48 +649,48 @@ const processExamMulti = function (titleAndWorkDesc, answer) {
// 先确定当前是以什么形式的小题序号来切分 --- 需要全部独立判断, 避免出现复合题中, 每小题内还包含小题的情况--- 1.回答以下问题 (1)***** (2)****** // 先确定当前是以什么形式的小题序号来切分 --- 需要全部独立判断, 避免出现复合题中, 每小题内还包含小题的情况--- 1.回答以下问题 (1)***** (2)******
let cliceSucc = false; let cliceSucc = false;
let arrAnswer = [] let arrAnswer = []
if(!cliceSucc){ if (!cliceSucc) {
regex = /<br \/>\s*\d+[..。]\s*/; regex = /<br \/>\s*\d+[..。]\s*/;
if (regex.test(titleAndWorkDesc)) { if (regex.test(titleAndWorkDesc)) {
// 再次以答案中的序号同步匹配一次 // 再次以答案中的序号同步匹配一次
regex = /^\s*\d+[..。]\s*/; regex = /^\s*\d+[..。]\s*/;
if(answer === '' || regex.test(answer)){ if (answer === '' || regex.test(answer)) {
regex = /<br \/>\s*\d+[..。]\s*/g; regex = /<br \/>\s*\d+[..。]\s*/g;
tmpSplit = titleAndWorkDesc.split(regex); tmpSplit = titleAndWorkDesc.split(regex);
if (answer !== '') { if (answer !== '') {
// 存在答案时, 再校验 // 存在答案时, 再校验
regex = /^\s*\d+[..。]\s*|<br \/>\s*\d+[..。]\s*|\s+\d+[..。]\s*/g; regex = /^\s*\d+[..。]\s*|<br \/>\s*\d+[..。]\s*|\s+\d+[..。]\s*/g;
arrAnswer = answer.split(regex); arrAnswer = answer.split(regex);
} }
cliceSucc = true; cliceSucc = true;
} }
} }
} }
if (!cliceSucc){ if (!cliceSucc) {
regex = /<br \/>\s*\d+\s*/; regex = /<br \/>\s*\d+\s*/;
if (regex.test(titleAndWorkDesc)) { if (regex.test(titleAndWorkDesc)) {
// 再次以答案中的序号同步匹配一次 // 再次以答案中的序号同步匹配一次
regex = /\s*\d+\s*/; regex = /\s*\d+\s*/;
if(answer === '' || regex.test(answer)){ if (answer === '' || regex.test(answer)) {
regex = /<br \/>\s*\d+\s*/g; regex = /<br \/>\s*\d+\s*/g;
tmpSplit = titleAndWorkDesc.split(regex); tmpSplit = titleAndWorkDesc.split(regex);
if (answer !== '') { if (answer !== '') {
// 存在答案时, 再校验 // 存在答案时, 再校验
regex = /^\s*\d+\s*|<br \/>\s*\d+\s*|\s+\d+\s*/g; regex = /^\s*\d+\s*|<br \/>\s*\d+\s*|\s+\d+\s*/g;
arrAnswer = answer.split(regex); arrAnswer = answer.split(regex);
} }
cliceSucc = true; cliceSucc = true;
} }
} }
} }
if (!cliceSucc){ if (!cliceSucc) {
regex = /<br \/>\s*\(\d+\)\s*/; regex = /<br \/>\s*\(\d+\)\s*/;
if (regex.test(titleAndWorkDesc)) { if (regex.test(titleAndWorkDesc)) {
// 再次以答案中的序号同步匹配一次 // 再次以答案中的序号同步匹配一次
regex = /^\s*\(\d+\)\s*/; regex = /^\s*\(\d+\)\s*/;
if(answer === '' || regex.test(answer)){ if (answer === '' || regex.test(answer)) {
regex = /<br \/>\s*\(\d+\)\s*/g; regex = /<br \/>\s*\(\d+\)\s*/g;
tmpSplit = titleAndWorkDesc.split(regex); tmpSplit = titleAndWorkDesc.split(regex);
if (answer !== '') { if (answer !== '') {
@ -689,19 +703,19 @@ const processExamMulti = function (titleAndWorkDesc, answer) {
} }
} }
} }
if (!cliceSucc){ if (!cliceSucc) {
examMulti.errMsg = '[复合题]小题与答案序号[不匹配]'; examMulti.errMsg = '[复合题]小题与答案序号[不匹配]';
return examMulti; return examMulti;
} }
if (tmpSplit.length < 2){ if (tmpSplit.length < 2) {
examMulti.errMsg = '[复合题]题干与小题[切分失败]'; examMulti.errMsg = '[复合题]题干与小题[切分失败]';
return examMulti; return examMulti;
} }
if (answer !== '' && arrAnswer.length < 2){ if (answer !== '' && arrAnswer.length < 2) {
examMulti.errMsg = '[复合题]答案切分小题失败'; examMulti.errMsg = '[复合题]答案切分小题失败';
return examMulti; return examMulti;
} }
if (answer !== '' && tmpSplit.length != arrAnswer.length){ if (answer !== '' && tmpSplit.length != arrAnswer.length) {
examMulti.errMsg = '[复合题]小题个数与答案个数[不一致]'; examMulti.errMsg = '[复合题]小题个数与答案个数[不一致]';
return examMulti; return examMulti;
} }
@ -710,13 +724,13 @@ const processExamMulti = function (titleAndWorkDesc, answer) {
examMulti.title = tmpSplit[0].trim(); examMulti.title = tmpSplit[0].trim();
// [选项]+[答案] - 逻辑处理 // [选项]+[答案] - 逻辑处理
for (let i=1; i<tmpSplit.length; i++){ for (let i = 1; i < tmpSplit.length; i++) {
const tmp = tmpSplit[i].trim(); const tmp = tmpSplit[i].trim();
// 因arrAnswer[0]对应为分隔出来的首位空数组, 故这里也可直接使用i=1作为下标获取答案 // 因arrAnswer[0]对应为分隔出来的首位空数组, 故这里也可直接使用i=1作为下标获取答案
const tmpAnswer = answer === '' ? '' : arrAnswer[i].trim(); const tmpAnswer = answer === '' ? '' : arrAnswer[i].trim();
// 单题处理 // 单题处理
const tmpExam = processExamSingle(tmp, tmpAnswer); const tmpExam = processExamSingle(tmp, tmpAnswer);
if(tmpExam.errMsg !== ''){ if (tmpExam.errMsg !== '') {
examMulti.errMsg = '[复合题]小题解析失败'; examMulti.errMsg = '[复合题]小题解析失败';
return examMulti; return examMulti;
} }
@ -768,10 +782,10 @@ const containsExactMatch = function (answer) {
answer = answer.replace("_____", ""); answer = answer.replace("_____", "");
let index = 0; let index = 0;
for (let item of EXAM_JUDGED_DICTIONARY) { for (let item of EXAM_JUDGED_DICTIONARY) {
if (answer === item) { if (answer === item) {
return index; return index;
} }
index++; index++;
} }
return -1; return -1;
} }

View File

@ -4,7 +4,7 @@
<div style="font-size: 18px; display: flex; flex-wrap: nowrap"> <div style="font-size: 18px; display: flex; flex-wrap: nowrap">
<div style="flex: 1"> <div style="flex: 1">
{{ classWorkAnalysis.title }}完成情况 {{ classWorkAnalysis.title }}完成情况
<el-tag :type="classWorkAnalysis.workclass" size="large" style="height: 25px">{{ <el-tag :type="classWorkAnalysis.workclass||'info'" size="large" style="height: 25px">{{
classWorkAnalysis.worktype classWorkAnalysis.worktype
}}</el-tag> }}</el-tag>
</div> </div>
@ -162,9 +162,12 @@ import ItemDialogScore from '@/views/classTask/container/classTask/item-dialog-s
import quizStats from '@/views/classTask/container/quizStats.vue' import quizStats from '@/views/classTask/container/quizStats.vue'
import ClassOverview from '@/views/classTask/container/classOverview.vue' import ClassOverview from '@/views/classTask/container/classOverview.vue'
import {sessionStore} from '@/utils/store' import {sessionStore} from '@/utils/store'
import Chat from '@/utils/chat' // im // import Chat from '@/utils/chat' // im
import { Homework } from '@/AixPPTist/src/api/index'
import MsgEnum from '@/plugins/imChat/msgEnum' // im
import ChatWs from '@/plugins/socket' // socket
import { set } from 'lodash'
if (!ChatWs.ws) ChatWs.init()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const emit = defineEmits(['cle-click']) const emit = defineEmits(['cle-click'])
const props = defineProps({ const props = defineProps({
@ -713,27 +716,48 @@ const closeDialog = () => {
// im // im
const msgHandle = (msg) => { const msgHandle = (msg) => {
const { type, data } = msg console.log('socket消息回调', msg)
switch(type) { if (typeof msg === 'object'){
case 'TIMAddRecvNewMsgCallback': // data=[] const { head, content, ...other } = msg
{ switch(head) {
(data||[]).forEach(o => { case MsgEnum.HEADS.MSG_closed: // :
const msgArr = o?.message_elem_array||[] Homework.win = null
msgArr.forEach(info => { window.close() //
const msgType = info?.elem_type // TIMElemType break
const msgData = !!info.text_elem_content ? JSON.parse(info.text_elem_content)||'' : '' case MsgEnum.HEADS.MSG_finishHomework: // :
// console.log('更新作业', head, content)
//console.log('msgData->', msgData); const data = JSON.parse(localStorage.getItem('teachClassWorkItem'));
if (msgData.msgKey == "finishHomework"){ openDialog(data, false);
// break
const data = JSON.parse(localStorage.getItem('teachClassWorkItem')); case MsgEnum.HEADS.MSG_slideFlapping: //
//console.log('data->', data); console.log('切换页面-关闭窗口')
openDialog(data, false); Homework.win = null
} window.close() //
}) break
}) case MsgEnum.HEADS.MSG_pushSreen_experiment: // :
} Homework.win = null
break window.close() //
break
// case 'TIMAddRecvNewMsgCallback': // data=[]
// {
// (data||[]).forEach(o => {
// const msgArr = o?.message_elem_array||[]
// msgArr.forEach(info => {
// const msgType = info?.elem_type // TIMElemType
// const msgData = !!info.text_elem_content ? JSON.parse(info.text_elem_content)||'' : ''
// //
// //console.log('msgData->', msgData);
// if (msgData.msgKey == "finishHomework"){
// //
// const data = JSON.parse(localStorage.getItem('teachClassWorkItem'));
// //console.log('data->', data);
// openDialog(data, false);
// }
// })
// })
// }
// break
}
} }
} }
@ -750,11 +774,22 @@ onMounted(() => {
isReloadTimer(); isReloadTimer();
// im // im
if (!Chat.imChat) { if (ChatWs.ws) {
Chat.init(true, true, msgHandle); console.log('socket监听消息')
} else { ChatWs.watch((msg, e) => {
Chat.listenMsg(msgHandle); try {
} msgHandle(JSON.parse(msg)?.msg)
} catch (error) {
console.error('socket 解析异常 ', error, e)
msgHandle(msg)
}
})
}
// if (!Chat.imChat) {
// Chat.init(true, true, msgHandle);
// } else {
// Chat.listenMsg(msgHandle);
// }
}) })
const isReloadTimer = () =>{ const isReloadTimer = () =>{
clearInterval(reloadTimer.value) // clearInterval(reloadTimer.value) //

View File

@ -59,10 +59,10 @@ const getHomework = async () => {
homeworkList.value = rows || []; homeworkList.value = rows || [];
homeworkList.value.forEach((item) => { homeworkList.value.forEach((item) => {
// UI // UI
if (item.worktype == '学习目标定位') { if (item.worktype == '课堂展示') {
item.workclass = 'success' item.workclass = 'success'
item.workcodesList = JSON.parse(item.workcodes) item.workcodesList = JSON.parse(item.workcodes)
} else if (item.worktype == '教材研读') { } else if (item.worktype == '科学实验') {
item.workclass = 'primary' item.workclass = 'primary'
} else if (item.worktype == '框架梳理') { } else if (item.worktype == '框架梳理') {
item.workclass = 'warning' item.workclass = 'warning'

View File

@ -0,0 +1,427 @@
<template>
<div class="login-container">
<div class="box-item desc">
<div class="welcome">
<p>欢迎登录 {{ homeTitle }}</p>
</div>
<img class="welcome-img" :src="leftBg2" />
</div>
<div class="box-item login" v-if="isRegister">
<WindowTools :is-has-max="false" />
<div class="login-title">账号登录</div>
<el-form ref="formRef" class="login-form" :model="loginForm" :rules="rules" size="large">
<el-form-item prop="username">
<el-input v-model.trim="loginForm.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item prop="password" style="margin-bottom: 15px">
<el-input v-model="loginForm.password" autocomplete="on" type="password" placeholder="请输入密码" />
</el-form-item>
<div class="flex mb-5">
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
<!-- <el-checkbox >阅读并同意xxx</el-checkbox> -->
</div>
<el-form-item>
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoreRegister">注册账号</a>
</div>
</el-form>
</div>
<div class="box-item login" v-else>
<WindowTools :is-has-max="false" />
<div class="login-title">账号注册</div>
<el-form ref="ruleFormRef" class="login-form" :model="ruleForm" label-width="auto" :rules="rules" size="large">
<el-form-item label="手机号" prop="username">
<el-input v-model="ruleForm.username" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="验证码" prop="smsCode" style="display: flex">
<el-input style="width:185px" v-model="ruleForm.smsCode" placeholder="请输入验证码" /><el-button style="margin-left:10px;width:100px" :disabled="codeName=='发送验证码'?false:true" type="primary" @click="sendyzm">{{ codeName }}</el-button>
</el-form-item>
<el-form-item label="密码" prop="password" >
<el-input autocomplete="on" type="password" v-model="ruleForm.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button class="btn" type="primary" @click="RegisterForm(ruleFormRef)">立即注册</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoLogin"> 返回登录 </a>
</div>
</el-form>
</div>
</div>
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
:close-on-press-escape="false" align-center>
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
status="success" />
</el-dialog>
<el-dialog
v-model="isImg"
title="人机验证"
width="500"
style=" -webkit-app-region: no-drag;"
>
<span>根据图片回答相关问题1</span>
<div style="display: flex;align-items: center;;margin-top:30px">
<img :src="isPeopleImg" style="width:200px;height:60px;cursor: pointer;" alt="" srcset="" @click="refreshImg">
<el-input v-model="ruleForm.imgCode" style="width: 250px;height:40px;margin-left:20px" placeholder="请根据图片填入答案" />
</div>
<div style="display: flex;justify-content: center;margin-top:30px">
<el-button type="primary" @click="sbmitImg">确定</el-button>
</div>
</el-dialog>
<!--选择学科-->
<SelectSubject v-model="isSubject" :login-data="loginForm" />
<!--注册弹框-->
<Register ref="RegModel"></Register>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import useUserStore from '@/store/modules/user'
import leftBg2 from '@/assets/images/login/left-bg2.png'
import WindowTools from '@/components/window-tools/index.vue'
import SelectSubject from '@/components/select-subject/index.vue'
import Register from './components/Register.vue'
import { sessionStore } from '@/utils/store'
import {sendcode,instructorregister,getCodeImg} from '@/api/login'
const { session } = require('@electron/remote')
const downloadProp = ref(0)
const showDownLoading = ref(false)
const { ipcRenderer } = window.electron || {}
const formRef = ref()
const userStore = useUserStore()
const btnLoading = ref(false)
const isSubject = ref(false)
const RegModel = ref(false)
const isRegister = ref(true)
const ruleFormRef = ref(null)
const codeName=ref('发送验证码')
const timer=ref(null)
const isImg=ref(false)
const isPeopleImg=ref(null)
const type=ref(1) // 1 2
const resImg = reactive({ imgData: {} });
//
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
//
const ruleForm = reactive({
})
//
const rules = reactive({
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
smsCode: [{ required: true, trigger: 'blur', message: '请输入您的验证码' }],
})
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
ipcRenderer.on('update-app-progress', (e, prop) => {
downloadProp.value = prop
showDownLoading.value = prop !== 100
})
const gotoreRegister=()=>{
codeName.value='发送验证码'
if(timer.value){
clearInterval(timer.value);
}
isRegister.value=false
}
//
const refreshImg=()=>{
getCodeImg().then(res=>{
isPeopleImg.value='data:image/jpg;base64,'+res.img
resImg.imgData=res
})
}
//
const sbmitImg=()=>{
if(ruleForm.imgCode){
// {mobile:ruleForm.phoneNumber,code:ruleForm.imgCode,uuid:resImg.imgData.uuid}
const { username:username,imgCode:code } = ruleForm
const params = {
username, code,
uuid: resImg.imgData.uuid,
source:4
}
sendcode(params).then(res=>{
if(res.code==200){
ElMessage.success('短信发送成功')
ruleForm.Code=res.data
isImg.value=false
codeName.value=60
timer.value=setInterval(()=>{
codeName.value--
if(codeName.value==0){
codeName.value='发送验证码'
clearInterval(timer.value);
}
},1000)
}
})
}else{
ElMessage.error('请根据图片输入验证码')
}
//
}
//
const sendyzm=()=>{
if(ruleForm.username){
const pattern = /^1[3-9]\d{9}$/;
if( pattern.test(ruleForm.username) ){
getCodeImg().then(res=>{
if(res.code==200){
ruleForm.imgCode=null
isPeopleImg.value='data:image/jpg;base64,'+res.img
isImg.value=true
resImg.imgData=res
// codeName.value=60
// timer.value=setInterval(()=>{
// codeName.value--
// if(codeName.value==0){
// codeName.value=''
// clearInterval(timer.value);
// }
// },1000)
}else{
ElMessage.error(res.msg)
}
})
}else{
ElMessage.error('请输入正确的手机号码')
}
// captchaImg({mobile:ruleForm.phoneNumber}).then(res=>{
// console.log('res->', res)
// })
}else{
ElMessage.error('请输入手机号码')
}
}
//
const RegisterModel = type => {
RegModel.value.OpenModel(type)
}
//
const submitForm = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
btnLoading.value = true
// cookie
if (loginForm.rememberMe) {
await setCookie('username', loginForm.username)
await setCookie('password', encrypt(loginForm.password))
await setCookie('rememberMe', loginForm.rememberMe.toString())
} else {
//
await session.defaultSession.clearStorageData({
origin: curWinUrl,
storages: ['cookies']
})
}
try {
await userStore.login(loginForm)
await userStore.getInfo()
if (userStore.user.edustage || userStore.user.edusubject || isStadium(userStore.user)) {
ElMessage.success('登录成功')
ipcRenderer && ipcRenderer.send('openMainWindow')
} else {
isSubject.value = true
}
} finally {
btnLoading.value = false
}
}
})
}
const isStadium = (user) => {
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')
}
const getCookie = async () => {
const username = (await getCookieDetail('username'))[0]
const password = (await getCookieDetail('password'))[0]
const rememberMe = (await getCookieDetail('rememberMe'))[0]
loginForm.username = username ? username.value : loginForm.username
loginForm.password = password ? decrypt(password.value) : loginForm.password
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
}
// cookie
const getCookieDetail = (name) => {
return session.defaultSession.cookies.get({ url: curWinUrl, name })
}
// cookie
const setCookie = (name, value) => {
// 30
let Days = 30
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
const cookie = {
url: curWinUrl,
name,
value,
expirationDate: times
}
return session.defaultSession.cookies.set(cookie)
}
const gotoLogin = () => {
codeName.value='发送验证码'
if (timer.value){
clearInterval(timer.value);
}
if (ruleFormRef.value) ruleFormRef.value.resetFields()
isRegister.value = true
}
//
const RegisterForm = async (formEl) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
instructorregister(ruleForm).then(res=>{
if(res.code==200){
ElMessage.success('您已注册成功')
if (ruleFormRef.value) ruleFormRef.value.resetFields()
gotoLogin()
}else{
ElMessage.error(res.msg)
}
})
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}
onMounted(() => {
localStorage.clear()
sessionStore.set('subject', {
bookList: null,
curBook: null,
curNode: null,
defaultExpandedKeys: [],
subjectTree: []
})
getCookie()
})
</script>
<style lang="scss" scoped>
.login-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
-webkit-app-region: drag;
.box-item {
width: 444px;
height: 520px;
&.desc {
background: #ffffff;
border-radius: 12px 0px 0px 12px;
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
padding: 23px 25px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
background-color: #003b94;
}
&.login {
background: #ffffff;
border-radius: 0px 12px 12px 0px;
padding: 34px 42px;
position: relative;
}
.welcome {
padding-top: 35px;
p {
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
}
.welcome-img {
margin-top: 20px;
width: 350px;
height: 350px;
}
.login-title {
font-size: 20px;
text-align: center;
color: #1e1e1e;
margin-bottom: 35px;
margin-top: 50px;
}
.login-form {
-webkit-app-region: no-drag;
.captcha-input {
width: 60%;
}
.captcha-img {
cursor: pointer;
}
}
.btn {
width: 350px;
height: 50px;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 50px;
cursor: pointer;
}
}
}
.header-tool {
position: absolute;
right: 0;
top: 0;
-webkit-app-region: no-drag;
span {
padding: 5px 10px;
cursor: pointer;
}
}
.el-form-item {
margin-bottom: 40px;
}
</style>

View File

@ -1,411 +1,13 @@
<template> <template>
<div class="login-container"> <ycLogin v-if="buildMode === 'yc'||buildMode === 'yc2'">
<div class="box-item desc"> </ycLogin>
<div class="welcome"> <defultLogin v-else>
<p>欢迎登录 {{ homeTitle }}</p> </defultLogin>
</div>
<img class="welcome-img" :src="leftBg2" />
</div>
<div class="box-item login" v-if="isRegister">
<WindowTools :is-has-max="false" />
<div class="login-title">账号登录</div>
<el-form ref="formRef" class="login-form" :model="loginForm" :rules="rules" size="large">
<el-form-item prop="username">
<el-input v-model.trim="loginForm.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item prop="password" style="margin-bottom: 15px">
<el-input v-model="loginForm.password" autocomplete="on" type="password" placeholder="请输入密码" />
</el-form-item>
<div class="flex mb-5">
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
<!-- <el-checkbox >阅读并同意xxx</el-checkbox> -->
</div>
<el-form-item>
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoreRegister">注册账号</a>
</div>
</el-form>
</div>
<div class="box-item login" v-else>
<WindowTools :is-has-max="false" />
<div class="login-title">账号注册</div>
<el-form ref="ruleFormRef" class="login-form" :model="ruleForm" label-width="auto" :rules="rules" size="large">
<el-form-item label="手机号" prop="username">
<el-input v-model="ruleForm.username" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="验证码" prop="smsCode" style="display: flex">
<el-input style="width:185px" v-model="ruleForm.smsCode" placeholder="请输入验证码" /><el-button style="margin-left:10px;width:100px" type="primary" @click="sendyzm">发送验证码</el-button>
</el-form-item>
<el-form-item label="密码" prop="password" >
<el-input autocomplete="on" type="password" v-model="ruleForm.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button class="btn" type="primary" @click="RegisterForm(ruleFormRef)">立即注册</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoLogin"> 返回登录 </a>
</div>
</el-form>
</div>
</div>
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
:close-on-press-escape="false" align-center>
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
status="success" />
</el-dialog>
<el-dialog
v-model="isImg"
title="人机验证"
width="500"
style=" -webkit-app-region: no-drag;"
>
<span>根据图片回答相关问题</span>
<div style="display: flex;align-items: center;;margin-top:30px">
<img :src="isPeopleImg" style="width:200px;height:60px;cursor: pointer;" alt="" srcset="" @click="refreshImg">
<el-input v-model="ruleForm.imgCode" style="width: 250px;height:40px;margin-left:20px" placeholder="请根据图片填入答案" />
</div>
<div style="display: flex;justify-content: center;margin-top:30px">
<el-button type="primary" @click="sbmitImg">确定</el-button>
</div>
</el-dialog>
<!--选择学科-->
<SelectSubject v-model="isSubject" :login-data="loginForm" />
<!--注册弹框-->
<Register ref="RegModel"></Register>
</template> </template>
<script setup> <script setup>
import { onMounted, reactive, ref } from 'vue' import ycLogin from './yc-login.vue'
import { ElMessage } from 'element-plus' import defultLogin from './defult-login.vue'
import { encrypt, decrypt } from '@/utils/jsencrypt' const buildMode = import.meta.env.MODE
import useUserStore from '@/store/modules/user'
import leftBg2 from '@/assets/images/login/left-bg2.png'
import WindowTools from '@/components/window-tools/index.vue'
import SelectSubject from '@/components/select-subject/index.vue'
import Register from './components/Register.vue'
import { sessionStore } from '@/utils/store'
import {sendcode,instructorregister,getCodeImg} from '@/api/login'
const { session } = require('@electron/remote')
const downloadProp = ref(0)
const showDownLoading = ref(false)
const { ipcRenderer } = window.electron || {}
const formRef = ref()
const userStore = useUserStore()
const btnLoading = ref(false)
const isSubject = ref(false)
const RegModel = ref(false)
const isRegister = ref(true)
const ruleFormRef = ref(null)
const codeName=ref('发送验证码')
const timer=ref(null)
const isImg=ref(false)
const isPeopleImg=ref(null)
const type=ref(1) // 1 2
const resImg = reactive({ imgData: {} });
//
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
//
const ruleForm = reactive({
})
//
const rules = reactive({
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
smsCode: [{ required: true, trigger: 'blur', message: '请输入您的验证码' }],
})
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
ipcRenderer.on('update-app-progress', (e, prop) => {
downloadProp.value = prop
showDownLoading.value = prop !== 100
})
const gotoreRegister=()=>{
codeName.value='发送验证码'
if(timer.value){
clearInterval(timer.value);
}
isRegister.value=false
}
//
const refreshImg=()=>{
getCodeImg().then(res=>{
isPeopleImg.value='data:image/jpg;base64,'+res.img
resImg.imgData=res
})
}
//
const sbmitImg=()=>{
if(ruleForm.imgCode){
// {mobile:ruleForm.phoneNumber,code:ruleForm.imgCode,uuid:resImg.imgData.uuid}
const { username:username,imgCode:code } = ruleForm
const params = {
username, code,
uuid: resImg.imgData.uuid,
source:4
}
sendcode(params).then(res=>{
if(res.code==200){
ElMessage.success('短信发送成功')
ruleForm.Code=res.data
isImg.value=false
}
})
}else{
ElMessage.error('请根据图片输入验证码')
}
//
}
//
const sendyzm=()=>{
if(ruleForm.username){
const pattern = /^1[3-9]\d{9}$/;
if( pattern.test(ruleForm.username) ){
getCodeImg().then(res=>{
if(res.code==200){
ruleForm.imgCode=null
isPeopleImg.value='data:image/jpg;base64,'+res.img
isImg.value=true
resImg.imgData=res
codeName.value=60
timer.value=setInterval(()=>{
codeName.value--
if(codeName.value==0){
codeName.value='发送验证码'
clearInterval(timer.value);
}
},1000)
}else{
ElMessage.error(res.msg)
}
})
}else{
ElMessage.error('请输入正确的手机号码')
}
// captchaImg({mobile:ruleForm.phoneNumber}).then(res=>{
// console.log('res->', res)
// })
}else{
ElMessage.error('请输入手机号码')
}
}
//
const RegisterModel = type => {
RegModel.value.OpenModel(type)
}
//
const submitForm = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
btnLoading.value = true
// cookie
if (loginForm.rememberMe) {
await setCookie('username', loginForm.username)
await setCookie('password', encrypt(loginForm.password))
await setCookie('rememberMe', loginForm.rememberMe.toString())
} else {
//
await session.defaultSession.clearStorageData({
origin: curWinUrl,
storages: ['cookies']
})
}
try {
await userStore.login(loginForm)
await userStore.getInfo()
if (userStore.user.edustage || userStore.user.edusubject) {
ElMessage.success('登录成功')
ipcRenderer && ipcRenderer.send('openMainWindow')
} else {
isSubject.value = true
}
} finally {
btnLoading.value = false
}
}
})
}
const getCookie = async () => {
const username = (await getCookieDetail('username'))[0]
const password = (await getCookieDetail('password'))[0]
const rememberMe = (await getCookieDetail('rememberMe'))[0]
loginForm.username = username ? username.value : loginForm.username
loginForm.password = password ? decrypt(password.value) : loginForm.password
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
}
// cookie
const getCookieDetail = (name) => {
return session.defaultSession.cookies.get({ url: curWinUrl, name })
}
// cookie
const setCookie = (name, value) => {
// 30
let Days = 30
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
const cookie = {
url: curWinUrl,
name,
value,
expirationDate: times
}
return session.defaultSession.cookies.set(cookie)
}
const gotoLogin = () => {
codeName.value='发送验证码'
if (ruleFormRef.value) ruleFormRef.value.resetFields()
isRegister.value = true
}
//
const RegisterForm = async (formEl) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
instructorregister(ruleForm).then(res=>{
if(res.code==200){
ElMessage.success('您已注册成功')
if (ruleFormRef.value) ruleFormRef.value.resetFields()
gotoLogin()
}else{
ElMessage.error(res.msg)
}
})
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}
onMounted(() => {
localStorage.clear()
sessionStore.set('subject', {
bookList: null,
curBook: null,
curNode: null,
defaultExpandedKeys: [],
subjectTree: []
})
getCookie()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.login-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
-webkit-app-region: drag;
.box-item {
width: 444px;
height: 520px;
&.desc {
background: #ffffff;
border-radius: 12px 0px 0px 12px;
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
padding: 23px 25px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
background-color: #003b94;
}
&.login {
background: #ffffff;
border-radius: 0px 12px 12px 0px;
padding: 34px 42px;
position: relative;
}
.welcome {
padding-top: 35px;
p {
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
}
.welcome-img {
margin-top: 20px;
width: 350px;
height: 350px;
}
.login-title {
font-size: 20px;
text-align: center;
color: #1e1e1e;
margin-bottom: 35px;
margin-top: 50px;
}
.login-form {
-webkit-app-region: no-drag;
.captcha-input {
width: 60%;
}
.captcha-img {
cursor: pointer;
}
}
.btn {
width: 350px;
height: 50px;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 50px;
cursor: pointer;
}
}
}
.header-tool {
position: absolute;
right: 0;
top: 0;
-webkit-app-region: no-drag;
span {
padding: 5px 10px;
cursor: pointer;
}
}
.el-form-item {
margin-bottom: 40px;
}
</style> </style>

View File

@ -0,0 +1,463 @@
<template>
<div class="login-container">
<div class="login-yc">
<img class="welcome-img" :src="buildMode === 'yc2'?leftBg2:leftBg1" />
</div>
<div class="box-item login" v-if="isRegister">
<WindowTools :is-has-max="false" />
<div style="display: flex;justify-content: center;"><img class="title-logo" :src="yclogo" /></div>
<div class="login-title">永川中小学</div>
<div class="login-title2">{{buildMode === 'yc2'?'虚拟仿真AI实训教学管理系统':'人工智能赋能科学素养与劳动技能系统'}}</div>
<el-form ref="formRef" class="login-form" :model="loginForm" :rules="rules" size="large">
<el-form-item prop="username">
<el-input v-model.trim="loginForm.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item prop="password" style="margin-bottom: 15px">
<el-input v-model="loginForm.password" autocomplete="on" type="password" placeholder="请输入密码" />
</el-form-item>
<div class="flex mb-5">
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
<!-- <el-checkbox >阅读并同意xxx</el-checkbox> -->
</div>
<el-form-item style="margin-bottom: 20px">
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoreRegister">注册账号</a>
</div>
<div class="title-bottom">
重庆市永川区教育委员会
</div>
</el-form>
</div>
<div class="box-item login" v-else>
<WindowTools :is-has-max="false" />
<div class="login-title">账号注册</div>
<el-form ref="ruleFormRef" class="login-form" :model="ruleForm" label-width="auto" :rules="rules" size="large">
<el-form-item label="手机号" prop="username">
<el-input v-model="ruleForm.username" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="验证码" prop="smsCode" style="display: flex">
<el-input style="width:185px" v-model="ruleForm.smsCode" placeholder="请输入验证码" /><el-button style="margin-left:10px;width:100px" :disabled="codeName=='发送验证码'?false:true" type="primary" @click="sendyzm">{{ codeName }}</el-button>
</el-form-item>
<el-form-item label="密码" prop="password" >
<el-input autocomplete="on" type="password" v-model="ruleForm.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button class="btn" type="primary" @click="RegisterForm(ruleFormRef)">立即注册</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoLogin"> 返回登录 </a>
</div>
</el-form>
</div>
</div>
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
:close-on-press-escape="false" align-center>
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
status="success" />
</el-dialog>
<el-dialog
v-model="isImg"
title="人机验证"
width="500"
style=" -webkit-app-region: no-drag;"
>
<span>根据图片回答相关问题1</span>
<div style="display: flex;align-items: center;;margin-top:30px">
<img :src="isPeopleImg" style="width:200px;height:60px;cursor: pointer;" alt="" srcset="" @click="refreshImg">
<el-input v-model="ruleForm.imgCode" style="width: 250px;height:40px;margin-left:20px" placeholder="请根据图片填入答案" />
</div>
<div style="display: flex;justify-content: center;margin-top:30px">
<el-button type="primary" @click="sbmitImg">确定</el-button>
</div>
</el-dialog>
<!--选择学科-->
<SelectSubject v-model="isSubject" :login-data="loginForm" />
<!--注册弹框-->
<Register ref="RegModel"></Register>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import useUserStore from '@/store/modules/user'
import WindowTools from '@/components/window-tools/index.vue'
import SelectSubject from '@/components/select-subject/index.vue'
import Register from './components/Register.vue'
import { sessionStore } from '@/utils/store'
import {sendcode,instructorregister,getCodeImg} from '@/api/login'
import yclogo from '@/assets/images/login/yc-logo.png'
import leftBg1 from '@/assets/images/login/ycpeitu.png'
import leftBg2 from '@/assets/images/login/ycpeitu2.jpg'
const { session } = require('@electron/remote')
const buildMode = import.meta.env.MODE
const downloadProp = ref(0)
const showDownLoading = ref(false)
const { ipcRenderer } = window.electron || {}
const formRef = ref()
const userStore = useUserStore()
const btnLoading = ref(false)
const isSubject = ref(false)
const RegModel = ref(false)
const isRegister = ref(true)
const ruleFormRef = ref(null)
const codeName=ref('发送验证码')
const timer=ref(null)
const isImg=ref(false)
const isPeopleImg=ref(null)
const type=ref(1) // 1 2
const resImg = reactive({ imgData: {} });
//
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
//
const ruleForm = reactive({
})
//
const rules = reactive({
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
smsCode: [{ required: true, trigger: 'blur', message: '请输入您的验证码' }],
})
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
ipcRenderer.on('update-app-progress', (e, prop) => {
downloadProp.value = prop
showDownLoading.value = prop !== 100
})
const gotoreRegister=()=>{
codeName.value='发送验证码'
if(timer.value){
clearInterval(timer.value);
}
isRegister.value=false
}
//
const refreshImg=()=>{
getCodeImg().then(res=>{
isPeopleImg.value='data:image/jpg;base64,'+res.img
resImg.imgData=res
})
}
//
const sbmitImg=()=>{
if(ruleForm.imgCode){
// {mobile:ruleForm.phoneNumber,code:ruleForm.imgCode,uuid:resImg.imgData.uuid}
const { username:username,imgCode:code } = ruleForm
const params = {
username, code,
uuid: resImg.imgData.uuid,
source:4
}
sendcode(params).then(res=>{
if(res.code==200){
ElMessage.success('短信发送成功')
ruleForm.Code=res.data
isImg.value=false
codeName.value=60
timer.value=setInterval(()=>{
codeName.value--
if(codeName.value==0){
codeName.value='发送验证码'
clearInterval(timer.value);
}
},1000)
}
})
}else{
ElMessage.error('请根据图片输入验证码')
}
//
}
//
const sendyzm=()=>{
if(ruleForm.username){
const pattern = /^1[3-9]\d{9}$/;
if( pattern.test(ruleForm.username) ){
getCodeImg().then(res=>{
if(res.code==200){
ruleForm.imgCode=null
isPeopleImg.value='data:image/jpg;base64,'+res.img
isImg.value=true
resImg.imgData=res
// codeName.value=60
// timer.value=setInterval(()=>{
// codeName.value--
// if(codeName.value==0){
// codeName.value=''
// clearInterval(timer.value);
// }
// },1000)
}else{
ElMessage.error(res.msg)
}
})
}else{
ElMessage.error('请输入正确的手机号码')
}
// captchaImg({mobile:ruleForm.phoneNumber}).then(res=>{
// console.log('res->', res)
// })
}else{
ElMessage.error('请输入手机号码')
}
}
//
const RegisterModel = type => {
RegModel.value.OpenModel(type)
}
//
const submitForm = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
btnLoading.value = true
// cookie
if (loginForm.rememberMe) {
await setCookie('username', loginForm.username)
await setCookie('password', encrypt(loginForm.password))
await setCookie('rememberMe', loginForm.rememberMe.toString())
} else {
//
await session.defaultSession.clearStorageData({
origin: curWinUrl,
storages: ['cookies']
})
}
try {
await userStore.login(loginForm)
await userStore.getInfo()
if (userStore.user.edustage || userStore.user.edusubject || isStadium(userStore.user)) {
ElMessage.success('登录成功')
ipcRenderer && ipcRenderer.send('openMainWindow')
} else {
isSubject.value = true
}
} finally {
btnLoading.value = false
}
}
})
}
const isStadium = (user) => {
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')
}
const getCookie = async () => {
const username = (await getCookieDetail('username'))[0]
const password = (await getCookieDetail('password'))[0]
const rememberMe = (await getCookieDetail('rememberMe'))[0]
loginForm.username = username ? username.value : loginForm.username
loginForm.password = password ? decrypt(password.value) : loginForm.password
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
}
// cookie
const getCookieDetail = (name) => {
return session.defaultSession.cookies.get({ url: curWinUrl, name })
}
// cookie
const setCookie = (name, value) => {
// 30
let Days = 30
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
const cookie = {
url: curWinUrl,
name,
value,
expirationDate: times
}
return session.defaultSession.cookies.set(cookie)
}
const gotoLogin = () => {
codeName.value='发送验证码'
if (timer.value){
clearInterval(timer.value);
}
if (ruleFormRef.value) ruleFormRef.value.resetFields()
isRegister.value = true
}
//
const RegisterForm = async (formEl) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
instructorregister(ruleForm).then(res=>{
if(res.code==200){
ElMessage.success('您已注册成功')
if (ruleFormRef.value) ruleFormRef.value.resetFields()
gotoLogin()
}else{
ElMessage.error(res.msg)
}
})
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}
onMounted(() => {
localStorage.clear()
sessionStore.set('subject', {
bookList: null,
curBook: null,
curNode: null,
defaultExpandedKeys: [],
subjectTree: []
})
getCookie()
})
</script>
<style lang="scss" scoped>
.login-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
-webkit-app-region: drag;
.login-yc{
width: 100%;
height: 100%;
img{
width: 100%;
height: 100%;
}
}
.box-item {
width: 370px;
height: 520px;
&.desc {
background: #ffffff;
border-radius: 12px 0px 0px 12px;
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
padding: 23px 25px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
background-color: #003b94;
}
&.login {
background: #ffffff;
border-radius: 0px 12px 12px 0px;
padding: 34px 42px;
position: relative;
.title-logo{
width: 50px;
}
}
.welcome {
padding-top: 35px;
p {
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
}
.welcome-img {
margin-top: 20px;
width: 350px;
height: 350px;
}
.login-title {
font-size: 20px;
text-align: center;
color: #1e1e1e;
margin-bottom: 35px;
margin-top: 50px;
}
.login-title {
font-size: 20px;
text-align: center;
color: #1e1e1e;
margin-bottom: 10px;
margin-top: 5px;
font-width: bold;
}
.login-title2 {
margin-bottom: 20px;
}
.login-form {
-webkit-app-region: no-drag;
.captcha-input {
width: 60%;
}
.captcha-img {
cursor: pointer;
}
.title-bottom{
text-align: center;
width: 100%;
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
}
}
.btn {
width: 350px;
height: 50px;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 50px;
cursor: pointer;
}
}
}
.header-tool {
position: absolute;
right: 0;
top: 0;
-webkit-app-region: no-drag;
span {
padding: 5px 10px;
cursor: pointer;
}
}
.el-form-item {
margin-bottom: 40px;
}
</style>

View File

@ -9,8 +9,8 @@
<el-icon class="item-icon"><Flag /></el-icon> <el-icon class="item-icon"><Flag /></el-icon>
<div class="content-header-title">{{item.name}}</div> <div class="content-header-title">{{item.name}}</div>
<div class="content-header-body"> <div class="content-header-body">
<div class="content-header-num">6</div> <div class="content-header-num">{{item.num}}</div>
<div class="content-header-text">分析结果</div> <div class="content-header-text">{{item.type}}</div>
</div> </div>
</div> </div>
</div> </div>
@ -18,7 +18,20 @@
<div class="content-body-left"> <div class="content-body-left">
<div class="content-body-left-title"> <div class="content-body-left-title">
文枢课件 文枢课件
<el-button class="add-btn" size="small" type="primary" @click="createAIPPT">新建</el-button> <!-- <el-button class="add-btn" size="small" type="primary" @click="createAIPPT">新建</el-button>-->
<el-dropdown class="prepare-center-dropdown add-btn">
<el-button type="primary">
新建<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="createAIPPT">新建文枢课件</el-dropdown-item>
<el-dropdown-item @click="router.push({path: '/model/design'})">AI一键生成</el-dropdown-item>
<el-dropdown-item @click="openFilePicker">导入PPT</el-dropdown-item>
<input type="file" ref="fileInput" style="display: none;" @change="handleFileChange" accept="application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation">
</el-dropdown-menu>
</template>
</el-dropdown>
</div> </div>
<div class="content-body-left-body"> <div class="content-body-left-body">
<kj-list-item <kj-list-item
@ -59,6 +72,7 @@
</div> </div>
</div> </div>
</el-row> </el-row>
<progress-dialog v-model:visible="pgDialog.visible" v-bind="pgDialog" />
</template> </template>
<script setup> <script setup>
@ -69,19 +83,30 @@ import useUserStore from '@/store/modules/user' // 用户信息
import ChooseTextbook from '@/components/choose-textbook/index.vue' import ChooseTextbook from '@/components/choose-textbook/index.vue'
import KjListItem from '@/views/prepare/container/kj-list-item.vue' import KjListItem from '@/views/prepare/container/kj-list-item.vue'
import FileImage from '@/components/file-image/index.vue' import FileImage from '@/components/file-image/index.vue'
import {creatAPT, getSmarttalkPage} from '@/api/file' import progressDialog from '@/views/teachingDesign/container/progress-dialog.vue'
import {Flag, Position} from '@element-plus/icons-vue' import ThumbnailSlide from '@/AixPPTist/src/views/components/ThumbnailSlide/index.vue'
import {creatAPT, getSmarttalkPage, getModelInfo} from '@/api/file'
import {ArrowDown, Flag, Position} from '@element-plus/icons-vue'
import {asyncLocalFile, parseCataByNode} from "@/utils/talkFile"; import {asyncLocalFile, parseCataByNode} from "@/utils/talkFile";
import { dataSetJson } from '@/utils/comm' // id import { dataSetJson } from '@/utils/comm' // id
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import {listEntpcourse} from "@/api/teaching/classwork"; import {listEntpcourse} from "@/api/teaching/classwork";
import {addEntpcoursefileReturnId, getEntpcoursefile} from "@/api/education/entpcoursefile"; import {addEntpcoursefileReturnId, getEntpcoursefile} from "@/api/education/entpcoursefile";
import {createWindow, ipcMsgSend} from "@/utils/tool"; import {createWindow, toLinkLeftWeb} from "@/utils/tool";
import {ElMessage} from "element-plus"; // import {ElMessage} from "element-plus";
import {PPTXFileToJson} from "@/AixPPTist/src/hooks/useImport";
import * as API_entpcoursefile from "@/api/education/entpcoursefile";
import msgUtils from "@/plugins/modal";
import * as commUtils from "@/utils/comm";
import * as Api_server from "@/api/apiService"; //
import useClassTaskStore from '@/store/modules/classTask'
import { slidesToImg } from '@/utils/ppt' // ppt
const router = useRouter() const router = useRouter()
const userStore = useUserStore().user // const userStore = useUserStore().user //
const currentNode = ref({}) const currentNode = ref({})
const refs = ref([]); const refs = ref([]);
const classTaskStore = useClassTaskStore();
const collectRef = (key) => { const collectRef = (key) => {
return (el) => { return (el) => {
@ -92,27 +117,33 @@ const collectRef = (key) => {
const tags = reactive([{ const tags = reactive([{
name: '课标分析', name: '课标分析',
path: '/model/curriculum', path: '/model/curriculum',
bgcolor: 'rgb(241,65,108)' bgcolor: 'rgb(241,65,108)',
type: '分析结果'
},{ },{
name: '教材分析', name: '教材分析',
path: '/model/teaching', path: '/model/teaching',
bgcolor: 'rgb(114,57,234)' bgcolor: 'rgb(114,57,234)',
type: '分析结果'
},{ },{
name: '考试分析', name: '考试分析',
path: '/model/examination', path: '/model/examination',
bgcolor: 'rgb(251,132,4)' bgcolor: 'rgb(251,132,4)',
type: '分析结果'
},{ },{
name: '素材设计', name: '素材设计',
path: '/model/aiKolors', path: '/model/aiKolors',
bgcolor: 'rgb(25,123,237)' bgcolor: 'rgb(25,123,237)',
type: '素材'
},{ },{
name: '作业设计', name: '作业设计',
path: '/model/newClassTaskAssign', path: '/model/newClassTaskAssign',
bgcolor: 'rgb(23,198,83)' bgcolor: 'rgb(23,198,83)',
type: '作业'
},{ },{
name: '框架设计', name: '框架设计',
path: '/model/design', path: '/model/design',
bgcolor: 'rgb(34,35,43)' bgcolor: 'rgb(34,35,43)',
type: '设计框架'
}]) }])
const tools = reactive([{ const tools = reactive([{
@ -135,6 +166,11 @@ const tools = reactive([{
name: '视频生成', name: '视频生成',
path: '', path: '',
img: 'aivideo' img: 'aivideo'
},{
name: '音乐生成',
path: 'https://ai.ysaix.com:7845/',
type: 'link',
img: 'aiyinyue'
}]) }])
const uploadData = ref({ const uploadData = ref({
@ -145,11 +181,13 @@ const uploadData = ref({
fileRoot: '备课' fileRoot: '备课'
}) })
const currentFileList = ref([]) const currentFileList = ref([])
//
onMounted(() => {
})
const gotoRoute = (item) => { const gotoRoute = (item) => {
if (item.type === 'link') {
//link
toLinkLeftWeb(item.path);
return
}
if (item.path) { if (item.path) {
if (item.path === '/model/aiKolors') { if (item.path === '/model/aiKolors') {
gotoAiKolors(item.path) gotoAiKolors(item.path)
@ -212,6 +250,156 @@ const createAIPPT = () => {
}) })
} }
const pgDialog = ref({ // -
visible: false,
title: 'PPT解析中...',
width: 300,
showClose: false,
draggable: true,
beforeClose: done => {}, // -
pg: { // -
percentage: 0, //
color: [
{ color: '#1989fa', percentage: 50 }, //
{ color: '#e6a23c', percentage: 80 }, //
{ color: '#5cb87a', percentage: 100 }, // 绿
]
}
})
const fileInput = ref(null)
const openFilePicker = () =>{
fileInput.value.click();
}
const handleFileChange = ()=> {
const file = event.target.files[0];
if (file) {
console.log(file);
createAIPPTByFile(file)
}
}
// pptPPT线
const createAIPPTByFile = async (file)=> {
// pgDialog.value.visible = true
// pgDialog.value.pg.percentage = 0
const resPptJson = await PPTXFileToJson(file)
const { def, slides, ...content } = resPptJson
//
const thumbnails = await slidesToImg(slides, content.width)
// || 线
let completed = 0
const total = slides.length
for( let o of slides ) {
completed++
await toRousrceUrl(o)
//
pgDialog.value.pg.percentage = Math.floor(completed / total * 100)
}
pgDialog.value.pg.percentage = 0
pgDialog.value.visible = false
listEntpcourse({
evalid: currentNode.value.id,
edituserid: userStore.userId,
pageSize: 500
}).then((response) => {
if (response.rows.length <= 0) return
let resCourse = response.rows[0]
//
let form = {
parentid: 0,
entpid: userStore.deptId,
entpcourseid: resCourse.id,
ppttype: 'file',
title: resCourse.coursetitle,
fileurl: '',
filetype: 'aippt',
datacontent: '',
parentContent: JSON.stringify(content),
filekey: '',
filetag: '',
fileidx: 0,
dflag: 0,
status: '',
edituserid: userStore.userId
}
addEntpcoursefileReturnId(form).then((slideid) => {
creatAPT({
...uploadData.value,
fileId: slideid,
fileFlag: 'aippt',
fileShowName: currentNode.value.itemtitle + '.aippt'
}).then(async (res) => {
const resSlides = slides.map(({id, ...slide}) => JSON.stringify(slide))
let params = {
parentid: slideid,
entpid: resCourse.entpid,
entpcourseid: resCourse.id,
title: '',
filetype: 'slide',
thumbnails, // -
slides: resSlides,
edituserid: userStore.userId
}
const res_3 = await API_entpcoursefile.batchAddNew(params)
if (res_3 && res_3.code == 200) {
msgUtils.msgSuccess('导入PPT课件成功')
currentFileList.value.unshift(res.resData)
await nextTick();
refs.value['kjItemRef'+res.resData.id].openFileWin(res.resData);
} else {
msgUtils.msgWarning('导入PPT课件失败')
}
})
})
})
}
const toRousrceUrl = async (o) => {
if (!!o.src) { // src
const isBase64 = /^data:image\/(\w+);base64,/.test(o.src)
const isBlobUrl = /^blob:/.test(o.src)
// console.log('isBase64', o, isBase64)
if (isBase64) {
const bolb = commUtils.base64ToBlob(o.src)
const fileName = Date.now() + '.png'
const file = commUtils.blobToFile(bolb, fileName)
// o.src = fileName
// console.log('file', file)
const formData = new FormData()
formData.append('file', file)
const res = await Api_server.Other.uploadFile(formData)
if (res && res.code == 200){
const url = res?.url
url &&(o.src = url)
}
} else if (isBlobUrl) { //
const res = await fetch(o.src)
const blob = await res.blob()
const fileName = o.type=='video'? Date.now() + '.mp4':Date.now() + '.mp3'
const file = commUtils.blobToFile(blob, fileName)
// o.src = fileName
// console.log('file', file)
const formData = new FormData()
formData.append('file', file)
const ress = await Api_server.Other.uploadFile(formData)
if (ress && ress.code == 200){
const url = ress?.url
url &&(o.src = url)
}
}
}
if (o?.background?.image) await toRousrceUrl(o.background.image)
if(o?.elements){
for (let element of o.elements) {
await toRousrceUrl(element);
}
}
}
const deleteTalk = (item) => { const deleteTalk = (item) => {
let index = currentFileList.value.indexOf(item) let index = currentFileList.value.indexOf(item)
currentFileList.value.splice(index, 1) currentFileList.value.splice(index, 1)
@ -219,13 +407,41 @@ const deleteTalk = (item) => {
// //
const changeBook = async(data) => { const changeBook = async(data) => {
console.log(data)
let cata = parseCataByNode(data.node) let cata = parseCataByNode(data.node)
currentNode.value = data.node currentNode.value = data.node
uploadData.value.levelFirstId = cata[0] uploadData.value.levelFirstId = cata[0]
uploadData.value.levelSecondId = cata[1] uploadData.value.levelSecondId = cata[1]
uploadData.value.levelThirdId = cata[2] uploadData.value.levelThirdId = cata[2]
uploadData.value.textbookId = data.textBook.curBookId uploadData.value.textbookId = data.textBook.curBookId
getModelInfo({cataid: currentNode.value.id}).then(res=>{
console.log(res)
if (res&&res.data) {
tags.filter(item=>{
switch (item.name) {
case '课标分析':
item.num = res.data.kbs
break;
case '教材分析':
item.num = res.data.jcs
break;
case '考试分析':
item.num = res.data.kss
break;
case '素材设计':
item.num = res.data.scs
break;
case '作业设计':
item.num = res.data.zys
break;
case '框架设计':
item.num = res.data.kjs
break;
default:
break;
}
})
}
})
getSmarttalkPage({ getSmarttalkPage({
...uploadData.value, ...uploadData.value,
orderByColumn: 'createTime', orderByColumn: 'createTime',
@ -303,6 +519,13 @@ const changeClass = async (type, row, other) => {
break break
} }
} }
//
onMounted(async () => {
//
await classTaskStore.initJYInfo(userStore);
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.model-wrap{ .model-wrap{

View File

@ -10,8 +10,8 @@
<div class="paragraphs"> <div class="paragraphs">
{{ outputText }} {{ outputText }}
</div> </div>
<el-button style="margin-bottom: 5px;" type="primary" @click="addMessage">从新生成</el-button> <el-button style="margin-bottom: 5px;" type="primary" @click="addMessage">{{ outputText ? '重新生成' : '生成大纲' }}</el-button>
<el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 1">下一步</el-button> <el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 1" :disabled="!outputText">下一步</el-button>
</el-card> </el-card>
<el-card v-if="activeStep === 1"> <el-card v-if="activeStep === 1">
<div style="padding-bottom: 10px">ppt模板选择</div> <div style="padding-bottom: 10px">ppt模板选择</div>
@ -57,7 +57,7 @@
</el-row> </el-row>
<div> <div>
<el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 0">上一步</el-button> <el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 0">上一步</el-button>
<el-button style="margin-bottom: 5px;" type="primary" v-loading="createPPTLoading" @click="outlineCreatePPT()">生成PPT</el-button> <el-button style="margin-bottom: 5px;" type="primary" :loading="createPPTLoading" @click="outlineCreatePPT()">生成PPT</el-button>
</div> </div>
</el-card> </el-card>
<el-card v-if="activeStep === 2"> <el-card v-if="activeStep === 2">
@ -177,6 +177,8 @@ const outlineCreatePPT = () => {
}; };
checkProgress(); checkProgress();
}).finally(()=>{
createPPTLoading.value = false
}) })
}; };

Some files were not shown because too many files have changed in this diff Show More