Compare commits

...

41 Commits

Author SHA1 Message Date
zhangxuelin f2faa882a8 Merge pull request 'zxl' (#110) from zxl into main
Reviewed-on: #110
2024-12-11 13:51:14 +08:00
zhangxuelin e4afb26e4e Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-11 13:50:50 +08:00
zhangxuelin d60d8822a6 修改动画不能展示问题 2024-12-11 13:50:25 +08:00
朱浩 90137bc3a2 Merge remote-tracking branch 'origin/main' 2024-12-11 13:37:07 +08:00
朱浩 82c61a3fa7 大模型首页 2024-12-11 13:36:49 +08:00
朱浩 48d631a17c 大模型首页 2024-12-11 11:11:47 +08:00
zhangxuelin 1835f76e56 Merge pull request 'pptist点赞组件' (#109) from zxl into main
Reviewed-on: #109
2024-12-11 11:02:44 +08:00
zhangxuelin 432c1ff71d pptist点赞组件 2024-12-11 11:02:22 +08:00
朱浩 7f595c09a9 大模型首页 2024-12-11 10:33:18 +08:00
朱浩 c8e10d4fe1 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/renderer/src/components/file-image/index.vue
#	src/renderer/src/views/model/index.vue
2024-12-11 10:31:29 +08:00
朱浩 f916660156 大模型首页 2024-12-11 10:28:55 +08:00
zhengdegang e0107814ea Merge pull request 'zdg_dev' (#108) from zdg_dev into main
Reviewed-on: #108
2024-12-11 10:07:58 +08:00
zdg bb0aa82e82 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-11 10:06:30 +08:00
zdg 8ed13a3146 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev
# Conflicts:
#	src/renderer/src/AixPPTist/src/api/index.ts
#	src/renderer/src/views/prepare/index.vue
2024-12-11 10:06:23 +08:00
zhangxuelin 82b70558c2 Merge pull request 'zxl' (#107) from zxl into main
Reviewed-on: #107
2024-12-11 09:59:16 +08:00
zhangxuelin bace41e12e Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-11 09:58:08 +08:00
zhangxuelin 04e204928d 添加ppt 视频url替换 2024-12-11 09:57:39 +08:00
zdg 9e5609fbdd ppt上课 2024-12-11 09:44:52 +08:00
lyc 50aff2f158 Merge pull request 'add icon' (#106) from lyc-dev into main 2024-12-11 09:41:16 +08:00
lyc a1a4e11de7 add icon 2024-12-11 09:40:51 +08:00
lyc 20fe5be502 Merge pull request 'lyc-dev' (#105) from lyc-dev into main 2024-12-10 15:44:00 +08:00
lyc 7e9ac2bf74 Merge branch 'main' into lyc-dev 2024-12-10 15:43:14 +08:00
lyc 013669dd35 edit 模板 2024-12-10 15:42:59 +08:00
yangws 3ca20cd327 Merge pull request 'fix:清除debugger;' (#104) from yws_dev into main
Reviewed-on: #104
2024-12-10 15:38:42 +08:00
小杨 c159d6be1a fix:清除debugger; 2024-12-10 15:38:19 +08:00
yangws cc44a86437 Merge pull request 'yws_dev' (#103) from yws_dev into main
Reviewed-on: #103
2024-12-10 15:35:20 +08:00
小杨 e7c6ab9e8d Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into yws_dev 2024-12-10 15:34:51 +08:00
小杨 85d06f306a fix:ppt活动添加; 2024-12-10 15:34:46 +08:00
朱浩 656a58693f Merge remote-tracking branch 'origin/main' 2024-12-10 14:20:08 +08:00
zouyf 34cfcf5a6f Merge pull request 'zouyf_dev' (#102) from zouyf_dev into main
Reviewed-on: #102
2024-12-10 11:10:55 +08:00
“zouyf” 9603406a0b 1 2024-12-10 11:09:32 +08:00
“zouyf” 10a7e73c64 Merge branch 'main' into zouyf_dev 2024-12-10 10:59:48 +08:00
朱浩 772b196b31 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/renderer/src/views/prepare/index.vue
2024-12-10 10:44:04 +08:00
zhangxuelin 91e9867b68 Merge pull request 'zxl' (#101) from zxl into main
Reviewed-on: #101
2024-12-10 10:41:08 +08:00
zhangxuelin 80ac4a6e1c Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl 2024-12-10 10:40:21 +08:00
朱浩 757edb0f67 改名AIPPT 2024-12-10 10:40:08 +08:00
zhangxuelin 1987783fda 添加作业弹窗 2024-12-10 10:40:00 +08:00
zdg 4f68ae27b3 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zdg_dev 2024-12-10 10:09:00 +08:00
zdg 38c041465e ppt上课 2024-12-10 10:08:53 +08:00
zhangxuelin f035cf87ea Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into zxl
# Conflicts:
#	src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/index.vue
2024-12-10 09:43:43 +08:00
zhangxuelin e56eb059be 替换上传图片 视频url为线上url 2024-12-10 09:41:59 +08:00
49 changed files with 1592 additions and 2208 deletions

View File

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

View File

@ -12,6 +12,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import 'animate.css'
import { ref, onMounted, watch, onBeforeMount } from 'vue' import { ref, onMounted, watch, onBeforeMount } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from './store' import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from './store'
@ -28,7 +29,6 @@ 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()

View File

@ -2,21 +2,51 @@
* @author zdg * @author zdg
* @description * @description
*/ */
import type { Classcourse } from './types' // import type { Classcourse } from './types'
import { sessionStore } from '@/utils/store' // electron-store 状态管理 import { sessionStore } from '@/utils/store' // electron-store 状态管理
import * as useStore from '../store' // pptist-状态管理 import * as useStore from '../store' // pptist-状态管理
import { ChatWs } from '@/plugins/socket' // 聊天socket import ChatWs from '@/plugins/socket' // 聊天socket
import msgUtils from '@/plugins/modal' // 消息工具
const screenStore = useStore.useScreenStore() // 全屏-状态管理 const screenStore = useStore.useScreenStore() // 全屏-状态管理
const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理 const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理
const classcourse: Classcourse = sessionStore.get('curr.classcourse') // 课堂信息 const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
// 如果课堂信息有值则连接socket export class Classcourse {
if (!!classcourse) { msgObj:ElMessageBox = null // 提示消息对象
// 连接socket
const ws = new ChatWs() constructor() {
console.log('ws- ',ws) this.load()
// ChatWs.connect(classcourse.id) }
classcourseStore.setClasscourse(classcourse) /**
* @description
*/
load() {
// 打开全屏
screenStore.setScreening(!!classcourse)
// 如果课堂信息有值则连接socket
if (!!classcourse) {
// 连接socket
if (!ChatWs.ws) ChatWs.init()
ChatWs.id = classcourse.timgroupid // 群组id
console.log('ws- ', classcourse)
classcourseStore.setClasscourse(classcourse)
// 待上课提示
if (!classcourse.status) {
this.msgObj = {
type: 'success',
title: '系统提示',
message: '公屏课堂已准备完毕,请等待老师开启课堂!',
center: true,
showClose: false,
showCancelButton: false,
showConfirmButton: false,
beforeClose: () => {}
}
msgUtils.ElMessageBox(this.msgObj)
}
}
}
} }
// 打开全屏
screenStore.setScreening(!!classcourse) export default new Classcourse()

View File

@ -3,18 +3,23 @@
* @author zdg * @author zdg
* @date 2024-11-26 * @date 2024-11-26
*/ */
import { toRaw } from 'vue' import { toRaw, nextTick } from 'vue'
import type { Result } from './types' // 接口类型 import type { Result } from './types' // 接口类型
import msgUtils from '@/plugins/modal' // 消息工具 import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
import * as API_smarttalk from '@/api/file' // 相关api import * as API_smarttalk from '@/api/file' // 相关api
import * as Api_server from '@/api/apiService' // 相关api
import * as CreateHomework from '@/views/tool/createHomework' // 统计相关
import * as useStore from '../store' // pptist-状态管理 import * as useStore from '../store' // pptist-状态管理
import { sessionStore } from '@/utils/store' // electron-store 状态管理 import { sessionStore } from '@/utils/store' // electron-store 状态管理
import useUserStore from '@/store/modules/user' // 外部-用户信息 import useUserStore from '@/store/modules/user' // 外部-用户信息
import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
// import * as commUtils from '@/utils/comm.js'
import { createWindow } from '@/utils/tool'
import { useToolState } from '@/store/modules/tool'
const slidesStore = useStore.useSlidesStore() const slidesStore = useStore.useSlidesStore()
const userStore = useUserStore() const userStore = useUserStore()
const toolStore = useToolState()
/** 工具类 */ /** 工具类 */
export class Utils { export class Utils {
static mxData: any = { static mxData: any = {
@ -55,7 +60,6 @@ export class PPTApi {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 } const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params) const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
console.log(res.rows,'res.rows');
if (res.code === 200) { if (res.code === 200) {
const slides = (res.rows || []).map(o => { const slides = (res.rows || []).map(o => {
if (!!o.datacontent) { if (!!o.datacontent) {
@ -69,7 +73,8 @@ export class PPTApi {
// 活动列表处理 // 活动列表处理
const workList = (res.rows || []).map(o => o.activityContent) const workList = (res.rows || []).map(o => o.activityContent)
const workItem = [...res.rows] const workItem = [...res.rows]
slidesStore.updateSlideIndex(0) // 下标0 为第一页 // 加入活动后刷新ppt数据内容不跟换为第一页
// slidesStore.updateSlideIndex(0) // 下标0 为第一页
slidesStore.setSlides(slides) // 写入数据 slidesStore.setSlides(slides) // 写入数据
// 写入作业列表数据 // 写入作业列表数据
slidesStore.setWorkList(workList) slidesStore.setWorkList(workList)
@ -142,6 +147,8 @@ export class PPTApi {
// 更新幻灯片 // 更新幻灯片
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 thumUrl = await this.getSlideThumUrl()
data.base64Code = thumUrl // 更新缩略图
const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data) const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data)
console.log(data,'data'); console.log(data,'data');
console.log(res,'dresata'); console.log(res,'dresata');
@ -186,6 +193,40 @@ export class PPTApi {
else msgUtils.msgError(res.msg || '更新失败');return false else msgUtils.msgError(res.msg || '更新失败');return false
}) })
} }
// thumbnail-slide thumbnail
static getSlideThumUrl(): Promise<Boolean> {
return nextTick().then(async() => {
const slideIndex = slidesStore.slideIndex
const elements = document.querySelectorAll('.thumbnail-slide')
if (elements.length && slideIndex >= 0) {
const element = elements[slideIndex]
return await toPng(element)
}
return null
})
}
// 图片|音频|视频 转换为在线地址
static toRousrceUrl =async (file: File|any) => {
const formData = new FormData()
formData.append('file', file)
const res = await Api_server.Other.uploadFile(formData)
if (res && res.code == 200){
const url = res?.url
url &&(o.src = url)
return url
}
}
} }
export class Homework{
// 作业弹窗
static async showHomework(id: any) {
let result = await CreateHomework.getClassWorkList(id)
result = await CreateHomework.getStudentClassWorkData()
localStorage.setItem('teachClassWorkItem', JSON.stringify(result[0]));
toolStore.isTaskWin=true; // 设置打开批改窗口
createWindow('open-taskwin',{url:'/teachClassTask'});
}
}
export default PPTApi export default PPTApi

View File

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

View File

@ -6,13 +6,17 @@ import { watch } from 'vue'
import { PPTApi } from './index' import { PPTApi } from './index'
import * as store from '../store' import * as store from '../store'
import { sessionStore } from '@/utils/store' // electron-store 状态管理 import { sessionStore } from '@/utils/store' // electron-store 状态管理
import { MsgEnum } from './types' // 消息枚举
import ChatWs from '@/plugins/socket' // 聊天socket
import Classcourse from './classcourse' // 课程相关
import msgUtils from '@/plugins/modal' // 消息工具
const slidesStore = store.useSlidesStore() const slidesStore = store.useSlidesStore()
const classcourseStore = store.useClasscourseStore() // 课堂信息-状态管理
const resource = sessionStore.get('curr.resource') // apt 资源 const resource = sessionStore.get('curr.resource') // apt 资源
const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源 const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源
/** /**
* @description * @description
*/ */
// 监听幻灯片内容变化 // 监听幻灯片内容变化
watch(() => slidesStore.slides, (newVal, oldVal) => { watch(() => slidesStore.slides, (newVal, oldVal) => {
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容 PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
@ -24,6 +28,20 @@ watch(() => slidesStore.title, (newVal, oldVal) => {
updatePPT({title: newVal}) updatePPT({title: newVal})
}) })
// 消息监听ws
console.log('监听器已开启', ChatWs)
if (ChatWs.ws) {
ChatWs.watch((msg, e) => {
try {
handleMessage(JSON.parse(msg))
} catch (error) {
console.error('socket 解析异常 ', error, e)
handleMessage(msg)
}
})
}
// 更新ppt内容
const updatePPT = async (data) => { const updatePPT = async (data) => {
if (!resource) return if (!resource) return
data.id = resource.id data.id = resource.id
@ -37,3 +55,40 @@ const updatePPT = async (data) => {
sessionStore.set('curr.smarttalk.fileShowName', params.fileShowName) sessionStore.set('curr.smarttalk.fileShowName', params.fileShowName)
} }
} }
// ws消息处理
const handleMessage = (msg) => {
if (typeof msg === 'object'){
const { head, content, ...other } = msg
switch (head) {
case MsgEnum.HEADS.MSG_open: // 开课
// 课堂信息不一致
if (Classcourse.id !== content.id) {
msgUtils.alertError('老师开课信息异常,请重新进入公屏!')
.then(() => { // 点击确定按钮,关闭窗口
window.close()
})
} else { // 正常更新数据
classcourseStore.classcourse.status = 'open'
// 更新课堂信息-关闭警告框
Classcourse?.msgObj?.onVanish()
}
break
case MsgEnum.HEADS.MSG_slideFlapping: // 幻灯片翻页
const slideIndex = content.current
slidesStore.updateSlideIndex(slideIndex) // 更新幻灯片下标
break
case MsgEnum.HEADS.MSG_closed: // 下课:
window.close() // 关闭窗口
break
default:
break
}
}
}
// console.log('监听器已开启', Classcourse)
// setTimeout(() => {
// console.log('关闭弹窗')
// // Classcourse.msgObj?.close()
// Classcourse?.msgObj?.onVanish()
// }, 10 * 1000)

View File

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

View File

@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
import type { Classcourse } from '../api/types' import type { Classcourse } from '../api/types'
export interface ClasscourseState { export interface ClasscourseState {
classcourse: Classcourse classcourse: Classcourse | any, // 课堂信息
} }
export const useClasscourseStore = defineStore('classcourse', { export const useClasscourseStore = defineStore('classcourse', {

View File

@ -30,7 +30,7 @@ let params = {
levelSecondId: null, levelSecondId: null,
fileSource: '个人', fileSource: '个人',
fileRoot: '备课', fileRoot: '备课',
orderByColumn: 'uploadTime', orderByColumn: 'createTime',
isAsc: 'desc', isAsc: 'desc',
pageSize: 500 pageSize: 500
} }

View File

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

View File

@ -161,6 +161,7 @@ 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 QuestToPPTist from '@/views/classTask/newClassTaskAssign/questToPPTist/index.vue'
import MaterialDialog from './MaterialDialog.vue' import MaterialDialog from './MaterialDialog.vue'
import { PPTApi } from '../../../api'
import TextCreateImg from '@/components/ai-kolors/index.vue' import TextCreateImg from '@/components/ai-kolors/index.vue'
import { toPng } from 'html-to-image' // html-to-image import { toPng } from 'html-to-image' // html-to-image
@ -195,13 +196,20 @@ const {
} = useCreateElement() } = useCreateElement()
const insertImageElement = (files: FileList) => { const insertImageElement = (files: FileList) => {
console.log('files', files)
const imageFile = files[0] const imageFile = files[0]
if (!imageFile) return if (!imageFile) return
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL)) // 线
PPTApi.toRousrceUrl(imageFile).then(data=>{
createImageElement(data)
})
// getImageDataURL(imageFile).then(dataURL => {
// createImageElement(dataURL)
// })
} }
const onhtml2canvas = async (html: HTMLElement) => { const onhtml2canvas = async (html: HTMLElement) => {
const ele = await toPng(html) const ele = await toPng(html);
createImageElement(ele); createImageElement(ele);
} }

View File

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

View File

@ -30,7 +30,7 @@
<Divider /> <Divider />
<!-- 作业列表 --> <!-- 作业列表 -->
<div class="c-apt-r"> <div class="c-apt-r" v-loading='loadingActive'>
<!-- 显示-作业内容 --> <!-- 显示-作业内容 -->
<template v-for="(item, index) in workList" :key="index"> <template v-for="(item, index) in workList" :key="index">
<div class="item"> <div class="item">
@ -45,9 +45,11 @@
</template> </template>
</div> </div>
<!-- // --> <!-- // -->
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="80%" height="500"> <el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="90%" height="500">
<el-scrollbar height="500"> <el-scrollbar height="550">
<NewClassTsakAssign :currentCourse='currentCourse' @getData="getData" /> <div style="height: 550px;">
<NewClassTsakAssign :currentCourse='currentCourse' @getData="getData" />
</div>
</el-scrollbar> </el-scrollbar>
</el-dialog> </el-dialog>
<!-- 活动引用 --> <!-- 活动引用 -->
@ -142,7 +144,6 @@ const currentCourse = reactive<CurrentCourse>({
worktype: '', worktype: '',
}) })
const dataList = ref<WorkItem[]>([])
const dialogVisible = ref<boolean>(false) const dialogVisible = ref<boolean>(false)
const tasklist_loading = ref<boolean>(false) const tasklist_loading = ref<boolean>(false)
@ -152,11 +153,6 @@ const taskList = ref<WorkItem[]>([])
// //
const activeVisible = ref<boolean>(false) const activeVisible = ref<boolean>(false)
const params = reactive<Params>({
parentid: 14766,
pageSize: 500,
orderby: 'fileidx'
})
const type = ref<WorkType[]>([ const type = ref<WorkType[]>([
{ {
@ -179,6 +175,8 @@ const workList = ref<WorkItem[]>([])
// //
const selectedWorkList = ref<WorkItem[]>([]) const selectedWorkList = ref<WorkItem[]>([])
// loading
const loadingActive = ref<boolean>(false)
const paramData = ref<{ id: number, activityContent: string }>({} as { id: number, activityContent: string }) const paramData = ref<{ id: number, activityContent: string }>({} as { id: number, activityContent: string })
@ -196,7 +194,6 @@ const formatClassWorkFile = async (postData: WorkItem[]): Promise<void> => {
} }
break; break;
case '习题训练': { case '习题训练': {
console.log(item,'item');
// let workIds = item.quizlist!.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 });
// const arr = ress.rows.map((item:{id:number}) => { // const arr = ress.rows.map((item:{id:number}) => {
@ -214,22 +211,19 @@ const formatClassWorkFile = async (postData: WorkItem[]): Promise<void> => {
// item.prevData = JSON.parse(item.workcodes); // item.prevData = JSON.parse(item.workcodes);
} }
} }
const arr = paramData.value.activityContent.split(',') workList.value.push(item)
arr.push(item.id.toString()) loadingActive.value = false
await PPTApi.updateSlide(paramData.value)
addWorkList(item)
} }
await nextTick(); await nextTick();
} }//
//
const addWorkList = (item: WorkItem) => {
workList.value.push(item)
}
//
const handleRemoveDemoActivityClassWork = (item: WorkItem) => { const handleRemoveDemoActivityClassWork = (item: WorkItem) => {
ElMessageBox.confirm('是否确认删除?') ElMessageBox.confirm('是否确认删除?')
.then(() => { .then(() => {
workList.value.splice(workList.value.indexOf(item), 1); workList.value = []
const arr = paramData.value.activityContent.split(',')
const filterArr = arr.filter(itemId => itemId!== item.id.toString())
paramData.value.activityContent = filterArr.join(',')
upDateData()
}) })
.catch(() => { }); .catch(() => { });
} }
@ -269,13 +263,51 @@ const savePPtData = async () => {
ElMessage.warning('请选择活动') ElMessage.warning('请选择活动')
return return
} }
workList.value = []
const arr = selectedWorkList.value.map(item => item.id) const arr = selectedWorkList.value.map(item => item.id)
// //
paramData.value.activityContent = arr.join(',') const existingIds = paramData.value.activityContent ? paramData.value.activityContent.split(',') : []
await PPTApi.updateSlide(paramData.value) paramData.value.activityContent = Array.from(new Set([...existingIds, ...arr])).join(',')
upDateData()
activeVisible.value = false activeVisible.value = false
} }
// ppt
const getCurrentPPtData = async () => {
workList.value = []
objItem.value = workItem.value[slideIndex.value]
paramData.value.id = objItem.value.id
paramData.value.activityContent = objItem.value?.activityContent
if (objItem.value?.activityContent) {
loadingActive.value = true
const res = await homeworklist({ ids: objItem.value?.activityContent, pageSize: 100 })
await formatClassWorkFile(res.rows)
}
}
//
const getData = async (data: WorkItem) => {
workList.value = []
if(paramData.value.activityContent){
const arr = paramData.value.activityContent.split(',')
arr.push(data.id.toString())
const unitArr = Array.from(new Set(arr))
paramData.value.activityContent = unitArr.join(',')
}else{
paramData.value.activityContent = data.id.toString()
}
upDateData()
dialogVisible.value = false
}
const upDateData = async () => {
await PPTApi.updateSlide(paramData.value)
loadingActive.value = true
const res = await homeworklist({ ids: paramData.value.activityContent, pageSize: 100 })
await formatClassWorkFile(res.rows)
const resource = sessionStore.get('curr.resource')
await PPTApi.getSlideList(resource.id)
}
onMounted(() => { onMounted(() => {
const curNode = sessionStore.get('subject.curNode') as CourseNode const curNode = sessionStore.get('subject.curNode') as CourseNode
currentCourse.textbookId = curNode.rootid currentCourse.textbookId = curNode.rootid
@ -283,33 +315,12 @@ onMounted(() => {
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: { rows: WorkItem[] }) => {
dataList.value = [...res.rows]
})
objItem.value = workItem.value[slideIndex.value] objItem.value = workItem.value[slideIndex.value]
getCurrentPPtData() getCurrentPPtData()
}) })
watch(() => slideIndex.value, () => { watch(() => slideIndex.value, () => {
getCurrentPPtData() 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{
@ -375,5 +386,8 @@ const getData = async (data: WorkItem) => {
--el-border-color: var(--current-color); --el-border-color: var(--current-color);
} }
} }
.page .page-resource{
height: 500px !important;
}
} }
</style> </style>

View File

@ -34,7 +34,10 @@
<IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" /> <IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" />
<IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" /> <IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" />
</div> </div>
<!-- 点赞组件 -->
<div style="z-index: 999;position: absolute;top:10px">
<upvote-vue ref="upvoteRef" :test='true' type="2"></upvote-vue>
</div>
<div <div
class="tools-right" :class="{ 'visible': rightToolsVisible }" class="tools-right" :class="{ 'visible': rightToolsVisible }"
@mouseleave="rightToolsVisible = false" @mouseleave="rightToolsVisible = false"
@ -69,7 +72,7 @@ import ScreenSlideList from './ScreenSlideList.vue'
import SlideThumbnails from './SlideThumbnails.vue' import SlideThumbnails from './SlideThumbnails.vue'
import WritingBoardTool from './WritingBoardTool.vue' import WritingBoardTool from './WritingBoardTool.vue'
import CountdownTimer from './CountdownTimer.vue' import CountdownTimer from './CountdownTimer.vue'
import upvoteVue from '@/views/tool/components/upvote.vue' // -
const props = defineProps<{ const props = defineProps<{
changeViewMode: (mode: 'base' | 'presenter') => void changeViewMode: (mode: 'base' | 'presenter') => void
}>() }>()

View File

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

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4723712 */ font-family: "iconfont"; /* Project id 4723712 */
src: url('iconfont.woff2?t=1732240267757') format('woff2'), src: url('iconfont.woff2?t=1733880548695') format('woff2'),
url('iconfont.woff?t=1732240267757') format('woff'), url('iconfont.woff?t=1733880548695') format('woff'),
url('iconfont.ttf?t=1732240267757') format('truetype'); url('iconfont.ttf?t=1733880548695') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,26 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-yuyin:before {
content: "\e648";
}
.icon-dianying:before {
content: "\e693";
}
.icon-jiqirenfushi:before {
content: "\e624";
}
.icon-xiangmuicon_maobishufa:before {
content: "\e651";
}
.icon-meishu-F:before {
content: "\e638";
}
.icon-shangchuan:before { .icon-shangchuan:before {
content: "\e61b"; content: "\e61b";
} }

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -29,6 +29,20 @@
</template> </template>
</div> </div>
</el-scrollbar> </el-scrollbar>
<div class="file-list">
<el-dropdown @command="changeFile">
<span class="el-dropdown-link">
{{ curFile.fileName }}
<i class="iconfont icon-xiangxia"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in fileList" :command="item" :key="item.id">{{ item.fileName
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="input-box flex"> <div class="input-box flex">
<el-input v-model="textarea" @keyup.enter="send" :disabled="loaded"/> <el-input v-model="textarea" @keyup.enter="send" :disabled="loaded"/>
<div class="ipt-icon" @click="send"> <div class="ipt-icon" @click="send">
@ -40,13 +54,15 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, watch } from 'vue' import { ref, reactive, onMounted, onUnmounted, watch } from 'vue'
import { completion } from '@/api/mode/index' import { completion, docList } from '@/api/mode/index'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { ElMessage } from 'element-plus'
import { dataSetJson } from '@/utils/comm.js' import { dataSetJson } from '@/utils/comm.js'
import useUserStore from '@/store/modules/user'
import emitter from '@/utils/mitt'; import emitter from '@/utils/mitt';
const userInfo = useUserStore().user
const textarea = ref('') const textarea = ref('')
const isDialog = defineModel() const isDialog = defineModel()
@ -106,7 +122,6 @@ const getCompletion = async (val) => {
const saveAdjust = (item) =>{ const saveAdjust = (item) =>{
isDialog.value = false isDialog.value = false
ElMessage.success('操作成功')
emitter.emit('onSaveAdjust', item.msg) emitter.emit('onSaveAdjust', item.msg)
} }
@ -124,6 +139,27 @@ watch(() => props.type, (newVal) => {
}, { immediate: false }) }, { immediate: false })
const curFile = reactive({})
const dataset_id = ref('')
const fileList = ref([])
const getList = () =>{
docList({
userId: userInfo.userId,
dataset_id: dataset_id.value
}).then( res =>{
fileList.value = res.rows
Object.assign(curFile, fileList.value[0])
params.document_ids = fileList.value[0].docId
})
}
emitter.on('changeCurFile', (item) =>{
changeFile(item)
})
const changeFile = (val) =>{
Object.assign(curFile, val);
params.document_ids = val.docId
}
onMounted(() => { onMounted(() => {
let data = sessionStore.get('subject.curNode') let data = sessionStore.get('subject.curNode')
@ -131,6 +167,15 @@ onMounted(() => {
Object.assign(curNode, data); Object.assign(curNode, data);
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}` let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey] params.dataset_id = dataSetJson[jsonKey]
if(props.type == 3){
getList()
}
})
//
onUnmounted(() => {
emitter.off('changeCurFile');
}) })
@ -267,4 +312,9 @@ onMounted(() => {
transform: scale(0.01); transform: scale(0.01);
} }
} }
.file-list{
display: flex;
margin-bottom: 10px;
}
</style> </style>

View File

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

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

View File

@ -2,12 +2,13 @@
<div class="container-left-page flex"> <div class="container-left-page flex">
<div class="container-left-header flex"> <div class="container-left-header flex">
<el-button link @click="onClick"> <el-button link @click="onClick">
{{ curNode.edustage }}{{ curNode.edusubject }}{{ type == 1 ? '课标研读' : '教材分析' }}<i {{ curNode.edustage }}{{ curNode.edusubject }}{{ type == 1 ? '课标研读' : type == 2 ? '教材分析' : '考试分析' }}<i
class="iconfont icon-xiangxia"></i> class="iconfont icon-xiangxia"></i>
</el-button> </el-button>
</div> </div>
<div class="container-left-pdf"> <div class="container-left-pdf">
<PDF :url="pdfUrl" :showCatalog="false" v-if="pdfUrl" /> <PDF :url="pdfUrl" :showCatalog="false" v-if="pdfUrl" />
<el-empty v-else description="暂无数据" />
</div> </div>
<!--弹窗--> <!--弹窗-->
<LeftDialog v-model="showDialog" :modeType="type" /> <LeftDialog v-model="showDialog" :modeType="type" />
@ -24,7 +25,7 @@ const props = defineProps(['curNode', 'type'])
const showDialog = ref(false) const showDialog = ref(false)
const onClick = () => { const onClick = () => {
if (props.type == 1) return if (props.type != 3) return
showDialog.value = true showDialog.value = true
} }
@ -37,7 +38,9 @@ onMounted(async () => {
if(props.type == 1){ if(props.type == 1){
fileurl = `${data.edustage}-${data.edusubject}-课标.txt` fileurl = `${data.edustage}-${data.edusubject}-课标.txt`
} }
if(fileurl == '') return
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf') pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf')
}) })
</script> </script>

View File

@ -39,7 +39,7 @@
<i class="iconfont icon-shenglvehao"></i></el-button> <i class="iconfont icon-shenglvehao"></i></el-button>
</template> </template>
<template #default> <template #default>
<el-button type="primary" link @click="editKeyWord(item)">编辑</el-button> <el-button type="primary" link @click="editKeyWord(item, false)">编辑</el-button>
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button> <el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
</template> </template>
</el-popover> </el-popover>
@ -79,7 +79,7 @@
<!--AI 对话调整--> <!--AI 对话调整-->
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" /> <AdjustDialog v-model="isAdjust" :type="type" :item="editItem" />
<!--添加编辑提示词--> <!--添加编辑提示词-->
<keywordDialog v-model="isWordDialog" :isAdd="isAdd" :item="editItem" /> <keywordDialog v-model="isWordDialog" :item="editItem" />
</template> </template>
<script setup> <script setup>
@ -105,21 +105,22 @@ const props = defineProps(['curNode', 'type'])
*/ */
const isWordDialog = ref(false) const isWordDialog = ref(false)
const isAdd = ref(false)
const editItem = reactive({}) const editItem = reactive({})
const onAdd = () => { const onAdd = () => {
isAdd.value = true
Object.assign(editItem, curTemplate) Object.assign(editItem, curTemplate)
editItem.isAdd = true
isWordDialog.value = true isWordDialog.value = true
} }
const editKeyWord = (item, val) => { const editKeyWord = (item, val) => {
/** /**
* isAdd: 字模板中的移除 为编辑 头部删除 添加提示词为新增 * isAdd: 子模板中的移除 为编辑false 头部删除 添加提示词为新增 true
*/ */
isAdd.value = val
Object.assign(editItem, item) Object.assign(editItem, item)
editItem.isAdd = val
isWordDialog.value = true isWordDialog.value = true
} }
/*******************模板相关**********************/ /*******************模板相关**********************/
@ -361,8 +362,8 @@ emitter.on('onSaveAdjust', (item) => {
// //
const onEditSave = async (item) => { const onEditSave = async (item) => {
const { res } = await editTempResult({ id: item.resultId, content: item.answer }) const { msg } = await editTempResult({ id: item.resultId, content: item.answer })
ElMessage.success(res) ElMessage.success(msg)
getChildTemplate() getChildTemplate()
} }

View File

@ -8,7 +8,7 @@
readonly readonly
resize="none" resize="none"
style="width: 100%;" style="width: 100%;"
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:15px" input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:14px"
/> />
</div> </div>
</template> </template>
@ -71,9 +71,6 @@ watch([() => props.text, () => props.delay], resetAndType);
</script> </script>
<style scoped> <style scoped>
.typing-effect {
font-family: monospace;
}
:deep(.el-textarea__inner:hover){ :deep(.el-textarea__inner:hover){
box-shadow: none; box-shadow: none;
} }

View File

@ -93,6 +93,12 @@ const headerMenus = [
icon: 'icon-jiaoxueshijian', icon: 'icon-jiaoxueshijian',
path: '/prepare' path: '/prepare'
}, },
{
name: '教学活动',
id: 5,
icon: 'icon-zuoyepigai',
path: '/classTask'
},
{ {
name: '资源中心', name: '资源中心',
id: 3, id: 3,

View File

@ -56,6 +56,8 @@ export class MsgEnum {
*/ */
static HEADS = { static HEADS = {
// === 旧定义-消息头(兼容以前) === // === 旧定义-消息头(兼容以前) ===
/** @desc: 开课 */
MSG_open : 'open',
/** @desc: 结束课程(下课) */ /** @desc: 结束课程(下课) */
MSG_closed : 'closed', MSG_closed : 'closed',
/** @desc: 在线状态 */ /** @desc: 在线状态 */
@ -93,6 +95,8 @@ export class MsgEnum {
/** @desc: 课堂讲授活动,选择不同的内容 */ /** @desc: 课堂讲授活动,选择不同的内容 */
MSG_classlecturePagesrc : 'classlecturePagesrc', MSG_classlecturePagesrc : 'classlecturePagesrc',
// === 新定义-消息头 === // === 新定义-消息头 ===
/** @desc: 课程创建-待开课 */
MSG_0000: 0x0000,
/** @desc: 点赞 */ /** @desc: 点赞 */
MSG_0001: 0x0001, MSG_0001: 0x0001,
/** @desc: 疑惑 */ /** @desc: 疑惑 */

View File

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

View File

@ -8,6 +8,7 @@ import useUserStore from '@/store/modules/user' // 用户信息
export class ChatWs { export class ChatWs {
instance = null; // 实例 instance = null; // 实例
id = null; // 群聊id || 单聊id-用户id(userId) id = null; // 群聊id || 单聊id-用户id(userId)
url = null; // ws地址
closed = false; // 关闭状态 closed = false; // 关闭状态
onmessage = null; // 自定义处理 onmessage = null; // 自定义处理
errCount = 5; // 重连次数 (ms) 暂时不使用 errCount = 5; // 重连次数 (ms) 暂时不使用
@ -19,12 +20,16 @@ export class ChatWs {
beat: 'heart_beat', // 心跳 beat: 'heart_beat', // 心跳
} }
static base = 'wss://file.ysaix.com:7868' static base = 'wss://file.ysaix.com:7868'
constructor() {
// 构造函数 是否自动连接
constructor(bool = true) {
if (!ChatWs.instance) { if (!ChatWs.instance) {
const userStore = useUserStore() // 用户信息 if (bool) { // 是否自动连接
const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址 const userStore = useUserStore() // 用户信息
const url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`; const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址
this.init(url); this.url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`;
// this.init(url);
}
ChatWs.instance = this; ChatWs.instance = this;
} }
return ChatWs.instance; return ChatWs.instance;
@ -32,11 +37,11 @@ export class ChatWs {
// 初始化 // 初始化
init(url) { init(url) {
this.url = url; !!url && (this.url = url);
this.ws = null; this.ws = null;
const _this = this const _this = this
this.heartCheck = { this.heartCheck = {
timeout: 1000 * 10, // 60s timeout: 1000 * 60, // 60s
timeoutObj: null, timeoutObj: null,
serverTimeoutObj: null, serverTimeoutObj: null,
reset() { reset() {
@ -78,10 +83,9 @@ export class ChatWs {
// 拿到任何消息都说明当前连接是正常的 // 拿到任何消息都说明当前连接是正常的
const isBeat = e.data == 'pong' const isBeat = e.data == 'pong'
isBeat && self.heartCheck.reset().start(); isBeat && self.heartCheck.reset().start();
const exts = ['sessionId', 'pong'] // 不处理的消息头
const isEmpty = !e.data const isEmpty = !e.data
const isExts = exts.some(item => e.data.includes(item)) const isExts = e.data.includes('sessionId') || e.data == ('pong')
if (isEmpty && isExts) return; if (isEmpty || isExts) return;
// 自定义处理 // 自定义处理
self.onmessage && self.onmessage(e.data, e); self.onmessage && self.onmessage(e.data, e);
}; };
@ -118,12 +122,12 @@ export class ChatWs {
this.ws.send(msg) this.ws.send(msg)
} }
// 发送消息-带消息头(key) // 发送消息-带消息头(key)
sendMsg(head, content, option = {}) { sendMsg(head, content, option = {}, ...arg) {
if (!head) throw new Error("head is not null") if (!head && head!==0) throw new Error("head is not null")
if (!content) throw new Error("content is not null") if (!content && content!==0) throw new Error("content is not null")
let msg = { head, content, ...option } let msg = { head, content, ...option }
// 发送消息 // 发送消息
this.send(this.getMsgObj(msg)) this.send(this.getMsgObj(msg, ...arg))
} }
// 发送心跳 // 发送心跳
sendMsgBeat() { sendMsgBeat() {
@ -137,7 +141,7 @@ export class ChatWs {
* @param {*} id 群聊id || 单聊id-用户id(userId) * @param {*} id 群聊id || 单聊id-用户id(userId)
*/ */
getMsgObj(msg, chatType = 'group', id) { getMsgObj(msg, chatType = 'group', id) {
if (typeof msg === "object") msg = JSON.stringify(msg) // if (typeof msg === "object") msg = JSON.stringify(msg)
const res = {msg, chatType} const res = {msg, chatType}
// if (!id) throw new Error(`${type=='group'?'群ID':'用户ID'} is not null`) // if (!id) throw new Error(`${type=='group'?'群ID':'用户ID'} is not null`)
if (chatType == 'group') res.groupId = id || this.id || '' if (chatType == 'group') res.groupId = id || this.id || ''
@ -160,5 +164,6 @@ export class ChatWs {
} }
// 连接socket // 连接socket
export const connect = () => new ChatWs() export const connect = () => new ChatWs()
export const getInstance = () => new ChatWs(false)
// 默认实例 // 默认实例
export default new ChatWs() export default new ChatWs()

View File

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

View File

@ -157,6 +157,7 @@ const props = defineProps({
currentCourse: Object, currentCourse: Object,
}) })
const emits = defineEmits(['getData']) const emits = defineEmits(['getData'])
// ppt
const isShow = ref(false) const isShow = ref(false)
const propsQueryCourseObj = route.query.courseObj;// const propsQueryCourseObj = route.query.courseObj;//
@ -490,12 +491,14 @@ const handleClassWorkFormQuizRemove = (index) =>{
// [] newWorkSpaceEdit true // [] newWorkSpaceEdit true
if(classWorkForm.id != '' ) {// id if(isShow.value === false){
if(classWorkForm.id != '' ) {// id
editWork(cform); // editWork(cform); //
return; return;
}
} }
if (classWorkForm.worktype === "课堂展示") { if (classWorkForm.worktype === "课堂展示") {
boardLoading.value = true boardLoading.value = true
let canvasJson = proxy.$refs.boardref.getCanvasJson() let canvasJson = proxy.$refs.boardref.getCanvasJson()
@ -596,7 +599,11 @@ const handleClassWorkFormQuizRemove = (index) =>{
} }
console.log('该清空左侧列表数据了'); console.log('该清空左侧列表数据了');
// //
currentRow.value = {id:0}; if(isShow.value){
currentRow.value = {id:1};
}else{
currentRow.value = {id:0};
}
initHomeWork(); initHomeWork();

View File

@ -1,306 +0,0 @@
<template>
<el-dialog v-model="isDialog" :show-close="false" width="800" destroy-on-close>
<template #header>
<div class="custom-header flex">
<span>{{ item.name }}</span>
<i class="iconfont icon-guanbi" @click="isDialog = false"></i>
</div>
</template>
<div class="dialog-content">
<el-scrollbar height="400px">
<div class="chart-con flex">
<template v-for="item in msgList">
<div class="flex-end flex" v-if="item.type == 'user'">
<div class="chart-item user">{{ item.msg }}</div>
</div>
<div class="flex-start flex" v-else>
<div class="flex" v-loading="!item.msg">
<div class="chart-item robot">{{ item.msg }}</div>
</div>
<div class="flex flex-end replace-item">
<span @click="saveAdjust(item)"><i class="iconfont icon-tihuan"></i>替换分析结果</span>
</div>
</div>
<div v-if="loaded" class="chart-loading">
<div></div>
<div></div>
<div></div>
</div>
</template>
</div>
</el-scrollbar>
<div class="file-list">
<el-dropdown @command="changeFile">
<span class="el-dropdown-link">
{{ curFile.fileName }}
<i class="iconfont icon-xiangxia"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in fileList" :command="item" :key="item.id">{{ item.fileName
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="input-box flex">
<el-input v-model="textarea" @keyup.enter="send" :disabled="loaded"/>
<div class="ipt-icon" @click="send">
<i class="iconfont icon-fasong"></i>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { conversation, completion, docList } from '@/api/mode/index'
import { sessionStore } from '@/utils/store'
import { ElMessage } from 'element-plus'
import { dataSetJson } from '@/utils/comm.js'
import useUserStore from '@/store/modules/user'
import emitter from '@/utils/mitt';
const userInfo = useUserStore().user
const textarea = ref('')
const isDialog = defineModel()
const props = defineProps({
item: {
type: Object,
default: () => {
return { name: '11' }
}
},
modeType: {
type: Number,
default: 1
}
})
const emit = defineEmits(['saveEdit'])
const loaded = ref(false)
const msgList = ref([])
const send = () =>{
if(loaded.value) return
msgList.value.push({
type: 'user',
msg: textarea.value
})
loaded.value = true
getConversation(textarea.value)
textarea.value = ''
}
const curNode = reactive({})
const params = reactive(
{
prompt: '',
dataset_id: '',
document_ids: '',
}
)
// ID
const getConversation = (val) => {
getCompletion(val)
}
//
const getCompletion = async (val) => {
try {
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}考试 对${curNode.itemtitle}进行教学分析`
const { data } = await completion(params)
let answer = data.answer
msgList.value.push({
type: 'robot',
msg: answer,
})
} finally {
loaded.value = false
}
}
const saveAdjust = (item) =>{
emit('saveAdjust', item.msg)
isDialog.value = false
ElMessage.success('操作成功')
}
const curFile = reactive({})
const dataset_id = ref('')
const fileList = ref([])
const getList = () =>{
docList({
userId: userInfo.userId,
dataset_id: dataset_id.value
}).then( res =>{
fileList.value = res.rows
Object.assign(curFile, fileList.value[0])
params.document_ids = fileList.value[0].docId
})
}
emitter.on('curFile', (item) =>{
changeFile(item)
})
const changeFile = (val) =>{
Object.assign(curFile, val);
params.document_ids = val.docId
}
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
let text = props.modeType == 1 ||props.modeType == 2 ? '课标' : '考试'
let jsonKey = `${text}-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey]
dataset_id.value = dataSetJson[jsonKey]
getList()
})
</script>
<style lang="scss" scoped>
.custom-header {
justify-content: space-between;
align-items: center;
.icon-guanbi {
cursor: pointer;
font-weight: bold;
}
}
.dialog-content {
padding-top: 10px;
padding-bottom: 20px;
.chart-con {
flex-direction: column;
align-items: flex-start;
.flex-end{
width: 100%;
justify-content: flex-end;
}
.flex-start{
width: 100%;
justify-content: flex-start;
flex-direction: column;
}
.chart-item {
border-radius: 5px;
padding: 5px;
text-align: left;
}
.user {
background: #F2F2F2;
margin-bottom: 10px;
}
.robot {
background: #409EFF;
color: #FFF;
}
.replace-item{
font-size: 12px;
color: #409EFF;
align-items: center;
cursor: pointer;
}
}
.input-box {
position: relative;
.ipt-icon {
cursor: pointer;
padding: 0 5px;
.icon-fasong {
font-size: 26px;
color: #409EFF;
}
}
}
}
.chart-loading,
.chart-loading>div {
position: relative;
box-sizing: border-box;
}
.chart-loading {
display: block;
font-size: 0;
color: #66b1ff;
}
.chart-loading.la-dark {
color: #66b1ff;
}
.chart-loading>div {
display: inline-block;
float: none;
background-color: currentColor;
border: 0 solid currentColor;
}
.chart-loading {
width: 54px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
}
.chart-loading>div:nth-child(1) {
animation-delay: -200ms;
}
.chart-loading>div:nth-child(2) {
animation-delay: -100ms;
}
.chart-loading>div:nth-child(3) {
animation-delay: 0ms;
}
.chart-loading>div {
width: 8px;
height: 8px;
border-radius: 100%;
margin-right: 4px;
animation: ball-pulse 1s ease infinite;
}
@keyframes ball-pulse {
0%,
60%,
100% {
opacity: 1;
transform: scale(1);
}
30% {
opacity: 0.1;
transform: scale(0.01);
}
}
.file-list{
display: flex;
margin-bottom: 10px;
}
</style>

View File

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

View File

@ -1,78 +0,0 @@
<template>
<el-dialog v-model="isDialog" :show-close="false" width="900">
<template #header>
<div class="custom-header flex">
<span>{{ item.name }}</span>
<i class="iconfont icon-guanbi" @click="isDialog = false"></i>
</div>
</template>
<div class="dialog-content">
<el-row>
<el-col :span="24">
<el-input v-model="textarea" :autosize="{ minRows: 5, maxRows: 15 }" type="textarea" />
</el-col>
</el-row>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="isDialog = false">取消</el-button>
<el-button type="primary" @click="onSave">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch} from 'vue'
import { ElMessage } from 'element-plus'
import { editTempResult } from '@/api/mode/index.js'
const textarea = ref('')
const isDialog = defineModel()
const props = defineProps({
item: {
type: Object,
default: () => {
return { name: '11' }
}
}
})
watch(() => props.item.answer, (newVal) => {
if (newVal) {
textarea.value = newVal
}
},{ deep: true })
const emit = defineEmits(['saveEdit'])
const onSave = () =>{
editTempResult({id: props.item.resultId, content: textarea.value}).then( res =>{
isDialog.value = false
ElMessage.success('操作成功')
emit('saveEdit', textarea.value)
})
}
</script>
<style lang="scss" scoped>
.custom-header {
justify-content: space-between;
align-items: center;
.icon-guanbi {
cursor: pointer;
font-weight: bold;
}
}
.dialog-content {
padding-top: 10px;
}
</style>

View File

@ -1,143 +0,0 @@
<template>
<div class="container-header flex">
<div class="header-left flex">
<el-button link @click="showDialog = true">
{{ curNode.edustage}}{{ curNode.edusubject }}考试分析<i class="iconfont icon-xiangxia"></i>
</el-button>
</div>
<div class="header-right flex">
<el-dropdown @command="changeTemplate" :hide-on-click="false">
<span class="el-dropdown-link">
{{ curTemplate.name }}
<i class="iconfont icon-xiangxia"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in templateList" :command="item" :key="item.id">{{ item.name
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div>
<el-button type="primary" link @click="onAdd">
<el-icon>
<Plus />
</el-icon>
添加提示词
</el-button>
<el-button type="primary" link>保存模板</el-button>
<el-button type="primary" @click="aiRead">一键研读</el-button>
</div>
</div>
</div>
<!--添加提示词-->
<keywordDialog v-model="wordDialog" :modeType="type" :isAdd="wordDialog" :item="curTemplate" />
<Dialog v-model="showDialog" :modeType="type" />
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue'
import { Plus } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'
import { modelList } from '@/api/mode/index'
import Dialog from './dialog.vue'
import keywordDialog from './keyword-dialog.vue'
import emitter from '@/utils/mitt';
import { sessionStore } from '@/utils/store'
const wordDialog = ref(false)
const props = defineProps({
type: {
type: Number,
default: 1
}
})
const emit = defineEmits(['changeTemp', 'onRead'])
const showDialog = ref(false)
const aiRead = () => {
emit('onRead')
}
//
const curTemplate = reactive({ name: '', id: '' })
//
const templateList = ref([])
//
const getTemplateList = () => {
modelList({ model: props.type, type: 1, pageNum: 1, pageSize: 10000 }).then(res => {
templateList.value = res.rows
Object.assign(curTemplate, res.rows[0]);
emit('changeTemp', res.rows[0].id)
})
}
//
const changeTemplate = (val) => {
ElMessageBox.confirm(
'切换模板将清除当前研读结果?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
Object.assign(curTemplate, val);
emit('changeTemp', curTemplate.id)
})
}
emitter.on('onGetMain', () =>{
getTemplateList()
})
const onAdd = () => {
wordDialog.value = true
}
onUnmounted(() => {
emitter.off('onGetMain')
})
const curNode = reactive({})
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
getTemplateList()
})
</script>
<style lang="scss" scoped>
.container-header {
height: 45px;
background: #fff;
border-radius: 5px 5px 0 0;
box-shadow: 0px 0px 20px 0px rgba(99, 99, 99, 0.06);
.header-left {
width: 50%;
align-items: center;
padding-left: 20px;
}
.header-right {
width: 50%;
justify-content: space-between;
align-items: center;
padding: 0 10px;
}
.icon-xiangxia {
margin-left: 5px;
font-weight: bold;
}
}
</style>

View File

@ -1,161 +0,0 @@
<template>
<el-dialog v-model="mode" :show-close="false" width="600" destroy-on-close>
<template #header>
<div class="custom-header flex">
<span>{{ item.ex3 == '1' ? '请输入新的模板名称' : isAdd ? '添加提示词' : '编辑提示词' }}</span>
<i class="iconfont icon-guanbi" @click="mode = false"></i>
</div>
</template>
<div class="dialog-content" v-loading="loading">
<p class="small-tip" v-if="item && item.ex3 == '1'">*当前模板为系统预设不支持直接操作需要复制一份为自己的然后再操作</p>
<el-form :model="form" label-width="auto">
<el-form-item label="名称">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="提示词" v-if="item.ex3 == '1' ? false : true">
<el-input v-model="form.prompt" type="textarea" />
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="mode = false">取消</el-button>
<el-button type="primary" @click="saveAdd">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import emitter from '@/utils/mitt';
import { addKeyWords, addChildTemp, editChildTemp } from '@/api/mode/index'
const mode = defineModel()
const props = defineProps({
modeType: {
type: Number,
default: 1
},
isAdd: {
type: Boolean,
default: true
},
item: { //
type: Object,
default: () => {
return { ex3: '' }
}
},
tempId: {
type: [String, Number],
default: ''
},
})
const form = reactive({
name: '',
prompt: '',
})
watch(() => props.isAdd, (newVal) => {
if (!newVal) {
form.name = props.item?.name
form.prompt = props.item?.prompt
}
}, { immediate: false })
const loading = ref(false)
const saveAdd = async () => {
loading.value = true
if (props.item.ex3 == '1') {
if (props.isAdd) {
try {
// copy
const { msg } = await addKeyWords({ name: form.name, id: props.item.id })
emitter.emit('onGetChild')
ElMessage.success(msg)
mode.value = false
} finally {
loading.value = false
}
}
else{
onAddChildTemp(props.item.parentId)
}
} else {
if (props.isAdd) {
onAddChildTemp(props.item.id)
}
else {
try {
let data = JSON.parse(JSON.stringify(props.item))
data.name = form.name;
data.prompt = form.prompt
const { msg } = await editChildTemp(data)
emitter.emit('onGetChild')
ElMessage.success(msg)
mode.value = false
} finally {
loading.value = false
}
}
}
}
//
const onAddChildTemp = async (parentId) => {
//
let obj = {
name: form.name,
type: 2, // 2
sortNum: 1,
parentId,
lmType: 1,
model: props.modeType,
prompt: form.prompt,
ex1: props.item.ex1, //
ex2: props.item.ex2, //
ex3: '', //
}
try {
var { msg } = await addChildTemp(obj)
emitter.emit('onGetChild')
ElMessage.success(msg)
mode.value = false
} finally {
loading.value = false
}
}
</script>
<style lang="scss" scoped>
.custom-header {
justify-content: space-between;
align-items: center;
.icon-guanbi {
cursor: pointer;
font-weight: bold;
}
}
.small-tip {
text-align: left;
font-size: 12px;
margin-bottom: 15px;
color: #F56C6C;
}
</style>

View File

@ -1,32 +0,0 @@
<template>
<div class="container-pdf">
<PDF :url="pdfUrl" :showCatalog="false" v-if="pdfUrl" />
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue';
import PDF from '@/components/PdfJs/index.vue'
import { sessionStore } from '@/utils/store'
const pdfUrl = ref('')
onMounted(async () =>{
await nextTick()
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>
<style lang="scss" scoped>
.container-pdf{
height: 100%;
}
</style>

View File

@ -1,513 +0,0 @@
<template>
<div class="read-container">
<el-scrollbar height="100%">
<div class="template-list">
<el-row v-for="(item, index) in childTempList">
<el-col :span="24">
<div class="template-item" v-loading="item.loading">
<div class="item-header">
<div>
<span class="blue">#</span>{{ item.name }}
</div>
<el-popover placement="bottom-end" trigger="hover" popper-class="template-custom-popover">
<template #reference>
<el-button link type="primary">
<i class="iconfont icon-shenglvehao"></i></el-button>
</template>
<template #default>
<el-button type="primary" link @click="editKeyWord(item)">编辑</el-button>
<el-button type="primary" link @click="removeItem(item)">移除</el-button>
</template>
</el-popover>
</div>
<div class="item-text" >
{{ item.prompt }}
</div>
<div class="item-text text-answer" v-if="item.answer">
<div class="item-icon">
<i class="iconfont icon-ai"></i>
</div>
<div class="item-answer" v-html="item.answer"></div>
</div>
<div class="ai-btn" v-if="item.answer">
<el-button type="primary" link @click="againResult(index, item)">
<i class="iconfont icon-ai1"></i>
重新研读
</el-button>
<el-button type="primary" link @click="onAdjust(index, item)">
<i class="iconfont icon-duihua"></i>
AI对话调整
</el-button>
<el-button type="primary" link @click="onEdit(index, item)">
<i class="iconfont icon-bianji1"></i>
手动编辑结果
</el-button>
</div>
</div>
</el-col>
</el-row>
<el-empty v-if="!childTempList.length" description="暂无模板数据" />
</div>
</el-scrollbar>
<!--编辑结果-->
<EditDialog v-model="isEdit" :item="editItem" @saveEdit="saveEdit" />
<!--AI 对话调整-->
<AdjustDialog v-model="isAdjust" :modeType="modeType" :item="editItem" @saveAdjust="saveAdjust" />
<!--编辑提示词-->
<keywordDialog v-model="isEditKeyWord" :isAdd="isAdd" :item="keywordItem" :tempId="tempId" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch, onUnmounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'
import EditDialog from './edit-dialog.vue'
import AdjustDialog from './adjust-dialog.vue'
import keywordDialog from './keyword-dialog.vue';
import { sessionStore } from '@/utils/store'
import useUserStore from '@/store/modules/user'
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
import { dataSetJson } from '@/utils/comm.js'
import emitter from '@/utils/mitt';
const userStore = useUserStore()
const props = defineProps({
tempId: {
type: [String, Number],
default: ''
},
modeType: {
type: Number,
default: 1
}
})
emitter.on('onGetChild', () =>{
getChildTemplate()
})
//
const tempLoading = ref(false)
const childTempList = ref([])
const getChildTemplate = () => {
tempLoading.value = true
modelList({ model: props.modeType, type: 2, parentId: props.tempId }).then(res => {
childTempList.value = res.rows
getTempResult()
}).finally(() => {
tempLoading.value = false
})
}
//
const getTempResult = () => {
tempResult({ mainModelId: props.tempId }).then(res => {
let rows = res.rows
childTempList.value.forEach(item => {
rows.forEach(el => {
if (item.id == el.modelId) {
item.answer = el.content
item.resultId = el.id
}
})
})
})
}
const isEdit = ref(false)
watch(() => props.tempId, (newVal) => {
if (newVal) {
// isEdit.value = true
getChildTemplate()
}
})
// ID
const params = reactive(
{
prompt: '',
dataset_id: ''
}
)
const curNode = reactive({})
const getConversation = () => {
getCompletion()
}
//
const getCompletion = async () => {
for (let item of childTempList.value) {
try {
item.loading = true
params.prompt = `按照${item.prompt}的要求,针对${curNode.edustage}${curNode.edusubject}考试 对${curNode.itemtitle}进行教学分析`
const { data } = await completion(params)
let answer = data.answer
item.oldAnswer = answer
item.answer = getResult(answer);
onSaveTemp(item)
} finally {
item.loading = false
}
}
}
//
const onSaveTemp = (item) => {
const data = {
mainModelId: props.tempId,
modelId: item.id,
examDocld: '',
content: item.oldAnswer
}
tempSave(data).then(res => {
console.log(res)
})
}
//
const againResult = async (index, item) => {
try {
childTempList.value[index].loading = true
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}考试 对${curNode.itemtitle}进行教学分析`
const { data } = await completion(params)
let answer = data.answer
childTempList.value[index].oldAnswer = answer
childTempList.value[index].answer = getResult(answer);
onEditSave(childTempList.value[index])
} finally {
childTempList.value[index].loading = false
}
}
//
const onEditSave = async (item) =>{
const { res } = await editTempResult({id: item.resultId, content: item.oldAnswer})
ElMessage.success(res)
getChildTemplate()
}
//
let getResult = (text) => {
text = text.replace(/^\n\n(.*?)\n\n$/s, '<div>$1</div>');
text = text.replace(/^\n(.*?)\n$/s, '<p>$1</p>');
text = text.replace(/\*\*(.*?)\*\*/g, "<div class='text-tit'>$1</div>");
text = text.replace(/(\d+\..*?)\n/g, "<div class='text-num'>$1</div>\n");
return text
}
//
const curIndex = ref(-1)
const editItem = reactive({})
const onEdit = (index, item) => {
curIndex.value = index
Object.assign(editItem, item)
isEdit.value = true
}
//
const saveEdit = (data) => {
// childTempList.value[curIndex.value].oldAnswer = data
// let answer = getResult(data);
// childTempList.value[curIndex.value].answer = answer
getChildTemplate()
}
const isAdjust = ref(false)
const onAdjust = (index, item) => {
curIndex.value = index
Object.assign(editItem, item)
isAdjust.value = true
}
const saveAdjust = (item) => {
childTempList.value[curIndex.value].oldAnswer = item
let answer = getResult(item);
childTempList.value[curIndex.value].answer = answer
}
//
const keywordItem = reactive({})
const isEditKeyWord = ref(false)
const isAdd = ref(true)
const editKeyWord = (item) => {
console.log(item)
isAdd.value = false
isEditKeyWord.value = true
Object.assign(keywordItem, item)
}
//
const removeItem = async (item) => {
if (item.ex3 != '1') {
ElMessageBox.confirm(
'确认是否移除?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
console.log(item)
removeChildTemp(item.id).then(res => {
ElMessage.success('操作成功')
getChildTemplate()
})
})
}
else{
isAdd.value = false
Object.assign(keywordItem, item)
isEditKeyWord.value = true
}
// const { msg } = await removeChildTemp(item.id)
// ElMessage.success(msg)
// getChildTemplate()
}
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
let text = props.modeType == 1 || props.modeType == 2? '课标' : '考试'
let jsonKey = `${text}-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey]
})
//
onUnmounted(() => {
emitter.off('onGetChild')
})
defineExpose({
getConversation
})
</script>
<style lang="scss" scoped>
.read-container {
display: flex;
flex-direction: column;
width: 100%;
padding: 15px 0;
height: 100%;
position: relative;
.el-scrollbar {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
.el-dropdown-link {
font-weight: bold;
.el-icon--right {
font-weight: bold;
}
}
.read-header {
justify-content: space-between;
align-items: center;
.add-btn {
font-size: 13px;
.icon-jiahao {
margin-right: 3px;
font-size: 14px;
}
}
}
.right-con {
display: flex;
}
.template-list {
.template-item {
background: #fff;
padding: 10px;
margin-top: 10px;
border-radius: 5px;
.item-header {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #000;
justify-content: space-between;
.blue {
font-size: 22px;
color: #409eff;
margin-right: 5px;
}
}
.item-text {
display: flex;
margin-top: 10px;
font-size: 14px;
text-align: left;
color: #606266;
.item-icon {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
background: #F6F6F6;
border-radius: 50%;
margin-right: 10px;
flex-shrink: 0;
}
.item-answer {
flex-direction: column;
padding-top: 5px;
:deep(.text-tit) {
font-weight: bold;
margin: 10px 0;
}
:deep(.text-num) {
padding-left: 2em;
}
}
}
.text-answer {
color: #409eff;
}
.ai-btn {
margin-top: 10px;
display: flex;
justify-content: flex-end;
.iconfont {
margin-right: 3px;
}
:deep(.el-button) {
font-size: 13px;
}
.icon-ai1 {
font-size: 18px;
}
}
}
}
.template-item-result {
background: #DDEAFD !important;
.result-item-header {
display: flex;
align-items: flex-start;
text-align: left;
font-size: 16px;
font-weight: bold;
color: #3D3D3D;
.icon-xiaoxi {
color: #5881D5;
font-weight: bold;
font-size: 20px;
margin-right: 10px;
}
}
.result-icon-btn {
justify-content: space-between;
font-size: 13px;
margin-top: 5px;
span {
display: flex;
align-items: center;
cursor: pointer;
margin-right: 10px;
&:hover {
background: #cfe0fa
}
}
.iconfont {
margin-right: 3px;
color: #3498fc;
}
}
.line {
height: 1px;
background: #D8D8D8;
margin: 10px 0;
}
.other-msg {
font-size: 13px;
.other-user {
align-items: center;
color: #BA4B0F;
font-size: 12px;
.icon-touxiang {
color: #BA4B0F;
font-weight: bold;
font-size: 16px;
margin-right: 5px;
}
}
.other-text {
color: #191919;
text-align: left;
}
}
}
}
.icon-shenglvehao {
font-weight: bold;
font-size: 22px;
}
:deep(.el-popover) {
min-width: 50px;
width: 50px !important;
}
.pl-25 {
padding-left: 25px;
}
</style>
<style>
.template-custom-popover {
width: 110px !important;
min-width: 110px !important;
}
</style>

View File

@ -1,52 +1,11 @@
<template> <template>
<div class="page-template flex"> <TemplateStudy :type="3"/>
<!--头部-->
<Header :type="type" @changeTemp="changeTemp" @onRead="onRead"/>
<el-row :gutter="20" class="tempalte-main">
<el-col :span="12">
<!--左侧pdf-->
<Pdf />
</el-col>
<el-col :span="12">
<!--右侧模板研读-->
<Result ref="resultRef" :modeType="type" :tempId="tempId"/>
</el-col>
</el-row>
</div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import TemplateStudy from '@/components/template-study/index.vue'
import Header from './container/header.vue'
import Pdf from './container/pdf.vue'
import Result from './container/result.vue'
const props = defineProps({
type: {
type: Number,
default: 3
},
})
const resultRef = ref()
const tempId = ref('')
const changeTemp = (id) =>{
tempId.value = id
}
const onRead = () =>{
resultRef.value.getConversation()
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page-template {
flex-direction: column;
height: 100%;
.tempalte-main {
flex: 1;
}
}
</style> </style>

View File

@ -1,388 +1,461 @@
<template> <template>
<div class="page-resource flex mb-4"> <el-row class="model-wrap">
<!-- 左侧 教材 目录 -->
<!-- <ChooseTextbook @change-book="getData" @node-click="getData" /> -->
<div class="page-right">
<div class="button-container">
<el-button type="primary" @click="onchange('/model/curriculum')">课标研读</el-button>
<!-- <el-button type="primary" @click="onchange('/model/management')">作业管理1</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="info" @click="onchange('/model/design')">教学框架设计</el-button>
<!-- <el-button type="success" @click="openPPTist">打开PPTist</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="success" @click="onchange('/model/aiKolors')">文生图片</el-button>
</div>
</div>
</div>
<el-row class="container">
<!-- 左侧 选择教材 目录 --> <!-- 左侧 选择教材 目录 -->
<ChooseTextbook @change-book="changeBook" @node-click="changeBook" /> <ChooseTextbook @change-book="changeBook" @node-click="changeBook" />
<!-- 中间 展示内容 --> <!-- 右侧 展示内容 -->
<el-col :span="10"> <div class="right-content">
<div class="c-item mb-4 mx-4"> <div class="content-header-wrap">
<div class="flex justify-between pb-2"> <div v-for="(item,index) in tags" :key="index" :style="{'background-color':item.bgcolor}" @click="onchange(item)">
<h3>教师资源</h3> <el-icon class="item-icon"><Flag /></el-icon>
<span class="c-btns"> <div class="content-header-title">{{item.name}}</div>
<template v-for="item in resourBtns"> <div class="content-header-body">
<el-button :size="item.size" text :icon="item.icon" @click="handleAll(item.prop)">{{ item.name }}</el-button> <div class="content-header-num">6</div>
</template> <div class="content-header-text">分析结果</div>
</span> </div>
</div> </div>
<c-table ref="resourRef" v-bind="sourceOpt" t-class="rounded">
<template #title="{row,value}">
<el-link :underline="false" @click="handleAll('open', row)">
<svg class="icon svg-icon" aria-hidden="true">
<use :xlink:href="`#icon-${getIcon(row)}`"></use>
</svg>
<b class="ml-1">{{ value }}</b>
</el-link>
</template>
</c-table>
</div> </div>
</el-col> <div class="content-body-wrap">
<div class="content-body-left">
<div class="content-body-left-title">
文枢课件
<el-button class="add-btn" size="small" type="primary" @click="createAIPPT">新建</el-button>
</div>
<div class="content-body-left-body">
<kj-list-item
v-for="(item, index) in currentFileList"
:key="index"
:ref="collectRef('kjItemRef'+item.id)"
:item="item"
:show-tool="false"
:index="index"
:curNode="currentNode"
@on-delete="deleteTalk"
@change="changeClass"
>
</kj-list-item>
<!-- <div class="content-body-left-item" v-for="item in 5">
<div class="content-body-left-item-img">
<FileImage :size="50" :file-name="'aaa.aippt'" />
</div>
<div class="content-body-left-item-text">
<div class="content-body-left-item-title">沁园春*长沙</div>
<div class="content-body-left-item-info">21 访问 100 引用 50 点赞 20 更新时间 2022-01-01 15:20</div>
</div>
<div></div>
</div>-->
</div>
</div>
<div class="content-body-right">
<div class="content-body-right-title">模型辅助工具</div>
<div class="content-body-right-body">
<div class="content-body-right-item" v-for="(item,index) in tools" :key="index" @click="gotoRoute(item)">
<div class="content-body-right-item-img">
<FileImage :fileName="item.img"/>
</div>
<div class="content-body-right-item-text">{{item.name}}</div>
</div>
</div>
</div>
</div>
</div>
</el-row> </el-row>
</template> </template>
<script setup> <script setup>
import { onMounted, ref, watch, reactive } from 'vue' import { onMounted, reactive, ref, nextTick } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
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 { createWindow } from '@/utils/tool' //
import * as API_smarttalk from '@/api/file' // api
import * as API_entpcourse from '@/api/education/entpcourse' // api
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api
import { dataSetJson } from '@/utils/comm' // id
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 KjListItem from '@/views/prepare/container/kj-list-item.vue'
import FileImage from '@/components/file-image/index.vue'
import {creatAPT, getSmarttalkPage} from '@/api/file'
import {Flag, Position} from '@element-plus/icons-vue'
import {asyncLocalFile, parseCataByNode} from "@/utils/talkFile";
import { dataSetJson } from '@/utils/comm' // id
import { sessionStore } from '@/utils/store'
import {listEntpcourse} from "@/api/teaching/classwork";
import {addEntpcoursefileReturnId, getEntpcoursefile} from "@/api/education/entpcoursefile";
import {createWindow, ipcMsgSend} from "@/utils/tool";
import {ElMessage} from "element-plus"; //
const router = useRouter() const router = useRouter()
const userStore = useUserStore() // const userStore = useUserStore().user //
const currentNode = ref({})
const refs = ref([]);
const courseObj = reactive({ const collectRef = (key) => {
// : id,id,id, return (el) => {
textbookId: '', refs.value[key] = el;
levelFirstId: '', };
levelSecondId: '', };
coursetitle: '',
node: null, //
entp: null, //
})
const dt = reactive({
curRow: null, //
menus: [ //
{ label: '打开', click: (_, args) => handleAll('open', args) },
{ label: '删除', click: (_, args) => handleAll('delete', args) },
],
})
// ref
const resourRef = ref() // ref
//
const resourBtns = [
{ name: '刷新', prop: 'refresh', size: 'small', icon: Refresh },
{ name: '资源库', prop:'resource', size:'small', icon: Files },
{ name: '上传', prop:'upload', size:'small', icon: UploadFilled },
{ name: '添加', prop:'add', size:'small', icon: Plus },
]
// -cTable
const sourceOpt = reactive({
data: [], //
option: [ //
{ label: '名称', prop: 'title', align: 'left' },
{ label: '类型', prop: 'filetype', width: 80, },
{ label: '时间', prop: 'timestamp', width: 160, sortable: true },
],
noPage: true, //
isMain: false, //
highlightCurrentRow: true, //
rowClick: (r, c, e) => { // -()
if (dt.curRow == r) { // -
resourRef.value.$refs.table.setCurrentRow()
dt.curRow = null
} else dt.curRow = r
},
rowContextmenu: (r, c, e) => { //
dt.menus.forEach(item => {
if(item.label == '打开') item.icon = getIcon(r, 'svg')
else if(item.label == '删除') item.icon = getIcon('icon-shanchu', 'class')
})
menusEvent(e, dt.menus, r)
},
})
const tags = reactive([{
name: '课标分析',
path: '/model/curriculum',
bgcolor: 'rgb(241,65,108)'
},{
name: '教材分析',
path: '/model/teaching',
bgcolor: 'rgb(114,57,234)'
},{
name: '考试分析',
path: '/model/examination',
bgcolor: 'rgb(251,132,4)'
},{
name: '素材设计',
path: '/model/aiKolors',
bgcolor: 'rgb(25,123,237)'
},{
name: '作业设计',
path: '/model/newClassTaskAssign',
bgcolor: 'rgb(23,198,83)'
},{
name: '框架设计',
path: '/model/design',
bgcolor: 'rgb(34,35,43)'
}])
const tools = reactive([{
name: '数字人生成',
path: '',
img: 'airobot'
},{
name: '语音生成',
path: '',
img: 'aiyuyin'
},{
name: '文生图片',
path: '/model/aiKolors',
img: 'aiimg'
},{
name: '文生连环画',
path: '',
img: 'aidraw'
},{
name: '视频生成',
path: '',
img: 'aivideo'
}])
const uploadData = ref({
textbookId: null,
levelFirstId: null,
levelSecondId: null,
fileSource: '个人',
fileRoot: '备课'
})
const currentFileList = ref([])
// //
onMounted(() => { onMounted(() => {
}) })
// -methods const gotoRoute = (item) => {
if (item.path) {
if (item.path === '/model/aiKolors') {
gotoAiKolors(item.path)
}else {
router.push(item.path)
}
}
}
const createAIPPT = () => {
console.log(userStore)
listEntpcourse({
evalid: currentNode.value.id,
edituserid: userStore.userId,
pageSize: 500
}).then((response) => {
if (response.rows.length <= 0) return
let resCourse = response.rows[0]
//
let form = {
parentid: 0,
entpid: userStore.deptId,
entpcourseid: resCourse.id,
ppttype: 'file',
title: resCourse.coursetitle,
fileurl: '',
filetype: 'aippt',
datacontent: '',
filekey: '',
filetag: '',
fileidx: 0,
dflag: 0,
status: '',
edituserid: userStore.userId
}
addEntpcoursefileReturnId(form).then((slideid) => {
//
var form = {
parentid: slideid,
entpid: resCourse.entpid,
entpcourseid: resCourse.id,
title: '第一页',
filetype: 'slide',
datacontent: '{"elements":[],"background":{"type":"solid","color":"#fff"}}',
edituserid: userStore.userId
}
addEntpcoursefileReturnId(form).then((res) => {
creatAPT({
...uploadData.value,
fileId: slideid,
fileFlag: 'aippt',
fileShowName: currentNode.value.itemtitle + '.aippt'
}).then(async (res) => {
currentFileList.value.unshift(res.resData)
await nextTick();
refs.value['kjItemRef'+res.resData.id].openFileWin(res.resData);
})
})
})
})
}
const deleteTalk = (item) => {
let index = currentFileList.value.indexOf(item)
currentFileList.value.splice(index, 1)
}
// //
const changeBook = async(data) => { const changeBook = async(data) => {
// console.log(data) console.log(data)
const { textBook, node } = data let cata = parseCataByNode(data.node)
let textbookId = textBook.curBookId currentNode.value = data.node
let levelSecondId = node.id uploadData.value.levelFirstId = cata[0]
let levelFirstId uploadData.value.levelSecondId = cata[1]
if (node.parentNode) { uploadData.value.levelThirdId = cata[2]
levelFirstId = node.parentNode.id uploadData.value.textbookId = data.textBook.curBookId
} else { getSmarttalkPage({
levelFirstId = node.id ...uploadData.value,
levelSecondId = '' orderByColumn: 'createTime',
} fileFlags: "'aippt'",
isAsc: 'desc',
courseObj.textbookId = textbookId // pageSize: 500
courseObj.levelFirstId = levelFirstId // }).then((res) => {
courseObj.levelSecondId = levelSecondId // currentFileList.value = [...res.rows]
courseObj.coursetitle = node.itemtitle // (/) })
courseObj.node = node; //
// ID
localStorage.setItem('unitId', JSON.stringify({ levelFirstId, levelSecondId }))
//
const params = { evalid: node.id, edituserid: userStore.id, pageSize: 1 }
const res = await HTTP_SERVER_API('getCourseList', params)
courseObj.entp = res?.rows?.[0] || null
sessionStore.set('curr.entp', courseObj.entp) //
// -
getResourceList()
} }
const onchange = (item) => {
const openPPTist = () => { let path = item.path
createWindow('open-win', { url: '/pptist' })
}
const onchange = (path) => {
if (path == '/model/newClassTaskAssign') { if (path == '/model/newClassTaskAssign') {
uploadData.value.coursetitle = currentNode.value.itemtitle // (/)
uploadData.value.node = currentNode.value; //
// //
router.push({ path, query: { courseObj: JSON.stringify(courseObj) } }) router.push({ path, query: { courseObj: JSON.stringify(uploadData.value) } })
} else if (path == '/model/aiKolors') { } else if (path == '/model/aiKolors') {
// ai gotoAiKolors(path)
let subjectdata = sessionStore.get('subject.curNode')
let datasubject = `课标-${subjectdata.edustage}-${subjectdata.edusubject}`
console.log(subjectdata)
router.push({
path,
query: {
datasetId: dataSetJson[datasubject],
coursetitle: courseObj.coursetitle,
levelFirstId: subjectdata.parentid,
levelSecondId: subjectdata.id,
textbookId: subjectdata.rootid,
}
});
} else { } else {
router.push(path) router.push(path)
} }
} }
//
const getResourceList = async () => { const gotoAiKolors = (path)=> {
const entpcourseidarray = courseObj?.entp?.id // ai
if (!entpcourseidarray) return msgUtils.msgWarning('请选择章节?') let subjectdata = sessionStore.get('subject.curNode')
const params = { let datasubject = `课标-${subjectdata.edustage}-${subjectdata.edusubject}`
pageSize: 100, parentid: 0, entpcourseidarray, console.log(subjectdata)
orderByColumn: 'timestamp', isAsc: 'desc', router.push({
} path,
const res = await HTTP_SERVER_API('getCourseFileList', params) query: {
if (res?.code == 200) { datasetId: dataSetJson[datasubject],
sourceOpt.data = res?.rows || [] coursetitle: currentNode.value.itemtitle,
} else { levelFirstId: subjectdata.parentid,
msgUtils.msgWarning('获取资源列表, 请重试') levelSecondId: subjectdata.id,
} textbookId: subjectdata.rootid,
}
});
} }
// HTTP
const HTTP_SERVER_API = (type, params = {}) => { const changeClass = async (type, row, other) => {
switch (type) { switch(type) {
case 'addSmarttalk': { // case 'click': { // --aippt
const def = { if (row.fileFlag === 'aippt' && !!row.fileId) {
fileId: '', // id - Entpcoursefile id const res = await getEntpcoursefile(row.fileId)
fileFlag: 'aptist', if (res && res.code === 200) {
fileShowName: courseObj.coursetitle + '.aptist', sessionStore.set('curr.resource', res.data) //
textbookId: courseObj.textbookId, sessionStore.set('curr.smarttalk', row) // smarttalk
levelFirstId: courseObj.levelFirstId, createWindow('open-win', {
levelSecondId: courseObj.levelSecondId, url: '/pptist', //
fileSource: '个人', close: () => {
fileRoot: '备课' sessionStore.set('curr.resource', null) //
} sessionStore.set('curr.smarttalk', null) //
return API_smarttalk.creatAPT({...def, ...params}) getSmarttalkPage({
} ...uploadData.value,
case 'addEntpcourse': { // orderByColumn: 'createTime',
const node = courseObj.node || {} fileFlags: "'aippt'",
if (!node) return msgUtils.msgWarning('请选择章节?') isAsc: 'desc',
const def = { // pageSize: 500
entpid: userStore.user.deptId, // id }).then((res) => {
level: 1, // currentFileList.value = [...res.rows]
parentid: 0, // id })
dictid: 0, // id }
evalid: node.id, // id })
evalparentid: node.parentid, // id(id) } else {
edusubject: node.edusubject, // ElMessage.warning(res.msg||'文件获取异常!')
edudegree: node.edudegree, //
edustage: node.edustage, //
coursetype: '课标学科', //
coursetitle: node.itemtitle, //
coursedesc: '', //
status: '', //
dflag: 0, //
edituserid: userStore.id, // id
createblankfile: 'no', //
}
courseObj.entp = def
return API_entpcourse.addEntpcourse(def)
}
case 'addEntpcoursefile': { //
const enpt = courseObj.entp
const def = {
parentid: 0,
entpid: userStore.user.deptId,
entpcourseid: enpt.id,
ppttype: 'file',
title: enpt.coursetitle,
fileurl: '',
filetype: 'aippt',
datacontent: '',
filekey: '',
filetag: '',
fileidx: 0,
dflag: 0,
status: '',
edituserid: userStore.id
}
// return Promise.resolve(1)
return API_entpcoursefile.addEntpcoursefileReturnId({...def,...params})
}
case 'getCourseList': { //
return API_entpcourse.listEntpcourse(params)
}
case 'getCourseFileList':{ //
return API_entpcoursefile.listEntpcoursefileNew(params)
}
}
}
//
const handleAll = async(type, row) =>{
// console.log(type)
switch (type) {
case 'refresh': //
getResourceList()
break;
case 'resource': //
break;
case 'upload': //
break;
case 'add':{ // PPT-list -
const enpt = courseObj.entp //
if (!enpt) { //
const resid = await HTTP_SERVER_API('addEntpcourse')
courseObj.entp.id = resid
}
// ppt-
const p_params = {parentContent: '{"width":1000,"ratio":0.5625}'}
const id = await HTTP_SERVER_API('addEntpcoursefile', p_params)
if (!!id??null) { //
const params = {
parentid: id,
title: '第一页',
filetype: 'slide',
datacontent: '{"elements":[],"background":{"type":"solid","color":"#fff"}}' // json
} }
// ppt-(slide) return
await HTTP_SERVER_API('addEntpcoursefile', params)
// -Smarttalk
await HTTP_SERVER_API('addSmarttalk',{fileId: id})
//
await getResourceList()
} else {
msgUtils.msgWarning('添加失败!')
} }
break; ElMessage.warning('该功能暂未开放!')
}
case 'open': { // -pptist
if (row.filetype != 'aippt') return msgUtils.msgWarning('暂不支持该类型文件操作!')
sessionStore.set('curr.resource', row) //
createWindow('open-win', {
url: '/pptist', //
close: () => {
sessionStore.set('curr.resource', null) //
getResourceList() //
}
})
break break
} }
case 'delete':{ // default:
if (!(row && row.id)) return msgUtils.msgWarning('请选择要删除的资源!') break
await msgUtils.confirm(`是否确认删除【${row.title}】课程课件?`)
await API_entpcoursefile.delEntpcoursefileNew(row.id)
msgUtils.msgSuccess('删除成功!')
//
await getResourceList()
break;
}
} }
} }
// icons type svg
const getIcon = (o, type) => {
let icon = typeof o == 'string' ? o : o?.filetype
if (['aippt'].includes(o?.filetype)) icon = 'pptx'
if (!!type) { // icon
switch(type) {
case 'svg': // svg
return `<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-${icon}"></use>
</svg>`
case 'class': // class
return `<span class="icon iconfont ${icon}"></span>`
case 'unicode': // unicode
return `<span class="icon iconfont">${icon}</span>`
default: // icon-class
return `icon-${icon}`
}
}
return icon
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page-resource { .model-wrap{
// height: 100%; width: 100%;
// padding: 10px 15px 0; height: 100%;
display: flex;
.page-right { flex-direction: row;
min-width: 0; .right-content{
flex: 1;
margin-left: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1;
// margin-left: 20px;
height: 100%; height: 100%;
background: #ffffff; .content-header-wrap{
border-radius: 10px; display: flex;
box-shadow: 0px 0px 20px 0px rgba(99, 99, 99, 0.06); flex-direction: row;
} justify-content: space-between;
align-items: center;
.button-container { width: 100%;
display: flex; //height: 215px;
flex-wrap: wrap; &>div{
gap: 10px; &:hover{
margin: 1rem; cursor: pointer;
justify-content: flex-start; }
width: calc(100%/6 - 10px);
.el-button { aspect-ratio: 0.718 / 1;
flex: 1 1 15%; border-radius: 10px;
max-width: 15%; color: white;
min-width: 15%; text-align: left;
box-sizing: border-box; background-color: #999999;
position: relative;
.item-icon{
position: absolute;
top: 10px;
left: 20px;
font-size: calc(1.8vw);
}
.content-header-title{
position: absolute;
bottom: 50%;
width: 100%;
font-size: calc(1.8vw);
padding-left: 20px;
}
.content-header-body{
position: absolute;
bottom: 0;
background-color: rgba(0, 0, 0, 0.27);
width: 100%;
height: 25%;
padding-left: 20px;
display: flex;
flex-direction: column;
justify-content: center;
font-size: calc(1vw);
border-radius: 0 0 10px 10px;
.content-header-num{
}
.content-header-text{
}
}
}
} }
} .content-body-wrap{
} display: flex;
.container{ flex-direction: row;
height: calc(100% - 32px - 3rem); justify-content: space-between;
.c-item{ margin-top: 20px;
.c-btns{ flex: 1;
.el-button{margin: 0;} overflow: auto;
height: 100%;
border-radius: 10px;
.content-body-left{
width: calc(50% - 7px);
background-color: white;
border-radius: 10px;
display: flex;
flex-direction: column;
.content-body-left-title{
text-align: left;
padding: 10px;
font-weight: bold;
position: relative;
height: 40px;
.add-btn{
position: absolute;
right: 10px;
}
}
.content-body-left-body{
flex: 1;
overflow: auto;
.content-body-left-item{
display: flex;
flex-direction: row;
border-bottom: 1px solid rgba(153, 153, 153, 0.29);
padding: 10px 0;
.content-body-left-item-img{
padding: 0 10px;
}
.content-body-left-item-text{
text-align: left;
.content-body-left-item-title{
font-weight: bold;
font-size: 16px;
}
.content-body-left-item-info{
font-size: 14px;
}
}
}
}
}
.content-body-right{
width: calc(50% - 8px);
background-color: white;
border-radius: 10px;
.content-body-right-title{
text-align: left;
padding: 10px;
font-weight: bold;
}
.content-body-right-body{
display: flex;
.content-body-right-item{
&:hover{
cursor: pointer;
}
height: 80px;
width: 100px;
display: flex;
flex-direction: column;
align-items: center;
.content-body-right-item-img{
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 50px;
border-radius: 25px;
border: 1px solid rgba(153, 153, 153, 0.18);
img{
border-radius: 25px;
width: 25px;
}
}
.content-body-right-item-text{
font-size: 14px;
}
}
}
}
} }
} }
} }

View File

@ -87,7 +87,8 @@ import { onMounted, reactive, ref, watchEffect, watch, nextTick, toRaw } from 'v
import { Refresh } from '@element-plus/icons-vue' import { Refresh } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus' // ui: import { ElMessage, ElMessageBox } from 'element-plus' // ui:
import vueQr from 'vue-qr/src/packages/vue-qr.vue' // : 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 ChatWs from '@/plugins/socket' // socket
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, createWindow, getStaticUrl, sessionStore } from '@/utils/tool' // - import { toLinkWeb, createWindow, getStaticUrl, sessionStore } from '@/utils/tool' // -
@ -151,9 +152,11 @@ const open = async (id, classObj) => {
teacherForm.form.classcourseid = classObj.id teacherForm.form.classcourseid = classObj.id
} }
// im-chat // im-chat
// nextTick(async() => { nextTick(async() => {
// chat = await imChatRef.value?.initImChat() // chat = await imChatRef.value?.initImChat()
// }) // socket
if (!ChatWs.ws) ChatWs.init()
})
} }
} }
// //
@ -267,10 +270,10 @@ const createClasscourse = async () => {
setTimeout(() => { setTimeout(() => {
msgEl.close() msgEl.close()
msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0}) msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0})
setTimeout(() => { setTimeout(async() => {
msgEl.close() msgEl.close()
const classcourse = {...params, id: teacherForm.form.classcourseid} const res = await Http_Classcourse.getClasscourse(teacherForm.form.classcourseid)
openPublicScreen(classcourse) openPublicScreen(res.data)
}, 2000); }, 2000);
}, 1000); }, 1000);
} }
@ -303,9 +306,10 @@ const classTeachingStart = async () => {
// -pptList // -pptList
if (myClassActive.value.filetype == 'aptist') { if (myClassActive.value.filetype == 'aptist') {
const msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0}) const msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0})
setTimeout(() => { setTimeout(async () => {
msgEl.close() msgEl.close()
openPublicScreen({id}) const res = await Http_Classcourse.getClasscourse(teacherForm.form.classcourseid)
openPublicScreen(res.data)
}, 2000); }, 2000);
}else { }else {
const url = `/teaching/classteaching?classcourseid=${id}&actor=classTeachingOnPublicScreen` const url = `/teaching/classteaching?classcourseid=${id}&actor=classTeachingOnPublicScreen`
@ -350,6 +354,10 @@ const getQrUrl = async() => {
// //
const openPublicScreen = (classcourse) => { const openPublicScreen = (classcourse) => {
console.log('打开公屏', classcourse) console.log('打开公屏', classcourse)
// app
const data = { id: classcourse.id }
ChatWs.sendMsg(MsgEnum.HEADS.MSG_0000, data, {}, ChatWs.TYPES.single, userStore.id)
//
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) //
@ -367,25 +375,25 @@ const openPublicScreen = (classcourse) => {
// ================== ======================= // ================== =======================
// im-chat: {type, data} // im-chat: {type, data}
const chatChange = (type, data, ...args) => { // const chatChange = (type, data, ...args) => {
if (type == 'msg') { // im-chat // if (type == 'msg') { // im-chat
console.log('msg:===== ',data, args) // console.log('msg:===== ',data, args)
// const msgId = (args||[])[0].message_msg_id // // const msgId = (args||[])[0].message_msg_id
const { msgKey:head, msgcontent:msg, senduserid:sendId, msgType } = data // const { msgKey:head, msgcontent:msg, senduserid:sendId, msgType } = data
switch(head) { // switch(head) {
case MsgEnum.HEADS.MSG_classcourseopen:{ // , // case MsgEnum.HEADS.MSG_classcourseopen:{ // ,
const { classcourseid:id } = teacherForm.form // const { classcourseid:id } = teacherForm.form
const { classcourseid: imId, status } = data // const { classcourseid: imId, status } = data
if (imId == id && status == 'open') { // if (imId == id && status == 'open') {
classTeachingStart() // // classTeachingStart() //
} // }
break} // break}
default: // default:
console.log('未知消息:', data) // console.log(':', data)
break // break
} // }
} // }
} // }
// -id // -id
watch(() => classForm.form.classid, (val)=> { watch(() => classForm.form.classid, (val)=> {

View File

@ -36,7 +36,7 @@
{{ item.async === 'on' ? '同步中' : '' }} {{ item.async === 'on' ? '同步中' : '' }}
</div> </div>
<template v-if="item.fileFlag ==='课件'">|</template> <template v-if="item.fileFlag ==='课件'">|</template>
<div style="width: 70px">访问 100</div> <!-- <div style="width: 70px">访问 100</div>
| |
<div style="width: 70px">引用 50</div> <div style="width: 70px">引用 50</div>
| |
@ -49,9 +49,19 @@
white-space: nowrap; white-space: nowrap;
width: 200px; width: 200px;
">点赞 20</div> ">点赞 20</div>
|-->
<div
style="
flex: 1;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 200px;
">{{formatDate(item.createTime, 'yyyy-MM-dd hh:mm:ss')}}</div>
</div> </div>
</div> </div>
<div class="prepare-body-main-item-btn"> <div class="prepare-body-main-item-btn" v-if="showTool">
<!-- <el-button v-if="activeClassId==item.id" type="success" @click="clickStartClass(item)">上课中</el-button> --> <!-- <el-button v-if="activeClassId==item.id" type="success" @click="clickStartClass(item)">上课中</el-button> -->
<el-button type="primary" @click="clickStartClass(item)">上课</el-button> <el-button type="primary" @click="clickStartClass(item)">上课</el-button>
</div> </div>
@ -107,6 +117,7 @@ import { endClass, getSelfReserv } from '@/api/classManage'
import { listEntpcourse } from '@/api/teaching/classwork' import { listEntpcourse } from '@/api/teaching/classwork'
import { createWindow } from '@/utils/tool' import { createWindow } from '@/utils/tool'
import { defineExpose } from 'vue' import { defineExpose } from 'vue'
import {formatDate} from '@/utils/comm'
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
export default { export default {
@ -134,6 +145,10 @@ export default {
activeClassId: { // id activeClassId: { // id
type: String, type: String,
default: '' default: ''
},
showTool: { //
type: Boolean,
default: true
} }
}, },
expose: ['openFileWin'], expose: ['openFileWin'],
@ -403,6 +418,11 @@ export default {
width: 100% !important; width: 100% !important;
} }
} }
.prepare-popper{
width: 80px !important;
min-width: 80px !important;
padding: 5px !important;
}
</style> </style>
<style scoped lang="scss"> <style scoped lang="scss">
.prepare-body-main-item { .prepare-body-main-item {

View File

@ -8,15 +8,16 @@
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item @click="createAptFile">新建文枢课件</el-dropdown-item> <el-dropdown-item @click="createAIPPT">新建文枢课件</el-dropdown-item>
<el-dropdown-item>AI一键生成</el-dropdown-item> <el-dropdown-item @click="aiTOPPT">AI一键生成</el-dropdown-item>
<el-dropdown-item>导入PPT</el-dropdown-item> <el-dropdown-item @click="openFilePicker">导入PPT</el-dropdown-item>
<input type="file" ref="fileInput" style="display: none;" @change="handleFileChange" accept="application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation">
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </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>
<div class="create-btn-info">智能交互课件</div> <div class="create-btn-info">智能交互课件</div>
@ -29,7 +30,7 @@
<div class="create-btn-title"><el-icon><Plus /></el-icon><label>PPT</label></div> <div class="create-btn-title"><el-icon><Plus /></el-icon><label>PPT</label></div>
<div class="create-btn-info">试验版</div> <div class="create-btn-info">试验版</div>
</div> </div>
</div> </div>-->
<div class="prepare-center-body"> <div class="prepare-center-body">
<kj-list-item <kj-list-item
v-for="(item, index) in currentKJFileList" v-for="(item, index) in currentKJFileList"
@ -103,10 +104,10 @@
</file-list-item> </file-list-item>
</el-checkbox-group> </el-checkbox-group>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="作业" name="作业"> <el-tab-pane label="教学活动" name="教学活动">
<div class="prepare-body-header"> <div class="prepare-body-header">
<div> <div>
<label style="font-size: 15px">{{ currentWorkList.length }}作业</label>&nbsp; <label style="font-size: 15px">{{ currentWorkList.length }}教学活动</label>&nbsp;
<!-- <el-button size="small" @click="handleOutLink('homeWork')">作业设计</el-button> --> <!-- <el-button size="small" @click="handleOutLink('homeWork')">作业设计</el-button> -->
<el-button size="small" @click="goNewClassTask()">作业设计</el-button> <el-button size="small" @click="goNewClassTask()">作业设计</el-button>
</div> </div>
@ -152,6 +153,7 @@
<!-- 上课配置 --> <!-- 上课配置 -->
<class-start ref="calssRef" @close="closeChange"/> <class-start ref="calssRef" @close="closeChange"/>
<PptDialog @add-success="addAiPPT" :currentNode="currentNode" :uploadData="uploadData" v-model="pptDialog"/> <PptDialog @add-success="addAiPPT" :currentNode="currentNode" :uploadData="uploadData" v-model="pptDialog"/>
<progress-dialog v-model:visible="pgDialog.visible" v-bind="pgDialog" />
<!-- 章节弹窗 --> <!-- 章节弹窗 -->
<TreeLog ref="treelogRef"/> <TreeLog ref="treelogRef"/>
<!-- <button @click="test">test</button> --> <!-- <button @click="test">test</button> -->
@ -161,8 +163,16 @@ 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'
import progressDialog from '@/views/teachingDesign/container/progress-dialog.vue'
import {useRouter} from "vue-router";
const router = useRouter()
const aiTOPPT = ()=> {
router.push({path: '/model/design'})
}
</script> </script>
<script> <script>
import {PPTXFileToJson} from "@/AixPPTist/src/hooks/useImport";
const Remote = require('@electron/remote') const Remote = require('@electron/remote')
import ChooseTextbook from '@/components/choose-textbook/index.vue' import ChooseTextbook from '@/components/choose-textbook/index.vue'
import uploadDialog from '@/components/upload-dialog/index.vue' import uploadDialog from '@/components/upload-dialog/index.vue'
@ -192,16 +202,18 @@ 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 * as commUtils from "@/utils/comm";
import * as Api_server from "@/api/apiService";
import msgUtils from "@/plugins/modal";
import * as API_entpcoursefile from "@/api/education/entpcoursefile";
import ChatWs from '@/plugins/socket' // socket
if (!ChatWs.ws) ChatWs.init()
// 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')
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
export default { export default {
name: 'Prepare', name: 'Prepare',
components: { components: {
@ -237,7 +249,7 @@ export default {
activeAptTab: "教学课件", activeAptTab: "教学课件",
uploadData: { uploadData: {
textbookId: null, textbookId: null,
levelFirstId: 39103, levelFirstId: null,
levelSecondId: null, levelSecondId: null,
fileSource: '个人', fileSource: '个人',
fileRoot: '备课' fileRoot: '备课'
@ -257,7 +269,23 @@ export default {
// //
treelogRef:null, treelogRef:null,
// Entpcourse // Entpcourse
entp: null entp: null,
pgDialog: { // -
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 }, // 绿
]
}
}
} }
}, },
computed: { computed: {
@ -313,6 +341,8 @@ export default {
// } // }
// }, // },
methods: { methods: {
//
sleep(ms){return new Promise(resolve => setTimeout(resolve, ms))},
addAiPPT(item) { addAiPPT(item) {
this.currentFileList.unshift(item.resData) this.currentFileList.unshift(item.resData)
KjListItem.methods.openFileWin(item.resData); KjListItem.methods.openFileWin(item.resData);
@ -415,6 +445,33 @@ export default {
ElMessage.warning('该功能暂未开放!') ElMessage.warning('该功能暂未开放!')
break break
} }
case 'wsApp': { // app
// console.log('wsApp', row)
const head = MsgEnum.HEADS.MSG_0000
const data = { id: row.id }
const type = ChatWs.TYPES.single
const userId = this.userStore.userId
ElMessage.success('APP端待开课消息-发送成功!')
await this.sleep(1000) // 1s
//
const msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0})
const res = await getEntpcoursefile(row.entpcoursefileid)
await this.sleep(2000) // 2s
ChatWs.sendMsg(head, data, null, type, userId)
msgEl.close() //
const resource = res?.data||{}
const classcourse = row
sessionStore.set('curr.resource', resource) //
sessionStore.set('curr.classcourse', classcourse) //
createWindow('open-win', {
url: '/pptist', //
close: () => {
sessionStore.set('curr.resource', null) //
sessionStore.set('curr.classcourse', null) //
}
})
break
}
default: default:
break break
} }
@ -476,6 +533,139 @@ export default {
},500) },500)
}) })
}, },
openFilePicker(){
this.$refs.fileInput.click();
},
handleFileChange(){
const file = event.target.files[0];
if (file) {
console.log(file);
console.log('文件名:', file.name);
console.log('文件类型:', file.type);
console.log('文件大小:', file.size);
this.createAIPPTByFile(file)
}
},
async toRousrceUrl(o) {
if (!!o.src) { // src
const isBase64 = /^data:image\/(\w+);base64,/.test(o.src)
const isBlobUrl = /^blob:/.test(o.src)
// console.log('isBase64', o, isBase64)
if (isBase64) {
const bolb = commUtils.base64ToBlob(o.src)
const fileName = Date.now() + '.png'
const file = commUtils.blobToFile(bolb, fileName)
// o.src = fileName
// console.log('file', file)
const formData = new FormData()
formData.append('file', file)
const res = await Api_server.Other.uploadFile(formData)
if (res && res.code == 200){
const url = res?.url
url &&(o.src = url)
}
} else if (isBlobUrl) { //
const res = await fetch(o.src)
const blob = await res.blob()
const fileName = o.type=='video'? Date.now() + '.mp4':Date.now() + '.mp3'
const file = commUtils.blobToFile(blob, fileName)
// o.src = fileName
// console.log('file', file)
const formData = new FormData()
formData.append('file', file)
const ress = await Api_server.Other.uploadFile(formData)
if (ress && ress.code == 200){
const url = ress?.url
url &&(o.src = url)
}
}
}
if (o?.background?.image) await this.toRousrceUrl(o.background.image)
// if (o?.elements) o.elements.forEach(async o => {await this.toRousrceUrl(o)})
if(o?.elements){
for (let element of o.elements) {
await this.toRousrceUrl(element);
}
}
},
async createAIPPTByFile(file) {
this.pgDialog.visible = true
this.pgDialog.pg.percentage = 0
const resPptJson = await PPTXFileToJson(file)
const { def, slides, ...content } = resPptJson
// || 线
let completed = 0
const total = slides.length
for( let o of slides ) {
completed++
await this.toRousrceUrl(o)
//
this.pgDialog.pg.percentage = Math.floor(completed / total * 100)
}
console.log('结束', slides)
return
this.pgDialog.pg.percentage = 0
this.pgDialog.visible = false
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: '',
parentContent: JSON.stringify(content),
filekey: '',
filetag: '',
fileidx: 0,
dflag: 0,
status: '',
edituserid: this.userStore.userId
}
addEntpcoursefileReturnId(form).then((slideid) => {
creatAPT({
...this.uploadData,
fileId: slideid,
fileFlag: 'aippt',
fileShowName: this.currentNode.itemtitle + '.aippt'
}).then(async (res) => {
const resSlides = slides.map(({id, ...slide}) => JSON.stringify(slide))
console.log(resSlides)
return
let params = {
parentid: slideid,
entpid: resCourse.entpid,
entpcourseid: resCourse.id,
title: '',
filetype: 'slide',
slides: resSlides,
edituserid: this.userStore.userId
}
const res_3 = await API_entpcoursefile.batchAddNew(params)
if (res_3 && res_3.code == 200) {
msgUtils.msgSuccess('导入PPT课件成功')
this.currentFileList.unshift(res.resData)
setTimeout(()=>{
this.$refs['kjItemRef'+res.resData.id][0].openFileWin(res.resData);
},500)
} else {
msgUtils.msgWarning('导入PPT课件失败')
}
})
})
})
},
createAIPPT() { createAIPPT() {
listEntpcourse({ listEntpcourse({
evalid: this.currentNode.id, evalid: this.currentNode.id,
@ -502,57 +692,22 @@ export default {
edituserid: this.userStore.userId edituserid: this.userStore.userId
} }
addEntpcoursefileReturnId(form).then((slideid) => { 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 = { var form = {
parentid: slideid, parentid: slideid,
entpid: resCourse.entpid, entpid: resCourse.entpid,
entpcourseid: resCourse.id, entpcourseid: resCourse.id,
ppttype: 'file',
title: '第一页', title: '第一页',
fileurl: '',
filetype: 'slide', filetype: 'slide',
datacontent: JSON.stringify(pagearray), datacontent: '{"elements":[],"background":{"type":"solid","color":"#fff"}}',
filekey: '',
filetag: '',
fileidx: 0,
dflag: 0,
status: '',
edituserid: this.userStore.userId edituserid: this.userStore.userId
} }
addEntpcoursefileReturnId(form).then((res) => { addEntpcoursefileReturnId(form).then((res) => {
creatAPT({ creatAPT({
...this.uploadData, ...this.uploadData,
fileId: slideid, fileId: slideid,
fileShowName: this.currentNode.itemtitle + '.apt' fileFlag: 'aippt',
fileShowName: this.currentNode.itemtitle + '.aippt'
}).then((res) => { }).then((res) => {
this.currentFileList.unshift(res.resData) this.currentFileList.unshift(res.resData)
setTimeout(()=>{ setTimeout(()=>{
@ -742,7 +897,7 @@ export default {
this.isLoading = true this.isLoading = true
return getSmarttalkPage({ return getSmarttalkPage({
...this.uploadData, ...this.uploadData,
orderByColumn: 'uploadTime', orderByColumn: 'createTime',
isAsc: 'desc', isAsc: 'desc',
pageSize: 500 pageSize: 500
}) })

View File

@ -53,7 +53,7 @@ export default defineStore('resource', {
fileFlags: resourceType[0].value, fileFlags: resourceType[0].value,
fileRoot: '资源', fileRoot: '资源',
fileName: '', fileName: '',
orderByColumn: 'uploadTime', orderByColumn: 'createTime',
isAsc: 'desc', isAsc: 'desc',
...structQuery ...structQuery
}, },

View File

@ -330,11 +330,27 @@ const toRousrceUrl = async(o) => {
url &&(o.src = url) url &&(o.src = url)
} }
} else if (isBlobUrl) { // } else if (isBlobUrl) { //
const res = await fetch(o.src)
const blob = await res.blob()
const fileName = o.type=='video'? Date.now() + '.mp4':Date.now() + '.mp3'
const file = commUtils.blobToFile(blob, fileName)
// o.src = fileName
// console.log('file', file)
const formData = new FormData()
formData.append('file', file)
const ress = await Api_server.Other.uploadFile(formData)
if (ress && ress.code == 200){
const url = ress?.url
url &&(o.src = url)
}
} }
} }
if (o?.background?.image) await toRousrceUrl(o.background.image) if (o?.background?.image) await toRousrceUrl(o.background.image)
if (o?.elements) o.elements.forEach(async o => {await toRousrceUrl(o)}) if(o?.elements){
for (let element of o.elements) {
await this.toRousrceUrl(element);
}
}
} }
// ======== zdg end ============ // ======== zdg end ============

View File

@ -203,7 +203,7 @@ const openFileLink = async (item) =>{
// //
const getResource = () => { const getResource = () => {
let querySearch = toRaw(toolStore.curSubjectNode).querySearch let querySearch = toRaw(toolStore.curSubjectNode).querySearch
querySearch.orderByColumn = 'uploadTime' querySearch.orderByColumn = 'createTime'
querySearch.isAsc = 'desc' querySearch.isAsc = 'desc'
querySearch.pageSize = 500 querySearch.pageSize = 500