Compare commits

...

388 Commits

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
朱浩 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
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
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
zhangxuelin 64d2dbcabb Merge pull request '点赞疑问' (#117) from zxl into main
Reviewed-on: #117
2024-12-12 10:58:09 +08:00
zhangxuelin f4cb3a8916 点赞疑问 2024-12-12 10:57:48 +08:00
yangws 69db6bd8b1 Merge pull request '清除debugger;' (#116) from yws_dev into main
Reviewed-on: #116
2024-12-12 10:53:38 +08:00
小杨 e522fc8ee5 清除debugger; 2024-12-12 10:53:15 +08:00
yangws 5b50404d3c Merge pull request 'fix:修改ppt添加活动的bug;' (#115) from yws_dev into main
Reviewed-on: #115
2024-12-12 10:52:40 +08:00
小杨 bbed835ffe fix:修改ppt添加活动的bug; 2024-12-12 10:52:10 +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
zhangxuelin d9bdd5424f Merge pull request '点赞和疑问' (#114) from zxl into main
Reviewed-on: #114
2024-12-12 10:29:16 +08:00
zhangxuelin 979621b515 点赞和疑问 2024-12-12 10:28:32 +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
lyc 7545e68a62 Merge pull request 'edit' (#113) from lyc-dev into main 2024-12-11 17:04:37 +08:00
lyc dad772713f edit 2024-12-11 17:04:12 +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
lyc c1325db9e7 Merge pull request 'edit 模板' (#112) from lyc-dev into main 2024-12-11 15:07:56 +08:00
lyc cbc12a3d43 edit 模板 2024-12-11 15:07:25 +08:00
baigl 44b341f7ac Merge pull request '作业管理:作业名称为空保存判断' (#111) from baigl into main
Reviewed-on: #111
2024-12-11 14:55:07 +08:00
白了个白 c8dd19382e 作业管理:作业名称为空保存判断 2024-12-11 14:51:07 +08:00
zhangxuelin f2faa882a8 Merge pull request 'zxl' (#110) from zxl into main
Reviewed-on: #110
2024-12-11 13:51:14 +08:00
zhangxuelin e4afb26e4e Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-11 13:50:50 +08:00
zhangxuelin d60d8822a6 修改动画不能展示问题 2024-12-11 13:50:25 +08:00
朱浩 90137bc3a2 Merge remote-tracking branch 'origin/main' 2024-12-11 13:37:07 +08:00
朱浩 82c61a3fa7 大模型首页 2024-12-11 13:36:49 +08:00
朱浩 48d631a17c 大模型首页 2024-12-11 11:11:47 +08:00
zhangxuelin 1835f76e56 Merge pull request 'pptist点赞组件' (#109) from zxl into main
Reviewed-on: #109
2024-12-11 11:02:44 +08:00
zhangxuelin 432c1ff71d pptist点赞组件 2024-12-11 11:02:22 +08:00
朱浩 7f595c09a9 大模型首页 2024-12-11 10:33:18 +08:00
朱浩 c8e10d4fe1 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/renderer/src/components/file-image/index.vue
#	src/renderer/src/views/model/index.vue
2024-12-11 10:31:29 +08:00
朱浩 f916660156 大模型首页 2024-12-11 10:28:55 +08:00
zhengdegang e0107814ea Merge pull request 'zdg_dev' (#108) from zdg_dev into main
Reviewed-on: #108
2024-12-11 10:07:58 +08:00
zdg bb0aa82e82 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-11 10:06:30 +08:00
zdg 8ed13a3146 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev
# Conflicts:
#	src/renderer/src/AixPPTist/src/api/index.ts
#	src/renderer/src/views/prepare/index.vue
2024-12-11 10:06:23 +08:00
zhangxuelin 82b70558c2 Merge pull request 'zxl' (#107) from zxl into main
Reviewed-on: #107
2024-12-11 09:59:16 +08:00
zhangxuelin bace41e12e Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-11 09:58:08 +08:00
zhangxuelin 04e204928d 添加ppt 视频url替换 2024-12-11 09:57:39 +08:00
zdg 9e5609fbdd ppt上课 2024-12-11 09:44:52 +08:00
lyc 50aff2f158 Merge pull request 'add icon' (#106) from lyc-dev into main 2024-12-11 09:41:16 +08:00
lyc a1a4e11de7 add icon 2024-12-11 09:40:51 +08:00
lyc 20fe5be502 Merge pull request 'lyc-dev' (#105) from lyc-dev into main 2024-12-10 15:44:00 +08:00
lyc 7e9ac2bf74 Merge branch 'main' into lyc-dev 2024-12-10 15:43:14 +08:00
lyc 013669dd35 edit 模板 2024-12-10 15:42:59 +08:00
yangws 3ca20cd327 Merge pull request 'fix:清除debugger;' (#104) from yws_dev into main
Reviewed-on: #104
2024-12-10 15:38:42 +08:00
小杨 c159d6be1a fix:清除debugger; 2024-12-10 15:38:19 +08:00
yangws cc44a86437 Merge pull request 'yws_dev' (#103) from yws_dev into main
Reviewed-on: #103
2024-12-10 15:35:20 +08:00
小杨 e7c6ab9e8d Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-10 15:34:51 +08:00
小杨 85d06f306a fix:ppt活动添加; 2024-12-10 15:34:46 +08:00
朱浩 656a58693f Merge remote-tracking branch 'origin/main' 2024-12-10 14:20:08 +08:00
zouyf 34cfcf5a6f Merge pull request 'zouyf_dev' (#102) from zouyf_dev into main
Reviewed-on: #102
2024-12-10 11:10:55 +08:00
“zouyf” 9603406a0b 1 2024-12-10 11:09:32 +08:00
“zouyf” 10a7e73c64 Merge branch 'main' into zouyf_dev 2024-12-10 10:59:48 +08:00
“zouyf” 2c238b5706 1 2024-12-10 10:54:21 +08:00
“zouyf” b47feb4a3a 1 2024-12-10 10:46:22 +08:00
朱浩 772b196b31 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/renderer/src/views/prepare/index.vue
2024-12-10 10:44:04 +08:00
zhangxuelin 91e9867b68 Merge pull request 'zxl' (#101) from zxl into main
Reviewed-on: #101
2024-12-10 10:41:08 +08:00
zhangxuelin 80ac4a6e1c Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-10 10:40:21 +08:00
朱浩 757edb0f67 改名AIPPT 2024-12-10 10:40:08 +08:00
“zouyf” c79911cc99 Merge branch 'main' into zouyf_dev 2024-12-10 10:40:07 +08:00
zhangxuelin 1987783fda 添加作业弹窗 2024-12-10 10:40:00 +08:00
“zouyf” 755cfc615a 试题分页查询回顶部 2024-12-10 10:39:39 +08:00
baigl fa776d2a8c Merge pull request 'baigl' (#100) from baigl into main
Reviewed-on: #100
2024-12-10 10:26:52 +08:00
白了个白 122487cf8b ppts习题插入:个人题库 习题截图放到ppts里面转换 2024-12-10 10:24:30 +08:00
白了个白 b7f11c5338 Merge branch 'zouyf_dev' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-10 10:19:06 +08:00
白了个白 c78233a2f4 1 2024-12-10 10:16:07 +08:00
“zouyf” 2e81c706b1 Merge branch 'main' into zouyf_dev 2024-12-10 10:13:00 +08:00
“zouyf” 2360d95f1c pptList试题转图片优化 2024-12-10 10:12:47 +08:00
zdg 4f68ae27b3 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-10 10:09:00 +08:00
zdg 38c041465e ppt上课 2024-12-10 10:08:53 +08:00
zhangxuelin f035cf87ea Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl
# Conflicts:
#	src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/index.vue
2024-12-10 09:43:43 +08:00
zhangxuelin e56eb059be 替换上传图片 视频url为线上url 2024-12-10 09:41:59 +08:00
yangws ffd6d6fab9 Merge pull request 'yws_dev' (#99) from yws_dev into main
Reviewed-on: #99
2024-12-09 17:26:08 +08:00
小杨 23e59531ea Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-09 17:25:30 +08:00
小杨 9e021edc67 fix:添加活动; 2024-12-09 17:25:25 +08:00
zouyf 238f08d9ac Merge pull request 'zouyf_dev' (#98) from zouyf_dev into main
Reviewed-on: #98
2024-12-09 17:16:42 +08:00
“zouyf” d90b7c695a 1 2024-12-09 17:14:37 +08:00
“zouyf” f936e726c0 Merge branch 'main' into zouyf_dev 2024-12-09 16:50:12 +08:00
“zouyf” d07dd4455c 1 2024-12-09 16:50:00 +08:00
zouyf b319aeb4b2 Merge pull request 'zouyf_dev' (#97) from zouyf_dev into main
Reviewed-on: #97
2024-12-09 15:03:29 +08:00
“zouyf” 23c397e18a Merge branch 'main' into zouyf_dev 2024-12-09 15:01:43 +08:00
“zouyf” 78858111bb pptList样式修改 2024-12-09 15:01:25 +08:00
zhengdegang 3c6ac1f77d Merge pull request 'zdg_dev' (#96) from zdg_dev into main
Reviewed-on: #96
2024-12-09 14:07:40 +08:00
zdg 97962d591f 删除无效代码 2024-12-09 13:54:44 +08:00
zdg 86154148c6 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev
# Conflicts:
#	src/renderer/src/views/prepare/container/class-start.vue
2024-12-09 13:52:24 +08:00
zdg 44092a21bf ppt上课 2024-12-09 13:51:00 +08:00
“zouyf” 728ce16c8b Merge branch 'main' into zouyf_dev 2024-12-09 13:46:32 +08:00
小杨 5b1d921378 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-09 11:16:48 +08:00
lyc f2637c7b15 Merge pull request 'lyc-dev' (#95) from lyc-dev into main 2024-12-09 10:56:53 +08:00
lyc 93fa916084 Merge branch 'main' into lyc-dev 2024-12-09 10:56:20 +08:00
lyc 24d6d5887f pptlist edit 2024-12-09 10:56:04 +08:00
朱浩 222b0f54f8 Merge remote-tracking branch 'origin/main' 2024-12-09 10:51:25 +08:00
朱浩 e0a56b37ef 改名AIPPT 2024-12-09 10:51:07 +08:00
baigl 045bc5e198 Merge pull request 'baigl' (#94) from baigl into main
Reviewed-on: #94
2024-12-09 10:17:51 +08:00
白了个白 464f39dac8 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-09 10:16:42 +08:00
白了个白 8e72b50c68 ppts:图片样式 裁剪逻辑样式修改 2024-12-09 10:16:17 +08:00
小杨 0aa09e9d9a Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-09 09:30:08 +08:00
lyc 97f55ca333 Merge pull request 'lyc-dev' (#93) from lyc-dev into main 2024-12-06 16:40:40 +08:00
lyc cb5445f9a9 Merge branch 'main' into lyc-dev 2024-12-06 16:40:03 +08:00
lyc 560e6f0e70 插入素材-pptlist 2024-12-06 16:39:47 +08:00
“zouyf” bbe2281781 Merge branch 'baigl' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zouyf_dev 2024-12-06 15:32:02 +08:00
“zouyf” 66881b0025 Merge branch 'main' into zouyf_dev 2024-12-06 15:30:41 +08:00
baigl 4010411c9f Merge pull request 'baigl' (#92) from baigl into main
Reviewed-on: #92
2024-12-06 15:30:38 +08:00
“zouyf” e43c9fd038 1 2024-12-06 15:30:09 +08:00
白了个白 da1c80406f Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-06 15:29:35 +08:00
白了个白 e3199b43de 1 2024-12-06 15:26:22 +08:00
朱浩 a78497b263 Merge remote-tracking branch 'origin/main' 2024-12-06 14:21:19 +08:00
朱浩 65670af54f s生成PPT 2024-12-06 14:21:05 +08:00
zhengdegang e5be55f12e Merge pull request '弹窗进度条' (#91) from zdg_dev into main
Reviewed-on: #91
2024-12-06 14:15:42 +08:00
zdg 209ab7fafc 弹窗进度条 2024-12-06 14:15:01 +08:00
朱浩 87dcd9d5c3 Merge remote-tracking branch 'origin/main' 2024-12-05 15:47:38 +08:00
zouyf 1f96b9b09e Merge pull request 'zouyf_dev' (#90) from zouyf_dev into main
Reviewed-on: #90
2024-12-05 14:55:32 +08:00
小杨 0e18d74bb9 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-05 14:47:27 +08:00
小杨 399c4b5461 add:添加活动页面; 2024-12-05 14:47:23 +08:00
“zouyf” 35c088c25a Merge branch 'main' into zouyf_dev 2024-12-05 14:37:13 +08:00
“zouyf” 028eb0f752 [考试分析] - 增加分页及优化题型获取 2024-12-05 14:36:50 +08:00
zhengdegang 9a68661fb0 Merge pull request 'zdg_dev' (#89) from zdg_dev into main
Reviewed-on: #89
2024-12-05 14:22:13 +08:00
zdg b28abdff50 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-05 14:21:43 +08:00
zdg b428260703 类型异常 2024-12-05 14:21:36 +08:00
lyc b5f824ceea Merge pull request 'edit' (#88) from lyc-dev into main 2024-12-05 10:36:59 +08:00
lyc d3b3e3bcb5 edit 2024-12-05 10:36:34 +08:00
zhengdegang dcabb80757 Merge pull request 'zdg_dev' (#87) from zdg_dev into main
Reviewed-on: #87
2024-12-05 09:47:15 +08:00
zdg 53f26d96d4 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-05 09:44:51 +08:00
zdg 7d7b50fa3e 类型定义 2024-12-05 09:44:45 +08:00
baigl eae2171c70 Merge pull request 'baigl' (#86) from baigl into main
Reviewed-on: #86
2024-12-04 17:31:51 +08:00
白了个白 5fe9359d64 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-04 17:30:30 +08:00
白了个白 be9d33fcd3 pptist: canvasTool 里面 新增插入试题 2024-12-04 17:30:01 +08:00
lyc 09e3264ee7 Merge pull request 'edit' (#85) from lyc-dev into main 2024-12-04 17:16:34 +08:00
lyc 7845d9bb1a edit 2024-12-04 17:15:20 +08:00
zhengdegang 2fb6828154 Merge pull request 'zdg_dev' (#84) from zdg_dev into main
Reviewed-on: #84
2024-12-04 17:09:39 +08:00
zdg 03d1a683be Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-04 17:08:57 +08:00
zdg d542064ee3 ppt生成课堂备课 2024-12-04 17:08:51 +08:00
朱浩 9803c09c43 s生成PPT 2024-12-04 17:04:20 +08:00
zhengdegang 57fdba4578 Merge pull request 'ppt生产' (#83) from zdg_dev into main
Reviewed-on: #83
2024-12-04 16:42:25 +08:00
zdg 0e5d84fcd2 ppt生产 2024-12-04 16:41:38 +08:00
朱浩 7811bbfe3e s生成PPT 2024-12-04 16:28:52 +08:00
zhengdegang 7ddf71c044 Merge pull request 'zdg_dev' (#82) from zdg_dev into main
Reviewed-on: #82
2024-12-04 15:46:59 +08:00
zdg 91ceb712bf 异常修复 2024-12-04 15:45:45 +08:00
zdg a192640899 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-04 15:42:38 +08:00
zdg 08a16929f7 还原本地测试代码 2024-12-04 15:32:56 +08:00
lyc 42e8149443 Merge pull request 'lyc-dev' (#81) from lyc-dev into main 2024-12-04 15:14:22 +08:00
lyc 8b429a4174 edit 2024-12-04 15:13:53 +08:00
白了个白 23795f2419 Merge branch 'yws_dev' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-04 15:09:05 +08:00
白了个白 ea204de407 1 2024-12-04 15:05:48 +08:00
白了个白 a31b8d7376 pptist:canvastool 新增 插入习题按钮 2024-12-04 14:58:47 +08:00
小杨 8d03c927b9 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-04 14:58:02 +08:00
小杨 edabc0336f add:新增pptlist活动列表; 2024-12-04 14:57:56 +08:00
白了个白 df474db4a5 1 2024-12-04 14:38:28 +08:00
白了个白 328e623db7 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into baigl 2024-12-04 14:36:53 +08:00
白了个白 8ad041e963 1 2024-12-04 14:34:25 +08:00
zhengdegang c22b360e0f Merge pull request 'zdg_dev' (#80) from zdg_dev into main
Reviewed-on: #80
2024-12-04 14:34:14 +08:00
zdg 3b851887ca Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-04 14:33:30 +08:00
zdg e6333e49f0 更新ppt 2024-12-04 14:33:08 +08:00
CYS cee81102da Merge pull request '文生图' (#79) from cys_dev into main
Reviewed-on: #79
2024-12-04 10:29:19 +08:00
lyc 3ad4391e10 edit 模板 2024-12-03 10:11:51 +08:00
“zouyf” 7b59d78ce3 1 2024-12-02 17:30:24 +08:00
156 changed files with 9056 additions and 3264 deletions

View File

@ -16,4 +16,10 @@ VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktx
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'
# websocket 地址
# VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
VITE_APP_WS_URL = 'wss://file.ysaix.com:7868'
# VITE_APP_WS_URL = 'ws://192.168.2.16:7865'
# 是否显示开发工具
VITE_SHOW_DEV_TOOLS = 'true'

View File

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

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,8 +33,9 @@ export default defineConfig({
proxy: {
'/dev-api': {
target: 'http://27.128.240.72:7865',
// target: 'https://prev.ysaix.com:7868/prod-api/',
// target: 'http://36.134.181.164:7863',
// target: 'http://192.168.2.52:7863',
// target: 'http://192.168.0.102:7865',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
},

5
env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '*.vue' {
import { ComponentOptions } from 'vue'
const componentOptions: ComponentOptions
export default componentOptions
}

View File

@ -1,6 +1,6 @@
{
"name": "aix-win-ws",
"version": "2.5.4",
"version": "2.5.9",
"description": "",
"main": "./out/main/index.js",
"author": "上海交大重庆人工智能研究院",
@ -16,6 +16,8 @@
"build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
"build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml",
"build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml",
"build:yc": "electron-vite build --mode yc && electron-builder --win --config ./electron-builder-yc.yml",
"build:yc2": "electron-vite build --mode yc2 && electron-builder --win --config ./electron-builder-yc2.yml",
"build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml",
"build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml",
"build:linux": "npm run build && electron-builder --linux"
@ -57,6 +59,7 @@
"file-saver": "^2.0.5",
"hfmath": "^0.0.2",
"html-to-image": "^1.11.11",
"html2canvas": "^1.4.1",
"im_electron_sdk": "^8.0.5904",
"js-cookie": "^3.0.5",
"jsencrypt": "^3.3.2",
@ -91,6 +94,8 @@
"tinycolor2": "^1.6.0",
"tinymce": "6.8.3",
"tippy.js": "^6.3.7",
"v-viewer": "^3.0.11",
"viewerjs": "^1.11.7",
"vite-plugin-electron": "^0.28.8",
"vue": "^3.4.34",
"vue-cropper": "1.0.3",

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
<script lang="ts" setup>
import 'animate.css'
import { ref, onMounted, watch, onBeforeMount } from 'vue'
import { storeToRefs } from 'pinia'
import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from './store'
@ -27,8 +28,9 @@ import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api
import { PPTApi } from './api'
import { sessionStore } from '@/utils/store' // electron-store
import './api/watcher' //
import watcher from './api/watcher' //
import emitter from '@/utils/mitt' //mitt 线
watcher() //
const loading = ref(true)
const _isPC = isPC()
@ -38,8 +40,8 @@ const slidesStore = useSlidesStore()
const { databaseId } = storeToRefs(mainStore)
const { screening } = storeToRefs(useScreenStore())
if (import.meta.env.MODE !== 'development') {
window.onbeforeunload = () => false
if (import.meta.env.MODE === 'development') {
// window.onbeforeunload = () => false
}
onMounted(async () => {
@ -60,14 +62,6 @@ window.addEventListener('unload', () => {
const newDiscardedDB = JSON.stringify(discardedDBList)
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
})
/** 接口类型 */
interface Result {
code?: number,
msg?: string,
data?: any
rows?: Array<any>,
total?: number
}
//
const initLoad: Function = () => {
// ppt
@ -89,5 +83,8 @@ const initLoad: Function = () => {
<style lang="scss">
#app {
height: 100%;
svg, canvas, img, audio, video, iframe {
display: unset;
}
}
</style>
</style>

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

@ -0,0 +1,76 @@
/**
* @author zdg
* @description
*/
// import type { Classcourse } from './types'
import { sessionStore } from '@/utils/store' // electron-store 状态管理
import * as useStore from '../store' // pptist-状态管理
import ChatWs from '@/plugins/socket' // 聊天socket
import msgUtils from '@/plugins/modal' // 消息工具
import emitter from '@/utils/mitt' //mitt 事件总线
import { nextTick } from 'vue'
const slidesStore = useStore.useSlidesStore() // 幻灯片-状态管理
const screenStore = useStore.useScreenStore() // 全屏-状态管理
const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
const isPublic = sessionStore.get('curr.isPublic') // 是否公屏开课
export class Classcourse {
msgObj:ElMessageBox = null // 提示消息对象
classcourse:any = null // 课堂信息
id: number|string = null // 课堂id
constructor() {
this.load()
}
// 延时
sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
/**
* @description
*/
async load() {
console.log('classcourse-load', classcourse)
// 打开全屏
const isCourse = !!classcourse
screenStore.setScreening(isCourse)
// 如果课堂信息有值则连接socket
if (isCourse) {
// 连接socket
ChatWs.id = classcourse.timgroupid // 群组id
if (!ChatWs.ws) {
ChatWs.init().then(_ => {
isPublic && ChatWs.sendMsg('open', {id: classcourse.id})
// isPublic && console.log('socket-开课消息-已发送')
})
}
this.classcourse = classcourse // 课堂信息
this.id = classcourse.id // 课堂id
// 如果课堂信息有paging则更新当前页码
const { paging, cartoonTimes } = classcourse
const isPaging = !!paging || paging === 0
// 如果课堂信息有paging则更新动画播放状态
const isAnim = !!cartoonTimes || cartoonTimes === 0
if (isPaging) slidesStore.updateSlideIndex(paging)
if (isAnim) slidesStore.updateAnimationIndex(cartoonTimes)
// 课堂信息-状态管理
classcourseStore.setClasscourse(classcourse)
// 待上课提示
if (!classcourse.status) {
this.msgObj = {
type: 'success',
title: '系统提示',
message: '公屏课堂已准备完毕,等待老师开启课堂...',
center: true,
showClose: false,
showCancelButton: false,
showConfirmButton: false,
beforeClose: () => {}
}
msgUtils.ElMessageBox(this.msgObj)
}
}
}
}
export default new Classcourse()

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,25 @@
* @author zdg
* @date 2024-11-26
*/
import { toRaw } from 'vue'
import { toRaw, nextTick } from 'vue'
import type { Result } from './types' // 接口类型
import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
import * as API_smarttalk from '@/api/file' // 相关api
import * as API_classwork from '@/api/teaching/classwork' // 相关api
import * as useStore from '../store' // pptist-状态管理
import { sessionStore } from '@/utils/store' // electron-store 状态管理
import useUserStore from '@/store/modules/user' // 外部-用户信息
import * as Api_server from '@/api/apiService' // 相关api
import * as commUtils from '@/utils/comm.js' // 工具
import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
const slidesStore = useStore.useSlidesStore()
const userStore = useUserStore()
import { getClassWorkList,getStudentClassWorkData } from '@/views/tool/createHomework'
import {createWindow} from '@/utils/tool'
import { useToolState } from '@/store/modules/tool'
const toolStore = useToolState()
/** 工具类 */
export class Utils {
static mxData: any = {
@ -41,6 +50,8 @@ export class Utils {
}, delay)
}
}
// 延时
static sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
}
/** ppt相关后端接口处理 */
@ -48,23 +59,39 @@ export class PPTApi {
// 变量
static isUpdate = true // 是否更新数据
// 获取所有幻灯片列表
static getSlideList(parentid: (Number | String)): Promise<Boolean> {
// 获取所有幻灯片列表 isUpdate为true不更新
static getSlideList(parentid: (Number | String),isUpdate?:Boolean): Promise<Boolean> {
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
return new Promise(async (resolve, reject) => {
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
if (res.code === 200) {
const slides = (res.rows || []).map(o => {
if (!!o.datacontent) {
const json = JSON.parse(o.datacontent)
!!json && (json.id = o.id)
return json
}
// 如果没有数据,默认空白页
return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}}
})
slidesStore.updateSlideIndex(0) // 下标0 为第一页
slidesStore.setSlides(slides) // 写入数据
if (res.code === 200) {
if(!isUpdate){
const slides = (res.rows || []).map(o => {
if (!!o.datacontent) {
const json = JSON.parse(o.datacontent)
!!json && (json.id = o.id)
return json
}
// 如果没有数据,默认空白页
return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}}
})
// slidesStore.updateSlideIndex(0) // 下标0 为第一页
slidesStore.setSlides(slides) // 写入数据
}
// 活动列表处理
// const workList = (res.rows || []).map(o => o.activityContent)
const workItem = res.rows ? [...res.rows] : []
// 获取所有的pptlist的数据
slidesStore.setWorkItem(workItem)
// 没有上课时调用-作业列表
if(!classcourse) this.updateWorkList()
// 没有上课时调用-批量更新缩略图
if(!classcourse) {
Utils.sleep(1500).then(() => {
this.batchUpdateThumUrl()
})
}
resolve(true)
} else msgUtils.msgError(res.msg || '获取数据失败');resolve(false)
})
@ -117,21 +144,34 @@ export class PPTApi {
const currentSlide = toRaw(slidesStore.currentSlide)
const isAdd = !/^\d+$/.test(currentSlide.id) // 是否新增
const currInd = toRaw(slidesStore.slideIndex) // 当前页索引-new
const oldInd = oldData.findIndex(o => o.id == currentSlide.id) // 当前页索引-old
const isBatch = oldVal && oldVal.length && currInd != oldInd // 是否批量更新-排序
if (isAdd) { // 新增的幻灯片(id 为非数字,说明是新增的幻灯片)
const bool = await this.addSlide(currentSlide)
bool && this.batchUpdateSlides(newData, true) // 批量更新-排序
bool && await this.batchUpdateSlides(newData, true) // 批量更新-排序
const resource = sessionStore.get('curr.resource')||{}
await PPTApi.getSlideList(resource.id)
} else { // 防抖-更新
if (!this.isUpdate) return this.isUpdate = true // 下次更新数据
const params = {
id: currentSlide.id,
datacontent: JSON.stringify(currentSlide),
if (isBatch) { // 批量更新-排序
this.batchUpdateSlides(newData, true)
} else { // 更新当前页幻灯片
const params = {
id: currentSlide.id,
datacontent: JSON.stringify(currentSlide),
}
Utils.mxThrottle(() => {this.updateSlide(params)}, 200, 2)
}
Utils.mxThrottle(() => {this.updateSlide(params)}, 1000, 2)
}
}
// 更新幻灯片
static updateSlide(data: object): Promise<Boolean> {
// 更新幻灯片 isThum 是否更新缩略图
static updateSlide(data: object, isThum = true): Promise<Boolean> {
return new Promise(async (resolve, reject) => {
if (isThum) { // 更新缩略图
const thumUrl = await this.getSlideThumUrl()
data.base64Code = thumUrl // 更新缩略图
}
const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data)
if (res.code === 200) {
resolve(true)
@ -166,6 +206,101 @@ export class PPTApi {
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
})
}
// 更新-备课资源 标题
static updateSmarttalk(data: object): Promise<Boolean> {
return API_smarttalk.updateSmarttalk(data).then(res => {
if (res.code === 200) return true
else msgUtils.msgError(res.msg || '更新失败');return false
})
}
// 更新-活动列表
static async updateWorkList(): Promise<Boolean> {
const resolveData = (resolve, data = []) => {
slidesStore.setWorkList(data)
return resolve()
}
return new Promise(async (resolve, reject) => {
const workItem = slidesStore.workItem // 所有的pptlist的数据-原始数据
const currentSlide = slidesStore.currentSlide // 当前页幻灯片
const slideId = currentSlide.id // 当前页幻灯片id
if (!slideId) return resolveData(resolve)
// slide详情获取-作业id
// const res = await API_entpcoursefile.getEntpcoursefile(slideId)
// const workIds = res?.data?.activityContent||''
// workItem-获取作业id
const workIds = workItem.find(o => o.id == slideId)?.activityContent
if (!workIds) return resolveData(resolve)
// 获取作业列表
const resW = await API_classwork.homeworklist({ ids: workIds, pageSize: 1000 })
if (resW && resW.rows) return resolveData(resolve, resW.rows)
else msgUtils.msgError(resW.msg || '更新失败');return resolveData(resolve)
})
}
// 批量更新缩略图-异步
static batchUpdateThumUrl() {
return nextTick().then(async () => {
const list = slidesStore.workItem || []
if (!list.length) return
const upList = []
for (const [ind,o] of list.entries()) {
const isCreate = !o.fileurl // 是否创建
if (isCreate) {
const thumUrl = await this.getSlideThumUrl(ind)
upList.push({ id: o.id, fileurl: thumUrl })
}
}
if (!upList.length) return
// 批量更新
return await API_entpcoursefile.batchUpdateNew(upList)
})
}
// thumbnail-slide thumbnail 缩略图
static getSlideThumUrl(index?:number): Promise<Boolean> {
return nextTick().then(async() => {
const slideIndex = index ?? slidesStore.slideIndex
const elements = document.querySelectorAll('.thumbnail-slide')
if (elements.length && slideIndex >= 0) {
const element = elements[slideIndex]
return await toPng(element)
}
return null
})
}
// 图片|音频|视频 转换为在线地址
static toRousrceUrl =async (o:any) => {
const formData = new FormData()
formData.append('file', o)
formData.append('ral', true)
const res = await Api_server.Other.uploadFile(formData)
if (res && res.code == 200){
const url = res?.url
url &&(o.src = url)
return url
}
}
}
export class Homework{
static win: null // 作业弹窗
// 作业弹窗
static async showHomework(id: any) {
let result = await getClassWorkList(id)
  result = await getStudentClassWorkData()
  localStorage.setItem('teachClassWorkItem', JSON.stringify(result[0]));
  toolStore.isTaskWin=true; // 设置打开批改窗口
//   emit('closeActive')
// 重复打开,先关闭弹窗
// if (this.win) this.win?.close?.()
this.win = await createWindow('open-taskwin',{url:'/teachClassTask'})
 return this.win;
}
static closeHomework() {
if (this.win) this.win?.close?.()
this.win = null
}
}
export default PPTApi

View File

@ -2,17 +2,18 @@
* @description api store循环引用
* @author zdg
*/
import type { Result } from './types' // 接口类型
import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
export default class {
// 删除幻灯片
static delSlide(id: string): Promise<Boolean> {
return new Promise(async (resolve, reject) => {
const res: Result = await API_entpcoursefile.delEntpcoursefile(id)
if (res.code === 200) {
resolve(true)
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
})
}
// 删除幻灯片
static delSlide(id: string): Promise<Boolean> {
return new Promise(async (resolve, reject) => {
const res: Result = await API_entpcoursefile.delEntpcoursefile(id)
if (res.code === 200) {
resolve(true)
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
})
}
}

View File

@ -0,0 +1,179 @@
/** 返回-接口类型 */
export interface Result {
code?: number,
msg?: string,
data?: any
rows?: Array<any>,
total?: number
}
/** 课程信息 */
export interface Classcourse {
id?: number|string, // 课程id
coursetitle?: string, // 课程名称
coursetype?: string, // 课程类型
courseverid?: string, // 课程版本id
coursedesc?: string, // 课程描述
status?: number, // 课程状态
teacherid?: number|string, // 教师id
entpcoursefileid?: number|string, // 课程文件id
classid?: number|string, // 班级id
entpcourseid?: number|string, // 章节中间表id
timgroupid?: number|string, // ws 群组id
plandate?: string, // 计划时间
opendate?: string, // 开课时间
}
/**
* @description
* @author zdg
* @date 2021-07-05 14:07:01
*/
export class MsgEnum {
/**
* @description:
*
* | | | enum |
* | ---- | ---- | ---- |
* | SYSTEM | | system |
* | TEACHER | | teacher |
* | STUDENT | | student |
* | NOTICE | | notice |
*/
static TYPES = {
/** @desc: 系统消息 */
SYSTEM: 'system',
/** @desc: 老师消息 */
TEACHER: 'teacher',
/** @desc: 学生消息 */
STUDENT: 'student',
/** @desc: 通知消息 */
NOTICE: 'notice'
}
/**
* @description: -
*
* | | | enum |
* | ---- | ---- | ---- |
* | --- | - | --- |
* | MSG_closed | () | closed |
* | MSG_onlineStatus | 线 | onlineStatus |
* | MSG_pushQuizOfClassWorkdata2Public | | pushQuizOfClassWorkdata2Public |
* | MSG_pushClassWorkdata2Public | | pushClassWorkdata2Public |
* | MSG_shareStudentPresentdata2All | | shareStudentPresentdata2All |
* | MSG_pushStudentPresentdata2Public | | pushStudentPresentdata2Public |
* | MSG_pushClassWorkPresentList2Public | | pushClassWorkPresentList2Public |
* | MSG_activePageType | - | activePageType |
* | MSG_slideFlapping | - | slideFlapping |
* | MSG_anmationclick | - | anmationclick |
* | MSG_classcourseopen | | classcourseopen |
* | MSG_classquizfeedback | | classquizfeedback |
* | MSG_classtaskfeedback | - | classtaskfeedback |
* | MSG_studentfeedback | feedbackkey | studentfeedback |
* | MSG_studentfeedbackcancel | | studentfeedbackcancel |
* | MSG_classshowdata | - | classshowdata |
* | MSG_classWorkOfPresentDataUpdate | | classWorkOfPresentDataUpdate |
* | MSG_classlecturePagesrc | | classlecturePagesrc |
* | --- | - | --- |
* | MSG_0001 | | 0x0001 |
* | MSG_0002 | xx | 0x0002 |
* | MSG_0003 | xx | 0x0003 |
*/
static HEADS = {
// === 旧定义-消息头(兼容以前) ===
/** @desc: 开课 */
MSG_open : 'open',
/** @desc: 结束课程(下课) */
MSG_closed : 'closed',
/** @desc: 在线状态 */
MSG_onlineStatus : 'onlineStatus',
/** @desc: 老师端:把选中的学生习题作业,推到大屏 */
MSG_pushQuizOfClassWorkdata2Public : 'pushQuizOfClassWorkdata2Public',
/** @desc: 老师端:把选中的学生作业,推到大屏 */
MSG_pushClassWorkdata2Public : 'pushClassWorkdata2Public',
/** @desc: 把某个学生的展示成果数据推给全班所有学生 */
MSG_shareStudentPresentdata2All : 'shareStudentPresentdata2All',
/** @desc: 老师端:课堂展示活动,把选中的学生展示数据,推到大屏 */
MSG_pushStudentPresentdata2Public : 'pushStudentPresentdata2Public',
/** @desc: 老师端:课堂展示活动,任务列表,推到大屏 */
MSG_pushClassWorkPresentList2Public : 'pushClassWorkPresentList2Public',
/** @desc: 课标研读-分页切换 */
MSG_activePageType : 'activePageType',
/** @desc: 幻灯片-切换 */
MSG_slideFlapping : 'slideFlapping',
/** @desc: 幻灯片-动画切换 */
MSG_anmationclick : 'anmationclick',
/** @desc: 群组创建成功 */
MSG_classcourseopen : 'classcourseopen',
/** @desc: 学生提交作业 */
MSG_finishHomework : 'finishHomework',
/** @desc: 学生的测练结果反馈 */
MSG_classquizfeedback : 'classquizfeedback',
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
MSG_classtaskfeedback : 'classtaskfeedback',
/** @desc: 老师端学生反馈的消息具体要看其中的feedbackkey类别较繁杂 */
MSG_studentfeedback : 'studentfeedback',
/** @desc: 老师端:学生反馈的消息取消,如取消学会了,取消困惑 */
MSG_studentfeedbackcancel : 'studentfeedbackcancel',
/** @desc: 学生提交的课堂展示数据-要在老师端显示,再由老师选择推送到公屏上 */
MSG_classshowdata : 'classshowdata',
/** @desc: 学生在公屏上展示并完善后,保存后,老师端要更新 */
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
/** @desc: 课堂讲授活动,选择不同的内容 */
MSG_classlecturePagesrc : 'classlecturePagesrc',
/** @desc: 课堂作业|活动 */
MSG_homework : 'HOMEWORK',
/** @desc: 公屏 - 课堂作业|活动 */
MSG_pushSreen_work : 'pushSreen_work',
/** @desc: 公屏 - 实验 */
MSG_pushSreen_experiment : 'pushSreen_experiment',
/** @desc: 点赞 */
MSG_dz : 'dz',
/** @desc: 疑惑 */
MSG_yh : 'yh',
/** @desc: 推图片上屏 */
MSG_pushSreen_ImgList : 'pushSreen_ImgList',
// === 新定义-消息头 ===
/** @desc: 课程创建-待开课 */
MSG_0000: 0x0000,
/** @desc: 点赞 */
MSG_0001: 0x0001,
/** @desc: 疑惑 */
MSG_0002: 0x0002,
MSG_0003: 0x0003,
MSG_0004: 0x0004,
MSG_0005: 0x0005,
MSG_0006: 0x0006,
MSG_0007: 0x0007,
MSG_0008: 0x0008,
MSG_0009: 0x0009,
MSG_0010: 0x000a,
MSG_0011: 0x000b,
MSG_0012: 0x000c,
MSG_0013: 0x000d,
MSG_0014: 0x000e,
MSG_0015: 0x000f,
/** @desc: 作业推送 */
MSG_0016: 0x0010,
MSG_0017: 0x0011,
MSG_0018: 0x0012,
MSG_0019: 0x0013,
MSG_0020: 0x0014,
MSG_0021: 0x0015,
MSG_0022: 0x0016,
MSG_0023: 0x0017,
MSG_0024: 0x0018,
MSG_0025: 0x0019,
MSG_0026: 0x001a,
MSG_0027: 0x001b,
MSG_0028: 0x001c,
MSG_0029: 0x001d,
MSG_0030: 0x001e,
MSG_0031: 0x001f,
MSG_0032: 0x0020,
MSG_0033: 0x0021,
MSG_0034: 0x0022,
MSG_0035: 0x0023,
}
}

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

@ -6,26 +6,156 @@ import { watch } from 'vue'
import { PPTApi } from './index'
import * as store from '../store'
import { sessionStore } from '@/utils/store' // electron-store 状态管理
const slidesStore = store.useSlidesStore()
const resource = sessionStore.get('curr.resource')
import { MsgEnum } from './types' // 消息枚举
import ChatWs from '@/plugins/socket' // 聊天socket
import Classcourse from './classcourse' // 课程相关
import msgUtils from '@/plugins/modal' // 消息工具
import * as dialogUtils from '@/utils/dialog' // 弹窗-函数
import { Homework } from './index' // api-作业相关
import emitter from '@/utils/mitt' //mitt 事件总线
import useExecPlay from '../views/Screen/hooks/useExecPlay' // 播放控制
import hooksUpvote from './upvote' // 点赞-工具
import gridPic from './gridPic' // 上屏-工具
/**
* @description
*/
export default () => {
const slidesStore = store.useSlidesStore()
const classcourseStore = store.useClasscourseStore() // 课堂信息-状态管理
const resource = sessionStore.get('curr.resource') // apt 资源
const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源
const { execNext, turnPrevSlide } = useExecPlay(false) // 不加载钩子
// 监听幻灯片内容变化
watch(() => slidesStore.slides, (newVal, oldVal) => {
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
},{ deep: true })
// 监听标题变化
watch(() => slidesStore.title, (newVal, oldVal) => {
if (oldVal == '未命名演示文稿') return // 初始加载,不需要更新数据
updatePPT({title: newVal})
})
// 监听幻灯片下标变化
watch(() => slidesStore.slideIndex, (newVal, oldVal) => {
if (!!Classcourse.id) return // 上课状态,不更新右侧作业列表
PPTApi.updateWorkList() // 更新作业列表
})
// 监听幻灯片下画布尺寸比例变化
watch(() => slidesStore.viewportRatio, (newVal, oldVal) => {
const width = slidesStore.viewportSize
const widthandration={width, ratio:newVal}
const data = { id: resource.id, parentContent: JSON.stringify(widthandration)}
PPTApi.updateSlide(data)
})
// 监听幻灯片内容变化
watch(() => slidesStore.slides, (newVal, oldVal) => {
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
},{ deep: true })
// 消息监听ws
// console.log('监听器已开启', ChatWs)
if (!!ChatWs.ws) {
ChatWs.watch((data, e) => {
try {
handleMessage(JSON.parse(data)?.msg)
} catch (error) {
console.error('socket 解析异常 ', error, e)
handleMessage(data)
}
})
}
// 更新ppt内容
const updatePPT = async (data) => {
if (!resource) return
data.id = resource.id
await PPTApi.updateSlide(data) // 更新ppt内容
sessionStore.set('curr.resource.title', data.title)
// 更新smarttalk内容
if (!!smarttalk && !!data.title) {
const {id, fileFlag} = smarttalk
const params = { id, fileShowName: `${data.title}.${fileFlag}` }
await PPTApi.updateSmarttalk(params) // 更新ppt内容
sessionStore.set('curr.smarttalk.fileShowName', params.fileShowName)
}
}
// ws消息处理
const handleMessage = (msg) => {
console.log('ws消息处理', msg)
if (typeof msg === 'object'){
const { head, content, ...other } = msg
switch (head) {
case MsgEnum.HEADS.MSG_open: // 开课
// 课堂信息不一致
if (Classcourse.id !== content.id) {
msgUtils.alertError('老师开课信息异常,请重新进入公屏!')
.then(() => { // 点击确定按钮,关闭窗口
close()
})
} else { // 正常更新数据
classcourseStore.classcourse.status = 'open'
sessionStore.set('curr.classcourse.status', 'open')
// 更新课堂信息-关闭警告框
Classcourse?.msgObj?.onVanish()
}
break
case MsgEnum.HEADS.MSG_slideFlapping: // 幻灯片翻页
emitter.emit('closegridPic') //如果有推图片窗口 就关闭
const slideIndex = content?.current || 0
const type = content?.animation // 上下动作
const steps = content?.animationSteps // 动画步骤
if (type === 'Nextsteps') execNext(true) // 下一步-异步动画
else if (type === 'Previoustep') turnPrevSlide() // 上一步清空-动画
else slidesStore.updateSlideIndex(slideIndex) // 更新幻灯片下标
// 更新本地缓存
sessionStore.set('curr.classcourse.paging', slideIndex)
sessionStore.set('curr.classcourse.cartoonTimes', steps)
classcourseStore.classcourse.paging = slideIndex
classcourseStore.classcourse.cartoonTimes = steps
break
// case MsgEnum.HEADS.MSG_homework: // 作业|活动-布置 不处理
case MsgEnum.HEADS.MSG_pushSreen_work: // 打开-作业|活动
if (!content.id) return
Homework.showHomework(content.id)
break
case MsgEnum.HEADS.MSG_pushSreen_experiment: // 打开实验:
if (!content.url) return
dialogUtils.openLink(content.url)
break
case MsgEnum.HEADS.MSG_closed: // 下课:
close()
break
case MsgEnum.HEADS.MSG_dz: // 点赞
hooksUpvote.trigger(1)
break
case MsgEnum.HEADS.MSG_yh: // 疑惑
hooksUpvote.trigger(2)
break
case MsgEnum.HEADS.MSG_pushSreen_ImgList: // 推图片上屏
const imgArray = content.ImgList.map((obj) => obj.url);
emitter.emit('opengridPic',{arr:imgArray}) // 打开推图片上屏窗口
break
case MsgEnum.HEADS.MSG_0010: // 备用
break
default:
break
}
}
}
// 关闭窗口
const close = () => {
ChatWs?.close() // 关闭ws
setTimeout(() => {
window.close() // 关闭窗口
}, 1000)
}
// setTimeout(async () => {
// emitter.emit('opengridPic',{arr:['https://prev.ysaix.com:7868/src/assets/images/homecard4.jpg']})
// }, 3000)
// 监听标题变化
watch(() => slidesStore.title, (newVal, oldVal) => {
if (oldVal == '未命名演示文稿') return // 初始加载,不需要更新数据
updatePPT({title: newVal})
})
const updatePPT = async (data) => {
if (!resource) return
data.id = resource.id
await PPTApi.updateSlide(data) // 更新ppt内容
sessionStore.set('curr.resource.title', data.title)
// setTimeout(async () => {
// emitter.emit('closegridPic')
// }, 6000)
// setTimeout(async () => {
// emitter.emit('opengridPic',{arr:['https://prev.ysaix.com:7868/src/assets/images/homecard4.jpg','https://prev.ysaix.com:7868/src/assets/images/homecard4.jpg']})
// }, 9000)
}

View File

@ -33,6 +33,48 @@ const { theme } = storeToRefs(useSlidesStore())
const { addSlidesFromData } = useAddSlidesOrElements()
const { isEmptySlide } = useSlideHandler()
const parseLineElement = (el: Shape) => {
let start: [number, number] = [0, 0]
let end: [number, number] = [0, 0]
if (!el.isFlipV && !el.isFlipH) { // 右下
start = [0, 0]
end = [el.width, el.height]
}
else if (el.isFlipV && el.isFlipH) { // 左上
start = [el.width, el.height]
end = [0, 0]
}
else if (el.isFlipV && !el.isFlipH) { // 右上
start = [0, el.height]
end = [el.width, 0]
}
else { // 左下
start = [el.width, 0]
end = [0, el.height]
}
const data: PPTLineElement = {
type: 'line',
id: nanoid(10),
width: el.borderWidth || 1,
left: el.left,
top: el.top,
start,
end,
style: el.borderType,
color: el.borderColor,
points: ['', /straightConnector/.test(el.shapType) ? 'arrow' : '']
}
if (/bentConnector/.test(el.shapType)) {
data.broken2 = [
Math.abs(start[0] - end[0]) / 2,
Math.abs(start[1] - end[1]) / 2,
]
}
return data
}
export default () => {
const exporting = ref(false)
@ -59,48 +101,7 @@ export default () => {
reader.readAsText(file)
}
const parseLineElement = (el: Shape) => {
let start: [number, number] = [0, 0]
let end: [number, number] = [0, 0]
if (!el.isFlipV && !el.isFlipH) { // 右下
start = [0, 0]
end = [el.width, el.height]
}
else if (el.isFlipV && el.isFlipH) { // 左上
start = [el.width, el.height]
end = [0, 0]
}
else if (el.isFlipV && !el.isFlipH) { // 右上
start = [0, el.height]
end = [el.width, 0]
}
else { // 左下
start = [el.width, 0]
end = [0, el.height]
}
const data: PPTLineElement = {
type: 'line',
id: nanoid(10),
width: el.borderWidth || 1,
left: el.left,
top: el.top,
start,
end,
style: el.borderType,
color: el.borderColor,
points: ['', /straightConnector/.test(el.shapType) ? 'arrow' : '']
}
if (/bentConnector/.test(el.shapType)) {
data.broken2 = [
Math.abs(start[0] - end[0]) / 2,
Math.abs(start[1] - end[1]) / 2,
]
}
return data
}
// 导入PPTX文件
const importPPTXFile = (files: FileList) => {
@ -493,6 +494,7 @@ export default () => {
importPPTXFile,
PPTXFileToJson,
exporting,
parseLineElement
}
}
@ -664,6 +666,7 @@ export const PPTXFileToJson = (data: File|ArrayBuffer) => {
}
else if (el.type === 'shape') {
if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
// 从返回对象中解构出 xx 函数并调用
const lineElement = parseLineElement(el)
slide.elements.push(lineElement)
}

View File

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

View File

@ -125,6 +125,8 @@ import {
User,
Switch,
More,
Material,
AddPicture
} from '@icon-park/vue-next'
export interface Icons {
@ -255,6 +257,8 @@ export const icons: Icons = {
IconUser: User,
IconSwitch: Switch,
IconMore: More,
IconMaterial: Material,
IconAddPicture: AddPicture
}
export default {

View File

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

View File

@ -3,6 +3,7 @@ import { useSlidesStore } from './slides'
import { useSnapshotStore } from './snapshot'
import { useKeyboardStore } from './keyboard'
import { useScreenStore } from './screen'
import { useClasscourseStore } from './classcourse'
export {
useMainStore,
@ -10,4 +11,5 @@ export {
useSnapshotStore,
useKeyboardStore,
useScreenStore,
useClasscourseStore,
}

View File

@ -1,17 +1,26 @@
import { defineStore } from 'pinia'
export interface ScreenState {
screening: boolean
screening: boolean,
like: number,
doubt: number,
}
export const useScreenStore = defineStore('screen', {
state: (): ScreenState => ({
screening: false, // 是否进入放映状态
like:0, // 点赞数量
doubt:0, // 疑问数量
}),
actions: {
setScreening(screening: boolean) {
this.screening = screening
},
// 打开点赞或疑问
openUpvote(type: 'like' | 'doubt'){
if (type === 'like') this.like++
else this.doubt++
}
},
})

View File

@ -5,9 +5,12 @@ import type { Slide, SlideTheme, PPTElement, PPTAnimation } from '../types/slide
import { slides } from '../mocks/slides'
import { theme } from '../mocks/theme'
import { layouts } from '../mocks/layout'
import { sessionStore } from '@/utils/store' // electron-store 状态管理
import useUserStore from '@/store/modules/user' // 外部-用户信息
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
import PPTApi from '../api/store'
const userStore = useUserStore()
interface RemovePropData {
id: string
propName: string | string[]
@ -30,7 +33,10 @@ export interface SlidesState {
slides: Slide[]
slideIndex: number
viewportSize: number
viewportRatio: number
viewportRatio: number,
animationIndex: number, // 不是从0开始
workList:Object[],
workItem:Object[],
}
export const useSlidesStore = defineStore('slides', {
@ -41,6 +47,9 @@ export const useSlidesStore = defineStore('slides', {
slideIndex: 0, // 当前页面索引
viewportSize: 1000, // 可视区域宽度基数
viewportRatio: 0.5625, // 可视区域比例默认16:9
animationIndex: 0, // 不是从0开始
workList:[],// 活动的列表
workItem:[],// 获取到的所有pptlist
}),
getters: {
@ -131,18 +140,23 @@ export const useSlidesStore = defineStore('slides', {
setSlides(slides: Slide[]) {
this.slides = slides
},
// 更新活动列表
setWorkList(list: Object[]) {
this.workList = list
},
setWorkItem(list: Object[]) {
this.workItem = list
},
addSlide(slide: Slide | Slide[]) {
const slides = Array.isArray(slide) ? slide : [slide]
for (const slide of slides) {
if (slide.sectionTag) delete slide.sectionTag
}
const addIndex = this.slideIndex + 1
this.slides.splice(addIndex, 0, ...slides)
this.slideIndex = addIndex
},
updateSlide(props: Partial<Slide>, slideId?: string) {
const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex
this.slides[slideIndex] = { ...this.slides[slideIndex], ...props }
@ -177,6 +191,7 @@ export const useSlidesStore = defineStore('slides', {
const isDel = await PPTApi.delSlide(deletedId)
if (isDel) {
// 后端删除成功,更新页面数据
this.workItem.splice(index, 1)
deleteSlidesIndex.push(index)
slides.splice(index, 1)
}
@ -193,6 +208,9 @@ export const useSlidesStore = defineStore('slides', {
updateSlideIndex(index: number) {
this.slideIndex = index
},
updateAnimationIndex(index: number) {
this.animationIndex = index
},
addElement(element: PPTElement | PPTElement[]) {
const elements = Array.isArray(element) ? element : [element]

View File

@ -6,4 +6,5 @@ export const enum ToolbarStates {
SLIDE_DESIGN = 'slideDesign',
SLIDE_ANIMATION = 'slideAnimation',
MULTI_POSITION = 'multiPosition',
EL_ACTIVE = 'elActive',
}

View File

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

View File

@ -0,0 +1,135 @@
<template>
<header class="flex material-header">
<span>选择素材</span>
<i class="iconfont icon-guanbi" @click="onClose"></i>
</header>
<div class="flex material-list" v-loading="loading">
<div class="flex material-item" v-for="item in list" :key="item.id" >
<div class="flex">
<el-image :src="fileUrl(item)" class="img" />
<el-text truncated>{{ item.fileShowName }}</el-text>
</div>
<el-button type="primary" @click="onInsert(item)">插入</el-button>
</div>
<el-empty description="暂无素材" v-if="!list.length" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
import { sessionStore } from '@/utils/store'
import { getSmarttalkPage } from '@/api/file'
import * as commUtils from '@/utils/comm.js'
import { getFileSuffix } from '@/utils/ruoyi.js'
import { PPTApi } from '../../../api'
const emit = defineEmits(['insertMaterial', 'close'])
const curNode = reactive({})
let params = {
textbookId: '',
levelFirstId: '',
levelSecondId: null,
fileSource: '个人',
fileRoot: '备课',
orderByColumn: 'createTime',
isAsc: 'desc',
pageSize: 500
}
const suffixAry = [ 'jpeg','jpg','png','gif','mp3','mp4','avi','mov']
const videoSuffix = ['mp3','mp4','avi','mov']
const list = ref([])
const loading = ref(false)
const init = () => {
loading.value = true
getSmarttalkPage(params).then(res => {
loading.value = false
if(res.rows && res.rows.length){
//
list.value = res.rows.filter( item => suffixAry.indexOf(getFileSuffix(item.fileShowName)) != -1)
}
})
}
const fileUrl = computed(() => (item) =>{
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
return item.coverPic
}
else{
return item.fileFullPath
}
})
//
const onInsert = async (item) =>{
loading.value = true
const res = await fetch(item.fileFullPath)
const bolb = await res.blob()
const file = commUtils.blobToFile(bolb, item.fileShowName)
try {
const data = await PPTApi.toRousrceUrl(file)
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
emit('insertMaterial',{ type: 'video', data })
}
else{
emit('insertMaterial',{ type: 'img', data })
}
} finally {
loading.value = false
}
}
//
const onClose = () =>{
emit('close')
}
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
params.textbookId = curNode.rootid
if (curNode.parentNode) {
params.levelFirstId = curNode.parentNode.id
params.levelSecondId = curNode.id
}
else {
params.levelFirstId = curNode.id
}
init()
})
</script>
<style lang="scss" scoped>
.material-header {
font-size: 17px;
font-weight: bold;
margin-bottom: 20px;
justify-content: space-between;
align-items: center;
.icon-guanbi{
font-size: 20px;
cursor: pointer;
}
}
.material-list{
flex-direction: column;
min-height: 150px;
max-height: 500px;
overflow-y: auto
}
.material-item {
align-items: center;
margin-bottom: 10px;
font-size: 14px;
justify-content: space-between;
.img{
width: 100px;
height: 100px;
margin-right: 20px;
}
}
</style>

View File

@ -7,19 +7,44 @@
/>
<template v-if="type === 'video'">
<Input v-model:value="videoSrc" placeholder="请输入视频地址e.g. https://xxx.mp4"></Input>
<div class="btns">
<el-tabs :tab-position="'left'" class="demo-tabs" v-model="tabvalue">
<el-tab-pane label="常规" >
<Input v-model:value="videoSrc" style="width:100%" placeholder="请输入视频地址e.g. https://xxx.mp4"></Input>
</el-tab-pane>
<el-tab-pane label="上传" >
<FileInput accept="video/*" @change="files => insertImageElementvideo(files)">
<div class="updivs">+点击上传视频</div>
</FileInput>
</el-tab-pane>
</el-tabs>
<div class="btns" v-if="tabvalue=='0'">
<Button @click="emit('close')" style="margin-right: 10px;">取消</Button>
<Button type="primary" @click="insertVideo()">确认</Button>
</div>
</template>
<template v-if="type === 'audio'">
<Input v-model:value="audioSrc" placeholder="请输入音频地址e.g. https://xxx.mp3"></Input>
<div class="btns">
<el-tabs :tab-position="'left'" class="demo-tabs" v-model="tabvalue1">
<el-tab-pane label="常规" >
<Input v-model:value="audioSrc" style="width:100%" placeholder="请输入音频地址e.g. https://xxx.mp3"></Input>
</el-tab-pane>
<el-tab-pane label="上传" >
<FileInput accept="audio/*" @change="files => insertImageElementaudio(files)">
<div class="updivs">+点击上传音频</div>
</FileInput>
</el-tab-pane>
</el-tabs>
<div class="btns" v-if="tabvalue1=='0'">
<Button @click="emit('close')" style="margin-right: 10px;">取消</Button>
<Button type="primary" @click="insertAudio()">确认</Button>
</div>
</template>
</div>
</template>
@ -30,6 +55,8 @@ import message from '../../../utils/message'
import Tabs from '../../../components/Tabs.vue'
import Input from '../../../components/Input.vue'
import Button from '../../../components/Button.vue'
import FileInput from '../../../components/FileInput.vue'
import { PPTApi } from '../../../api'
type TypeKey = 'video' | 'audio'
interface TabItem {
@ -45,9 +72,33 @@ const emit = defineEmits<{
const type = ref<TypeKey>('video')
const videoSrc = ref('https://mazwai.com/videvo_files/video/free/2019-01/small_watermarked/181004_04_Dolphins-Whale_06_preview.webm')
const audioSrc = ref('https://freesound.org/data/previews/614/614107_11861866-lq.mp3')
const videoSrc = ref('')
const audioSrc = ref('')
// https://freesound.org/data/previews/614/614107_11861866-lq.mp3
const tabvalue = ref('0')
const tabvalue1 = ref('0')
const insertImageElementvideo = (files: FileList) => {
console.log('files', files)
const imageFile = files[0]
if (!imageFile) return
PPTApi.toRousrceUrl(imageFile).then(data=>{
videoSrc.value=data
insertVideo()
})
}
const insertImageElementaudio = (files: FileList) => {
console.log('files', files)
const imageFile = files[0]
if (!imageFile) return
PPTApi.toRousrceUrl(imageFile).then(data=>{
videoSrc.value=data
insertAudio()
})
}
const tabs: TabItem[] = [
{ key: 'video', label: '视频' },
{ key: 'audio', label: '音频' },
@ -74,4 +125,33 @@ const insertAudio = () => {
margin-top: 10px;
text-align: right;
}
.updivs{
width: 100%;
height: 100%;
background: #f5f7fa;
border: 1px dashed #d9d9d9;
border-radius: 6px;
text-align: center;
line-height: 100px;
cursor: pointer;
}
.demo-tabs{
:deep(.el-tabs__content){
display: flex;
align-items: center;
div{
width: 100%;
}
}
:deep( .el-tabs__item.is-active) {
color: #d14424;
}
:deep( .el-tabs__item:hover) {
color: #d14424;
}
:deep(.el-tabs__active-bar) {
background-color: #d14424;
height: 3px;
}
}
</style>

View File

@ -81,6 +81,9 @@
</template>
<IconVideoTwo class="handler-item" v-tooltip="'插入音视频'" />
</Popover>
<IconPreviewOpen class="handler-item" v-tooltip="'插入试题'" @click="classWorkTaskVisible = true" />
<IconMaterial class="handler-item" v-tooltip="'插入素材'" @click="materiaVisible = true"/>
<IconAddPicture class="handler-item" v-tooltip="'文生图'" @click="imgVisible = true" />
</div>
<div class="right-handler">
@ -110,6 +113,27 @@
@update="data => { createLatexElement(data); latexEditorVisible = false }"
/>
</Modal>
<!--插入试题-->
<el-dialog v-model="classWorkTaskVisible" append-to-body :show-close="false" width="70%">
<QuestToPPTist
class="class-work-task-modal"
@close="classWorkTaskVisible = false"
@update="data => { onhtml2canvas(data); classWorkTaskVisible = false }"
/>
</el-dialog>
<!--插入素材-->
<Modal
v-model:visible="materiaVisible"
:width="880">
<MaterialDialog @close="materiaVisible = false" @insertMaterial="insertMaterial"/>
</Modal>
<!--文生图-->
<Modal
v-model:visible="imgVisible"
:width="1300">
<TextCreateImg hasPPt @insertImg="insertImg" />
</Modal>
</div>
</template>
@ -117,7 +141,7 @@
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore, useSnapshotStore } from '../../../store'
import { getImageDataURL } from '../../../utils/image'
import { getImageDataURL, base64ToFile } from '../../../utils/image'
import type { ShapePoolItem } from '../../../configs/shapes'
import type { LinePoolItem } from '../../../configs/lines'
import useScaleCanvas from '../../../hooks/useScaleCanvas'
@ -135,6 +159,12 @@ import Modal from '../../../components/Modal.vue'
import Divider from '../../../components/Divider.vue'
import Popover from '../../../components/Popover.vue'
import PopoverMenuItem from '../../../components/PopoverMenuItem.vue'
import QuestToPPTist from '@/views/classTask/newClassTaskAssign/questToPPTist/index.vue'
import MaterialDialog from './MaterialDialog.vue'
import { PPTApi } from '../../../api'
import TextCreateImg from '@/components/ai-kolors/index.vue'
import { toPng } from 'html-to-image' // html-to-image
const mainStore = useMainStore()
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
@ -167,9 +197,27 @@ const {
} = useCreateElement()
const insertImageElement = (files: FileList) => {
console.log('files', files)
const imageFile = files[0]
if (!imageFile) return
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
// 线
PPTApi.toRousrceUrl(imageFile).then(data=>{
createImageElement(data)
})
// getImageDataURL(imageFile).then(dataURL => {
// createImageElement(dataURL)
// })
}
const onhtml2canvas = async (html: HTMLElement) => {
const base64Dta = await toPng(html);
// base64File
const toFile = base64ToFile(base64Dta)
// 线
PPTApi.toRousrceUrl(toFile).then(data=>{
createImageElement(data)
})
// createImageElement(ele);
}
const shapePoolVisible = ref(false)
@ -178,9 +226,11 @@ const chartPoolVisible = ref(false)
const tableGeneratorVisible = ref(false)
const mediaInputVisible = ref(false)
const latexEditorVisible = ref(false)
const classWorkTaskVisible = ref(false)
const textTypeSelectVisible = ref(false)
const shapeMenuVisible = ref(false)
const moreVisible = ref(false)
const materiaVisible = ref(false)
//
const drawText = (vertical = false) => {
@ -227,6 +277,32 @@ const toggleSraechPanel = () => {
const toggleNotesPanel = () => {
mainStore.setNotesPanelState(!showNotesPanel.value)
}
//
interface MaterialParams {
type: string,
data: string
}
const insertMaterial = async (item: MaterialParams) =>{
const { type, data } = item
if(type == 'video'){
createVideoElement(data)
}
else{
createImageElement(data)
}
materiaVisible.value = false
}
//
const imgVisible = ref(false)
const insertImg = async (file: any) =>{
PPTApi.toRousrceUrl(file).then(data=>{
createImageElement(data)
imgVisible.value = false
})
}
</script>
<style lang="scss" scoped>
@ -343,6 +419,9 @@ const toggleNotesPanel = () => {
font-size: 13px;
}
}
.class-work-task-modal{
height: 70vh;
}
@media screen and (width <= 1200px) {
.right-handler .text {

View File

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

View File

@ -66,6 +66,7 @@
</Draggable>
<div class="page-number">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
</div>
</template>
@ -90,7 +91,7 @@ const mainStore = useMainStore()
const slidesStore = useSlidesStore()
const keyboardStore = useKeyboardStore()
const { selectedSlidesIndex: _selectedSlidesIndex, thumbnailsFocus } = storeToRefs(mainStore)
const { slides, slideIndex, currentSlide } = storeToRefs(slidesStore)
const { slides, slideIndex, currentSlide, workList, workItem } = storeToRefs(slidesStore)
const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)
const { slidesLoadLimit } = useLoadSlides()
@ -123,6 +124,7 @@ const {
updateSectionTitle,
} = useSectionHandler()
//
const thumbnailsRef = ref<InstanceType<typeof Draggable>>()
watch(() => slideIndex.value, () => {
@ -145,6 +147,7 @@ watch(() => slideIndex.value, () => {
//
const changeSlideIndex = (index: number) => {
mainStore.setActiveElementIdList([])
if (slideIndex.value === index) return
@ -393,12 +396,17 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => {
.icon {
margin-right: 3px;
font-size: 14px;
:deep(svg) {
display: block !important;
}
}
}
.thumbnail-list {
padding: 5px 0;
flex: 1;
overflow: auto;
border-bottom: 1px solid $borderColor;
}
.thumbnail-item {
display: flex;
@ -477,7 +485,6 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => {
.page-number {
height: 40px;
font-size: 12px;
border-top: 1px solid $borderColor;
line-height: 40px;
text-align: center;
color: #666;

View File

@ -245,7 +245,6 @@ const runAnimation = (elId: string, effect: string, duration: number) => {
const animationName = `${ANIMATION_CLASS_PREFIX}${effect}`
document.documentElement.style.setProperty('--animate-duration', `${duration}ms`)
elRef.classList.add(`${ANIMATION_CLASS_PREFIX}animated`, animationName)
const handleAnimationEnd = () => {
document.documentElement.style.removeProperty('--animate-duration')
elRef.classList.remove(`${ANIMATION_CLASS_PREFIX}animated`, animationName)

View File

@ -0,0 +1,346 @@
<template>
<div>
<div style="display: flex;flex-wrap: wrap;">
<el-button size="small" title="活动引用" text style="height: 54px" @click="openList()">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M133.36576 308.12842667v407.74314666c0 96.39253333 78.31552 174.76266667 174.76266667 174.76266667h407.74314666c96.39253333 0 174.76266667-78.31552 174.76266667-174.76266667V308.12842667c0-96.39253333-78.31552-174.76266667-174.76266667-174.76266667H308.12842667a174.87189333 174.87189333 0 0 0-174.76266667 174.76266667zM75.09333333 308.12842667A233.19893333 233.19893333 0 0 1 308.12842667 75.09333333h407.74314666A233.19893333 233.19893333 0 0 1 948.90666667 308.12842667v407.74314666A233.19893333 233.19893333 0 0 1 715.87157333 948.90666667H308.12842667A233.19893333 233.19893333 0 0 1 75.09333333 715.87157333V308.12842667z m706.9696 192.07509333c0 24.46677333-5.67978667 48.22357333-16.98474666 71.21578667-11.35957333 22.99221333-26.76053333 43.52682667-46.25749334 61.54922666-19.44234667 18.0224-42.05226667 32.44032-67.61130666 43.25376a207.20298667 207.20298667 0 0 1-81.21002667 16.16554667h-26.54208c-10.37653333 0-21.62688 0.10922667-33.64181333 0.27306667-12.01493333 0.21845333-23.86602667 0.32768-35.66250667 0.32768h-31.9488c-13.1072 0-23.53834667 1.25610667-31.23882667 3.71370666-7.70048 2.51221333-11.35957333 7.91893333-10.92266666 16.16554667 0 6.22592-0.10922667 13.38026667-0.32768 21.46304-0.21845333 8.08277333-0.32768 15.45557333-0.32768 22.06378667 0 10.81344-3.16757333 17.74933333-9.50272 20.86229333-6.33514667 3.11296-14.7456 0.92842667-25.12213334-6.5536-10.92266667-7.42741333-23.37450667-16.27477333-37.41013333-26.43285333a36528.56490667 36528.56490667 0 0 0-126.42986667-91.09504c-10.37653333-7.42741333-15.72864-14.47253333-15.94709333-21.13536-0.27306667-6.60821333 4.36906667-13.65333333 13.9264-21.13536 9.93962667-7.48202667 21.62688-16.27477333 34.95253333-26.43285334 13.38026667-10.15808 27.30666667-20.58922667 41.83381334-31.40266666l42.81685333-31.67573334c14.03562667-10.37653333 26.48746667-19.71541333 37.41013333-28.01664 11.74186667-9.12042667 21.62688-12.61568 29.4912-10.54037333 7.97354667 2.07530667 11.96032 8.73813333 11.96032 19.87925333 0 3.2768 0.10922667 7.3728 0.32768 12.12416a2794.40042667 2794.40042667 0 0 1 1.69301334 43.85450667c0 7.86432 2.62144 12.72490667 7.80970666 14.58176 5.24288 1.85685333 12.34261333 2.83989333 21.40842667 2.83989333 20.42538667-0.43690667 43.30837333-0.65536 68.64896-0.65536h70.66965333c10.43114667 0 20.86229333-2.18453333 31.29344-6.5536 10.37653333-4.36906667 19.93386667-10.10346667 28.56277334-17.36704 8.57429333-7.26357333 15.61941333-15.67402667 21.02613333-25.17674666 5.46133333-9.55733333 8.192-19.6608 8.192-30.47424v-43.52682667c0-16.60245333-0.10922667-32.65877333-0.32768-48.22357333a2840.00256 2840.00256 0 0 1-0.38229333-40.68693334V341.6064c0-9.12042667 3.05834667-16.65706667 9.17504-22.66453333 6.11669333-6.00746667 13.81717333-10.59498667 23.10144-13.70794667 9.28426667-3.11296 19.38773333-4.64213333 30.25578666-4.64213333 10.92266667 0 20.97152 1.41994667 30.25578667 4.36906666 9.28426667 2.83989333 16.98474667 7.09973333 23.10144 12.72490667 6.11669333 5.57056 9.17504 12.56106667 9.17504 20.80768v46.03904c0 10.75882667 0.10922667 22.28224 0.32768 34.51562667 0.27306667 12.23338667 0.38229333 23.92064 0.38229333 35.11637333V500.20352z" p-id="7882"></path></svg>
<div style="margin-top: 10px">活动引用</div>
</div>
</el-button>
<el-button size="small" title="习题训练" text style="height: 54px" @click="showDialog('习题训练')">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M473.6 204.8c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4H243.2c-21.2 0-38.4-17.2-38.4-38.4s17.2-38.4 38.4-38.4h230.4z m-51.2 153.6c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4H243.2c-21.2 0-38.4-17.2-38.4-38.4s17.2-38.4 38.4-38.4h179.2zM371.2 512c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4h-128c-21.2 0-38.4-17.2-38.4-38.4S222 512 243.2 512h128z m192-384H153.6c-12.6 0-23 9.1-25.2 21l-0.4 4.6v512c0 12.6 9.1 23 21 25.2l4.6 0.4h257.1c-0.7-8.5-1.1-17-1.1-25.6 0-124 73.5-230.8 179.2-279.4V153.6c0-12.6-9.1-23-21-25.2l-4.6-0.4zM768 460.8H665.6v153.6H512v102.4h153.6v153.6H768V716.8h153.6V614.4H768V460.8zM563.2 51.2c56.6 0 102.4 45.8 102.4 102.4v209c16.6-2.8 33.7-4.2 51.2-4.2 169.7 0 307.2 137.5 307.2 307.2S886.5 972.8 716.8 972.8c-133.7 0-247.5-85.5-289.7-204.8H153.6C97 768 51.2 722.2 51.2 665.6v-512C51.2 97 97 51.2 153.6 51.2h409.6z" p-id="13225"></path></svg>
<div style="margin-top: 10px">习题训练</div>
</div>
</el-button>
<el-button size="small" title="课堂展示" text style="height: 54px" @click="showDialog('课堂展示')">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M741.75298 604.605763l3.836774 0.697596 17.43988 3.767014c154.656857 35.089039 260.133252 103.732407 260.133252 194.070985 0 88.455072-101.360583 156.261326-251.134274 191.838682l-9.068737 2.092785-17.370121 3.836774-17.788678 3.487976c-23.927515 4.39485-48.831664 8.092104-74.642686 11.022004l-19.462907 2.023026-24.69487 2.092786-5.022685 0.348797-20.090742 1.325431-10.18489 0.488317-10.25465 0.418557-20.579058 0.697595c-10.324409 0.209279-20.788337 0.348798-31.322025 0.348798l-15.695892-0.139519-15.556373-0.279038-20.648818-0.627836-10.18489-0.418557-10.18489-0.488317-20.160501-1.325431-9.975612-0.697595-19.741944-1.743988a1100.665713 1100.665713 0 0 1-75.968118-9.905852l-18.137475-3.139178-17.788678-3.487976-17.37012-3.767014C105.406635 961.983786 0 893.410178 0 803.141358c0-88.594591 101.360583-156.261326 251.134273-191.9782l8.998979-2.092785 17.43988-3.767014 3.767014-0.697596v93.756796l-10.952245 2.581102-15.207575 3.906533-14.6495 4.046052-13.951904 4.185572c-75.340282 23.857756-121.660604 61.946454-121.660603 89.989781 0 27.206213 43.250903 61.527897 114.196335 85.036855l7.394509 2.371824 14.021663 4.255331 14.6495 4.046052 15.207575 3.906533c8.580421 2.092786 17.43988 4.115812 26.578377 5.999319l13.951904 2.790381 17.021323 3.139178 8.7897 1.39519 17.788678 2.790381 9.068737 1.255672 18.556033 2.302064 14.161182 1.604469 14.370462 1.39519 19.532665 1.53471a1139.731044 1139.731044 0 0 0 142.867498 1.255671l19.881463-1.255671 19.532666-1.53471c8.580421-0.767355 17.021323-1.674228 25.392466-2.581102l12.417194-1.53471 18.276995-2.441583 17.858437-2.790381 8.71994-1.39519 17.091082-3.139178c9.417535-1.813748 18.486273-3.697255 27.415492-5.720281l13.11479-3.069419 15.207575-3.906533 14.649499-4.046052 13.951904-4.185572c75.340282-23.857756 121.590844-59.365352 121.590845-87.408679 0-27.206213-43.250903-64.178759-114.196335-87.617957l-7.39451-2.371824-14.021663-4.255331-14.649499-4.115811-15.207576-3.836774-10.952245-2.581102V604.605763zM451.204578 588.072757l121.660604 69.82928-131.078139 86.153008 9.417535-155.982288z m280.921589-484.131072l121.660603 69.82928-257.552149 443.810069-121.590844-69.899039 257.48239-443.74031zM842.904285 6.278357l40.530281 23.22992a46.459841 46.459841 0 0 1 17.160842 63.690442l-23.439198 40.321003L755.565365 63.620683l23.36944-40.321003a46.948157 46.948157 0 0 1 63.96948-17.021323z" p-id="59383"></path></svg>
<div style="margin-top: 10px">课堂展示</div>
</div>
</el-button>
<el-button size="small" title="常规作业" text style="height: 54px;margin-left: 0" @click="showDialog('常规作业')">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M901.705143 511.926857h-55.954286a8.045714 8.045714 0 0 1-8.045714-8.045714V183.881143H181.686857v656.091428H501.76c4.388571 0 8.045714 3.510857 8.045714 7.899429v56.027429c0 4.388571-3.657143 8.045714-8.045714 8.045714H141.750857a31.963429 31.963429 0 0 1-32.036571-32.036572V143.872c0-17.627429 14.336-31.963429 32.036571-31.963429H877.714286c17.700571 0 32.036571 14.336 32.036571 31.963429V503.954286c0 4.388571-3.657143 8.045714-8.045714 8.045714zM731.428571 911.945143a36.571429 36.571429 0 0 1-36.571428-36.571429v-109.714285H585.142857a36.571429 36.571429 0 0 1 0-73.142858h109.714286v-109.714285a36.571429 36.571429 0 0 1 73.142857 0v109.714285H877.714286a36.571429 36.571429 0 1 1 0 73.142858h-109.714286v109.714285a36.571429 36.571429 0 0 1-36.571429 36.571429z" p-id="22184"></path></svg>
<div style="margin-top: 10px">常规作业</div>
</div>
</el-button>
<el-button size="small" title="科学实验" text style="height: 54px;margin-left: 0" @click="showDialog('科学实验')">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M901.705143 511.926857h-55.954286a8.045714 8.045714 0 0 1-8.045714-8.045714V183.881143H181.686857v656.091428H501.76c4.388571 0 8.045714 3.510857 8.045714 7.899429v56.027429c0 4.388571-3.657143 8.045714-8.045714 8.045714H141.750857a31.963429 31.963429 0 0 1-32.036571-32.036572V143.872c0-17.627429 14.336-31.963429 32.036571-31.963429H877.714286c17.700571 0 32.036571 14.336 32.036571 31.963429V503.954286c0 4.388571-3.657143 8.045714-8.045714 8.045714zM731.428571 911.945143a36.571429 36.571429 0 0 1-36.571428-36.571429v-109.714285H585.142857a36.571429 36.571429 0 0 1 0-73.142858h109.714286v-109.714285a36.571429 36.571429 0 0 1 73.142857 0v109.714285H877.714286a36.571429 36.571429 0 1 1 0 73.142858h-109.714286v109.714285a36.571429 36.571429 0 0 1-36.571429 36.571429z" p-id="22184"></path></svg>
<div style="margin-top: 10px">科学实验</div>
</div>
</el-button>
</div>
<Divider />
<!-- 作业列表 -->
<div class="c-apt-r" v-loading='loadingActive'>
<!-- 显示-作业内容 -->
<template v-for="(item, index) in slidesStore.workList" :key="index">
<div class="item">
<div class="item-title">
<el-tag :type="getTagType(item.worktype) || 'primary'">{{item.worktype}}</el-tag>
<el-tooltip :content="item.uniquekey" placement="top">
<div class="tt">{{item.uniquekey}}</div>
</el-tooltip>
<el-button class="btn-del" type="danger" link @click="removeWork(item, index)">删除</el-button>
</div>
</div>
</template>
</div>
<!-- // -->
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="85%" height="500">
<el-scrollbar>
<div style="height: 75vh;">
<NewClassTsakAssign :currentCourse='currentCourse' @getData="getData" />
</div>
</el-scrollbar>
</el-dialog>
<!-- 活动引用 -->
<el-dialog
v-loading="tasklist_loading"
v-model="activeVisible"
append-to-body
:show-close="false"
width="40%"
>
<el-table :data="taskList" style="width: 100%" height="500" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" :selectable="selectable"/>
<el-table-column prop="uniquekey" label="活动名称" width="150" />
<el-table-column prop="worktype" label="活动类型" width="120" sortable>
<template #default="scope">
<el-tag :type="getTagType(scope.row.worktype) || 'primary'">{{ scope.row.worktype }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="timestamp" label="创建时间" sortable/>
</el-table>
<template #footer>
<el-button @click="activeVisible = false"> </el-button>
<el-button type="primary" @click="savePPtData"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick, watch } from 'vue'
import Divider from '../../../../../components/Divider.vue'
import { ElMessageBox,ElMessage } from 'element-plus'
import NewClassTsakAssign from '@/views/classTask/newClassTaskAssign/index.vue'
import { sessionStore } from '@/utils/store'
import { useGetHomework } from '@/hooks/useGetHomework'
import { PPTApi } from '../../../../../api/index'
import {useSlidesStore} from '../../../../../store'
const slidesStore = useSlidesStore()
interface CourseNode {
rootid: number;
parentNode: { id: number };
id: number;
itemtitle: string;
}
interface CurrentCourse {
textbookId: number;
levelFirstId: number;
levelSecondId: number;
coursetitle: string;
node: CourseNode;
id: number;
worktype: string;
}
interface WorkType {
label: string;
value: string;
}
interface WorkItem {
status: string;
activityContent?: string;
worktype: string;
quizlist?: { id: number }[];
workcodes: string;
base64?: string;
prevData?: any;
id: number;
uniquekey?: string; // evaltitle
}
const currentCourse = reactive<CurrentCourse>({
textbookId: 0,
levelFirstId: 0,
levelSecondId: 0,
coursetitle: '',
node: {} as CourseNode,
id: 1,
worktype: '',
})
const dialogVisible = ref<boolean>(false)
const tasklist_loading = ref<boolean>(false)
//
const taskList = ref<WorkItem[]>([])
//
const activeVisible = ref<boolean>(false)
const type = ref<WorkType[]>([
{
label: '习题训练',
value: 'danger'
},
{
label: '课堂展示',
value: 'success'
},
{
label: '常规作业',
value: 'primary'
},
{
label: '框架梳理',
value: 'primary'
},
{
label: '科学实验',
value: 'primary'
}
])
//
const selectedWorkList = ref<WorkItem[]>([])
// loading
const loadingActive = ref<boolean>(false)
//
const selectable = (row: WorkItem, index: number): boolean => {
return row.status === '10';
};
//
const removeWork = (item: WorkItem, index: number) => {
ElMessageBox.confirm('是否确认删除?')
.then(async() => {
// workList
// slidesStore.workList.splice(index, 1)
console.log('删除作业', item)
await upDateData('del', [item.id])
ElMessage.success('删除成功')
})
.catch(() => { });
}
// tag
const getTagType = (worktype: string): string => {
return type.value.find(item => item.label === worktype)!.value
}
//
const initHomeWork = async () => {
tasklist_loading.value = true;
const { res, chapterId } = await useGetHomework(sessionStore.get('subject.curNode'));
taskList.value = res;
tasklist_loading.value = false;
}
//
const handleSelectionChange = (val: WorkItem[]) => {
selectedWorkList.value = [...val]
}
//
const showDialog = (item: string) => {
currentCourse.worktype = item
dialogVisible.value = true
}
//
const openList = () => {
activeVisible.value = true
initHomeWork()
}
//
const savePPtData = async () => {
if (!selectedWorkList.value.length) {
ElMessage.warning('请选择活动')
return
}
const arr = selectedWorkList.value.map(item => item.id)
await upDateData('add', arr)
activeVisible.value = false
}
//
const getData = async (data: WorkItem) => {
await upDateData('add', [data.id])
dialogVisible.value = false
}
// - type add/ | del/ ids id
const upDateData = (type: string, ids: number[]): Promise<any> => {
return new Promise(async (resolve, reject) => {
loadingActive.value = true
//
let workIds = slidesStore.workList.map((o:any) => o.id)
const id = slidesStore.currentSlide.id
if (type === 'del') { //
workIds = workIds.filter(id => !ids.includes(id))
} else { //
workIds = Array.from(new Set([...workIds, ...ids]))
}
//
const wItem:any = slidesStore.workItem.find((o:any) => o.id === id)
const workIdsStr = workIds.join(',') // id->
if (!!wItem) wItem.activityContent = workIdsStr
const data = { id, activityContent: workIdsStr }
//
await PPTApi.updateSlide(data, false)
//
await PPTApi.updateWorkList()
loadingActive.value = false
resolve(true)
})
}
onMounted(() => {
const curNode = sessionStore.get('subject.curNode') as CourseNode
currentCourse.textbookId = curNode.rootid
currentCourse.levelFirstId = curNode.parentNode.id
currentCourse.levelSecondId = curNode.id
currentCourse.coursetitle = curNode.itemtitle
currentCourse.node = curNode
})
</script>
<style scoped lang="scss">
.buttonDiv{
margin-top: 4px;
display: flex;
align-items: center;
justify-content: space-between;
flex-direction: column;
}
// (apt)-
.c-apt-r{
flex:1;
overflow: auto;
.item{
position: relative;
margin-top: 5px;
.item-title{
display: flex;
align-items: center;
.tt{
flex: 1;
padding-left: 10px;
cursor: default;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 13px;
color: #606266;
}
.btn-del{
margin-right: 10px;
}
}
.item-body{
position: relative;
border: 1px solid silver;
padding: 10px;
background: #fff;
border-radius: 3px;
margin: 5px 10px;
cursor: default;
&:hover{
border-color: var(--el-color-primary);
.el-text{
--el-text-color: var(--el-color-primary);
}
.el-tag{
--el-tag-bg-color: var(--el-color-primary);
--el-tag-border-color: var(--el-color-primary);
--el-tag-text-color: var(--el-color-white);
}
}
.c-icon-info{
position: absolute;
top: -5px;
right: -5px;
}
}
.item-divider{
margin: 5px 0;
margin-left: 10px;
width: calc(100% - 20px);
--el-border-color: var(--current-color);
}
}
.page .page-resource{
height: 500px !important;
}
}
</style>

View File

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

View File

@ -25,6 +25,8 @@ import SlideDesignPanel from './SlideDesignPanel.vue'
import SlideAnimationPanel from './SlideAnimationPanel.vue'
import MultiPositionPanel from './MultiPositionPanel.vue'
import SymbolPanel from './SymbolPanel.vue'
//
import SymbolActivePanel from './ElementStylePanel/Active/index.vue'
import Tabs from '../../../components/Tabs.vue'
interface ElementTabs {
@ -54,6 +56,7 @@ const slideTabs = [
{ label: '设计', key: ToolbarStates.SLIDE_DESIGN },
{ label: '切换', key: ToolbarStates.SLIDE_ANIMATION },
{ label: '动画', key: ToolbarStates.EL_ANIMATION },
{ label: '活动', key: ToolbarStates.EL_ACTIVE },
]
const multiSelectTabs = [
{ label: '样式', key: ToolbarStates.EL_STYLE },
@ -86,6 +89,7 @@ const currentPanelComponent = computed(() => {
[ToolbarStates.SLIDE_ANIMATION]: SlideAnimationPanel,
[ToolbarStates.MULTI_POSITION]: MultiPositionPanel,
[ToolbarStates.SYMBOL]: SymbolPanel,
[ToolbarStates.EL_ACTIVE]: SymbolActivePanel,//
}
return panelMap[toolbarState.value] || null
})

View File

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

View File

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

View File

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

View File

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

View File

@ -160,7 +160,7 @@ const topImgPositionStyle = computed(() => {
return {
left: -left * (100 / width) + '%',
top: -top * (100 / height) + '%',
width: bottomWidth / width * 100 + '%',
width: bottomWidth / width * 100 + '%' ,
height: bottomHeight / height * 100 + '%',
}
})
@ -228,6 +228,7 @@ const updateRange = () => {
width: parseInt(topImgPositionStyle.value.width),
height: parseInt(topImgPositionStyle.value.height),
}
console.log('retPosition', retPosition)
const widthScale = 100 / retPosition.width
const heightScale = 100 / retPosition.height
@ -475,7 +476,7 @@ const scaleClipRange = (e: MouseEvent, type: OperateResizeHandlers) => {
isMouseDown = false
document.onmousemove = null
document.onmouseup = null
console.log('----------------------------------')
updateRange()
setTimeout(() => isSettingClipRange.value = false, 0)
@ -537,6 +538,7 @@ const edgePoints = [
img {
position: absolute;
max-width: none !important;
}
}
}

View File

@ -183,6 +183,7 @@ const handleClip = (data: ImageClipedEmitData | null) => {
}
img {
position: absolute;
max-width: none !important; //
}
}
.color-mask {

View File

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

View File

@ -56,4 +56,5 @@ export class Other {
static baseUrl = "/common/upload"
// 测试
static uploadFile = data => ApiService.publicHttp(this.baseUrl, data, 'post', null, 'file')
}

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
export function updateClasswork(data) {
return request({

View File

@ -71,3 +71,11 @@ export const addFileToKj = (id) => {
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-family: "iconfont"; /* Project id 4723712 */
src: url('iconfont.woff2?t=1732240267757') format('woff2'),
url('iconfont.woff?t=1732240267757') format('woff'),
url('iconfont.ttf?t=1732240267757') format('truetype');
src: url('iconfont.woff2?t=1734337029245') format('woff2'),
url('iconfont.woff?t=1734337029245') format('woff'),
url('iconfont.ttf?t=1734337029245') format('truetype');
}
.iconfont {
@ -13,6 +13,30 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-yinle:before {
content: "\e6c9";
}
.icon-yuyin:before {
content: "\e648";
}
.icon-dianying:before {
content: "\e693";
}
.icon-jiqirenfushi:before {
content: "\e624";
}
.icon-xiangmuicon_maobishufa:before {
content: "\e651";
}
.icon-meishu-F:before {
content: "\e638";
}
.icon-shangchuan:before {
content: "\e61b";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,48 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "11819186",
"name": "音乐",
"font_class": "yinle",
"unicode": "e6c9",
"unicode_decimal": 59081
},
{
"icon_id": "6338162",
"name": "语音生成",
"font_class": "yuyin",
"unicode": "e648",
"unicode_decimal": 58952
},
{
"icon_id": "6880941",
"name": "视频生成",
"font_class": "dianying",
"unicode": "e693",
"unicode_decimal": 59027
},
{
"icon_id": "11532042",
"name": "数字人生成",
"font_class": "jiqirenfushi",
"unicode": "e624",
"unicode_decimal": 58916
},
{
"icon_id": "13522843",
"name": "文生图片",
"font_class": "xiangmuicon_maobishufa",
"unicode": "e651",
"unicode_decimal": 58961
},
{
"icon_id": "37635062",
"name": "文生连环画",
"font_class": "meishu-F",
"unicode": "e638",
"unicode_decimal": 58936
},
{
"icon_id": "4942656",
"name": "上传",

Binary file not shown.

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

@ -144,8 +144,14 @@ import { convertTextToPicture, getQueue, getPromptId, getPicture, chattoprompt,
import CryptoJS from 'crypto-js'
import { useRoute } from 'vue-router'
export default {
props: {
hasPPt: {
type: Boolean,
default: false
}
},
data() {
return {
form: {
ratio: "512",
@ -384,7 +390,7 @@ export default {
urls.push(url0)
buttonState.push({
disabled: false,
text: "插入本课素材资源库",
text: this.hasPPt ? '插入' : "插入本课素材资源库",
})
}
this.skeletonNumber = 0
@ -476,6 +482,7 @@ export default {
//
async saveImage(resultIndex, index, url, resultItem) {
this.buttonStates[resultIndex][index].disabled = true;
this.buttonStates[resultIndex][index].text = "正在保存...";
const numberIndex = url.indexOf('filename=');
@ -484,6 +491,7 @@ export default {
const finalPath = path.substring(0, pngIndex + 4);
try {
const blob = await this.getImageBlob(`https://ai.ysaix.com:7853/view?filename=${finalPath}&type=temp`);
const hash = CryptoJS.MD5(blob).toString();
@ -492,7 +500,10 @@ export default {
let file = new File([blob], `${resultItem}.png`, {
type: 'image/png'
})
if(this.hasPPt){
this.$emit('insertImg', file)
return
}
//
formData.append('md5', hash);
formData.append('file', file);

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

View File

@ -34,7 +34,14 @@ const getFileTypeIcon = () => {
gif: 'icon-gif',
txt: 'icon-txt',
rar: 'icon-rar',
apt: 'icon-A'
apt: 'icon-A',
aippt: 'icon-a-ziyuan91',
aiyuyin: 'icon-yuyin', //
aivideo: 'icon-dianying', //
airobot: 'icon-jiqirenfushi', //
aiimg: 'icon-xiangmuicon_maobishufa', //
aidraw: 'icon-meishu-F', //
aiyinyue: 'icon-yinle' //
}
if (iconObj[name]) {
return '#' + iconObj[name]

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,
label: nodeData.parenttitle,
itemtitle: nodeData.parenttitle
}
}
const parentNode = nodeData.parentid ? parent : null
nodeData.parentNode = parentNode
@ -204,7 +204,11 @@ onMounted(async () => {
curBook.data = sessionStore.get('subject.curBook')
}
else{
curBook.data = subjectList.value[0]
if (subjectList.value) {
curBook.data = subjectList.value[0]
}else {
curBook.data = {}
}
}
// ""
@ -220,7 +224,7 @@ onMounted(async () => {
}
handleNodeClick(curNode.data)
})
})
</script>

View File

@ -29,6 +29,20 @@
</template>
</div>
</el-scrollbar>
<div class="file-list">
<el-dropdown @command="changeFile" v-if="type == 3">
<span class="el-dropdown-link">
{{ curFile.fileName }}
<i class="iconfont icon-xiangxia"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in fileList" :command="item" :key="item.id">{{ item.fileName
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="input-box flex">
<el-input v-model="textarea" @keyup.enter="send" :disabled="loaded"/>
<div class="ipt-icon" @click="send">
@ -40,13 +54,16 @@
</template>
<script setup>
import { ref, reactive, onMounted, watch } from 'vue'
import { completion } from '@/api/mode/index'
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { completion, docList } from '@/api/mode/index'
import { sessionStore } from '@/utils/store'
import { ElMessage } from 'element-plus'
import { dataSetJson } from '@/utils/comm.js'
import useUserStore from '@/store/modules/user'
import { sendChart } from '@/api/ai/index'
import emitter from '@/utils/mitt';
const userInfo = useUserStore().user
const textarea = ref('')
const isDialog = defineModel()
@ -55,12 +72,20 @@ const props = defineProps({
item: {
type: Object,
default: () => {
return { name: '11' }
return { name: '' }
}
},
type: {
type: Number,
default: 1
},
curMode:{
type: Number,
default: 1
},
conversation_id: {
type: [Number, String],
default: ''
}
})
@ -84,7 +109,8 @@ const curNode = reactive({})
const params = reactive(
{
prompt: '',
dataset_id: ''
dataset_id: '',
template: ''
}
)
@ -92,7 +118,24 @@ const params = reactive(
const getCompletion = async (val) => {
try {
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params)
params.template = props.item.prompt
let data = null;
//
if(props.curMode == 1){
const res = await sendChart({
content: params.prompt,
conversationId: props.conversation_id,
stream: false
})
data = res.data
}
else{
//
const res = await completion(params)
data = res.data
}
let answer = data.answer
msgList.value.push({
type: 'robot',
@ -106,31 +149,48 @@ const getCompletion = async (val) => {
const saveAdjust = (item) =>{
isDialog.value = false
ElMessage.success('操作成功')
emitter.emit('onSaveAdjust', item.msg)
}
const modeType = ref('课标')
watch(() => props.type, (newVal) => {
if (newVal == 1){
modeType.value = '课标'
}
if (newVal == 2){
modeType.value = '教材'
}
if (newVal == 2){
modeType.value = '考试'
}
}, { immediate: false })
const curFile = reactive({})
const dataset_id = ref('')
const fileList = ref([])
const getList = () =>{
docList({
userId: userInfo.userId,
dataset_id: dataset_id.value
}).then( res =>{
fileList.value = res.rows
Object.assign(curFile, fileList.value[0])
params.document_ids = fileList.value[0].docId
})
}
emitter.on('changeCurFile', (item) =>{
changeFile(item)
})
const changeFile = (val) =>{
Object.assign(curFile, val);
params.document_ids = val.docId
}
const modeType = ref('')
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
modeType.value = props.type == 1 ? '课标' : props.type == 2 ? '教材' : '考试'
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey]
if(props.type == 3){
getList()
}
})
//
onUnmounted(() => {
emitter.off('changeCurFile');
})
@ -267,4 +327,9 @@ onMounted(() => {
transform: scale(0.01);
}
}
.file-list{
display: flex;
margin-bottom: 10px;
}
</style>

View File

@ -2,7 +2,7 @@
<el-dialog v-model="mode" :show-close="false" width="600" append-to-body destroy-on-close>
<template #header>
<div class="custom-header flex">
<span>{{ item.ex3 == '1' ? '请输入新的模板名称' : isAdd ? '添加提示词' : '编辑提示词' }}</span>
<span>{{ item.ex3 == '1' ? '请输入新的模板名称' : item.isAdd ? '添加提示词' : '编辑提示词' }}</span>
<i class="iconfont icon-guanbi" @click="mode = false"></i>
</div>
</template>
@ -40,11 +40,7 @@ const props = defineProps({
type: Number,
default: 1
},
isAdd: {
type: Boolean,
default: true
},
item: { //
item: { //
type: Object,
default: () => {
return { ex3: '' }
@ -58,37 +54,45 @@ const form = reactive({
prompt: '',
})
watch(() => props.isAdd, (newVal) => {
if (!newVal) {
form.name = props.item?.name
form.prompt = props.item?.prompt
watch(() => mode.value, (newVal) => {
if(newVal){
if (props.item.isAdd) {
form.name = ''
form.prompt = ''
}
else{
form.name = props.item?.name
form.prompt = props.item?.prompt
}
}
}, { immediate: false })
},{ deep: true})
const loading = ref(false)
const saveAdd = async () => {
loading.value = true
if (props.item.ex3 == '1') {
if (props.isAdd) {
try {
// copy
const { msg } = await addKeyWords({ name: form.name, id: props.item.id })
emitter.emit('onGetMain')
ElMessage.success(msg)
mode.value = false
} finally {
loading.value = false
}
let id; // id id
if (props.item.isAdd) {
id = props.item.id
}
else{
onAddChildTemp(props.item.parentId)
// item item.parentId
id = props.item.parentId
}
try {
// copy
const { msg } = await addKeyWords({ name: form.name, id })
emitter.emit('onGetMain')
ElMessage.success(msg)
mode.value = false
} finally {
loading.value = false
}
} else {
if (props.isAdd) {
if (props.item.isAdd) {
onAddChildTemp(props.item.id)
}
else {

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="isDialog" :show-close="false" width="900" destroy-on-close>
<el-dialog v-model="isDialog" :show-close="false" width="900" append-to-body destroy-on-close>
<template #header>
<div class="custom-header flex">
<span>选择{{ title }}</span>
@ -7,25 +7,54 @@
</div>
</template>
<div class="dialog-content">
<div class="flex">
<el-radio-group v-model="radio" @change="changeRadio">
<el-radio :value="item.value" v-for="item in radioList">{{ item.label }}</el-radio>
</el-radio-group>
</div>
<div class="content-list">
<ul>
<li v-for="(item, index) in list" :class="activeIndex == index ? 'li-active' : ''" @click="clickItem(index)">
<el-image class="img" :src="item.url" />
<span>{{ item.name }}</span>
<li v-for="(item, index) in fileList" :class="activeIndex == index ? 'li-active' : ''"
@click="clickItem(index, item)">
<el-image class="img" :src="url" />
<el-button type="primary" class="prev-btn" @click.stop="onPrevItem(item)">预览</el-button>
<el-text truncated>{{ item.fileName }}</el-text>
</li>
</ul>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="isDialog = false">取消</el-button>
<el-button type="primary" @click="isDialog = false">
确定
<el-upload class="upload-demo" :action="uploadFileUrl" :limit="1" :show-file-list="false" :headers="headers"
:on-success="onSuccess">
<el-button type="primary">上传</el-button>
</el-upload>
<div>
<el-button @click="isDialog = false">取消</el-button>
<el-button type="primary" @click="isDialog = false">
确定
</el-button>
</div>
</div>
</template>
</el-dialog>
<el-dialog v-model="prevVisible" fullscreen :show-close="false" class="prev-dialog">
<template #header>
<div class="custom-header flex">
<span>预览</span>
<i class="iconfont icon-guanbi" @click="prevVisible = false"></i>
</div>
</template>
<div style="height: calc(100vh - 120px);">
<template v-if="getFileSuffix(prevItem.fileUrl) == 'pdf'">
<iframe :src="prevItem.fileUrl"
frameborder="0" width="100%" height="100%"></iframe>
</template>
<template v-else>
<el-image :src="prevItem.fileUrl" style="height:100%"/>
</template>
</div>
<template #footer>
<div class="dialog-footer">
<div></div>
<el-button type="primary" @click="prevVisible = false">
关闭
</el-button>
</div>
</template>
@ -33,11 +62,24 @@
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref, computed, onMounted, reactive } from 'vue'
import { completion, addDoc, docList } from '@/api/mode/index.js'
import { getToken } from "@/utils/auth";
import { sessionStore } from '@/utils/store'
import { dataSetJson } from '@/utils/comm.js'
import { ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'
import { getFileSuffix } from '@/utils/ruoyi.js'
import emitter from '@/utils/mitt';
const userInfo = useUserStore().user
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload");
const headers = ref({ Authorization: "Bearer " + getToken() });
const url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F11044b08-04c1-41a0-a453-1fd20b58a614%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1732953359&t=7ab1d1b3a903db85b1149914407aea35'
const isDialog = defineModel()
const prevVisible = ref(false)
const props = defineProps({
modeType: {
@ -52,14 +94,13 @@ const title = computed(() => {
if (props.modeType == 3) return '考试';
})
const radio = ref(1)
const radioList = ref([
{ label: '浏览研读', value: 1 },
// { label: '', value: 2 },
// { label: '', value: 3 },
// { label: '', value: 4 },
// { label: '', value: 5 },
{ label: '跨学科研读', value: 2 },
{ label: '跨学段研读', value: 3 },
{ label: '课标修订研读', value: 4 },
{ label: '自由研读', value: 5 },
])
const list = ref([
{
@ -76,15 +117,70 @@ const changeRadio = () => {
})
}
}
const activeIndex = ref(0)
const activeIndex = ref(-1)
const dataset_id = ref('')
const clickItem = (index) => {
activeIndex.value = index
//
const onSuccess = async (response) => {
let data = {
url: response.url,
dataset_id: dataset_id.value
}
const res = await completion(data)
if (res.data.code != 200) return
let docData = {
fileUrl: response.url,
fileId: response.file.id,
fileName: response.file.fileName,
filesize: response.file.fileSize,
datasetId: dataset_id.value,
docId: res.data.document_id,
edustage: curNode.edustage,
edusubject: curNode.edusubject
}
const { msg } = await addDoc(docData)
ElMessage.success(msg)
getList()
}
const curNode = reactive({})
const fileList = ref([])
const curFile = reactive({})
const getList = () => {
docList({
userId: userInfo.userId,
dataset_id: dataset_id.value
}).then(res => {
fileList.value = [...res.rows]
Object.assign(curFile, fileList.value[0])
})
}
</script>
const clickItem = (index, item) => {
activeIndex.value = index
Object.assign(curFile, item)
emitter.emit('changeCurFile', item)
}
const prevItem = reactive({})
const onPrevItem = (item) => {
Object.assign(prevItem, item)
prevVisible.value = true
}
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
// "-"
let jsonKey = `考试-${curNode.edustage}-${curNode.edusubject}`
dataset_id.value = dataSetJson[jsonKey]
getList()
})
</script>
<style lang="scss" scoped>
.custom-header {
justify-content: space-between;
@ -98,13 +194,16 @@ const clickItem = (index) => {
.dialog-content {
padding-top: 10px;
.content-list {
padding-top: 10px;
ul {
display: flex;
flex-wrap: wrap;
li {
width: 130px;
display: flex;
flex-direction: column;
font-size: 13px;
@ -114,9 +213,11 @@ const clickItem = (index) => {
overflow: hidden;
margin-right: 20px;
margin-bottom: 10px;
position: relative;
overflow: hidden;
.img {
width: 100px;
width: 100%;
height: 130px;
border: solid #ccc 1px;
margin-bottom: 10px;
@ -125,6 +226,10 @@ const clickItem = (index) => {
&:hover {
background: #E0EAFF;
}
&:hover .prev-btn {
transform: translate(-50%, -40px)
}
}
.li-active {
@ -134,4 +239,20 @@ const clickItem = (index) => {
}
}
}
.dialog-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.prev-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) translateY(-110px);
/* 按钮初始位置在容器外 */
transition: transform 0.3s ease-in-out;
/* 设置过渡效果 */
}
</style>

View File

@ -2,12 +2,13 @@
<div class="container-left-page flex">
<div class="container-left-header flex">
<el-button link @click="onClick">
{{ curNode.edustage }}{{ curNode.edusubject }}{{ type == 1 ? '课标研读' : '教材分析' }}<i
{{ curNode.edustage }}{{ curNode.edusubject }}{{ type == 1 ? '课标研读' : type == 2 ? '教材分析' : '考试分析' }}<i
class="iconfont icon-xiangxia"></i>
</el-button>
</div>
<div class="container-left-pdf">
<PDF :url="pdfUrl" :showCatalog="false" v-if="pdfUrl" />
<el-empty v-else description="暂无数据" />
</div>
<!--弹窗-->
<LeftDialog v-model="showDialog" :modeType="type" />
@ -15,29 +16,36 @@
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import { ref, onMounted, nextTick, reactive } from 'vue'
import { sessionStore } from '@/utils/store'
import PDF from '@/components/PdfJs/index.vue'
import LeftDialog from './left-dialog.vue'
const props = defineProps(['curNode', 'type'])
const props = defineProps(['type'])
const showDialog = ref(false)
const onClick = () => {
if (props.type == 1) return
if (props.type != 3) return
showDialog.value = true
}
// PDF
const pdfUrl = ref('')
const curNode = reactive({})
onMounted(async () => {
await nextTick()
//
let nodeData = sessionStore.get('subject.curNode')
Object.assign(curNode, nodeData);
let data = sessionStore.get('subject.curBook')
let fileurl = data.fileurl
if(props.type == 1){
fileurl = `${data.edustage}-${data.edusubject}-课标.txt`
}
if(fileurl == '') return
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf')
})
</script>

View File

@ -14,11 +14,14 @@
</el-dropdown-menu>
</template>
</el-dropdown>
<div>
<el-button type="danger" link @click="removeItem(curTemplate, false)">
<div class="flex">
<el-select v-model="curMode" placeholder="Select" class="mr-4 w-30">
<el-option v-for="item in modeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button type="danger" link :disabled="!(templateList.length)" @click="removeItem(curTemplate, false)">
删除
</el-button>
<el-button type="primary" link @click="onAdd">
<el-button type="primary" link :disabled="!(templateList.length)" @click="onAdd">
<i class="iconfont icon-jiahao"></i>
添加提示词
</el-button>
@ -26,75 +29,92 @@
</div>
</div>
<!--List-->
<div class="container-right-list">
<template v-for="(item, index) in childTempList">
<div class="template-item" v-loading="item.loading">
<div class="item-header">
<div>
<span class="blue">#</span>{{ item.name }}
</div>
<el-popover placement="bottom-end" trigger="hover" popper-class="template-custom-popover">
<template #reference>
<el-button link type="primary">
<i class="iconfont icon-shenglvehao"></i></el-button>
</template>
<template #default>
<el-button type="primary" link @click="editKeyWord(item)">编辑</el-button>
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
</template>
</el-popover>
</div>
<div class="item-text">
{{ item.prompt }}
</div>
<div class="item-text text-answer" v-if="item.answer">
<div class="item-icon">
<i class="iconfont icon-ai"></i>
</div>
<div class="item-answer">
<TypingEffect :text="item.oldAnswer" :delay="10" :aiShow="item.aiShow" @complete="onSaveTemp(item)" />
</div>
</div>
<div class="ai-btn" v-if="item.answer">
<el-button type="primary" link @click="againResult(index, item)">
<i class="iconfont icon-ai1"></i>
重新研读
</el-button>
<el-button type="primary" link @click="onAdjust(index, item)">
<i class="iconfont icon-duihua"></i>
AI对话调整
</el-button>
<el-button type="primary" link @click="onEdit(index, item)">
<i class="iconfont icon-bianji1"></i>
手动编辑结果
</el-button>
</div>
</div>
</template>
<el-empty v-if="!childTempList.length" description="暂无模板数据" />
<div class="container-right-list" ref="listRef">
<template v-for="(item, index) in childTempList">
<div class="template-item" v-loading="item.loading">
<div class="item-header">
<div>
<span class="blue">#</span>{{ item.name }}
</div>
<el-popover placement="bottom-end" trigger="hover" popper-class="template-custom-popover">
<template #reference>
<el-button link type="primary">
<i class="iconfont icon-shenglvehao"></i></el-button>
</template>
<template #default>
<el-button type="primary" link @click="editKeyWord(item, false)">编辑</el-button>
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
</template>
</el-popover>
</div>
<div class="item-text">
{{ item.prompt }}
</div>
<div class="item-text text-answer" v-if="item.answer">
<div class="item-icon">
<i class="iconfont icon-ai"></i>
</div>
<div class="item-answer">
<TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow"
@complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" />
</div>
</div>
<div class="ai-btn" v-if="item.answer">
<el-button type="primary" link @click="againResult(index, item)">
<i class="iconfont icon-ai1"></i>
重新研读
</el-button>
<el-button type="primary" link @click="onAdjust(index, item)">
<i class="iconfont icon-duihua"></i>
AI对话调整
</el-button>
<el-button type="primary" link @click="onEdit(index, item)">
<i class="iconfont icon-bianji1"></i>
手动编辑结果
</el-button>
</div>
</div>
</template>
<el-empty v-if="!childTempList.length" description="暂无模板数据" />
</div>
</div>
<!--编辑结果-->
<EditDialog v-model="isEdit" :item="editItem" />
<!--AI 对话调整-->
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" />
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" :curMode="curMode" :conversation_id="conversation_id"/>
<!--添加编辑提示词-->
<keywordDialog v-model="isWordDialog" :isAdd="isAdd" :item="editItem" />
<keywordDialog v-model="isWordDialog" :item="editItem" :modeType="type" />
</template>
<script setup>
import { ref, reactive, onMounted, watch, onUnmounted } from 'vue'
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
import { createChart, sendChart } from '@/api/ai/index'
import { sessionStore } from '@/utils/store'
import keywordDialog from './keyword-dialog.vue';
import AdjustDialog from './adjust-dialog.vue'
import EditDialog from './edit-dialog.vue'
import TypingEffect from '@/components/typing-effect/index.vue'
import useUserStore from '@/store/modules/user'
import emitter from '@/utils/mitt';
import { dataSetJson } from '@/utils/comm.js'
import { cloneDeep } from 'lodash'
const props = defineProps(['curNode', 'type'])
const props = defineProps(['type'])
const { user } = useUserStore()
const curMode = ref(2)
const modeOptions = ref([
{
label: '教学大模型',
value: 1
},
{
label: '知识库模型',
value: 2
}
])
/*****************提示词相关****************/
@ -105,21 +125,22 @@ const props = defineProps(['curNode', 'type'])
*/
const isWordDialog = ref(false)
const isAdd = ref(false)
const editItem = reactive({})
const onAdd = () => {
isAdd.value = true
Object.assign(editItem, curTemplate)
editItem.isAdd = true
isWordDialog.value = true
}
const editKeyWord = (item, val) => {
/**
* isAdd: 字模板中的移除 为编辑 头部删除 添加提示词为新增
* isAdd: 子模板中的移除 为编辑false 头部删除 添加提示词为新增 true
*/
isAdd.value = val
Object.assign(editItem, item)
editItem.isAdd = val
isWordDialog.value = true
}
/*******************模板相关**********************/
@ -136,39 +157,69 @@ const curTemplate = reactive({ name: '', id: '' })
const templateList = ref([])
const childTempList = ref([])
const getTemplateList = () => {
modelList({ model: props.type, type: 1, pageNum: 1, pageSize: 10000 }).then(res => {
modelList({ createUser: user.userId, model: props.type, type: 1, pageNum: 1, pageSize: 10000, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
templateList.value = res.rows
Object.assign(curTemplate, res.rows[0]);
getChildTemplate()
if (res.rows.length > 0) {
Object.assign(curTemplate, res.rows[0]);
getChildTemplate()
}
})
}
const getChildTemplate = () => {
tempLoading.value = true
modelList({ model: props.type, type: 2, parentId: curTemplate.id }).then(res => {
modelList({ model: props.type, type: 2, parentId: curTemplate.id, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
childTempList.value = res.rows
if (childTempList.value.length) {
childTempList.value.forEach(item => item.answer = '')
}
getTempResult()
}).finally(() => {
tempLoading.value = false
})
}
const isStarted = ref([]);
const listRef = ref()
//
const getTempResult = () => {
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000 }).then(res => {
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(res => {
let rows = res.rows
childTempList.value.forEach(item => {
rows.forEach(el => {
if (item.id == el.modelId) {
item.answer = el.content
item.answer = getResult(el.content)
item.resultId = el.id
}
})
})
if (rows.length > 0) {
isStarted.value = new Array(rows.length).fill(true)
}
})
}
const scrollToBottom = (height, index) => {
if (listRef.value) {
let sum = 0
let listDom = listRef.value.children
if (index == 0) {
// 220
let screenHeight = window.innerHeight - 220
if (height > screenHeight) {
listRef.value.scrollTop = (height - screenHeight + 50)
}
}
else {
for (let i = 0; i < index; i++) {
sum += listDom[i].clientHeight
}
listRef.value.scrollTop = sum + height
}
}
}
//
const changeTemplate = (val) => {
ElMessageBox.confirm(
@ -189,7 +240,7 @@ const changeTemplate = (val) => {
const removeItem = async (item, isChild) => {
/**
* item: 当前操作的模板
* isChild: 子模板中的移除为 true
* isChild: 子模板中的移除为 true
*/
if (item.ex3 != '1') {
ElMessageBox.confirm(
@ -203,11 +254,11 @@ const removeItem = async (item, isChild) => {
).then(() => {
removeChildTemp(item.id).then(res => {
ElMessage.success('操作成功')
if(isChild){
if (isChild) {
//
getChildTemplate()
}
else{
else {
//
getTemplateList()
}
@ -215,11 +266,10 @@ const removeItem = async (item, isChild) => {
})
}
else {
editKeyWord(item,!isChild)
editKeyWord(item, !isChild)
}
}
// Ai
const curIndex = ref(-1)
const isAdjust = ref(false)
@ -237,22 +287,6 @@ const onEdit = (index, item) => {
isEdit.value = true
}
const modeType = ref('课标')
watch(() => props.type, (newVal) => {
if (newVal == 1){
modeType.value = '课标'
}
if (newVal == 2){
modeType.value = '教材'
}
if (newVal == 2){
modeType.value = '考试'
}
}, { immediate: false })
//
const params = reactive(
{
@ -260,76 +294,147 @@ const params = reactive(
dataset_id: ''
}
)
const prompt = ref('')
//
const isAgain = ref(false)
const againResult = async (index, item) => {
isAgain.value = true
isStarted.value[index] = false
childTempList.value[index].answer = ''
if (index == 0) {
listRef.value.scrollTop = 0
} else {
scrollToBottom(50, index)
}
try {
await nextTick()
childTempList.value[index].loading = true
item.aiShow = true
childTempList.value[index].oldAnswer = ''
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params)
let answer = data.answer
childTempList.value[index].oldAnswer = answer
childTempList.value[index].answer = getResult(answer);
// onEditSave(item)
let str = cloneDeep(prompt.value)
str = str.replace('{模板标题}',item.name)
str = str.replace('{模板内容}',item.prompt)
params.prompt = str
params.template = item.prompt
let data = null;
//
if (curMode.value == 1) {
const res = await sendChart({
content: params.prompt,
conversationId: conversation_id.value,
stream: false
})
data = res.data
} else {
//
const res = await completion(params)
data = res.data
}
childTempList.value[index].answer = getResult(data.answer);
isStarted.value[index] = true
} finally {
childTempList.value[index].loading = false
}
}
//
const getCompletion = async () => {
isStarted.value = new Array(childTempList.length).fill(false)
isStarted.value[0] = true
childTempList.value.forEach(item => {
if (item.answer) {
item.answer = ''
}
})
for (let item of childTempList.value) {
try {
item.loading = true
item.aiShow = true
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params)
let answer = data.answer
item.oldAnswer = answer
item.answer = getResult(answer);
let str = cloneDeep(prompt.value)
str = str.replace('{模板标题}',item.name)
str = str.replace('{模板内容}',item.prompt)
params.prompt = str
params.template = item.prompt
//
let data = null
if (curMode.value == 1) {
const res = await sendChart({
content: params.prompt,
conversationId: conversation_id.value,
stream: false
})
data = res.data
}
//
else {
const res = await completion(params)
data = res.data
}
item.answer = getResult(data.answer)
onSaveTemp(item)
} finally {
item.loading = false
}
}
}
const handleCompleteText = async (answer, index) => {
if (index < childTempList.value.length - 1) {
isStarted.value[index + 1] = true; //
}
if (isAgain.value) {
try {
await editTempResult({ id: childTempList.value[index].resultId, content: answer })
} finally {
isAgain.value = false
}
}
}
//
emitter.on('onSaveAdjust', (item) => {
childTempList.value[curIndex.value].oldAnswer = item
let answer = getResult(item);
childTempList.value[curIndex.value].oldAnswer = item
childTempList.value[curIndex.value].answer = answer
childTempList.value[curIndex.value].answer = item
onEditSave(childTempList.value[curIndex.value])
})
//
const onEditSave = async (item) =>{
const { res } = await editTempResult({id: item.resultId, content: item.oldAnswer})
ElMessage.success(res)
const onEditSave = async (item) => {
const { msg } = await editTempResult({ id: item.resultId, content: item.answer })
ElMessage.success(msg)
getChildTemplate()
}
//
const onSaveTemp = (item) => {
if(item.oldAnswer == '') return
const onSaveTemp = async (item) => {
if (item.answer == '') return
const data = {
mainModelId: curTemplate.id,
modelId: item.id,
examDocld: '',
content: item.oldAnswer
content: item.answer,
ex1: curNode.id
}
const res = await tempSave(data)
if(!item.resultId){
item.resultId = res.data
}
tempSave(data).then(res => {})
}
//
let getResult = (text) => {
text = text.replace(/^\n\n(.*?)\n\n$/s, '<div>$1</div>');
text = text.replace(/^\n(.*?)\n$/s, '<p>$1</p>');
text = text.replace(/\*\*(.*?)\*\*/g, "<div class='text-tit'>$1</div>");
text = text.replace(/(\d+\..*?)\n/g, "<div class='text-num'>$1</div>\n");
return text
// ### **
let getResult = (str) => {
let newStr = str.replace(/#+|(\*\*)/g, '');
return newStr
}
//
@ -342,13 +447,49 @@ emitter.on('onGetMain', () => {
})
//
const conversation_id = ref('')
const getChartId = () => {
createChart({ app_id: '712ff0df-ed6b-470f-bf87-8cfbaf757be5' }).then(res => {
localStorage.setItem("conversation_id", res.data.conversation_id);
conversation_id.value = res.data.conversation_id;
})
}
// prompt
const getPrompt = async () => {
const { rows } = await modelList({ model: 5 })
let str = rows.find(item => item.name.indexOf(modeType.value) != -1).prompt
str = str.replace('{学段}', curNode.edustage)
str = str.replace('{学科}', curNode.edusubject)
let bookV = curNode.roottitle.split('-')[1] + '版本'
str = str.replace('{教材版本}', bookV)
str = str.replace('{课程名称}', `${curNode.itemtitle}`)
if(modeType.value == '课标'){
str = str.replace('{课标名称}', `${curNode.edustage}${curNode.edusubject}课标`)
}
prompt.value = str
}
const curNode = reactive({})
const modeType = ref('')
onMounted(() => {
getTemplateList()
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
modeType.value = props.type == 1 ? '课标' : props.type == 2 ? '教材' : '考试'
getTemplateList()
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey]
// ID
conversation_id.value = localStorage.getItem('conversation_id')
if (!conversation_id.value) {
getChartId();
}
// prompt
getPrompt()
})
//
@ -381,87 +522,85 @@ onUnmounted(() => {
padding: 5px 15px;
box-sizing: border-box;
.template-item {
background: #fff;
padding: 10px;
margin-top: 10px;
border-radius: 5px;
.template-item {
background: #fff;
padding: 10px;
margin-top: 10px;
border-radius: 5px;
.item-header {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #000;
justify-content: space-between;
.item-header {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #000;
justify-content: space-between;
.blue {
font-size: 22px;
color: #409eff;
margin-right: 5px;
}
}
.item-text {
display: flex;
margin-top: 10px;
font-size: 14px;
text-align: left;
color: #606266;
.item-icon {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
background: #F6F6F6;
border-radius: 50%;
margin-right: 10px;
flex-shrink: 0;
}
.item-answer {
flex-direction: column;
padding-top: 5px;
width: 100%;
:deep(.text-tit) {
font-weight: bold;
margin: 10px 0;
}
:deep(.text-num) {
padding-left: 2em;
}
}
}
.text-answer {
.blue {
font-size: 22px;
color: #409eff;
}
.ai-btn {
margin-top: 10px;
display: flex;
justify-content: flex-end;
.iconfont {
margin-right: 3px;
}
:deep(.el-button) {
font-size: 13px;
}
.icon-ai1 {
font-size: 18px;
}
margin-right: 5px;
}
}
.item-text {
display: flex;
margin-top: 10px;
font-size: 14px;
text-align: left;
color: #606266;
.item-icon {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
background: #F6F6F6;
border-radius: 50%;
margin-right: 10px;
flex-shrink: 0;
}
.item-answer {
flex-direction: column;
padding-top: 5px;
width: 100%;
:deep(.text-tit) {
font-weight: bold;
margin: 10px 0;
}
:deep(.text-num) {
padding-left: 2em;
}
}
}
.text-answer {
color: #409eff;
}
.ai-btn {
margin-top: 10px;
display: flex;
justify-content: flex-end;
.iconfont {
margin-right: 3px;
}
:deep(.el-button) {
font-size: 13px;
}
.icon-ai1 {
font-size: 18px;
}
}
}
}
}
</style>
@ -470,4 +609,4 @@ onUnmounted(() => {
width: 110px !important;
min-width: 110px !important;
}
</style>
</style>

View File

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

View File

@ -1,5 +1,5 @@
<template>
<div class="typing-effect">
<div class="typing-effect" ref="typingEffectRef">
<!-- <span v-html="displayedText"></span> -->
<el-input
v-model="displayedText"
@ -8,17 +8,17 @@
readonly
resize="none"
style="width: 100%;"
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:15px"
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:14px"
/>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { ref, onMounted, watch, nextTick } from 'vue';
const props = defineProps({
text: {
type: String,
type: [String, Object],
required: true
},
delay: {
@ -26,41 +26,51 @@ const props = defineProps({
default: 100 //
},
aiShow: {
type: [Boolean]
type: [Boolean] // true
}
});
const emit = defineEmits(['complete']);
const typingEffectRef = ref(null);
const emit = defineEmits(['complete', 'updateScroll']);
const displayedText = ref('');
const index = ref(0);
const type = () => {
if(!props.aiShow) return
const type = async () => {
await nextTick()
if(!props.aiShow) {
displayedText.value = props.text
return
}
if (index.value <= props.text.length) {
displayedText.value += props.text.charAt(index.value);
index.value++;
setTimeout(() => type(), props.delay);
setTimeout(() => {
type();
emit('updateScroll', typingEffectRef.value.clientHeight); //
}, props.delay);
} else {
// complete
emit('complete');
emit('complete',displayedText.value);
}
};
onMounted(() => {
type();
resetAndType();
});
// props 便 text delay
watch([() => props.text, () => props.delay], () => {
const resetAndType = () =>{
displayedText.value = '';
index.value = 0;
type();
});
}
// props 便 text delay
watch([() => props.text, () => props.delay], resetAndType);
</script>
<style scoped>
.typing-effect {
font-family: monospace;
}
:deep(.el-textarea__inner:hover){
box-shadow: none;
}

View File

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

View File

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

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

View File

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

View File

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

View File

@ -36,13 +36,13 @@ export const processList = (row, aloneOption=false) => {
row[i].method = jjj.method
row[i].discuss = jjj.discuss
//row[i].discusscollapse = false;
if (row[i].examdate !== null && row[i].examdate !== undefined) {
if (row[i].examdate && row[i].examdate != "") {
row[i].examdate = row[i].examdate.substring(0, 10)
}
// 具体题型数据结构处理
if (row[i].worktype == '复合题') {
// 旧类型
// 复合题 - 旧格式
if (row[i].title.indexOf('!@#$%') !== -1) {
// 1.选项解析替换
const options = JSON.parse(row[i].workdesc)
@ -129,7 +129,9 @@ export const processList = (row, aloneOption=false) => {
row[i].workanswerFormat = answer
} else {
// 处理[题干显示] - 不再需要处理
// 复合题 - 现格式
// 处理[题干显示] - 不再需要处理(头部已处理)
// row[i].titleFormat = row[i].title; // 仅占位提示
/**
@ -222,6 +224,7 @@ export const processList = (row, aloneOption=false) => {
row[i].workanswerFormat = workAnswerHtml
}
} else if (
/** 主观题/非基础题(其中主要为第三方的各类解答题) */
row[i].worktype == '主观题' ||
(row[i].worktype !== '单选题' &&
row[i].worktype !== '多选题' &&
@ -236,7 +239,7 @@ export const processList = (row, aloneOption=false) => {
row[i].workanswerFormat = JSON.parse(row[i].workanswer)
}
} else {
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
// 基础题: 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
// 通用选项结构 ['ABC123','ABC123'] | ['ABC123','ABC123'] | [](填空题无选项) | [](判断题无选项)
let workDescArr = []
if (

View File

@ -5,13 +5,13 @@
<el-popover ref="popoverRef" placement="right" trigger="hover" popper-class="popoverStyle" :tabindex="999" >
<template #reference>
<div class="user-info">
<el-image class="user-img" :src="dev_api + userStore.user.avatar" />
<el-image class="user-img" :src="userStore.user.avatar ==='/img/avatar-default.jpg' || userStore.user.avatar ==='/images/img-avatar.png' ? defaultUserImg : dev_api + userStore.user.avatar" />
<span>{{ userStore.user.nickName }}</span>
</div>
</template>
<div class="head-aside">
<ul >
<li :class="computedregistertype==1 || computedregistertype==3?'auth-li':'auth-li pointer-events'" @click="onUserTo('/schoolCertification')" >
<li v-if="isStadium() !== true" :class="computedregistertype==1 || computedregistertype==3?'auth-li':'auth-li pointer-events'" @click="onUserTo('/schoolCertification')" >
<i class="iconfont icon-renzheng-" :style="computedregistertype==4?'color:green;':''"></i>
<span class="mlr-5" v-if="computedregistertype!=4">学校认证</span>
<span class="mlr-5" v-else>{{ userStore.DeptInfo.register.schoolName }}</span>
@ -19,8 +19,8 @@
</li>
<li v-if="computedregistertype!=4" :class="computedregistertype==1 || computedregistertype==2 ? '':'pointer-events'" @click="onUserTo('/joinSchool')">加入学校</li>
<li @click="onUserTo('/profile')">个人中心</li>
<li @click="onUserTo('/schoolManagement')">学校管理</li>
<li @click="onUserTo('/class')">班级中心</li>
<li v-if="isStadium() !== true" @click="onUserTo('/schoolManagement')">学校管理</li>
<li v-if="isStadium() !== true" @click="onUserTo('/class')">班级中心</li>
<li @click="logout">退出登录</li>
</ul>
</div>
@ -48,17 +48,18 @@
</div>
<div class="verson">V{{ version }}</div>
</div>
</div>
</template>
<script setup>
import { ref, watch , reactive, onMounted,computed} from 'vue'
import { ref, watch , reactive, onMounted, onBeforeMount, computed} from 'vue'
import { useRouter } from 'vue-router'
import { ElMessageBox, ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'
import { sessionStore } from '@/utils/store'
import pkc from "../../../../../package.json"
import defaultUserImg from '@/assets/images/img-avatar.png'
import { sessionStore } from '@/utils/store'
const { ipcRenderer } = window.electron || {}
@ -74,18 +75,42 @@ const version = ref(pkc.version)
const popoverRef = ref('')
const headerMenus = [
//
const isStadium = () => {
let user = userStore.user
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')
}
const headerMenus = isStadium() ?[{
name: '教学实践',
id: 6,
icon: 'icon-jiaoxueshijian',
path: '/prepare'
},]:[
{
name: '教学大模型',
id: 1,
icon: 'icon-shouye',
path: '/model/index'
},
// {
// name: '',
// id: 2,
// icon: 'icon-gongzuotai',
// path: '/desktop'
// },
{
name: '教学工作台',
id: 2,
icon: 'icon-gongzuotai',
path: '/desktop'
name: '教学实践',
id: 4,
icon: 'icon-jiaoxueshijian',
path: '/prepare'
},
{
name: '教学活动',
id: 5,
icon: 'icon-zuoyepigai',
path: '/classTask'
},
{
name: '资源中心',
@ -126,12 +151,12 @@ const computedregistertype = computed(() => {
if(type==3 && userStore.DeptInfo.register.auditStatus==0){
return 2
}
//
if(type==4 && userStore.DeptInfo.register.auditStatus==0){
return 3
}
})
const clickMenu = ({ id, disabled, path }) => {
if (disabled) return
@ -157,9 +182,9 @@ watch(
const logout = () => {
const hasClass = sessionStore.has('activeClass.id')
const hasTool = sessionStore.get('isToolWin')
if (hasClass || hasTool) return ElMessage.warning('当前正在上课,请先结束上课')
if(!!sessionStore.get('curr.classcourse'))return ElMessage.warning('当前正在上课,请先结束上课')
ElMessageBox.confirm('确认退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@ -193,7 +218,7 @@ onMounted(() => {
}
.popoverStyle .head-aside{
width: 100%;
display: flex;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
@ -246,7 +271,7 @@ onMounted(() => {
align-items: center;
font-size: 12px;
.user-img{
width: 56px;
width: 56px;
height: 56px;
border-radius: 50%;
cursor: pointer;
@ -312,4 +337,4 @@ onMounted(() => {
font-size: 18px;
font-weight: 800;
}
</style>
</style>

View File

@ -44,9 +44,8 @@ const breadList = ref([])
watch(
() => router.currentRoute.value,
(newValue) => {
let path = newValue.path
if (path.includes('/model') && path !== ('/model/index')) {
const { showBread } = newValue.meta
if (showBread) {
isShowBack.value = true
breadList.value = newValue.matched.map(item => item.meta)
let data = sessionStore.get('subject.curNode')

View File

@ -8,8 +8,8 @@
<el-header>
<Header/>
</el-header>
<el-main :style="{ 'padding-top' : isShowBread ? 0 : '20px'}">
<template v-if="isShowBread">
<el-main style="padding-top : 20px">
<!-- <template v-if="isShowBread">
<div class="bread-row">
<div class="back" @click="onBack">
<i class="iconfont icon-fanhui"></i>
@ -19,8 +19,8 @@
<el-breadcrumb-item v-for="item in breadList"> {{ item.title }} </el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<AppMain :style="{ height: isShowBread ? 'calc(100% - 45px)' : '100%' }" />
</template> -->
<AppMain />
</el-main>
</el-container>
<Uploader v-if="uploaderStore.uploadList && uploaderStore.uploadList.length > 0" />

View File

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

View File

@ -56,6 +56,8 @@ export class MsgEnum {
*/
static HEADS = {
// === 旧定义-消息头(兼容以前) ===
/** @desc: 开课 */
MSG_open : 'open',
/** @desc: 结束课程(下课) */
MSG_closed : 'closed',
/** @desc: 在线状态 */
@ -78,6 +80,8 @@ export class MsgEnum {
MSG_anmationclick : 'anmationclick',
/** @desc: 群组创建成功 */
MSG_classcourseopen : 'classcourseopen',
/** @desc: 学生提交作业 */
MSG_finishHomework : 'finishHomework',
/** @desc: 学生的测练结果反馈 */
MSG_classquizfeedback : 'classquizfeedback',
/** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */
@ -92,7 +96,19 @@ export class MsgEnum {
MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate',
/** @desc: 课堂讲授活动,选择不同的内容 */
MSG_classlecturePagesrc : 'classlecturePagesrc',
/** @desc: 课堂作业|活动 */
MSG_homework : 'HOMEWORK',
/** @desc: 公屏 - 课堂作业|活动 */
MSG_pushSreen_work : 'pushSreen_work',
/** @desc: 公屏 - 实验 */
MSG_pushSreen_experiment : 'pushSreen_experiment',
/** @desc: 点赞 */
MSG_dz : 'dz',
/** @desc: 疑惑 */
MSG_yh : 'yh',
// === 新定义-消息头 ===
/** @desc: 课程创建-待开课 */
MSG_0000: 0x0000,
/** @desc: 点赞 */
MSG_0001: 0x0001,
/** @desc: 疑惑 */
@ -134,4 +150,4 @@ export class MsgEnum {
}
}
export { MsgEnum as default }
export default MsgEnum;

View File

@ -5,51 +5,51 @@ let loadingInstance;
export default {
// 消息提示
msg(content) {
ElMessage.info(content)
return ElMessage.info(content)
},
// 错误消息
msgError(content) {
ElMessage.error(content)
return ElMessage.error(content)
},
// 成功消息
msgSuccess(content) {
ElMessage.success(content)
return ElMessage.success(content)
},
// 警告消息
msgWarning(content) {
ElMessage.warning(content)
return ElMessage.warning(content)
},
// 弹出提示
alert(content) {
ElMessageBox.alert(content, "系统提示")
alert(content, option = {}) {
return ElMessageBox.alert(content, "系统提示", option)
},
// 错误提示
alertError(content) {
ElMessageBox.alert(content, "系统提示", { type: 'error' })
alertError(content, option = {}) {
return ElMessageBox.alert(content, "系统提示", { type: 'error', ...option })
},
// 成功提示
alertSuccess(content) {
ElMessageBox.alert(content, "系统提示", { type: 'success' })
alertSuccess(content, option = {}) {
return ElMessageBox.alert(content, "系统提示", { type: 'success', ...option })
},
// 警告提示
alertWarning(content) {
ElMessageBox.alert(content, "系统提示", { type: 'warning' })
alertWarning(content, option = {}) {
return ElMessageBox.alert(content, "系统提示", { type: 'warning', ...option })
},
// 通知提示
notify(content) {
ElNotification.info(content)
return ElNotification.info(content)
},
// 错误通知
notifyError(content) {
ElNotification.error(content);
return ElNotification.error(content);
},
// 成功通知
notifySuccess(content) {
ElNotification.success(content)
return ElNotification.success(content)
},
// 警告通知
notifyWarning(content) {
ElNotification.warning(content)
return ElNotification.warning(content)
},
// 确认窗体
confirm(content) {
@ -78,5 +78,8 @@ export default {
// 关闭遮罩层
closeLoading() {
loadingInstance.close();
}
},
// messageBox: opt => ElMessageBox(opt),
// 其他实例放出去,方便调用
ElMessage, ElMessageBox, ElNotification, ElLoading
}

View File

@ -0,0 +1,181 @@
/**
* websocket 工具类(im 自己实现)
* 单例模式: 保证一个类仅有一个实例并提供一个访问它的全局访问点
* 实现的方法为先判断实例存在与否如果存在则直接返回不存在就创建了再返回这就确保了一个类只有一个实例对象
*/
import useUserStore from '@/store/modules/user' // 用户信息
export class ChatWs {
instance = null; // 实例
id = null; // 群聊id || 单聊id-用户id(userId)
url = null; // ws地址
closed = false; // 关闭状态
onmessage = null; // 自定义处理
errCount = 5; // 重连次数 (ms) 暂时不使用
errTime = null; // 重连时间 (ms) 1秒内zhi间内不重连
// 类型定义
TYPES = {
group: 'group', // 群发
single: 'single', // 单发
beat: 'heart_beat', // 心跳
}
static base = 'wss://file.ysaix.com:7868'
// 构造函数 是否自动连接
constructor(bool = true) {
if (!ChatWs.instance) {
if (bool) { // 是否自动连接
const userStore = useUserStore() // 用户信息
const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址
this.url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`;
this.closed = false; // 关闭状态 防止重连失败
// this.init(url);
}
ChatWs.instance = this;
}
return ChatWs.instance;
}
// 初始化
init(url) {
!!url && (this.url = url);
this.closed = false; // 关闭状态 防止重连失败
this.ws = null;
const _this = this
this.heartCheck = {
timeout: 1000 * 60, // 60s
timeoutObj: null,
serverTimeoutObj: null,
reset() {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start() {
const self = this;
this.timeoutObj = setTimeout(function () {
// 这里发送一个心跳,后端收到后,返回一个心跳消息,
// onmessage拿到返回的心跳就说明连接正常
console.log("websocket-发送心跳")
_this.sendMsgBeat();
self.serverTimeoutObj = setTimeout(function () {
console.log("websocket-心跳响应超时")
// 如果超过一定时间还没重置,说明后端主动断开了
_this.ws.close(); // 如果onclose会执行reconnect我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
}, self.timeout);
}, this.timeout);
},
};
return this.reconnect();
}
// 重连
reconnect() {
return new Promise((resolve, reject) => {
const self = this;
if (!!this.ws) { // 关闭之前的链接
this.ws.close()
this.ws = null
}
this.ws = new WebSocket(this.url);
this.ws.onopen = function () {
console.log("websocket-连接成功")
self.heartCheck.reset().start();
resolve()
};
this.ws.onmessage = function (e) {
// console.log("websocket-收到消息", e)
// 拿到任何消息都说明当前连接是正常的
const isBeat = e.data == 'pong'
isBeat && self.heartCheck.reset().start();
const isEmpty = !e.data
const isExts = e.data.includes('sessionId') || e.data == ('pong')
if (isEmpty || isExts) return;
// 自定义处理
self.onmessage && self.onmessage(e.data, e);
};
this.ws.onerror = function (e) {
console.log("websocket-连接异常", e)
self.connectSocket() // 重连
};
this.ws.onclose = function (e) {
console.log("websocket-连接断开", e)
self.connectSocket() // 重连
};
})
}
connectSocket() {
this.heartCheck.reset() // 重置心跳
if (this.closed) return this.ws = null; // 关闭状态不重连
// if(self.errCount <= 0) return; // 超过重连次数
// self.errCount--; // 重连次数减1
if (this.errTime) {
const nowTime = Date.now();
const bool = nowTime - this.errTime < 1000 // 1s内zhi间内不重连
if (bool) return; // 1s内不重连
}
this.errTime = Date.now();
// 延时5s 后重连
console.log('重连中...')
this.sleep(5000).then(_ => {this.reconnect()})
}
// 发送消息
send(msg) {
if (!msg) throw new Error("msg is not null")
if (!this.ws) throw new Error("ws is not null")
if (typeof msg === "object") msg = JSON.stringify(msg)
if (!msg.includes('"msg":')) throw new Error("msg 格式错误请重试")
this.ws.send(msg)
}
// 发送消息-带消息头(key)
sendMsg(head, content, option = {}, ...arg) {
if (!head && head!==0) throw new Error("head is not null")
if (!content && content!==0) throw new Error("content is not null")
let msg = { head, content, ...option }
// 发送消息
this.send(this.getMsgObj(msg, ...arg))
}
// 发送心跳
sendMsgBeat() {
// this.send(this.getMsgObj('ping', this.TYPES.beat))
this.ws.send('ping')
}
/**
* @description 获取消息对象
* @param {*} msg 消息内容
* @param {*} chatType 群发 group| 单发 single| 心态 heart_beat
* @param {*} id 群聊id || 单聊id-用户id(userId)
*/
getMsgObj(msg, chatType = 'group', id) {
// if (typeof msg === "object") msg = JSON.stringify(msg)
const res = {msg, chatType}
// if (!id) throw new Error(`${type=='group'?'群ID':'用户ID'} is not null`)
if (chatType == 'group') res.groupId = id || this.id || ''
else if (chatType == 'single') res.to = id || this.id || ''
return res
}
// 监听
watch(callback) {
callback && (this.onmessage = callback);
}
// 关闭链接
close() {
this.closed = true;
this.ws.close();
}
// 下课
closedCourse(id) {
return new Promise((resolve, reject) => {
this.sendMsg('closed', '下课', null, 'group', id)
resolve()
})
}
// 延时 ms 毫秒
sleep(ms){
return new Promise(resolve => setTimeout(resolve, ms))
}
}
// 连接socket
export const connect = () => new ChatWs()
export const getInstance = () => new ChatWs(false)
// 默认实例
export default new ChatWs()

View File

@ -31,6 +31,11 @@ export const constantRoutes = [
component: () => import('@/AixPPTist/src/App.vue'),
hidden: true
},
{
path: '/gridPic',
component: () => import('@/components/grid-pic/index.vue'),
hidden: true
},
{
path: '/model',
component: Layout,
@ -47,19 +52,19 @@ export const constantRoutes = [
path: 'curriculum',
component: () => import('@/views/curriculum-standards/index.vue'),
name: 'curriculum-standard',
meta: { title: '课标研读' }
meta: { title: '课标研读', showBread: true }
},
{
path: 'teaching',
component: () => import('@/views/teaching-material/index.vue'),
name: 'teaching-material',
meta: { title: '教材研读' }
meta: { title: '教材研读', showBread: true }
},
{
path: 'examination',
component: () => import('@/views/examination-analysis/index.vue'),
name: 'examination-analysis',
meta: { title: '考试分析' }
meta: { title: '考试分析', showBread: true }
},
{
path: 'management',
@ -71,25 +76,25 @@ export const constantRoutes = [
path: 'design',
component: () => import('@/views/teachingDesign/index.vue'),
name: 'teaching-design',
meta: { title: '教学框架设计' },
meta: { title: '教学框架设计' , showBread: true},
},
{
path: 'newClassTaskAssign',
component: () => import('@/views/classTask/newClassTaskAssign/index.vue'),
name: 'newClassTaskAssign',
meta: { title: '作业管理' }
meta: { title: '作业设计', showBread: true }
},
{
path: 'questionUpload',
component: () => import('@/views/classTask/newClassTaskAssign/questionUpload/index.vue'),
name: 'questionUpload',
meta: { title: '习题上传' }
meta: { title: '习题上传', showBread: true }
},
{
path: 'aiKolors',
component: () => import('@/components/ai-kolors/index.vue'),
name: 'aiKolors',
meta: { title: '文生图片' }
meta: { title: '文生图片', showBread: true }
},
]
},
@ -101,7 +106,7 @@ const dynamicRoutes = [
{
path: '/',
component: Layout,
redirect: '/desktop',
redirect: '/model/index',
meta: { title: '教学工作台' },
children: [
{
@ -132,7 +137,7 @@ const dynamicRoutes = [
path: 'prepare',
component: () => import('@/views/prepare/index.vue'),
name: 'prepare',
meta: { title: '教学实践', showBread: true }
meta: { title: '教学实践' }
},
{
path: 'newClassTask',
@ -152,13 +157,6 @@ const dynamicRoutes = [
name: 'classCorrect',
meta: { title: '作业批改', showBread: true }
},
{
path: '/teach',
component: () => import('@/views/teach/index.vue'),
name: 'teach',
meta: { title: '授课' }
},
{
path: '/profile',
component: () => import('@/views/profile/index.vue'),

View File

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

View File

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

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 = [
{
id:1,
label: '课例库',
value: "'apt','课件','教案'"
},
{
label: '作业库',
value: '作业',
disabled: true
},
// {
// label: '作业库',
// value: '作业',
// disabled: true
// },
{
id:2,
label: '素材库',
value: "'素材'"
},
{
label: '习题库',
value: '习题',
disabled: true
}
id:3,
label: '实验室',
value: "'素材'"
},
// {
// label: '习题库',
// value: '习题',
// disabled: true
// }
]
// 年级划分
export const gradeList = [

View File

@ -258,16 +258,45 @@ export const getFileName = (filename) => {
return filename.replace(/\.[^/.]+$/, "");
}
// 清除当前选中的教材 章节 相关信息
export const clearBookInfo = () =>{
//当前选中的教材
localStorage.removeItem('curBook')
// 当前选中的节点
localStorage.removeItem('curNode')
// 所有章节单元数据
localStorage.removeItem('unitList')
// 所有教材数据
localStorage.removeItem('subjectList')
// 展开的节点
localStorage.removeItem('defaultExpandedKeys')
/**
* 根据图片的url转换对应的base64值
* @param { String } url http://xxxx/xxx.png
* @returns base64取值
*/
export const urlToBase64 = (url) => {
return new Promise((resolve, reject) => {
if (!url) {
reject('请传入url内容')
}
if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(url)) {
// 图片地址
let image = new Image()
// 设置跨域问题
image.setAttribute('crossOrigin', 'anonymous')
// 图片地址
image.src = url +"?v=" + Math.random(); // 处理缓存,fix缓存bug,有缓存,浏览器会报错;
image.onload = () => {
let canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = image.width
canvas.height = image.height
ctx.drawImage(image, 0, 0, image.width, image.height)
// 获取图片后缀
const ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase()
// 转base64
const dataUrl = canvas.toDataURL(`image/${ext}`)
resolve(dataUrl || '')
canvas = null // 清除canvas元素
image = null // 清除img元素
}
} else {
// 非图片地址
reject('非(png/jpe?g/gif/svg等)图片地址');
}
})
}

View File

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

View File

@ -208,6 +208,10 @@ export const createWindow = async (type, data) => {
autoHideMenuBar: true,
maximizable: false,
}
// pptlist的时候可以选择最大化
if (data.url == '/pptist'){
defOption.maximizable = true;
}
data.isConsole = true // 是否开启控制台
data.option = {...defOption, ...option}
const win = await toolWindow(type, data)
@ -221,7 +225,7 @@ export const createWindow = async (type, data) => {
.filter(k => typeof data[k] === 'function')
.forEach(k => events[k] = data[k])
eventHandles(type, win, events) // 事件监听处理
break
return win
}
default:
break
@ -244,10 +248,11 @@ export function toolWindow(type, {url, isConsole, isWeb=true, option={}}) {
const devUrl = `${BaseUrl}${url}`
const buildUrl = path.join(__dirname, 'index.html')
const urlAll = isDev ? devUrl : buildUrl
let logoIco = import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?'/resources/yc-logo.png':'/resources/logo2.ico'
return new Promise(async(resolve) => {
const config = {
width, height,
icon: path.join(appPath, '/resources/logo2.ico'),
icon: path.join(appPath, logoIco),
webPreferences: {
preload: path.join(API.preloadPath, '/index.js'),
sandbox: false,
@ -418,6 +423,19 @@ export const toLinkWeb = (path) => {
cookieData: { ...config }
})
}
/**
* @description 外部跳转-web网页
* @param {*} url
*/
export const toLinkLeftWeb = (url) => {
// 通知主进程
ipcRenderer.send('openWindow', {
key: `win-${Date.now()}`,
fullPath: url
})
}
/**
* @description 外部跳转-web网页
* @param {*} path

View File

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

View File

@ -7,28 +7,35 @@
<span>{{item.caption}}</span>
</div>
<div class="class-reserv-item-tool" style="width: 200px;max-width: 300px">
<el-tag v-if="item.status === 'closed'" style="margin-right: 5px" type="success">已结束</el-tag>
<el-tag v-if="item.status === 'open'" style="margin-right: 5px" type="danger">上课中</el-tag>
<el-button v-if="item.status === 'open'" :disabled="toolStore.isToolWin" size="small" type="primary" @click="startClassR(item)"
>继续上课</el-button
>
<!-- <el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>-->
<el-button v-if="item.status === 'open'" :loading="loading" size="small" type="info" @click="endClassR(item)"
>下课{{ loading?'中...':'' }}</el-button
>
<el-tag v-if="item.status === ''" style="margin-right: 5px" type="warning">待开课</el-tag>
<el-tag v-else-if="item.status === 'closed'" style="margin-right: 5px" type="success">已结束</el-tag>
<el-tag v-else-if="item.status === 'open'" style="margin-right: 5px" type="danger">上课中</el-tag>
<template v-if="!item.status">
<el-button size="small" type="primary" :icon="ChatDotRound" @click="chatSend()">上课(APP)</el-button>
</template>
<template v-else-if="item.status === 'open'">
<el-button :disabled="toolStore.isToolWin" size="small" type="primary" @click="startClassR(item)"
>继续上课</el-button>
<!--<el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>-->
<el-button :loading="loading" size="small" type="info" @click="endClassR(item)"
>下课{{ loading?'中...':'' }}</el-button>
</template>
</div>
<div class="class-reserv-item-tool" style="width: 50px;">
<!-- <el-button v-if="item.status!='open'" size="small" type="danger" @click="deleteReserv">删除</el-button>-->
<el-tag>APT</el-tag>
<!-- <el-tag>APT</el-tag> -->
<el-tag>AIPPT</el-tag>
</div>
<div style="min-width: 150px;"><span> 浏览25955 点赞26605</span></div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ref, reactive } from 'vue'
import { useToolState } from '@/store/modules/tool'
import { deleteSmartReserv } from '@/api/classManage'
import { ElMessage } from 'element-plus'
import { ChatDotRound } from '@element-plus/icons-vue'
const emit = defineEmits(['openEdit', 'deleteReserv', 'change'])
const props = defineProps({
item: {
@ -39,6 +46,7 @@ const props = defineProps({
const basePath = import.meta.env.VITE_APP_BUILD_BASE_PATH
const toolStore = useToolState() // -tool
const loading = ref(false) // loading
const msg = reactive({ id: null, time: null }) //
const openEdit = () => {
emit('openEdit', props.item)
}
@ -61,11 +69,24 @@ const startClassR = (item) => {
const endClassR = (item) => {
emit('change', 'close', item, { type: 2, loading })
}
//
const chatSend = () => {
const time = Date.now()
if(msg.id) { // 30
const isEq = msg.id == props.item.id && (time - msg.time) < 30 * 1000
if(isEq) return ElMessage.warning('请勿重复发送(30秒内)!')
} else { //
msg.id = props.item.id
msg.time = time
}
emit('change', 'wsApp', props.item)
}
</script>
<style scoped lang="scss">
.class-reserv-item {
display: flex;
align-items: center;
background-color: white;
border-radius: 10px;
padding: 5px;
@ -91,7 +112,7 @@ const endClassR = (item) => {
}
}
.class-reserv-item-tool {
margin-left: 15px;
margin: 0 7px;
display: flex;
align-items: center;
}

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