Compare commits

...

77 Commits

Author SHA1 Message Date
小杨 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
白了个白 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
lyc 3ad4391e10 edit 模板 2024-12-03 10:11:51 +08:00
“zouyf” 7b59d78ce3 1 2024-12-02 17:30:24 +08:00
50 changed files with 1622 additions and 455 deletions

View File

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

View File

@ -34,7 +34,7 @@ export default defineConfig({
'/dev-api': { '/dev-api': {
target: 'http://27.128.240.72:7865', target: 'http://27.128.240.72:7865',
// target: 'http://36.134.181.164:7863', // target: 'http://36.134.181.164:7863',
// target: 'http://192.168.2.52:7863', // target: 'http://192.168.0.102:7865',
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '') rewrite: (p) => p.replace(/^\/dev-api/, '')
}, },

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

@ -57,6 +57,7 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"hfmath": "^0.0.2", "hfmath": "^0.0.2",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"html2canvas": "^1.4.1",
"im_electron_sdk": "^8.0.5904", "im_electron_sdk": "^8.0.5904",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",

View File

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

View File

@ -28,6 +28,7 @@ import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关ap
import { PPTApi } from './api' import { PPTApi } from './api'
import { sessionStore } from '@/utils/store' // electron-store import { sessionStore } from '@/utils/store' // electron-store
import './api/watcher' // import './api/watcher' //
import './api/classcourse' //
const loading = ref(true) const loading = ref(true)
const _isPC = isPC() const _isPC = isPC()
@ -60,14 +61,6 @@ window.addEventListener('unload', () => {
const newDiscardedDB = JSON.stringify(discardedDBList) const newDiscardedDB = JSON.stringify(discardedDBList)
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB) localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
}) })
/** 接口类型 */
interface Result {
code?: number,
msg?: string,
data?: any
rows?: Array<any>,
total?: number
}
// //
const initLoad: Function = () => { const initLoad: Function = () => {
// ppt // ppt
@ -89,5 +82,8 @@ const initLoad: Function = () => {
<style lang="scss"> <style lang="scss">
#app { #app {
height: 100%; height: 100%;
svg, canvas, img, audio, video, iframe {
display: unset;
}
} }
</style> </style>

View File

@ -0,0 +1,22 @@
/**
* @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
const screenStore = useStore.useScreenStore() // 全屏-状态管理
const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理
const classcourse: Classcourse = sessionStore.get('curr.classcourse') // 课堂信息
// 如果课堂信息有值则连接socket
if (!!classcourse) {
// 连接socket
const ws = new ChatWs()
console.log('ws- ',ws)
// ChatWs.connect(classcourse.id)
classcourseStore.setClasscourse(classcourse)
}
// 打开全屏
screenStore.setScreening(!!classcourse)

View File

@ -4,6 +4,7 @@
* @date 2024-11-26 * @date 2024-11-26
*/ */
import { toRaw } from 'vue' import { toRaw } from 'vue'
import type { Result } from './types' // 接口类型
import msgUtils from '@/plugins/modal' // 消息工具 import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
import * as API_smarttalk from '@/api/file' // 相关api import * as API_smarttalk from '@/api/file' // 相关api
@ -135,13 +136,15 @@ export class PPTApi {
id: currentSlide.id, id: currentSlide.id,
datacontent: JSON.stringify(currentSlide), datacontent: JSON.stringify(currentSlide),
} }
Utils.mxThrottle(() => {this.updateSlide(params)}, 1000, 2) Utils.mxThrottle(() => {this.updateSlide(params)}, 200, 2)
} }
} }
// 更新幻灯片 // 更新幻灯片
static updateSlide(data: object): Promise<Boolean> { static updateSlide(data: object): Promise<Boolean> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data) const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data)
console.log(data,'data');
console.log(res,'dresata');
if (res.code === 200) { if (res.code === 200) {
resolve(true) resolve(true)
} else msgUtils.msgError(res.msg || '更新失败');resolve(false) } else msgUtils.msgError(res.msg || '更新失败');resolve(false)

View File

@ -2,6 +2,7 @@
* @description api store循环引用 * @description api store循环引用
* @author zdg * @author zdg
*/ */
import type { Result } from './types' // 接口类型
import msgUtils from '@/plugins/modal' // 消息工具 import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api

View File

@ -0,0 +1,24 @@
/** 返回-接口类型 */
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
plandate?: string, // 计划时间
opendate?: string, // 开课时间
}

View File

@ -1,9 +1,10 @@
import { useScreenStore, useSlidesStore } from '../store' import { useScreenStore, useSlidesStore, useClasscourseStore } from '../store'
import { enterFullscreen, exitFullscreen, isFullscreen } from '../utils/fullscreen' import { enterFullscreen, exitFullscreen, isFullscreen } from '../utils/fullscreen'
export default () => { export default () => {
const screenStore = useScreenStore() const screenStore = useScreenStore()
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const classcourseStore = useClasscourseStore() // 课堂信息
// 进入放映状态(从当前页开始) // 进入放映状态(从当前页开始)
const enterScreening = () => { const enterScreening = () => {
@ -19,7 +20,11 @@ export default () => {
// 退出放映状态 // 退出放映状态
const exitScreening = () => { const exitScreening = () => {
screenStore.setScreening(false) const classcourse = classcourseStore.classcourse
if (!!classcourse) { //DOTO 有课堂,执行退相关操作
console.log('退出放映状态')
window.close()
} else screenStore.setScreening(false)
if (isFullscreen()) exitFullscreen() if (isFullscreen()) exitFullscreen()
} }

View File

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

View File

@ -0,0 +1,18 @@
import { defineStore } from 'pinia'
import type { Classcourse } from '../api/types'
export interface ClasscourseState {
classcourse: Classcourse
}
export const useClasscourseStore = defineStore('classcourse', {
state: (): ClasscourseState => ({
classcourse: null, // 课堂信息
}),
actions: {
setClasscourse(classcourse: Classcourse) {
this.classcourse = classcourse
},
},
})

View File

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

View File

@ -0,0 +1,172 @@
<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 { getFileSuffix, urlToBase64 } from '@/utils/ruoyi.js'
const emit = defineEmits(['insertMaterial', 'close'])
const curNode = reactive({})
let params = {
textbookId: '',
levelFirstId: '',
levelSecondId: null,
fileSource: '个人',
fileRoot: '备课',
orderByColumn: 'uploadTime',
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) =>{
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
emit('insertMaterial',{ type: 'video', data: item.fileFullPath })
}
else{
const base64 = await urlToBase64(item.fileFullPath)
emit('insertMaterial',{ type: 'img', data: base64 })
}
}
const GetUrlParameters = (parameters) => {
let resData = "";
let url = document.location.toString();
let arrUrl = url.split("?");
//
if (arrUrl.length > 1) {
//
let parametersArr = arrUrl[1].split("&");
//
for (let i = 0; i <= parametersArr.length; i++) {
if (parametersArr[i]) {
//
let parameterStr = parametersArr[i].split("=");
if (parameters == parameterStr[0]) {
resData = parameterStr[1];
break;
}
}
}
}
return resData;
}
const proxyToBase64 = (url)=> {
const dourl = GetUrlParameters(url)
console.log(dourl,'dourl')
return
axios({
url: "/api/logo.png",
method: "get",
responseType: "blob",
}).then((res) => {
const reader = new FileReader();
reader.readAsDataURL(res.data);
reader.onload = () => {
console.log(reader.result);
};
});
}
//
const onClose = () =>{
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

@ -81,6 +81,9 @@
</template> </template>
<IconVideoTwo class="handler-item" v-tooltip="'插入音视频'" /> <IconVideoTwo class="handler-item" v-tooltip="'插入音视频'" />
</Popover> </Popover>
<IconPreviewOpen class="handler-item" v-tooltip="'插入试题'" @click="classWorkTaskVisible = true" />
<IconMaterial class="handler-item" v-tooltip="'插入素材'" @click="materiaVisible = true"/>
<IconAddPicture class="handler-item" v-tooltip="'文生图'" @click="imgVisible = true" />
</div> </div>
<div class="right-handler"> <div class="right-handler">
@ -110,6 +113,27 @@
@update="data => { createLatexElement(data); latexEditorVisible = false }" @update="data => { createLatexElement(data); latexEditorVisible = false }"
/> />
</Modal> </Modal>
<el-dialog v-model="classWorkTaskVisible" append-to-body :show-close="false" width="70%">
<QuestToPPTist
class="class-work-task-modal"
@close="classWorkTaskVisible = false"
@update="data => { onhtml2canvas(data); classWorkTaskVisible = false }"
/>
</el-dialog>
<!--插入素材-->
<Modal
v-model:visible="materiaVisible"
:width="880">
<MaterialDialog @close="materiaVisible = false" @insertMaterial="insertMaterial"/>
</Modal>
<!--文生图-->
<Modal
v-model:visible="imgVisible"
:width="1300">
<TextCreateImg hasPPt @insertImg="(url: string) => { createImageElement(url); imgVisible = false }" />
</Modal>
</div> </div>
</template> </template>
@ -135,6 +159,9 @@ import Modal from '../../../components/Modal.vue'
import Divider from '../../../components/Divider.vue' import Divider from '../../../components/Divider.vue'
import Popover from '../../../components/Popover.vue' import Popover from '../../../components/Popover.vue'
import PopoverMenuItem from '../../../components/PopoverMenuItem.vue' import PopoverMenuItem from '../../../components/PopoverMenuItem.vue'
import QuestToPPTist from '@/views/classTask/newClassTaskAssign/questToPPTist/index.vue'
import MaterialDialog from './MaterialDialog.vue'
import TextCreateImg from '@/components/ai-kolors/index.vue'
const mainStore = useMainStore() const mainStore = useMainStore()
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore) const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
@ -172,15 +199,21 @@ const insertImageElement = (files: FileList) => {
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL)) getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
} }
const onhtml2canvas = (imgbs64: string) => {
createImageElement(imgbs64)
}
const shapePoolVisible = ref(false) const shapePoolVisible = ref(false)
const linePoolVisible = ref(false) const linePoolVisible = ref(false)
const chartPoolVisible = ref(false) const chartPoolVisible = ref(false)
const tableGeneratorVisible = ref(false) const tableGeneratorVisible = ref(false)
const mediaInputVisible = ref(false) const mediaInputVisible = ref(false)
const latexEditorVisible = ref(false) const latexEditorVisible = ref(false)
const classWorkTaskVisible = ref(false)
const textTypeSelectVisible = ref(false) const textTypeSelectVisible = ref(false)
const shapeMenuVisible = ref(false) const shapeMenuVisible = ref(false)
const moreVisible = ref(false) const moreVisible = ref(false)
const materiaVisible = ref(false)
// //
const drawText = (vertical = false) => { const drawText = (vertical = false) => {
@ -227,6 +260,25 @@ const toggleSraechPanel = () => {
const toggleNotesPanel = () => { const toggleNotesPanel = () => {
mainStore.setNotesPanelState(!showNotesPanel.value) mainStore.setNotesPanelState(!showNotesPanel.value)
} }
//
interface MaterialParams {
type: string,
data: string
}
const insertMaterial = (item: MaterialParams) =>{
const { type, data } = item
if(type == 'video'){
createVideoElement(data)
}
else{
createImageElement(data)
}
materiaVisible.value = false
}
//
const imgVisible = ref(false)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -343,6 +395,9 @@ const toggleNotesPanel = () => {
font-size: 13px; font-size: 13px;
} }
} }
.class-work-task-modal{
height: 70vh;
}
@media screen and (width <= 1200px) { @media screen and (width <= 1200px) {
.right-handler .text { .right-handler .text {

View File

@ -162,6 +162,10 @@ const setDialogForExport = (type: DialogForExportTypes) => {
.icon { .icon {
font-size: 18px; font-size: 18px;
color: #666; color: #666;
:deep(svg) {
display: block !important;
}
} }
&:hover { &:hover {

View File

@ -67,8 +67,6 @@
<div class="page-number">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div> <div class="page-number">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
<!-- 引入活动的列表页面 -->
<Active ref="activeRef" v-show="false"/>
</div> </div>
</template> </template>
@ -88,7 +86,6 @@ import ThumbnailSlide from '../../../views/components/ThumbnailSlide/index.vue'
import LayoutPool from './LayoutPool.vue' import LayoutPool from './LayoutPool.vue'
import Popover from '../../../components/Popover.vue' import Popover from '../../../components/Popover.vue'
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
import Active from '../Toolbar/ElementStylePanel/Active/index.vue'
const mainStore = useMainStore() const mainStore = useMainStore()
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
@ -127,7 +124,6 @@ const {
updateSectionTitle, updateSectionTitle,
} = useSectionHandler() } = useSectionHandler()
const activeRef = ref()
// //
const thumbnailsRef = ref<InstanceType<typeof Draggable>>() const thumbnailsRef = ref<InstanceType<typeof Draggable>>()
@ -151,8 +147,6 @@ watch(() => slideIndex.value, () => {
// //
const changeSlideIndex = (index: number) => { const changeSlideIndex = (index: number) => {
console.log(workItem.value[index],'hasSection');
activeRef.value.clickPPTList(workItem.value[index])
mainStore.setActiveElementIdList([]) mainStore.setActiveElementIdList([])
@ -402,12 +396,17 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => {
.icon { .icon {
margin-right: 3px; margin-right: 3px;
font-size: 14px; font-size: 14px;
:deep(svg) {
display: block !important;
}
} }
} }
.thumbnail-list { .thumbnail-list {
padding: 5px 0; padding: 5px 0;
flex: 1; flex: 1;
overflow: auto; overflow: auto;
border-bottom: 1px solid $borderColor;
} }
.thumbnail-item { .thumbnail-item {
display: flex; display: flex;
@ -486,7 +485,6 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => {
.page-number { .page-number {
height: 40px; height: 40px;
font-size: 12px; font-size: 12px;
border-top: 1px solid $borderColor;
line-height: 40px; line-height: 40px;
text-align: center; text-align: center;
color: #666; color: #666;

View File

@ -32,12 +32,12 @@
<!-- 作业列表 --> <!-- 作业列表 -->
<div class="c-apt-r"> <div class="c-apt-r">
<!-- 显示-作业内容 --> <!-- 显示-作业内容 -->
<template v-for="(item, index) in workList"> <template v-for="(item, index) in workList" :key="index">
<div class="item"> <div class="item">
<div class="item-title"> <div class="item-title">
<el-tag :type="getTagType(item.worktype) || 'primary'">{{item.worktype}}</el-tag> <el-tag :type="getTagType(item.worktype) || 'primary'">{{item.worktype}}</el-tag>
<el-tooltip :content="item.title||item.uniquekey" placement="top"> <el-tooltip :content="item.evaltitle" placement="top">
<div class="tt">{{item.title||item.uniquekey}}</div> <div class="tt">{{item.evaltitle}}</div>
</el-tooltip> </el-tooltip>
<el-button class="btn-del" type="danger" link @click="handleRemoveDemoActivityClassWork(item)">删除</el-button> <el-button class="btn-del" type="danger" link @click="handleRemoveDemoActivityClassWork(item)">删除</el-button>
</div> </div>
@ -45,8 +45,10 @@
</template> </template>
</div> </div>
<!-- // --> <!-- // -->
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="80%"> <el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="80%" height="500">
<NewClassTsakAssign :currentCourse='currentCourse'/> <el-scrollbar height="500">
<NewClassTsakAssign :currentCourse='currentCourse' @getData="getData" />
</el-scrollbar>
</el-dialog> </el-dialog>
<!-- 活动引用 --> <!-- 活动引用 -->
<el-dialog <el-dialog
@ -55,9 +57,9 @@
append-to-body append-to-body
:show-close="false" :show-close="false"
width="40%" width="40%"
@selection-change="handleSelectionChange"> >
<el-table :data="taskList" style="width: 100%" height="500"> <el-table :data="taskList" style="width: 100%" height="500" @selection-change="handleSelectionChange">
<el-table-column type="selection" :selectable="selectable" width="55" /> <el-table-column type="selection" width="55" :selectable="selectable"/>
<el-table-column prop="evaltitle" label="活动名称" width="150" /> <el-table-column prop="evaltitle" label="活动名称" width="150" />
<el-table-column prop="worktype" label="活动类型" width="120" sortable> <el-table-column prop="worktype" label="活动类型" width="120" sortable>
<template #default="scope"> <template #default="scope">
@ -68,78 +70,125 @@
</el-table> </el-table>
<template #footer> <template #footer>
<el-button @click="activeVisible = false"> </el-button> <el-button @click="activeVisible = false"> </el-button>
<el-button type="primary" @click="save"> </el-button> <el-button type="primary" @click="savePPtData"> </el-button>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import { ref, reactive, onMounted, onBeforeMount, defineExpose } from 'vue' import { ref, reactive, onMounted, nextTick, watch } from 'vue'
import Divider from '../../../../../components/Divider.vue' import Divider from '../../../../../components/Divider.vue'
import {listEntpcoursefile} from '@/api/education/entpcoursefile' import { listEntpcoursefile } from '@/api/education/entpcoursefile'
import {homeworklist} from '@/api/teaching/classwork' import { homeworklist } from '@/api/teaching/classwork'
import { processList } from "@/hooks/useProcessList"; import { processList } from "@/hooks/useProcessList";
import { listEntpcoursework } from "@/api/classTask/index"; import { listEntpcoursework } from "@/api/classTask/index";
import { ElMessageBox } from 'element-plus' import { ElMessageBox,ElMessage } from 'element-plus'
import NewClassTsakAssign from '@/views/classTask/newClassTaskAssign/index.vue' import NewClassTsakAssign from '@/views/classTask/newClassTaskAssign/index.vue'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { useGetHomework } from '@/hooks/useGetHomework' import { useGetHomework } from '@/hooks/useGetHomework'
const currentCourse = reactive({ import { PPTApi } from '../../../../../api/index'
textbookId:0, import { storeToRefs } from 'pinia'
levelFirstId:0, import {useSlidesStore} from '../../../../../store'
levelSecondId:0, const slidesStore = useSlidesStore()
coursetitle:'', const { slides, slideIndex, currentSlide, workItem } = storeToRefs(slidesStore)
node:{},
id:1, interface CourseNode {
worktype:'', rootid: number;
}) parentNode: { id: number };
const dataList = ref([]) id: number;
const dialogVisible = ref(false) itemtitle: string;
const tasklist_loading = ref(false)
//
const taskList = ref([])
//
const activeVisible = ref(false)
const params = reactive({
parentid:14766,
pageSize:500,
orderby:'fileidx'
})
const type = ref([
{
label:'习题训练',
value:'danger'
},
{
label:'课堂展示',
value:'success'
},
{
label:'常规作业',
value:'primary'
},
])
//
const workList = ref([])
const selectable = (row,index) => {
console.log(row,index,'row,index');
return true
} }
const clickPPTList = (item) => { interface CurrentCourse {
console.log(item,'点击了') textbookId: number;
workList.value = [] levelFirstId: number;
let datacontent = item.datacontent; levelSecondId: number;
let pptJson = ""; coursetitle: string;
if(typeof datacontent === 'string') pptJson = JSON.parse(datacontent) node: CourseNode;
if(pptJson&&pptJson[0]&&pptJson[0].classworkList) { id: number;
homeworklist({ids:pptJson[0].classworkList, pageSize: 100}).then( async res => { worktype: string;
await formatClassWorkFile(res.rows)
})
}
} }
const formatClassWorkFile = async (postData) => {
return new Promise(async (resolve, reject)=>{ interface Params {
parentid: number;
pageSize: number;
orderby: 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;
evaltitle?: string; // evaltitle
}
const currentCourse = reactive<CurrentCourse>({
textbookId: 0,
levelFirstId: 0,
levelSecondId: 0,
coursetitle: '',
node: {} as CourseNode,
id: 1,
worktype: '',
})
const dataList = ref<WorkItem[]>([])
const dialogVisible = ref<boolean>(false)
const tasklist_loading = ref<boolean>(false)
//
const taskList = ref<WorkItem[]>([])
//
const activeVisible = ref<boolean>(false)
const params = reactive<Params>({
parentid: 14766,
pageSize: 500,
orderby: 'fileidx'
})
const type = ref<WorkType[]>([
{
label: '习题训练',
value: 'danger'
},
{
label: '课堂展示',
value: 'success'
},
{
label: '常规作业',
value: 'primary'
},
])
const objItem = ref<any>({})
//
const workList = ref<WorkItem[]>([])
//
const selectedWorkList = ref<WorkItem[]>([])
const paramData = ref<{ id: number, activityContent: string }>({} as { id: number, activityContent: string })
const selectable = (row: WorkItem, index: number): boolean => {
return row.status === '10';
};
const formatClassWorkFile = async (postData: WorkItem[]): Promise<void> => {
for (let i = 0; i < postData.length; i++) { for (let i = 0; i < postData.length; i++) {
let item = postData[i]; let item = postData[i];
switch (item.worktype) { switch (item.worktype) {
@ -147,81 +196,120 @@ const formatClassWorkFile = async (postData) => {
} }
break; break;
case '习题训练': { case '习题训练': {
item.entpcourseworklistarray = item.entpcourseworklist?JSON.parse('['+item.entpcourseworklist+']'):[]; console.log(item,'item');
let workIds = item.entpcourseworklistarray.map(items=>items.id).join(',') // let workIds = item.quizlist!.map(items => items.id).join(',');
let ress = await listEntpcoursework({ids:workIds}) // let ress = await listEntpcoursework({ ids: workIds });
processList(ress.rows) // const arr = ress.rows.map((item:{id:number}) => {
item.workShowList = ress.rows // return item.id
// })
// processList(ress.rows);
} }
break; break;
case '课堂展示': { case '课堂展示': {
item.base64 = JSON.parse(item.workcodes).base64 // item.base64 = JSON.parse(item.workcodes).base64;
} }
break; break;
case '常规作业': { case '常规作业': {
item.prevData = JSON.parse(item.workcodes) // item.prevData = JSON.parse(item.workcodes);
} }
} }
const arr = paramData.value.activityContent.split(',')
arr.push(item.id.toString())
await PPTApi.updateSlide(paramData.value)
addWorkList(item)
}
await nextTick();
}
//
const addWorkList = (item: WorkItem) => {
workList.value.push(item) workList.value.push(item)
}
resolve()
})
} }
// //
const handleRemoveDemoActivityClassWork = (item) => { const handleRemoveDemoActivityClassWork = (item: WorkItem) => {
ElMessageBox.confirm('是否确认删除?') ElMessageBox.confirm('是否确认删除?')
.then(function () { .then(() => {
workList.value.splice(workList.value.indexOf(item), 1); workList.value.splice(workList.value.indexOf(item), 1);
}) })
.catch(() => {}); .catch(() => { });
} }
// tag // tag
const getTagType = (worktype) => { const getTagType = (worktype: string): string => {
return type.value.find(item => item.label == worktype).value return type.value.find(item => item.label === worktype)!.value
} }
// //
const initHomeWork = async()=> { const initHomeWork = async () => {
tasklist_loading.value = true; tasklist_loading.value = true;
const { res, chapterId } = await useGetHomework(sessionStore.get('subject.curNode')); const { res, chapterId } = await useGetHomework(sessionStore.get('subject.curNode'));
taskList.value = res; taskList.value = res;
tasklist_loading.value = false; tasklist_loading.value = false;
} }
// //
const handleSelectionChange = (val) => { const handleSelectionChange = (val: WorkItem[]) => {
console.log(val,'多选') selectedWorkList.value = [...val]
} }
// //
const showDialog = (item) => { const showDialog = (item: string) => {
currentCourse.worktype = item currentCourse.worktype = item
dialogVisible.value = true dialogVisible.value = true
} }
const openList = () => { const openList = () => {
activeVisible.value = true activeVisible.value = true
initHomeWork() initHomeWork()
} }
// //
const save = () => { const savePPtData = async () => {
console.log('添加了') if (selectedWorkList.value.length === 0) {
ElMessage.warning('请选择活动')
return
}
const arr = selectedWorkList.value.map(item => item.id)
//
paramData.value.activityContent = arr.join(',')
await PPTApi.updateSlide(paramData.value)
activeVisible.value = false activeVisible.value = false
} }
onMounted(() => { onMounted(() => {
// console.log(sessionStore.get('subject.curBook'),'curBook'); const curNode = sessionStore.get('subject.curNode') as CourseNode
// console.log(sessionStore.get('subject.subjectTree'),'subjectTree');
// console.log(sessionStore.get('subject.bookList'),'bookList');
console.log(sessionStore.get('subject.curNode'),'curNode');
const curNode = sessionStore.get('subject.curNode')
currentCourse.textbookId = curNode.rootid currentCourse.textbookId = curNode.rootid
currentCourse.levelFirstId = curNode.parentNode.id currentCourse.levelFirstId = curNode.parentNode.id
currentCourse.levelSecondId = curNode.id currentCourse.levelSecondId = curNode.id
currentCourse.coursetitle = curNode.itemtitle, currentCourse.coursetitle = curNode.itemtitle
currentCourse.node = curNode currentCourse.node = curNode
listEntpcoursefile(params).then((res) => { listEntpcoursefile(params).then((res: { rows: WorkItem[] }) => {
dataList.value = [...res.rows] dataList.value = [...res.rows]
}) })
objItem.value = workItem.value[slideIndex.value]
getCurrentPPtData()
}) })
defineExpose({ watch(() => slideIndex.value, () => {
clickPPTList getCurrentPPtData()
}) })
// ppt
const getCurrentPPtData = async () => {
workList.value = []
objItem.value = workItem.value[slideIndex.value]
paramData.value.id = objItem.value.id
if (objItem.value?.activityContent) {
paramData.value.activityContent = objItem.value?.activityContent
const res = await homeworklist({ ids: objItem.value?.activityContent, pageSize: 100 })
await formatClassWorkFile(res.rows)
}
}
//
const getData = async (data: WorkItem) => {
console.log(data, 'data')
await formatClassWorkFile([data])
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.buttonDiv{ .buttonDiv{

View File

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

View File

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

View File

@ -144,8 +144,14 @@ import { convertTextToPicture, getQueue, getPromptId, getPicture, chattoprompt,
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
export default { export default {
props: {
hasPPt: {
type: Boolean,
default: false
}
},
data() { data() {
return { return {
form: { form: {
ratio: "512", ratio: "512",
@ -384,7 +390,7 @@ export default {
urls.push(url0) urls.push(url0)
buttonState.push({ buttonState.push({
disabled: false, disabled: false,
text: "插入本课素材资源库", text: this.hasPPt ? '插入' : "插入本课素材资源库",
}) })
} }
this.skeletonNumber = 0 this.skeletonNumber = 0
@ -476,6 +482,10 @@ export default {
// //
async saveImage(resultIndex, index, url, resultItem) { async saveImage(resultIndex, index, url, resultItem) {
if(this.hasPPt){
this.$emit('insertImg', url)
return
}
this.buttonStates[resultIndex][index].disabled = true; this.buttonStates[resultIndex][index].disabled = true;
this.buttonStates[resultIndex][index].text = "正在保存..."; this.buttonStates[resultIndex][index].text = "正在保存...";
const numberIndex = url.indexOf('filename='); const numberIndex = url.indexOf('filename=');

View File

@ -142,13 +142,19 @@ const handleNodeClick = (data) => {
//label label //label label
nodeData.label = nodeData.itemtitle nodeData.label = nodeData.itemtitle
// null let parentNode
let parent = { // children
if(nodeData.children){
//
parentNode = null
}
else{
parentNode = {
id: nodeData.parentid, id: nodeData.parentid,
label: nodeData.parenttitle, label: nodeData.parenttitle,
itemtitle: nodeData.parenttitle itemtitle: nodeData.parenttitle
} }
const parentNode = nodeData.parentid ? parent : null }
nodeData.parentNode = parentNode nodeData.parentNode = parentNode
let curData = { let curData = {
textBook: { textBook: {

View File

@ -35,7 +35,7 @@ const getFileTypeIcon = () => {
txt: 'icon-txt', txt: 'icon-txt',
rar: 'icon-rar', rar: 'icon-rar',
apt: 'icon-A', apt: 'icon-A',
aptist: 'icon-A', aippt: 'icon-A',
} }
if (iconObj[name]) { if (iconObj[name]) {
return '#' + iconObj[name] return '#' + iconObj[name]

View File

@ -26,7 +26,7 @@
</div> </div>
</div> </div>
<!--List--> <!--List-->
<div class="container-right-list"> <div class="container-right-list" ref="listRef">
<template v-for="(item, index) in childTempList"> <template v-for="(item, index) in childTempList">
<div class="template-item" v-loading="item.loading"> <div class="template-item" v-loading="item.loading">
<div class="item-header"> <div class="item-header">
@ -52,7 +52,7 @@
<i class="iconfont icon-ai"></i> <i class="iconfont icon-ai"></i>
</div> </div>
<div class="item-answer"> <div class="item-answer">
<TypingEffect :text="item.oldAnswer" :delay="10" :aiShow="item.aiShow" @complete="onSaveTemp(item)" /> <TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow" @complete="handleCompleteText($event,index)" @updateScroll="scrollToBottom($event,index)" />
</div> </div>
</div> </div>
<div class="ai-btn" v-if="item.answer"> <div class="ai-btn" v-if="item.answer">
@ -83,7 +83,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, watch, onUnmounted } from 'vue' import { ref, reactive, onMounted, watch, onUnmounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index' import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
@ -146,14 +146,16 @@ const getChildTemplate = () => {
tempLoading.value = true tempLoading.value = true
modelList({ model: props.type, type: 2, parentId: curTemplate.id }).then(res => { modelList({ model: props.type, type: 2, parentId: curTemplate.id }).then(res => {
childTempList.value = res.rows childTempList.value = res.rows
if(childTempList.value.length){
childTempList.value.forEach(item => item.answer = '')
}
getTempResult() getTempResult()
}).finally(() => { }).finally(() => {
tempLoading.value = false tempLoading.value = false
}) })
} }
const isStarted = ref([]);
const listRef = ref()
// //
const getTempResult = () => { const getTempResult = () => {
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000 }).then(res => { tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000 }).then(res => {
@ -161,14 +163,40 @@ const getTempResult = () => {
childTempList.value.forEach(item => { childTempList.value.forEach(item => {
rows.forEach(el => { rows.forEach(el => {
if (item.id == el.modelId) { if (item.id == el.modelId) {
item.answer = el.content item.answer = getResult(el.content)
item.resultId = el.id item.resultId = el.id
} }
}) })
}) })
if(rows.length > 0){
isStarted.value = new Array(rows.length).fill(true)
}
}) })
} }
const scrollToBottom = (height,index) =>{
if (listRef.value) {
let sum = 0
let listDom = listRef.value.children
if(index == 0){
// 220
let screenHeight = window.innerHeight - 220
if(height > screenHeight){
listRef.value.scrollTop = (height - screenHeight + 50)
}
}
else{
for(let i = 0; i < index; i++){
sum += listDom[i].clientHeight
}
listRef.value.scrollTop = sum + height
}
}
}
// //
const changeTemplate = (val) => { const changeTemplate = (val) => {
ElMessageBox.confirm( ElMessageBox.confirm(
@ -203,11 +231,11 @@ const removeItem = async (item, isChild) => {
).then(() => { ).then(() => {
removeChildTemp(item.id).then(res => { removeChildTemp(item.id).then(res => {
ElMessage.success('操作成功') ElMessage.success('操作成功')
if(isChild){ if (isChild) {
// //
getChildTemplate() getChildTemplate()
} }
else{ else {
// //
getTemplateList() getTemplateList()
} }
@ -215,7 +243,7 @@ const removeItem = async (item, isChild) => {
}) })
} }
else { else {
editKeyWord(item,!isChild) editKeyWord(item, !isChild)
} }
} }
@ -240,19 +268,18 @@ const onEdit = (index, item) => {
const modeType = ref('课标') const modeType = ref('课标')
watch(() => props.type, (newVal) => { watch(() => props.type, (newVal) => {
if (newVal == 1){ if (newVal == 1) {
modeType.value = '课标' modeType.value = '课标'
} }
if (newVal == 2){ if (newVal == 2) {
modeType.value = '教材' modeType.value = '教材'
} }
if (newVal == 2){ if (newVal == 3) {
modeType.value = '考试' modeType.value = '考试'
} }
}, { immediate: false }) }, { immediate: false })
// //
const params = reactive( const params = reactive(
{ {
@ -260,76 +287,102 @@ const params = reactive(
dataset_id: '' dataset_id: ''
} }
) )
// //
const isAgain = ref(false)
const againResult = async (index, item) => { const againResult = async (index, item) => {
isAgain.value = true
isStarted.value[index] = false
childTempList.value[index].answer = ''
if(index == 0){
listRef.value.scrollTop = 0
}else{
scrollToBottom(50, index)
}
try { try {
await nextTick()
childTempList.value[index].loading = true childTempList.value[index].loading = true
item.aiShow = true item.aiShow = true
childTempList.value[index].oldAnswer = ''
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析` params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params) const { data } = await completion(params)
let answer = data.answer childTempList.value[index].answer = getResult(data.answer);
childTempList.value[index].oldAnswer = answer isStarted.value[index] = true
childTempList.value[index].answer = getResult(answer);
// onEditSave(item)
} finally { } finally {
childTempList.value[index].loading = false childTempList.value[index].loading = false
} }
} }
// //
const getCompletion = async () => { const getCompletion = async () => {
isStarted.value = new Array(childTempList.length).fill(false)
isStarted.value[0] = true
childTempList.value.forEach(item =>{
if(item.answer){
item.answer = ''
}
})
for (let item of childTempList.value) { for (let item of childTempList.value) {
try { try {
item.loading = true item.loading = true
item.aiShow = true item.aiShow = true
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析` params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params) const { data } = await completion(params)
let answer = data.answer item.answer = getResult(data.answer)
item.oldAnswer = answer onSaveTemp(item)
item.answer = getResult(answer);
} finally { } finally {
item.loading = false item.loading = false
} }
} }
} }
const handleCompleteText = async (answer, index) =>{
if (index < childTempList.value.length - 1) {
isStarted.value[index + 1] = true; //
}
if(isAgain.value){
try{
await editTempResult({ id: childTempList.value[index].resultId, content: answer })
}finally{
isAgain.value = false
}
}
}
// //
emitter.on('onSaveAdjust', (item) => { emitter.on('onSaveAdjust', (item) => {
childTempList.value[curIndex.value].oldAnswer = item childTempList.value[curIndex.value].answer = item
let answer = getResult(item);
childTempList.value[curIndex.value].oldAnswer = item
childTempList.value[curIndex.value].answer = answer
onEditSave(childTempList.value[curIndex.value]) onEditSave(childTempList.value[curIndex.value])
}) })
// //
const onEditSave = async (item) =>{ const onEditSave = async (item) => {
const { res } = await editTempResult({id: item.resultId, content: item.oldAnswer}) const { res } = await editTempResult({ id: item.resultId, content: item.answer })
ElMessage.success(res) ElMessage.success(res)
getChildTemplate() getChildTemplate()
} }
// //
const onSaveTemp = (item) => { const onSaveTemp = (item) => {
if(item.oldAnswer == '') return if (item.answer == '') return
const data = { const data = {
mainModelId: curTemplate.id, mainModelId: curTemplate.id,
modelId: item.id, modelId: item.id,
examDocld: '', examDocld: '',
content: item.oldAnswer content: item.answer
} }
tempSave(data).then(res => {}) tempSave(data).then(res => { })
} }
// // ### **
let getResult = (text) => { let getResult = (str) => {
text = text.replace(/^\n\n(.*?)\n\n$/s, '<div>$1</div>'); let newStr = str.replace(/#+|(\*\*)/g, '');
text = text.replace(/^\n(.*?)\n$/s, '<p>$1</p>'); return newStr
text = text.replace(/\*\*(.*?)\*\*/g, "<div class='text-tit'>$1</div>");
text = text.replace(/(\d+\..*?)\n/g, "<div class='text-num'>$1</div>\n");
return text
} }
// //
@ -381,8 +434,6 @@ onUnmounted(() => {
padding: 5px 15px; padding: 5px 15px;
box-sizing: border-box; box-sizing: border-box;
.template-item { .template-item {
background: #fff; background: #fff;
padding: 10px; padding: 10px;

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="typing-effect"> <div class="typing-effect" ref="typingEffectRef">
<!-- <span v-html="displayedText"></span> --> <!-- <span v-html="displayedText"></span> -->
<el-input <el-input
v-model="displayedText" v-model="displayedText"
@ -14,11 +14,11 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch } from 'vue'; import { ref, onMounted, watch, nextTick } from 'vue';
const props = defineProps({ const props = defineProps({
text: { text: {
type: String, type: [String, Object],
required: true required: true
}, },
delay: { delay: {
@ -26,35 +26,48 @@ const props = defineProps({
default: 100 // default: 100 //
}, },
aiShow: { aiShow: {
type: [Boolean] type: [Boolean] // true
} }
}); });
const emit = defineEmits(['complete']); const typingEffectRef = ref(null);
const emit = defineEmits(['complete', 'updateScroll']);
const displayedText = ref(''); const displayedText = ref('');
const index = ref(0); const index = ref(0);
const type = () => { const type = async () => {
if(!props.aiShow) return await nextTick()
if(!props.aiShow) {
displayedText.value = props.text
return
}
if (index.value <= props.text.length) { if (index.value <= props.text.length) {
displayedText.value += props.text.charAt(index.value); displayedText.value += props.text.charAt(index.value);
index.value++; index.value++;
setTimeout(() => type(), props.delay); setTimeout(() => {
type();
emit('updateScroll', typingEffectRef.value.clientHeight); //
}, props.delay);
} else { } else {
// complete // complete
emit('complete'); emit('complete',displayedText.value);
} }
}; };
onMounted(() => { onMounted(() => {
type(); resetAndType();
}); });
// props 便 text delay const resetAndType = () =>{
watch([() => props.text, () => props.delay], () => {
displayedText.value = ''; displayedText.value = '';
index.value = 0; index.value = 0;
type(); type();
}); }
// props 便 text delay
watch([() => props.text, () => props.delay], resetAndType);
</script> </script>
<style scoped> <style scoped>

View File

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

View File

@ -87,6 +87,12 @@ const headerMenus = [
icon: 'icon-gongzuotai', icon: 'icon-gongzuotai',
path: '/desktop' path: '/desktop'
}, },
{
name: '教学实践',
id: 4,
icon: 'icon-jiaoxueshijian',
path: '/prepare'
},
{ {
name: '资源中心', name: '资源中心',
id: 3, id: 3,

View File

@ -0,0 +1,164 @@
/**
* websocket 工具类(im 自己实现)
* 单例模式: 保证一个类仅有一个实例并提供一个访问它的全局访问点
* 实现的方法为先判断实例存在与否如果存在则直接返回不存在就创建了再返回这就确保了一个类只有一个实例对象
*/
import useUserStore from '@/store/modules/user' // 用户信息
export class ChatWs {
instance = null; // 实例
id = null; // 群聊id || 单聊id-用户id(userId)
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() {
if (!ChatWs.instance) {
const userStore = useUserStore() // 用户信息
const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址
const url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`;
this.init(url);
ChatWs.instance = this;
}
return ChatWs.instance;
}
// 初始化
init(url) {
this.url = url;
this.ws = null;
const _this = this
this.heartCheck = {
timeout: 1000 * 10, // 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);
},
};
this.reconnect();
}
// 重连
reconnect() {
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();
};
this.ws.onmessage = function (e) {
// console.log("websocket-收到消息", e)
// 拿到任何消息都说明当前连接是正常的
const isBeat = e.data == 'pong'
isBeat && self.heartCheck.reset().start();
const exts = ['sessionId', 'pong'] // 不处理的消息头
const isEmpty = !e.data
const isExts = exts.some(item => e.data.includes(item))
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 (self.closed) return; // 关闭状态不重连
// 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 = {}) {
if (!head) throw new Error("head is not null")
if (!content) throw new Error("content is not null")
let msg = { head, content, ...option }
// 发送消息
this.send(this.getMsgObj(msg))
}
// 发送心跳
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();
}
// 延时 ms 毫秒
sleep(ms){
return new Promise(resolve => setTimeout(resolve, ms))
}
}
// 连接socket
export const connect = () => new ChatWs()
// 默认实例
export default new ChatWs()

View File

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

View File

@ -208,6 +208,10 @@ export const createWindow = async (type, data) => {
autoHideMenuBar: true, autoHideMenuBar: true,
maximizable: false, maximizable: false,
} }
// pptlist的时候可以选择最大化
if (data.url == '/pptist'){
defOption.maximizable = true;
}
data.isConsole = true // 是否开启控制台 data.isConsole = true // 是否开启控制台
data.option = {...defOption, ...option} data.option = {...defOption, ...option}
const win = await toolWindow(type, data) const win = await toolWindow(type, data)

View File

@ -126,7 +126,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref, watch, reactive, getCurrentInstance, nextTick } from 'vue' import { onMounted, ref, watch, reactive, getCurrentInstance, nextTick, defineEmits } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { Plus, Delete } from '@element-plus/icons-vue' import { Plus, Delete } from '@element-plus/icons-vue'
@ -156,6 +156,7 @@ const { proxy } = getCurrentInstance()
const props = defineProps({ const props = defineProps({
currentCourse: Object, currentCourse: Object,
}) })
const emits = defineEmits(['getData'])
const isShow = ref(false) const isShow = ref(false)
const propsQueryCourseObj = route.query.courseObj;// const propsQueryCourseObj = route.query.courseObj;//
@ -460,10 +461,8 @@ const handleClassWorkFormQuizRemove = (index) =>{
await nextTick(); // DOM await nextTick(); // DOM
proxy.$refs["classWorkFormRef"].validate(async valid => { proxy.$refs["classWorkFormRef"].validate(async valid => {
if (valid) { if (valid) {
//
// const { chapterId } = await useGetHomework(courseObj.node) // const { chapterId } = await useGetHomework(courseObj.node)
// this.entpcourseid = chapterId // this.entpcourseid = chapterId
const cform = { const cform = {
id: 0, id: 0,
workdate: classWorkForm.workdate, // //web workdate: classWorkForm.workdate, // //web
@ -507,7 +506,7 @@ const handleClassWorkFormQuizRemove = (index) =>{
cform.workcodes = JSON.stringify({json: canvasJson, base64: canvasBase64}); cform.workcodes = JSON.stringify({json: canvasJson, base64: canvasBase64});
cform.entpcourseworklist = JSON.stringify([{'id':-1, 'score': '10'}]); cform.entpcourseworklist = JSON.stringify([{'id':-1, 'score': '10'}]);
try { try {
addClassworkReturnId(cform).then(() => { addClassworkReturnId(cform).then((res) => {
ElMessage({ type: 'success', message: '作业设计成功!'}); ElMessage({ type: 'success', message: '作业设计成功!'});
// //
classWorkForm.worktype = "课堂展示"; classWorkForm.worktype = "课堂展示";
@ -519,7 +518,8 @@ const handleClassWorkFormQuizRemove = (index) =>{
// //
classWorkForm.chooseWorkLists = []; // list classWorkForm.chooseWorkLists = []; // list
classWorkForm.whiteboardObj = ''; // ? // classWorkForm.whiteboardObj = ''; // ? //
classWorkForm.id = res
emits('getData',classWorkForm)
boardLoading.value = false boardLoading.value = false
}) })
} finally { } finally {
@ -531,7 +531,7 @@ const handleClassWorkFormQuizRemove = (index) =>{
cform.workcodes = JSON.stringify(classWorkForm.fileHomeworkList); cform.workcodes = JSON.stringify(classWorkForm.fileHomeworkList);
cform.entpcourseworklist = JSON.stringify([{'id':-2, 'score': '10'}]); cform.entpcourseworklist = JSON.stringify([{'id':-2, 'score': '10'}]);
try { try {
addClassworkReturnId(cform).then(() => { addClassworkReturnId(cform).then((res) => {
ElMessage({ type: 'success', message: '作业设计成功!'}); ElMessage({ type: 'success', message: '作业设计成功!'});
// //
classWorkForm.worktype = "常规作业"; classWorkForm.worktype = "常规作业";
@ -543,7 +543,8 @@ const handleClassWorkFormQuizRemove = (index) =>{
classWorkForm.chooseWorkLists = []; // list classWorkForm.chooseWorkLists = []; // list
classWorkForm.whiteboardObj = ''; // ? // classWorkForm.whiteboardObj = ''; // ? //
classWorkForm.fileHomeworkList = []; // list classWorkForm.fileHomeworkList = []; // list
classWorkForm.id = res
emits('getData',classWorkForm)
fileLoading.value = false fileLoading.value = false
}) })
} finally { } finally {
@ -574,7 +575,7 @@ const handleClassWorkFormQuizRemove = (index) =>{
console.log(cform,'提交的数据'); console.log(cform,'提交的数据');
if(cform.entpcourseworklist == '') return ElMessage({ type: 'warning', message: '请先添加作业资源!'}); if(cform.entpcourseworklist == '') return ElMessage({ type: 'warning', message: '请先添加作业资源!'});
addClassworkReturnId(cform).then(workres => { addClassworkReturnId(cform).then(res => {
ElMessage({ type: 'success', message: '作业设计成功!'}); ElMessage({ type: 'success', message: '作业设计成功!'});
// //
classWorkForm.worktype = "习题训练"; classWorkForm.worktype = "习题训练";
@ -585,6 +586,8 @@ const handleClassWorkFormQuizRemove = (index) =>{
// //
classWorkForm.chooseWorkLists = []; classWorkForm.chooseWorkLists = [];
classWorkForm.whiteboardObj = ''; // ? // classWorkForm.whiteboardObj = ''; // ? //
classWorkForm.id = res
emits('getData',classWorkForm)
// refresh the list // refresh the list
// //
// this.getClassWorkAllList(); // this.getClassWorkAllList();
@ -604,6 +607,7 @@ const handleClassWorkFormQuizRemove = (index) =>{
// // // //
// router.push({ path: '/classTaskAssign' }); // router.push({ path: '/classTaskAssign' });
// } // }
} }
}); });
}; };

View File

@ -19,7 +19,7 @@
</el-col> </el-col>
<el-col :span="7"> <el-col :span="7">
<el-form-item label="年份" label-width="70"> <el-form-item label="年份" label-width="70">
<el-select v-model="entpCourseWorkQueryParams.yearStr" placeholder="请选择" > <el-select v-model="entpCourseWorkQueryParams.yearStr" placeholder="请选择">
<el-option v-for="(item, index) in entpCourseWorkYearList" :key="index" :label="item.label" :value="item.value"></el-option> <el-option v-for="(item, index) in entpCourseWorkYearList" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -40,7 +40,7 @@
<el-button @click="handleQueryParamFromEntpCourseWork(1)"><el-icon><Search /></el-icon> </el-button> <el-button @click="handleQueryParamFromEntpCourseWork(1)"><el-icon><Search /></el-icon> </el-button>
</el-col> </el-col>
<el-col :span="5"> <el-col :span="5">
<el-button type="primary" @click="goToQuestUpload()">添加习题</el-button> <el-button v-if="!props.isHtml2canvas" type="primary" @click="goToQuestUpload()">添加习题</el-button>
</el-col> </el-col>
</el-row> </el-row>
<!-- 习题表格 --> <!-- 习题表格 -->
@ -58,9 +58,9 @@
</div> </div>
</template> </template>
<template #default="scope"> <template #default="scope">
<div @click="showExamAnalyseDrawer(scope.row)"> <div @click="showExamAnalyseDrawer(scope.row)" :id=" `screenshot-target-${scope.row.id}` " style="padding: 5px;">
<div style="overflow: hidden; text-overflow: ellipsis" v-html="scope.row.titleFormat"></div> <div style="overflow: hidden; text-overflow: ellipsis; padding: 2px;" v-html="scope.row.titleFormat"></div>
<div style="overflow: hidden; text-overflow: ellipsis; font-size: 0.9em; margin-top: 6px;" v-html="scope.row.workdescFormat"></div> <div style="overflow: hidden; text-overflow: ellipsis; font-size: 0.9em; margin-top: 6px; padding: 2px;" v-html="scope.row.workdescFormat"></div>
<el-col :span="24" style="display: flex"> <el-col :span="24" style="display: flex">
<div style="font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.entpname }} {{ scope.row.editusername }}</div> <div style="font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.entpname }} {{ scope.row.editusername }}</div>
<div style="margin-left: 30px; font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.worktag }}</div> <div style="margin-left: 30px; font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.worktag }}</div>
@ -70,7 +70,8 @@
</el-table-column> </el-table-column>
<el-table-column width="100"> <el-table-column width="100">
<template #default="scope"> <template #default="scope">
<div> <el-button v-if="props.isHtml2canvas" type="primary" @click="captureScreenshot(scope.row.id)">选取该题</el-button>
<div v-else>
<el-button type="primary" @click="handleClassWorkQuizAdd('entpcourseworklist', scope.row.id)">添加</el-button> <el-button type="primary" @click="handleClassWorkQuizAdd('entpcourseworklist', scope.row.id)">添加</el-button>
<div style="padding: 2px;"></div> <div style="padding: 2px;"></div>
<el-button type="warning" @click="handleImportSingleDlg(scope.row)">纠错</el-button> <el-button type="warning" @click="handleImportSingleDlg(scope.row)">纠错</el-button>
@ -117,6 +118,7 @@
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue' import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import html2canvas from 'html2canvas';
import { listEntpcoursework, listEntpcourseworkLocal } from '@/api/education/entpCourseWork' import { listEntpcoursework, listEntpcourseworkLocal } from '@/api/education/entpCourseWork'
import { listEvaluationclue } from '@/api/classTask' import { listEvaluationclue } from '@/api/classTask'
@ -136,7 +138,7 @@ const router = useRouter()
// emit // emit
const emit = defineEmits(['addQuiz']) let emit = defineEmits(['addQuiz', 'addQuizImgBs64'])
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const userStore = useUserStore().user const userStore = useUserStore().user
const { const {
@ -150,6 +152,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => ({}) default: () => ({})
}, },
isHtml2canvas: {//
type: Boolean,
default: () => false
},
}) })
const knowledgePointProps = ref({value: 'thirdId', label: 'title'}); const knowledgePointProps = ref({value: 'thirdId', label: 'title'});
@ -440,6 +446,22 @@ const handleDelete = async(item, index) => {
// ElMessage('') // ElMessage('')
// } // }
}; };
/**
* 把该题区域id 获取为截屏区域
* @param id 试题id
*/
const captureScreenshot = (id) => {
const targetElement = document.getElementById('screenshot-target-' + id);
html2canvas(targetElement).then(canvas => {
// canvasURL
const screenshotUrl = canvas.toDataURL('image/png');
//
// console.log(screenshotUrl);
emit('addQuizImgBs64', screenshotUrl);
});
}
// //
const debounceQueryData = debounce(() => { const debounceQueryData = debounce(() => {
console.log("防抖 加载数据中...") console.log("防抖 加载数据中...")

View File

@ -0,0 +1,115 @@
<template>
<div class="page">
<div class="page-resource">
<div class="page-center">
<el-tabs v-model="activeAptTab" style="height: 100%;">
<el-tab-pane label="自主搜题" name="自主搜题" class="prepare-center-zzst">
<SearchQuestion :bookobj="courseObj" :isHtml2canvas="true" @addQuizImgBs64="handleaddQuizImgBs64" />
</el-tab-pane>
<el-tab-pane label="校本题库" name="校本题库" class="prepare-center-xbtk">
<SchoolQuestion />
</el-tab-pane>
<el-tab-pane label="个人题库" name="个人题库" class="prepare-center-grst">
<MyQuestion :bookobj="courseObj" :isHtml2canvas="true" @addQuizImgBs64="handleaddQuizImgBs64"/>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, watch, reactive, getCurrentInstance, nextTick } from 'vue'
import MyQuestion from '@/views/classTask/newClassTaskAssign/myQuestion/index.vue'
import SchoolQuestion from '@/views/classTask/newClassTaskAssign/schoolQuestion/index.vue'
import SearchQuestion from '@/views/classTask/newClassTaskAssign/searchQuestion/index.vue'
import { sessionStore } from '@/utils/store'
import { useRouter, useRoute } from 'vue-router'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore().user
const emit = defineEmits(['update']);
const courseObj = reactive({
// : id,id,id,
textbookId: '',
levelFirstId: '',
levelSecondId: '',
coursetitle:'',
node: null, //
//
})
const activeAptTab = ref("自主搜题");
onMounted(() => {
const curNode = sessionStore.get('subject.curNode')
courseObj.textbookId = curNode.rootid
courseObj.levelFirstId = curNode.parentNode.id
courseObj.levelSecondId = curNode.id
courseObj.coursetitle = curNode.itemtitle,
courseObj.node = curNode
})
const handleaddQuizImgBs64 = (quizbs64) => {
emit('update', quizbs64)
}
defineExpose({
})
</script>
<style scoped lang="scss">
.page {
height: 100%;
.page-resource {
user-select: none;
height: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex: 1;
:deep(.el-tabs__nav) {
.el-tabs__item{
font-weight: bold;
font-size: 18px;
}
}
}
.page-center{
flex: 1;
//min-width: calc(100% - 675px);
height: 100%;
padding: 0 5px;
margin: 0 5px;
overflow: hidden;
border-radius: 10px;
background-color: white;
.prepare-center-zzst{
height: 100%;
display: flex;
flex-direction: column;
}
.prepare-center-xbtk{
height: 100%;
}
.prepare-center-grst{
height: 100%;
}
.upload-homework{
padding: 20px;
box-sizing: border-box;
}
}
}
</style>

View File

@ -60,7 +60,7 @@
<div class="page-table" > <div class="page-table" >
<el-table <el-table
:data="workResource.entpCourseWorkList" :data="workResource.entpCourseWorkList"
style="width: 100%; height: calc(100% - 55px);" style="width: 100%; height: calc(100% - 50px);"
v-loading="pageParams.loading" v-loading="pageParams.loading"
> >
<el-table-column type="index" width="60" /> <el-table-column type="index" width="60" />
@ -71,9 +71,9 @@
</div> </div>
</template> </template>
<template #default="scope"> <template #default="scope">
<div @click="showExamAnalyseDrawer(scope.row)"> <div @click="showExamAnalyseDrawer(scope.row)" :id=" `screenshot-target-${scope.row.id}` " style="padding: 5px;">
<div style="overflow: hidden; text-overflow: ellipsis" v-html="scope.row.titleFormat"></div> <div style="overflow: hidden; text-overflow: ellipsis; padding: 2px;" v-html="scope.row.titleFormat"></div>
<div style="overflow: hidden; text-overflow: ellipsis; font-size: 0.9em; margin-top: 6px;" v-html="scope.row.workdescFormat"></div> <div style="overflow: hidden; text-overflow: ellipsis; font-size: 0.9em; margin-top: 6px; padding: 2px;" v-html="scope.row.workdescFormat"></div>
<el-col :span="24" style="display: flex"> <el-col :span="24" style="display: flex">
<div style="font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.entpname }} {{ scope.row.editusername }}</div> <div style="font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.entpname }} {{ scope.row.editusername }}</div>
<div style="margin-left: 30px; font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.worktag }}</div> <div style="margin-left: 30px; font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.worktag }}</div>
@ -83,18 +83,19 @@
</el-table-column> </el-table-column>
<el-table-column align="left" width="100"> <el-table-column align="left" width="100">
<template #default="scope"> <template #default="scope">
<el-button type="primary" @click="handleClassWorkQuizAdd('entpcourseworklist', scope.row.id)">添加</el-button> <el-button v-if="props.isHtml2canvas" type="primary" @click="captureScreenshot(scope.row.id)">选取该题</el-button>
<el-button v-else type="primary" @click="handleClassWorkQuizAdd('entpcourseworklist', scope.row.id)">添加</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 分页--> <!-- 分页-->
<div style="height: 55px;"> <div style="height: 50px;">
<el-pagination <el-pagination
v-show="pageParams.total > 0" v-show="pageParams.total > 0"
v-model:page="paginationParams.pageNum" v-model:page="paginationParams.pageNum"
v-model:limit="paginationParams.pageSize" v-model:limit="paginationParams.pageSize"
:total="pageParams.total" :total="pageParams.total"
:style="{ position: 'relative', 'margin-top': '5px' }" :style="{ position: 'relative', 'padding-top': '10px' }"
@change="getPaginationList" /> @change="getPaginationList" />
</div> </div>
</div> </div>
@ -104,7 +105,7 @@
</template> </template>
<script setup> <script setup>
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import html2canvas from 'html2canvas';
import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue' import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue'
import {listEntpcoursework, listEntpcourseworkNew, getEntpcoursework} from '@/api/education/entpCourseWork' import {listEntpcoursework, listEntpcourseworkNew, getEntpcoursework} from '@/api/education/entpCourseWork'
@ -122,7 +123,7 @@ import useUserStore from '@/store/modules/user'
import useClassTaskStore from '@/store/modules/classTask' import useClassTaskStore from '@/store/modules/classTask'
// emit // emit
const emit = defineEmits(['addQuiz']) let emit = defineEmits(['addQuiz', 'addQuizImgBs64'])
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const userStore = useUserStore().user const userStore = useUserStore().user
const { const {
@ -136,6 +137,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => ({}) default: () => ({})
}, },
isHtml2canvas: {//
type: Boolean,
default: () => false
},
}) })
const entpCourseWorkPointList = ref([ const entpCourseWorkPointList = ref([
@ -183,6 +188,7 @@ const workResource = reactive({
}); // }); //
onMounted(() => { onMounted(() => {
console.log('entpCourseWorkTypeList', entpCourseWorkTypeList);
debounceQueryData(); // debounceQueryData(); //
}) })
@ -427,6 +433,20 @@ const getPaginationList = ( page, limit ) => {
// ElMessage('') // ElMessage('')
// } // }
}; };
/**
* 把该题区域id 获取为截屏区域
* @param id 试题id
*/
const captureScreenshot = (id) => {
const targetElement = document.getElementById('screenshot-target-' + id);
html2canvas(targetElement).then(canvas => {
// canvasURL
const screenshotUrl = canvas.toDataURL('image/png');
//
// console.log(screenshotUrl);
emit('addQuizImgBs64', screenshotUrl);
});
}
// //

View File

@ -16,6 +16,16 @@
<el-table-column align="center" prop="worktype" width="100"></el-table-column> <el-table-column align="center" prop="worktype" width="100"></el-table-column>
<el-table-column align="center" prop="worktag" width="120"></el-table-column> <el-table-column align="center" prop="worktag" width="120"></el-table-column>
</el-table> </el-table>
<!-- 分页-->
<div style="height: 48px;">
<el-pagination
v-show="paginationParams.total > 0"
v-model:page="paginationParams.pageNum"
v-model:limit="paginationParams.pageSize"
:total="paginationParams.total"
:style="{ position: 'relative', 'padding': '8px 20px 0 0' }"
@change="changePageNum" />
</div>
<!-- 试题详细信息 --> <!-- 试题详细信息 -->
<examDetailsDrawer ref="examDetailsDrawerRef"></examDetailsDrawer> <examDetailsDrawer ref="examDetailsDrawerRef"></examDetailsDrawer>
@ -54,11 +64,13 @@ const { proxy } = getCurrentInstance()
const props = defineProps({ const props = defineProps({
listExamQuestion: {type: Array}, listExamQuestion: {type: Array},
loading: {type: Boolean} loading: {type: Boolean},
paginationParams: {type: Object},
}) })
const activeExamInfoDrawer = ref(false); const activeExamInfoDrawer = ref(false);
const activeExam = ref({}); const activeExam = ref({});
const emit = defineEmits(['updatePageNum'])
const showExamAnalyseDrawer = (row) => { const showExamAnalyseDrawer = (row) => {
nextTick(() => { nextTick(() => {
@ -69,13 +81,17 @@ const showExamAnalyseDrawer = (row) => {
}) })
} }
const changePageNum = (pageNum) => {
emit('updatePageNum', pageNum);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.table-main { .table-main {
width: 100%; width: 100%;
height: 100%; height: calc(100% - 48px);
text-align: left; text-align: left;
.main-title { .main-title {

View File

@ -47,10 +47,13 @@
</el-select> </el-select>
</div> </div>
<div :style="{'width': (viewportWidth - 400) + 'px','height': (viewportHeight-38) + 'px','overflow-y': 'auto'}"> <!-- <div :style="{'width': (viewportWidth - 400) + 'px','height': (viewportHeight-38) + 'px','overflow-y': 'auto'}"> -->
<div style="width: 100%; height: 100%; overflow-y: auto; border-radius: 10px;">
<examReview <examReview
:loading="loading" :loading="loading"
:listExamQuestion="listExamQuestion" :listExamQuestion="listExamQuestion"
:paginationParams="paginationParams"
@updatePageNum="updatePageNum"
v-if="curTask.viewkey=='真题回顾' " v-if="curTask.viewkey=='真题回顾' "
/> />
@ -77,12 +80,15 @@ import ChooseTextbook from '@/components/choose-textbook/index.vue'
import {listEntpcoursework, listEntpcourseworkNew} from '@/api/education/entpCourseWork' import {listEntpcoursework, listEntpcourseworkNew} from '@/api/education/entpCourseWork'
import { processList } from '@/hooks/useProcessList' import { processList } from '@/hooks/useProcessList'
import { JYApiListCT} from "@/utils/examQuestion/jyeoo" import { JYApiListCT} from "@/utils/examQuestion/jyeoo"
import useClassTaskStore from '@/store/modules/classTask'
import examReview from './container/examReview.vue' import examReview from './container/examReview.vue'
import pointAnalysis from './container/pointAnalysis.vue' import pointAnalysis from './container/pointAnalysis.vue'
import examMocks from './container/examMocks.vue' import examMocks from './container/examMocks.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const classTaskStore = useClassTaskStore();
const {proxy} = getCurrentInstance(); const {proxy} = getCurrentInstance();
const sourceStore = useResoureStore(); const sourceStore = useResoureStore();
const viewportHeight = ref(0); const viewportHeight = ref(0);
@ -91,6 +97,11 @@ const viewportWidth = ref(0);
const curNode = ref({}); const curNode = ref({});
// //
const listExamQuestion = ref([]); const listExamQuestion = ref([]);
const paginationParams = reactive({
pageNum: 1,
pageSize: 10,
total: 0,
});
const loading = ref(false); const loading = ref(false);
const curTask = reactive({ const curTask = reactive({
viewkey: '真题回顾', viewkey: '真题回顾',
@ -107,6 +118,22 @@ const listWorkType = ref([{
value: 0, value: 0,
}]); }]);
/**
* @desc: 翻页
* @return: {*}
* @param {*} pageNum
*/
const updatePageNum = (pageNum) => {
paginationParams.pageNum = pageNum;
queryExamQuestionByParams();
}
/**
* @desc: 具体获取试题及格式化
* @return: {*}
* @param {*} params
*/
const getCourseWorkList = async (params) => { const getCourseWorkList = async (params) => {
const res = await listEntpcourseworkNew(params); const res = await listEntpcourseworkNew(params);
if(res.data == null) { if(res.data == null) {
@ -115,7 +142,14 @@ const getCourseWorkList = async (params) => {
return; return;
} }
listExamQuestion.value = res.data; listExamQuestion.value = res.data;
// queryParams.total = res.total; //
const total = parseInt(res.msg)
if (!isNaN(total)) {
paginationParams.total = total;
} else {
console.error('无法将 res.msg 转换为数字');
paginationParams.total = 0; //
}
// //
processList(listExamQuestion.value); processList(listExamQuestion.value);
} }
@ -132,7 +166,8 @@ const getData = async (data) => {
loading.value = true; loading.value = true;
// 1. // 1.
getWorkType(data.node); //getWorkType(data.node);
listWorkType.value = classTaskStore.entpCourseWorkTypeList;
// 2. // 2.
curNode.value = data.node; curNode.value = data.node;
@ -166,6 +201,8 @@ const getData = async (data) => {
edusubject: curNode.value.edusubject, edusubject: curNode.value.edusubject,
edustage: curNode.value.edustage, edustage: curNode.value.edustage,
sectionName: curNode.value.itemtitle, sectionName: curNode.value.itemtitle,
currentPage: paginationParams.pageNum,
pageSize: paginationParams.pageSize,
} }
await getCourseWorkList(params); await getCourseWorkList(params);
loading.value = false; loading.value = false;
@ -181,6 +218,7 @@ const changeTaskView = (item, key) => {
curTask.viewkey = item; curTask.viewkey = item;
} }
// 使, classTaskStore
const getWorkType = async (data) => { const getWorkType = async (data) => {
const selName = `${data.edustage}${data.edusubject}` const selName = `${data.edustage}${data.edusubject}`
const curName = `${curNode.value.edustage}${curNode.value.edusubject}` const curName = `${curNode.value.edustage}${curNode.value.edusubject}`
@ -231,6 +269,8 @@ const queryExamQuestionByParams = async () => {
edusubject: curNode.value.edusubject, edusubject: curNode.value.edusubject,
edustage: curNode.value.edustage, edustage: curNode.value.edustage,
sectionName: curNode.value.itemtitle, sectionName: curNode.value.itemtitle,
currentPage: paginationParams.pageNum,
pageSize: paginationParams.pageSize,
} }
await getCourseWorkList(params); await getCourseWorkList(params);
loading.value = false; loading.value = false;

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog v-model="isDialog" :show-close="false" width="900" destroy-on-close> <el-dialog v-model="isDialog" :show-close="false" width="900" append-to-body destroy-on-close>
<template #header> <template #header>
<div class="custom-header flex"> <div class="custom-header flex">
<span>选择{{ title }}</span> <span>选择{{ title }}</span>
@ -15,8 +15,10 @@
</div> </div>
<div class="content-list"> <div class="content-list">
<ul> <ul>
<li v-for="(item, index) in fileList" :class="activeIndex == index ? 'li-active' : ''" @click="clickItem(index, item)"> <li v-for="(item, index) in fileList" :class="activeIndex == index ? 'li-active' : ''"
@click="clickItem(index, item)">
<el-image class="img" :src="url" /> <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> <el-text truncated>{{ item.fileName }}</el-text>
</li> </li>
</ul> </ul>
@ -24,8 +26,8 @@
</div> </div>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-upload class="upload-demo" :action="uploadFileUrl" :limit="1" :show-file-list="false" <el-upload class="upload-demo" :action="uploadFileUrl" :limit="1" :show-file-list="false" :headers="headers"
:headers="headers" :on-success="onSuccess"> :on-success="onSuccess">
<el-button type="primary">上传</el-button> <el-button type="primary">上传</el-button>
</el-upload> </el-upload>
<div> <div>
@ -37,6 +39,31 @@
</div> </div>
</template> </template>
</el-dialog> </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>
</el-dialog>
</template> </template>
<script setup> <script setup>
@ -47,6 +74,7 @@ import { sessionStore } from '@/utils/store'
import { dataSetJson } from '@/utils/comm.js' import { dataSetJson } from '@/utils/comm.js'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { getFileSuffix } from '@/utils/ruoyi.js'
import emitter from '@/utils/mitt'; import emitter from '@/utils/mitt';
const userInfo = useUserStore().user const userInfo = useUserStore().user
@ -56,6 +84,7 @@ const headers = ref({ Authorization: "Bearer " + getToken() });
const url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F11044b08-04c1-41a0-a453-1fd20b58a614%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1732953359&t=7ab1d1b3a903db85b1149914407aea35' const url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F11044b08-04c1-41a0-a453-1fd20b58a614%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1732953359&t=7ab1d1b3a903db85b1149914407aea35'
const isDialog = defineModel() const isDialog = defineModel()
const prevVisible = ref(false)
const props = defineProps({ const props = defineProps({
modeType: { modeType: {
@ -99,15 +128,14 @@ const activeIndex = ref(0)
const dataset_id = ref('') const dataset_id = ref('')
// //
const onSuccess = async (response) =>{ const onSuccess = async (response) => {
console.log(response,'response')
let data = { let data = {
url: response.url, url: response.url,
dataset_id: dataset_id.value dataset_id: dataset_id.value
} }
const res = await completion(data) const res = await completion(data)
if(res.data.code != 200) return if (res.data.code != 200) return
let docData = { let docData = {
fileUrl: response.url, fileUrl: response.url,
fileId: response.file.id, fileId: response.file.id,
@ -127,11 +155,11 @@ const curNode = reactive({})
const fileList = ref([]) const fileList = ref([])
const curFile = reactive({}) const curFile = reactive({})
const getList = () =>{ const getList = () => {
docList({ docList({
userId: userInfo.userId, userId: userInfo.userId,
dataset_id: dataset_id.value dataset_id: dataset_id.value
}).then( res =>{ }).then(res => {
fileList.value = [...res.rows] fileList.value = [...res.rows]
Object.assign(curFile, fileList.value[0]) Object.assign(curFile, fileList.value[0])
}) })
@ -140,12 +168,16 @@ const getList = () =>{
const clickItem = (index, item) => { const clickItem = (index, item) => {
activeIndex.value = index activeIndex.value = index
Object.assign(curFile, item) Object.assign(curFile, item)
emitter.emit('curFile',item) emitter.emit('curFile', item)
} }
const prevItem = reactive({})
const onPrevItem = (item) => {
Object.assign(prevItem, item)
prevVisible.value = true
}
onMounted(() => {
onMounted(() =>{
let data = sessionStore.get('subject.curNode') let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data); Object.assign(curNode, data);
@ -155,7 +187,6 @@ onMounted(() =>{
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.custom-header { .custom-header {
justify-content: space-between; justify-content: space-between;
@ -192,9 +223,11 @@ onMounted(() =>{
overflow: hidden; overflow: hidden;
margin-right: 20px; margin-right: 20px;
margin-bottom: 10px; margin-bottom: 10px;
position: relative;
overflow: hidden;
.img { .img {
width: 100px; width: 100%;
height: 130px; height: 130px;
border: solid #ccc 1px; border: solid #ccc 1px;
margin-bottom: 10px; margin-bottom: 10px;
@ -203,6 +236,10 @@ onMounted(() =>{
&:hover { &:hover {
background: #E0EAFF; background: #E0EAFF;
} }
&:hover .prev-btn {
transform: translate(-50%, -50%)
}
} }
.li-active { .li-active {
@ -212,9 +249,20 @@ onMounted(() =>{
} }
} }
} }
.dialog-footer{
.dialog-footer {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
.prev-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) translateY(-110px);
/* 按钮初始位置在容器外 */
transition: transform 0.3s ease-in-out;
/* 设置过渡效果 */
}
</style> </style>

View File

@ -14,8 +14,14 @@ const pdfUrl = ref('')
onMounted(async () =>{ onMounted(async () =>{
await nextTick() await nextTick()
const { fileurl } = sessionStore.get('subject.curBook')
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt','.pdf') let data = sessionStore.get('subject.curBook')
let fileurl = data.fileurl
if(fileurl == ''){
fileurl = `${data.edustage}-${data.edusubject}-课标.txt`
}
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf')
}) })
</script> </script>

View File

@ -10,7 +10,7 @@
<el-button type="primary" @click="onchange('/model/newClassTaskAssign')">作业管理</el-button> <el-button type="primary" @click="onchange('/model/newClassTaskAssign')">作业管理</el-button>
<el-button type="success" @click="onchange('/model/teaching')">教材研读</el-button> <el-button type="success" @click="onchange('/model/teaching')">教材研读</el-button>
<el-button type="info" @click="onchange('/model/design')">教学框架设计</el-button> <el-button type="info" @click="onchange('/model/design')">教学框架设计</el-button>
<el-button type="success" @click="openPPTist">打开PPTist</el-button> <!-- <el-button type="success" @click="openPPTist">打开PPTist</el-button>-->
<el-button type="info" @click="onchange('/model/examination')">考试分析</el-button> <el-button type="info" @click="onchange('/model/examination')">考试分析</el-button>
<el-button type="primary" v-menus="dt.menus">测试</el-button> <el-button type="primary" v-menus="dt.menus">测试</el-button>
<el-button type="success" @click="onchange('/model/aiKolors')">文生图片</el-button> <el-button type="success" @click="onchange('/model/aiKolors')">文生图片</el-button>
@ -52,7 +52,7 @@ import { useRouter } from 'vue-router'
import { Plus, Refresh, Upload, Files, UploadFilled } from '@element-plus/icons-vue' import { Plus, Refresh, Upload, Files, UploadFilled } from '@element-plus/icons-vue'
import useUserStore from '@/store/modules/user' // import useUserStore from '@/store/modules/user' //
import msgUtils from '@/plugins/modal' // import msgUtils from '@/plugins/modal' //
import { createWindow, sessionStore } from '@/utils/tool' // import { createWindow } from '@/utils/tool' //
import * as API_smarttalk from '@/api/file' // api import * as API_smarttalk from '@/api/file' // api
import * as API_entpcourse from '@/api/education/entpcourse' // api import * as API_entpcourse from '@/api/education/entpcourse' // api
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api
@ -61,7 +61,6 @@ import { sessionStore } from '@/utils/store' // 学科名字文生图
// //
import ChooseTextbook from '@/components/choose-textbook/index.vue' import ChooseTextbook from '@/components/choose-textbook/index.vue'
import { menusEvent } from '@/plugins/vue3-menus' // import { menusEvent } from '@/plugins/vue3-menus' //
const router = useRouter() const router = useRouter()
const userStore = useUserStore() // const userStore = useUserStore() //
@ -243,7 +242,7 @@ const HTTP_SERVER_API = (type, params = {}) => {
ppttype: 'file', ppttype: 'file',
title: enpt.coursetitle, title: enpt.coursetitle,
fileurl: '', fileurl: '',
filetype: 'aptist', filetype: 'aippt',
datacontent: '', datacontent: '',
filekey: '', filekey: '',
filetag: '', filetag: '',
@ -302,7 +301,7 @@ const handleAll = async(type, row) =>{
break; break;
} }
case 'open': { // -pptist case 'open': { // -pptist
if (row.filetype != 'aptist') return msgUtils.msgWarning('暂不支持该类型文件操作!') if (row.filetype != 'aippt') return msgUtils.msgWarning('暂不支持该类型文件操作!')
sessionStore.set('curr.resource', row) // sessionStore.set('curr.resource', row) //
createWindow('open-win', { createWindow('open-win', {
url: '/pptist', // url: '/pptist', //
@ -327,7 +326,7 @@ const handleAll = async(type, row) =>{
// icons type svg // icons type svg
const getIcon = (o, type) => { const getIcon = (o, type) => {
let icon = typeof o == 'string' ? o : o?.filetype let icon = typeof o == 'string' ? o : o?.filetype
if (['aptist'].includes(o?.filetype)) icon = 'pptx' if (['aippt'].includes(o?.filetype)) icon = 'pptx'
if (!!type) { // icon if (!!type) { // icon
switch(type) { switch(type) {
case 'svg': // svg case 'svg': // svg

View File

@ -57,11 +57,11 @@
</el-row> </el-row>
<div> <div>
<el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 0">上一步</el-button> <el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 0">上一步</el-button>
<el-button style="margin-bottom: 5px;" type="primary" @click="outlineCreatePPT()">生成PPT</el-button> <el-button style="margin-bottom: 5px;" type="primary" v-loading="createPPTLoading" @click="outlineCreatePPT()">生成PPT</el-button>
</div> </div>
</el-card> </el-card>
<el-card v-if="activeStep === 2"> <el-card v-if="activeStep === 2">
<el-progress :percentage="30" type="circle" v-if="percentage === 30"></el-progress> <el-progress :percentage="percentage" type="circle"></el-progress>
</el-card> </el-card>
</div> </div>
</div> </div>
@ -79,6 +79,7 @@ import {
import CryptoJS from "crypto-js" import CryptoJS from "crypto-js"
import { getSignature } from "@/utils/index.js"; import { getSignature } from "@/utils/index.js";
import {sessionStore} from "@/utils/store";
let appId = "01ec9aa3"; let appId = "01ec9aa3";
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy"; let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
@ -98,8 +99,8 @@ let secondArray = ref([]); //大纲的文字部分
const backGroundList = ref([]); const backGroundList = ref([]);
let subjectdata = sessionStore.get('subject.curNode')
const inputTheme = ref("高中语文《沁园春雪》的授课课件"); // const inputTheme = ref(subjectdata.edustage + subjectdata.edusubject + "《" + subjectdata.itemtitle + "》的授课课件"); //
const inputRequire = ref("") // const inputRequire = ref("") //
const activeStep = ref(0); // const activeStep = ref(0); //
const combined = ref('') // ppt const combined = ref('') // ppt
@ -109,6 +110,8 @@ const status = ref("init");
const percentage = ref(0); const percentage = ref(0);
const createPPTLoading = ref(false);
const getBackgrounds = () => { const getBackgrounds = () => {
treeData.value = []; treeData.value = [];
getBackGroundV2().then((res) => { getBackGroundV2().then((res) => {
@ -126,6 +129,8 @@ const outlineData = ref({
// templateId: 'auto', // ppt // templateId: 'auto', // ppt
author: 'AIX平台', author: 'AIX平台',
isFigure: false, // isFigure: false, //
search: true,
language: "cn"
} }
) )
@ -145,20 +150,17 @@ function updateStagingData(role, newData) {
const outlineCreatePPT = () => { const outlineCreatePPT = () => {
const newOutlineData = { ...outlineData.value, }; const newOutlineData = { ...outlineData.value, };
newOutlineData.query = outputText.value; newOutlineData.query = outputText.value;
createPPTLoading.value = true;
createPPTV2(newOutlineData).then((res) => { createPPTV2(newOutlineData).then((res) => {
console.log(res, "正在生成中"); console.log(res, "正在生成中");
createPPTLoading.value = false;
activeStep.value = 2 activeStep.value = 2
const checkProgress = () => { const checkProgress = () => {
getProgressV2(res.sid).then((response) => { getProgressV2(res.sid).then(response => {
percentage.value = response.process; percentage.value = Math.round(response?.donePages*100/response?.totalPages);
if (response && response.pptUrl && response.pptUrl.length > 4) { if (response.pptStatus === "done") {
console.log('PPT',response) emit('addSuccess',{...res,url:response.pptUrl})
// window.location.href = response.data.pptUrl;
//URLURL
// let url = "https://bjcdn.openstorage.cn/xinghuo-privatedata/%2Ftmp/apiTempFiledf28bf990a4c40ffb7477ed4b65392c27232357022409613439/%E3%80%8A%E9%9D%99%E5%A5%B3%E3%80%8B%E6%B7%B1%E5%BA%A6%E8%A7%A3%E8%AF%BB%E4%B8%8E%E7%A0%94%E7%A9%B6.pptx"
emit('addSuccess',res)
ElMessage.success("生成成功"); ElMessage.success("生成成功");
} else { } else {
const sleepTime = 2000; const sleepTime = 2000;
@ -265,7 +267,6 @@ function webSocketSend(ws, data) {
function result1(resultData) { function result1(resultData) {
let jsonData = JSON.parse(resultData); let jsonData = JSON.parse(resultData);
console.log(jsonData)
outputText.value += jsonData.payload.choices.text[0].content; outputText.value += jsonData.payload.choices.text[0].content;
const div = document.querySelector('.paragraphs'); const div = document.querySelector('.paragraphs');
if (div) { if (div) {

View File

@ -78,7 +78,7 @@
</div> </div>
</el-dialog> </el-dialog>
<!-- im-chat 聊天组件 --> <!-- im-chat 聊天组件 -->
<im-chat ref="imChatRef" v-if="visible" @change="chatChange" /> <!-- <im-chat ref="imChatRef" v-if="visible" @change="chatChange" /> -->
</template> </template>
<script setup> <script setup>
@ -90,7 +90,7 @@ import vueQr from 'vue-qr/src/packages/vue-qr.vue' // 插件: 二维码
import imChat from '@/views/tool/components/imChat.vue' // im-chat- import imChat from '@/views/tool/components/imChat.vue' // im-chat-
import MsgEnum from '@/plugins/imChat/msgEnum' // -(nuem) import MsgEnum from '@/plugins/imChat/msgEnum' // -(nuem)
import * as commUtil from '@/utils/comm' // - import * as commUtil from '@/utils/comm' // -
import { toLinkWeb, getStaticUrl } from '@/utils/tool' // - import { toLinkWeb, createWindow, getStaticUrl, sessionStore } from '@/utils/tool' // -
import * as Http_ClassManage from '@/api/classManage' // api import * as Http_ClassManage from '@/api/classManage' // api
import * as Http_Classcourse from '@/api/teaching/classcourse' // api import * as Http_Classcourse from '@/api/teaching/classcourse' // api
@ -103,7 +103,7 @@ let baseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
const userStore = useUserStore() const userStore = useUserStore()
const visible = ref(false) // const visible = ref(false) //
const myClassActive = ref({}) // APT const myClassActive = ref({}) // APT
const imChatRef = ref(null) // im-chat ref // const imChatRef = ref(null) // im-chat ref
const emit = defineEmits(['close']) const emit = defineEmits(['close'])
const classForm = reactive({ // () const classForm = reactive({ // ()
form: {}, itemOption: [], option: {} form: {}, itemOption: [], option: {}
@ -177,7 +177,7 @@ const initData = () => {
teacherForm.itemOption = [ teacherForm.itemOption = [
// { label: '', prop: 'classid' }, // { label: '', prop: 'classid' },
// { label: '', prop: 'classcourseid' }, // { label: '', prop: 'classcourseid' },
{ label: '老师扫码', prop: 'qrUrl', show: false }, // { label: '', prop: 'qrUrl', show: false },
{ label: '手机登录', prop: 'mobile', show: false }, { label: '手机登录', prop: 'mobile', show: false },
{ label: '故障备用', prop: 'backup', show: false }, { label: '故障备用', prop: 'backup', show: false },
] ]
@ -257,18 +257,22 @@ const createClasscourse = async () => {
entpcourseid, evalid, coursetitle, entpcourseid, evalid, coursetitle,
plandate: curDate, opendate: curDate plandate: curDate, opendate: curDate
} }
// teacherForm.form.classcourseid = 100
teacherForm.form.classcourseid = await Http_Classcourse.addClasscourseReturnId(params) teacherForm.form.classcourseid = await Http_Classcourse.addClasscourseReturnId(params)
dt.loading = false dt.loading = false
// getClasscourseList('update') // // getClasscourseList('update') //
ElMessage.success('创建课程-成功') let msgEl = ElMessage.success('创建课程-成功')
// -pptList // -pptList
if (myClassActive.value.filetype == 'aptist') { if (myClassActive.value.filetype == 'aippt') {
const msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0}) setTimeout(() => {
msgEl.close()
msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0})
setTimeout(() => { setTimeout(() => {
msgEl.close() msgEl.close()
const classcourse = {...params, id: teacherForm.form.classcourseid} const classcourse = {...params, id: teacherForm.form.classcourseid}
openPublicScreen(classcourse) openPublicScreen(classcourse)
}, 1500); }, 2000);
}, 1000);
} }
} }
// //
@ -296,14 +300,23 @@ const removeClasscourse = async () => {
const classTeachingStart = async () => { const classTeachingStart = async () => {
const { classcourseid:id } = teacherForm.form const { classcourseid:id } = teacherForm.form
if (id) { // if (id) { //
// -pptList
if (myClassActive.value.filetype == 'aptist') {
const msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0})
setTimeout(() => {
msgEl.close()
openPublicScreen({id})
}, 2000);
}else {
const url = `/teaching/classteaching?classcourseid=${id}&actor=classTeachingOnPublicScreen` const url = `/teaching/classteaching?classcourseid=${id}&actor=classTeachingOnPublicScreen`
toLinkWeb(url) // web- toLinkWeb(url) // web-
visible.value = false // visible.value = false //
handleClose() // im-chat }
} }
} }
// //
const getQrUrl = async() => { const getQrUrl = async() => {
// console.log('')
const { classcourseid:id } = teacherForm.form const { classcourseid:id } = teacherForm.form
const { userName, userId } = userStore.user const { userName, userId } = userStore.user
if (!id||!userName) return if (!id||!userName) return
@ -336,6 +349,7 @@ const getQrUrl = async() => {
// //
const openPublicScreen = (classcourse) => { const openPublicScreen = (classcourse) => {
console.log('打开公屏', classcourse)
const resource = toRaw(myClassActive.value) const resource = toRaw(myClassActive.value)
sessionStore.set('curr.resource', resource) // sessionStore.set('curr.resource', resource) //
sessionStore.set('curr.classcourse', classcourse) // sessionStore.set('curr.classcourse', classcourse) //
@ -346,6 +360,7 @@ const openPublicScreen = (classcourse) => {
sessionStore.set('curr.classcourse', null) // sessionStore.set('curr.classcourse', null) //
} }
}) })
visible.value = false //
} }
// //
@ -386,12 +401,14 @@ watch(() => classForm.form.classid, (val)=> {
// -id // -id
watch(() => teacherForm.form.classcourseid, (val) => { watch(() => teacherForm.form.classcourseid, (val) => {
const bool = !!val const bool = !!val
const isApt = myClassActive.filetype=='apt'
// - // -
bool && getQrUrl() isApt && bool && getQrUrl()
// id // id
teacherForm.itemOption.forEach(o => { teacherForm.itemOption.forEach(o => {
// id // id
if (['qrUrl','backup'].includes(o.prop)) o.show = bool const arr = isApt ? ['qrUrl','backup'] : ['backup']
if (arr.includes(o.prop)) o.show = bool
// id // id
if (['mobile'].includes(o.prop)) o.show = !bool if (['mobile'].includes(o.prop)) o.show = !bool
}) })

View File

@ -332,10 +332,6 @@ export default {
const { id, rootid } = sessionStore.get('subject.curNode') const { id, rootid } = sessionStore.get('subject.curNode')
const path="/teaching/aptindex?id="+items.fileId + "&unitId=" + id + "&bookId=" + rootid; const path="/teaching/aptindex?id="+items.fileId + "&unitId=" + id + "&bookId=" + rootid;
let configObj = outLink().getBaseData() let configObj = outLink().getBaseData()
configObj.fullPath = 'https://localhost:7860/'
configObj.data.url = 'https://localhost:7860/'
configObj.data.domain = 'localhost'
console.log(configObj)
let fullPath = configObj.fullPath + path let fullPath = configObj.fullPath + path
fullPath = fullPath.replaceAll('//', '/') fullPath = fullPath.replaceAll('//', '/')
// //
@ -345,7 +341,7 @@ export default {
cookieData: { ...configObj.data } cookieData: { ...configObj.data }
}) })
return return
} else if(items.fileFlag === 'aptist') { // aptist PPT-List } else if(items.fileFlag === 'aippt') { // aippt PPT-List
return this.$emit('change', 'click', items) return this.$emit('change', 'click', items)
} }
if (!items||!items.fileSuffix) return; if (!items||!items.fileSuffix) return;

View File

@ -13,7 +13,7 @@
</template> </template>
<script setup> <script setup>
import AiPptist from './ai-pptist.vue'; import AiPptist from './ai-pptistV2.vue';
const model = defineModel() const model = defineModel()
const emit = defineEmits(['addSuccess']) const emit = defineEmits(['addSuccess'])
const props = defineProps({ const props = defineProps({

View File

@ -2,8 +2,20 @@
<div v-loading="isLoading" class="page-resource flex"> <div v-loading="isLoading" class="page-resource flex">
<ChooseTextbook @node-click="nodeClick" /> <ChooseTextbook @node-click="nodeClick" />
<div class="page-center-wrap"> <div class="page-center-wrap">
<el-dropdown class="prepare-center-dropdown">
<el-button type="primary">
新建<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="createAptFile">新建文枢课件</el-dropdown-item>
<el-dropdown-item>AI一键生成</el-dropdown-item>
<el-dropdown-item>导入PPT</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-tabs v-model="activeAptTab" style="height: 100%;"> <el-tabs v-model="activeAptTab" style="height: 100%;">
<el-tab-pane label="教学课件" name="教学课件" class="prepare-center-jxkj"> <el-tab-pane label="文枢课件" name="教学课件" class="prepare-center-jxkj">
<div class="prepare-center-header"> <div class="prepare-center-header">
<div class="center-create-btn" style="background-color: rgb(64,158,255)" @click="createAptFile"> <div class="center-create-btn" style="background-color: rgb(64,158,255)" @click="createAptFile">
<div class="create-btn-title"><el-icon><Plus /></el-icon><label>APT</label></div> <div class="create-btn-title"><el-icon><Plus /></el-icon><label>APT</label></div>
@ -145,7 +157,7 @@
<!-- <button @click="test">test</button> --> <!-- <button @click="test">test</button> -->
</template> </template>
<script setup> <script setup>
import { Check,Plus } from '@element-plus/icons-vue' import {Check, Plus, Position} from '@element-plus/icons-vue'
import Reserv from '@/views/prepare/container/reserv.vue' import Reserv from '@/views/prepare/container/reserv.vue'
import { ArrowDown } from '@element-plus/icons-vue' import { ArrowDown } from '@element-plus/icons-vue'
import PptDialog from '@/views/prepare/container/ppt-dialog.vue' import PptDialog from '@/views/prepare/container/ppt-dialog.vue'
@ -180,8 +192,11 @@ import ClassReserv from '@/views/classManage/classReserv.vue'
import TreeLog from '@/views/prepare/components/treeLog.vue' import TreeLog from '@/views/prepare/components/treeLog.vue'
import classStart from './container/class-start.vue' // import classStart from './container/class-start.vue' //
import MsgEnum from '@/plugins/imChat/msgEnum' // im import MsgEnum from '@/plugins/imChat/msgEnum' // im
import Chat from '@/utils/chat' // im // import Chat from '@/utils/chat' // im
if (!Chat.imChat) Chat.init() // if (!Chat.imChat) Chat.init()
// import ChatWs from '@/plugins/socket'
// console.log('xxxx',ChatWs)
// ChatWs.watch((data,e) => console.log('ws', data, e))
const toolStore = useToolState() const toolStore = useToolState()
const fs = require('fs') const fs = require('fs')
@ -253,11 +268,11 @@ export default {
}, },
currentKJFileList() { currentKJFileList() {
// return this.currentFileList.filter((item) => item.fileFlag === 'apt' || item.fileFlag === '') // return this.currentFileList.filter((item) => item.fileFlag === 'apt' || item.fileFlag === '')
return this.currentFileList.filter((item) => ['apt','aptist','课件'].includes(item.fileFlag)) return this.currentFileList.filter((item) => ['apt','aippt','课件'].includes(item.fileFlag))
}, },
currentSCFileList() { currentSCFileList() {
// return this.currentFileList.filter((item) => item.fileFlag !== 'apt' && item.fileFlag !== '') // return this.currentFileList.filter((item) => item.fileFlag !== 'apt' && item.fileFlag !== '')
return this.currentFileList.filter((item) => !['apt','aptist','课件'].includes(item.fileFlag)) return this.currentFileList.filter((item) => !['apt','aippt','课件'].includes(item.fileFlag))
} }
}, },
@ -321,7 +336,7 @@ export default {
if(item.fileFlag === 'apt') { if(item.fileFlag === 'apt') {
this.$refs.calssRef.open(item.fileId, classObj) this.$refs.calssRef.open(item.fileId, classObj)
} }
if(item.fileFlag === 'aptist') { if(item.fileFlag === 'aippt') {
this.$refs.calssRef.open(item.fileId, classObj) this.$refs.calssRef.open(item.fileId, classObj)
} }
}, },
@ -378,8 +393,8 @@ export default {
}, 1000) }, 1000)
break break
} }
case 'click': { // --aptist case 'click': { // --aippt
if (row.fileFlag === 'aptist' && !!row.fileId) { if (row.fileFlag === 'aippt' && !!row.fileId) {
const res = await getEntpcoursefile(row.fileId) const res = await getEntpcoursefile(row.fileId)
if (res && res.code === 200) { if (res && res.code === 200) {
sessionStore.set('curr.resource', res.data) // sessionStore.set('curr.resource', res.data) //
@ -461,6 +476,93 @@ export default {
},500) },500)
}) })
}, },
createAIPPT() {
listEntpcourse({
evalid: this.currentNode.id,
edituserid: this.userStore.userId,
pageSize: 500
}).then((response) => {
if (response.rows.length <= 0) return
let resCourse = response.rows[0]
//
let form = {
parentid: 0,
entpid: this.userStore.deptId,
entpcourseid: resCourse.id,
ppttype: 'file',
title: resCourse.coursetitle,
fileurl: '',
filetype: 'aippt',
datacontent: '',
filekey: '',
filetag: '',
fileidx: 0,
dflag: 0,
status: '',
edituserid: this.userStore.userId
}
addEntpcoursefileReturnId(form).then((slideid) => {
let pagearray = []
//
pagearray.push({
key: '公屏',
title: '公屏页',
slidedata: {
attrs: { width: 1333, height: 749.8125 },
className: 'Stage',
children: [
{
attrs: {},
className: 'Layer',
children: [
{
attrs: {
width: 1333,
height: 749.8125,
fill: 'white',
name: 'fixedbackground',
listening: true
},
className: 'Rect'
}
]
}
]
}
})
//
var form = {
parentid: slideid,
entpid: resCourse.entpid,
entpcourseid: resCourse.id,
ppttype: 'file',
title: '第一页',
fileurl: '',
filetype: 'slide',
datacontent: JSON.stringify(pagearray),
filekey: '',
filetag: '',
fileidx: 0,
dflag: 0,
status: '',
edituserid: this.userStore.userId
}
addEntpcoursefileReturnId(form).then((res) => {
creatAPT({
...this.uploadData,
fileId: slideid,
fileShowName: this.currentNode.itemtitle + '.apt'
}).then((res) => {
this.currentFileList.unshift(res.resData)
setTimeout(()=>{
this.$refs['kjItemRef'+res.resData.id][0].openFileWin(res.resData);
},500)
})
})
})
})
},
createAptFile() { createAptFile() {
listEntpcourse({ listEntpcourse({
evalid: this.currentNode.id, evalid: this.currentNode.id,
@ -653,7 +755,7 @@ export default {
for (let i = 0; i < this.currentFileList.length; i++) { for (let i = 0; i < this.currentFileList.length; i++) {
let item = this.currentFileList[i] let item = this.currentFileList[i]
if (item.fileFlag === 'apt') continue; if (item.fileFlag === 'apt') continue;
if (item.fileFlag === 'aptist') continue; if (item.fileFlag === 'aippt') continue;
await asyncLocalFile(item) await asyncLocalFile(item)
} }
this.asyncAllFileVisiable = false this.asyncAllFileVisiable = false
@ -872,6 +974,13 @@ export default {
margin: 0 5px; margin: 0 5px;
border-radius: 10px; border-radius: 10px;
background-color: white; background-color: white;
position: relative;
.prepare-center-dropdown{
z-index: 9999;
position: absolute;
right: 10px;
top: 4px;
}
.prepare-center-jxkj{ .prepare-center-jxkj{
height: 100%; height: 100%;
display: flex; display: flex;

View File

@ -5,7 +5,7 @@
<div class="info"> <div class="info">
<div class="info-name">{{ state.user.nickName }}</div> <div class="info-name">{{ state.user.nickName }}</div>
<div class="infomation"> <div class="infomation">
<selectClass/> <selectClass v-if="!isSubject"/>
</div> </div>
</div> </div>
</div> </div>
@ -46,8 +46,10 @@ const state = reactive({
postGroup: {} postGroup: {}
}) })
const isSubject = ref(false)
async function getUser() { async function getUser() {
getUserProfile().then((response) => { getUserProfile().then((response) => {
isSubject.value = response.roleGroup.indexOf('场馆管理员') != -1
// response.data.avatar = import.meta.env.VITE_APP_BASE_API + response.data.avatar // response.data.avatar = import.meta.env.VITE_APP_BASE_API + response.data.avatar
Object.assign(state.user,response.data) Object.assign(state.user,response.data)
state.roleGroup = response.roleGroup state.roleGroup = response.roleGroup

View File

@ -34,7 +34,6 @@ import {getDept } from '@/api/login'
import { listEvaluation } from '@/api/subject/index' import { listEvaluation } from '@/api/subject/index'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import {ElMessage} from 'element-plus' import {ElMessage} from 'element-plus'
import { clearBookInfo } from '@/utils/ruoyi'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import {listClassmain} from '@/api/classManage/index' import {listClassmain} from '@/api/classManage/index'
// //

View File

@ -0,0 +1,33 @@
<template>
<el-dialog v-model="open" v-bind="dAttrs">
<el-progress type="dashboard" v-bind="$attrs.pg" />
</el-dialog>
</template>
<script setup>
//
import { computed, useAttrs } from 'vue'
const attrs = useAttrs()
const props = defineProps({
visible: { //
type: Boolean,
default: false
},
})
//
const emit = defineEmits(['update:visible'])
// -
const open = computed({
get: () => props.visible,
set: val => emit('update:visible', val)
})
//
const dAttrs = computed(() => {
const attrsNew = { ...attrs }
delete attrsNew.pg
return attrsNew
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -50,6 +50,7 @@
<EditDialog v-model="isEdit" :item="curItem" /> <EditDialog v-model="isEdit" :item="curItem" />
<AdjustDialog v-model="isAdjust" :item="curItem" /> <AdjustDialog v-model="isAdjust" :item="curItem" />
<PptDialog @add-success="addAiPPT" :dataList="resultList" v-model="pptDialog"/> <PptDialog @add-success="addAiPPT" :dataList="resultList" v-model="pptDialog"/>
<progress-dialog v-model:visible="pgDialog.visible" v-bind="pgDialog" />
</template> </template>
<script setup> <script setup>
@ -58,6 +59,7 @@ import { sessionStore } from '@/utils/store'
import emitter from '@/utils/mitt' import emitter from '@/utils/mitt'
import EditDialog from './edit-dialog.vue' import EditDialog from './edit-dialog.vue'
import AdjustDialog from './adjust-dialog.vue' import AdjustDialog from './adjust-dialog.vue'
import progressDialog from './progress-dialog.vue'
import { completion, tempResult } from '@/api/mode/index.js' import { completion, tempResult } from '@/api/mode/index.js'
// import { dataSetJson } from '@/utils/comm.js' // import { dataSetJson } from '@/utils/comm.js'
import * as commUtils from '@/utils/comm.js' import * as commUtils from '@/utils/comm.js'
@ -68,6 +70,7 @@ import {PPTXFileToJson} from '@/AixPPTist/src/hooks/useImport' // ppt转json
import * as API_entpcourse from '@/api/education/entpcourse' // api import * as API_entpcourse from '@/api/education/entpcourse' // api
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api
import * as Api_server from '@/api/apiService' // api import * as Api_server from '@/api/apiService' // api
import * as API_smarttalk from '@/api/file' // api
import msgUtils from '@/plugins/modal' // import msgUtils from '@/plugins/modal' //
const userStore = useUserStore() const userStore = useUserStore()
@ -76,7 +79,22 @@ const resultList = ref([])
const courseObj = reactive({ const courseObj = reactive({
node: null, // node: null, //
}) })
const pgDialog = reactive({ // -
visible: false,
title: 'PPT解析中...',
width: 300,
showClose: false,
draggable: true,
beforeClose: done => {}, // -
pg: { // -
percentage: 0, //
color: [
{ color: '#1989fa', percentage: 50 }, //
{ color: '#e6a23c', percentage: 80 }, //
{ color: '#5cb87a', percentage: 100 }, // 绿
]
}
})
emitter.on('changeMode', (item) => { emitter.on('changeMode', (item) => {
console.log(item, 'item') console.log(item, 'item')
resultList.value = item.child resultList.value = item.child
@ -113,7 +131,10 @@ const params = reactive(
const addAiPPT = async(res) => { const addAiPPT = async(res) => {
let node = courseObj.node let node = courseObj.node
pptDialog.value = false;
if (!node) return msgUtils.msgWarning('请选择章节?') if (!node) return msgUtils.msgWarning('请选择章节?')
pgDialog.visible = true
pgDialog.pg.percentage = 0
//TODO resPPT //TODO resPPT
const params = { evalid: node.id, edituserid: userStore.id, pageSize: 1 } const params = { evalid: node.id, edituserid: userStore.id, pageSize: 1 }
const resEnpt = await HTTP_SERVER_API('getCourseList', params) const resEnpt = await HTTP_SERVER_API('getCourseList', params)
@ -128,14 +149,22 @@ const addAiPPT = async(res) => {
const resPptJson = await PPTXFileToJson(buffer) const resPptJson = await PPTXFileToJson(buffer)
const { def, slides, ...content } = resPptJson const { def, slides, ...content } = resPptJson
// || 线 // || 线
let completed = 0
const total = slides.length
for( let o of slides ) { for( let o of slides ) {
completed++
await toRousrceUrl(o) await toRousrceUrl(o)
//
pgDialog.pg.percentage = Math.floor(completed / total * 100)
} }
// return pgDialog.pg.percentage = 0
pgDialog.visible = false
// ppt- // ppt-
const p_params = {parentContent: JSON.stringify(content)} const p_params = {parentContent: JSON.stringify(content)}
const parentid = await HTTP_SERVER_API('addEntpcoursefile', p_params) const parentid = await HTTP_SERVER_API('addEntpcoursefile', p_params)
if (!!parentid??null) { // if (!!parentid??null) { //
// -Smarttalk
HTTP_SERVER_API('addSmarttalk',{fileId: parentid})
if (slides.length > 0) { if (slides.length > 0) {
const resSlides = slides.map(({id, ...slide}) => JSON.stringify(slide)) const resSlides = slides.map(({id, ...slide}) => JSON.stringify(slide))
const params = {parentid, filetype: 'slide', title: '', slides: resSlides } const params = {parentid, filetype: 'slide', title: '', slides: resSlides }
@ -206,6 +235,20 @@ emitter.on('changeResult', (item) => {
// HTTP // HTTP
const HTTP_SERVER_API = (type, params = {}) => { const HTTP_SERVER_API = (type, params = {}) => {
switch (type) { switch (type) {
case 'addSmarttalk': { //
const node = courseObj.node || {}
const def = {
fileId: '', // id - Entpcoursefile id
fileFlag: 'aippt',
fileShowName: node.itemtitle + '.aippt',
textbookId: node.rootid,
levelFirstId: node.parentid||node.id,
levelSecondId: node.parentid && node.id,
fileSource: '个人',
fileRoot: '备课'
}
return API_smarttalk.creatAPT({...def, ...params})
}
case 'addEntpcourse': { // case 'addEntpcourse': { //
const node = courseObj.node || {} const node = courseObj.node || {}
if (!node) return msgUtils.msgWarning('请选择章节?') if (!node) return msgUtils.msgWarning('请选择章节?')
@ -256,7 +299,7 @@ const getDefParams = (params) => {
ppttype: 'file', ppttype: 'file',
title: enpt.coursetitle, title: enpt.coursetitle,
fileurl: '', fileurl: '',
filetype: 'aptist', filetype: 'aippt',
datacontent: '', datacontent: '',
filekey: '', filekey: '',
filetag: '', filetag: '',
@ -298,6 +341,7 @@ const toRousrceUrl = async(o) => {
const curNode = reactive({}) const curNode = reactive({})
onMounted(() => { onMounted(() => {
let data = sessionStore.get('subject.curNode') let data = sessionStore.get('subject.curNode')
console.log('data', sessionStore)
Object.assign(curNode, data); Object.assign(curNode, data);
courseObj.node = data courseObj.node = data