Merge pull request 'zdg_dev' (#108) from zdg_dev into main

Reviewed-on: #108
This commit is contained in:
zhengdegang 2024-12-11 10:07:58 +08:00
commit e0107814ea
14 changed files with 424 additions and 121 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

@ -28,7 +28,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,22 +3,22 @@
* @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 * as Api_server from '@/api/apiService' // 相关api import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
import * as commUtils from '@/utils/comm.js' // 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()
import { getClassWorkList,getStudentClassWorkData } from '@/views/tool/createHomework'
import {createWindow} from '@/utils/tool'
import { useToolState } from '@/store/modules/tool'
const toolStore = useToolState() const toolStore = useToolState()
/** 工具类 */ /** 工具类 */
export class Utils { export class Utils {
@ -147,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');
@ -191,30 +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 (o:any) => { static toRousrceUrl =async (file: File|any) => {
const formData = new FormData() const formData = new FormData()
formData.append('file', o) formData.append('file', file)
const res = await Api_server.Other.uploadFile(formData) const res = await Api_server.Other.uploadFile(formData)
if (res && res.code == 200){ if (res && res.code == 200){
const url = res?.url const url = res?.url
url &&(o.src = url) url &&(o.src = url)
return url return url
} }
}
}
} }
export class Homework{ export class Homework{
// 作业弹窗 // 作业弹窗
static async showHomework(id: any) { static async showHomework(id: any) {
let result = await getClassWorkList(id) let result = await CreateHomework.getClassWorkList(id)
  result = await getStudentClassWorkData() result = await CreateHomework.getStudentClassWorkData()
  localStorage.setItem('teachClassWorkItem', JSON.stringify(result[0])); localStorage.setItem('teachClassWorkItem', JSON.stringify(result[0]));
  toolStore.isTaskWin=true; // 设置打开批改窗口 toolStore.isTaskWin=true; // 设置打开批改窗口
//   emit('closeActive') createWindow('open-taskwin',{url:'/teachClassTask'});
  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
@ -36,4 +54,41 @@ const updatePPT = async (data) => {
await PPTApi.updateSmarttalk(params) // 更新ppt内容 await PPTApi.updateSmarttalk(params) // 更新ppt内容
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

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

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

@ -199,8 +199,8 @@ const HTTP_SERVER_API = (type, params = {}) => {
case 'addSmarttalk': { // case 'addSmarttalk': { //
const def = { const def = {
fileId: '', // id - Entpcoursefile id fileId: '', // id - Entpcoursefile id
fileFlag: 'aptist', fileFlag: 'aippt',
fileShowName: courseObj.coursetitle + '.aptist', fileShowName: courseObj.coursetitle + '.aippt',
textbookId: courseObj.textbookId, textbookId: courseObj.textbookId,
levelFirstId: courseObj.levelFirstId, levelFirstId: courseObj.levelFirstId,
levelSecondId: courseObj.levelSecondId, levelSecondId: courseObj.levelSecondId,

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

@ -202,17 +202,14 @@ import ClassReserv from '@/views/classManage/classReserv.vue'
import TreeLog from '@/views/prepare/components/treeLog.vue' import TreeLog from '@/views/prepare/components/treeLog.vue'
import classStart from './container/class-start.vue' // import classStart from './container/class-start.vue' //
import MsgEnum from '@/plugins/imChat/msgEnum' // im import MsgEnum from '@/plugins/imChat/msgEnum' // im
import Chat from '@/utils/chat'
import * as commUtils from "@/utils/comm"; import * as commUtils from "@/utils/comm";
import * as Api_server from "@/api/apiService"; import * as Api_server from "@/api/apiService";
import msgUtils from "@/plugins/modal"; import msgUtils from "@/plugins/modal";
import * as API_entpcoursefile from "@/api/education/entpcoursefile"; // im import * as API_entpcoursefile from "@/api/education/entpcoursefile";
if (!Chat.imChat) Chat.init() 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')
@ -344,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);
@ -446,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
} }