diff --git a/.env.development b/.env.development index f3b45f2..b3a7114 100644 --- a/.env.development +++ b/.env.development @@ -16,4 +16,9 @@ VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktx VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/' +# websocket 地址 +# VITE_APP_WS_URL = 'wss://file.ysaix.com:7868' +VITE_APP_WS_URL = 'ws://192.168.2.16:7865' + +# 是否显示开发工具 VITE_SHOW_DEV_TOOLS = 'true' diff --git a/.env.production b/.env.production index e103054..0919367 100644 --- a/.env.production +++ b/.env.production @@ -18,4 +18,8 @@ VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktx VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/' +# websocket 地址 +VITE_APP_WS_URL = 'wss://file.ysaix.com:7868' + +# 是否显示开发工具 VITE_SHOW_DEV_TOOLS = 'false' diff --git a/src/renderer/src/AixPPTist/src/App.vue b/src/renderer/src/AixPPTist/src/App.vue index 7cb504f..77b800d 100644 --- a/src/renderer/src/AixPPTist/src/App.vue +++ b/src/renderer/src/AixPPTist/src/App.vue @@ -28,6 +28,7 @@ import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关ap import { PPTApi } from './api' import { sessionStore } from '@/utils/store' // electron-store 状态管理 import './api/watcher' // 监听 +import './api/classcourse' // 课程相关 const loading = ref(true) const _isPC = isPC() diff --git a/src/renderer/src/AixPPTist/src/api/classcourse.ts b/src/renderer/src/AixPPTist/src/api/classcourse.ts new file mode 100644 index 0000000..ca162e1 --- /dev/null +++ b/src/renderer/src/AixPPTist/src/api/classcourse.ts @@ -0,0 +1,22 @@ +/** + * @author zdg + * @description 上课相关内容 + */ +import type { Classcourse } from './types' +import { sessionStore } from '@/utils/store' // electron-store 状态管理 +import * as useStore from '../store' // pptist-状态管理 +import { ChatWs } from '@/plugins/socket' // 聊天socket +const screenStore = useStore.useScreenStore() // 全屏-状态管理 +const classcourseStore = useStore.useClasscourseStore() // 课堂信息-状态管理 +const classcourse: Classcourse = sessionStore.get('curr.classcourse') // 课堂信息 + +// 如果课堂信息有值,则连接socket +if (!!classcourse) { + // 连接socket + const ws = new ChatWs() + console.log('ws- ',ws) + // ChatWs.connect(classcourse.id) + classcourseStore.setClasscourse(classcourse) +} +// 打开全屏 +screenStore.setScreening(!!classcourse) \ No newline at end of file diff --git a/src/renderer/src/AixPPTist/src/api/index.ts b/src/renderer/src/AixPPTist/src/api/index.ts index 534675b..c893a00 100644 --- a/src/renderer/src/AixPPTist/src/api/index.ts +++ b/src/renderer/src/AixPPTist/src/api/index.ts @@ -136,7 +136,7 @@ export class PPTApi { id: currentSlide.id, datacontent: JSON.stringify(currentSlide), } - Utils.mxThrottle(() => {this.updateSlide(params)}, 1000, 2) + Utils.mxThrottle(() => {this.updateSlide(params)}, 200, 2) } } // 更新幻灯片 diff --git a/src/renderer/src/AixPPTist/src/api/types.ts b/src/renderer/src/AixPPTist/src/api/types.ts index 8321031..8d4f81c 100644 --- a/src/renderer/src/AixPPTist/src/api/types.ts +++ b/src/renderer/src/AixPPTist/src/api/types.ts @@ -5,4 +5,20 @@ export interface Result { data?: any rows?: Array, total?: number +} + +/** 课程信息 */ +export interface Classcourse { + id?: number|string, // 课程id + coursetitle?: string, // 课程名称 + coursetype?: string, // 课程类型 + courseverid?: string, // 课程版本id + coursedesc?: string, // 课程描述 + status?: number, // 课程状态 + teacherid?: number|string, // 教师id + entpcoursefileid?: number|string, // 课程文件id + classid?: number|string, // 班级id + entpcourseid?: number|string, // 章节中间表id + plandate?: string, // 计划时间 + opendate?: string, // 开课时间 } \ No newline at end of file diff --git a/src/renderer/src/AixPPTist/src/hooks/useScreening.ts b/src/renderer/src/AixPPTist/src/hooks/useScreening.ts index dd7d66b..e8cc32a 100644 --- a/src/renderer/src/AixPPTist/src/hooks/useScreening.ts +++ b/src/renderer/src/AixPPTist/src/hooks/useScreening.ts @@ -1,9 +1,10 @@ -import { useScreenStore, useSlidesStore } from '../store' +import { useScreenStore, useSlidesStore, useClasscourseStore } from '../store' import { enterFullscreen, exitFullscreen, isFullscreen } from '../utils/fullscreen' export default () => { const screenStore = useScreenStore() const slidesStore = useSlidesStore() + const classcourseStore = useClasscourseStore() // 课堂信息 // 进入放映状态(从当前页开始) const enterScreening = () => { @@ -19,7 +20,11 @@ export default () => { // 退出放映状态 const exitScreening = () => { - screenStore.setScreening(false) + const classcourse = classcourseStore.classcourse + if (!!classcourse) { //DOTO 有课堂,执行退相关操作 + console.log('退出放映状态') + window.close() + } else screenStore.setScreening(false) if (isFullscreen()) exitFullscreen() } diff --git a/src/renderer/src/AixPPTist/src/store/classcourse.ts b/src/renderer/src/AixPPTist/src/store/classcourse.ts new file mode 100644 index 0000000..7abd3fe --- /dev/null +++ b/src/renderer/src/AixPPTist/src/store/classcourse.ts @@ -0,0 +1,18 @@ +import { defineStore } from 'pinia' +import type { Classcourse } from '../api/types' + +export interface ClasscourseState { + classcourse: Classcourse +} + +export const useClasscourseStore = defineStore('classcourse', { + state: (): ClasscourseState => ({ + classcourse: null, // 课堂信息 + }), + + actions: { + setClasscourse(classcourse: Classcourse) { + this.classcourse = classcourse + }, + }, +}) \ No newline at end of file diff --git a/src/renderer/src/AixPPTist/src/store/index.ts b/src/renderer/src/AixPPTist/src/store/index.ts index 1ed4f2f..fe0cf22 100644 --- a/src/renderer/src/AixPPTist/src/store/index.ts +++ b/src/renderer/src/AixPPTist/src/store/index.ts @@ -3,6 +3,7 @@ import { useSlidesStore } from './slides' import { useSnapshotStore } from './snapshot' import { useKeyboardStore } from './keyboard' import { useScreenStore } from './screen' +import { useClasscourseStore } from './classcourse' export { useMainStore, @@ -10,4 +11,5 @@ export { useSnapshotStore, useKeyboardStore, useScreenStore, + useClasscourseStore, } \ No newline at end of file diff --git a/src/renderer/src/AixPPTist/src/views/Editor/EditorHeader/index.vue b/src/renderer/src/AixPPTist/src/views/Editor/EditorHeader/index.vue index f96b051..29d7ca5 100644 --- a/src/renderer/src/AixPPTist/src/views/Editor/EditorHeader/index.vue +++ b/src/renderer/src/AixPPTist/src/views/Editor/EditorHeader/index.vue @@ -162,6 +162,10 @@ const setDialogForExport = (type: DialogForExportTypes) => { .icon { font-size: 18px; color: #666; + + :deep(svg) { + display: block !important; + } } &:hover { diff --git a/src/renderer/src/AixPPTist/src/views/Editor/Thumbnails/index.vue b/src/renderer/src/AixPPTist/src/views/Editor/Thumbnails/index.vue index 880e4e4..2d40712 100644 --- a/src/renderer/src/AixPPTist/src/views/Editor/Thumbnails/index.vue +++ b/src/renderer/src/AixPPTist/src/views/Editor/Thumbnails/index.vue @@ -396,12 +396,17 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => { .icon { margin-right: 3px; font-size: 14px; + + :deep(svg) { + display: block !important; + } } } .thumbnail-list { padding: 5px 0; flex: 1; overflow: auto; + border-bottom: 1px solid $borderColor; } .thumbnail-item { display: flex; @@ -480,7 +485,6 @@ const contextmenusThumbnailItem = (): ContextmenuItem[] => { .page-number { height: 40px; font-size: 12px; - border-top: 1px solid $borderColor; line-height: 40px; text-align: center; color: #666; diff --git a/src/renderer/src/plugins/socket/index.js b/src/renderer/src/plugins/socket/index.js new file mode 100644 index 0000000..968ac53 --- /dev/null +++ b/src/renderer/src/plugins/socket/index.js @@ -0,0 +1,164 @@ +/** + * websocket 工具类(im 自己实现) +* 单例模式: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 +* 实现的方法为先判断实例存在与否,如果存在则直接返回,不存在就创建了再返回,这就确保了一个类只有一个实例对象。 +*/ +import useUserStore from '@/store/modules/user' // 用户信息 + +export class ChatWs { + instance = null; // 实例 + id = null; // 群聊id || 单聊id-用户id(userId) + closed = false; // 关闭状态 + onmessage = null; // 自定义处理 + errCount = 5; // 重连次数 (ms) 暂时不使用 + errTime = null; // 重连时间 (ms) 1秒内zhi间内不重连 + // 类型定义 + TYPES = { + group: 'group', // 群发 + single: 'single', // 单发 + beat: 'heart_beat', // 心跳 + } + static base = 'wss://file.ysaix.com:7868' + constructor() { + if (!ChatWs.instance) { + const userStore = useUserStore() // 用户信息 + const wsBase = import.meta.env.VITE_APP_WS_URL; // ws地址 + const url = `${wsBase||ChatWs.base}/ws/websocket/${userStore.id}`; + this.init(url); + ChatWs.instance = this; + } + return ChatWs.instance; + } + + // 初始化 + init(url) { + this.url = url; + this.ws = null; + const _this = this + this.heartCheck = { + timeout: 1000 * 10, // 60s + timeoutObj: null, + serverTimeoutObj: null, + reset() { + clearTimeout(this.timeoutObj); + clearTimeout(this.serverTimeoutObj); + return this; + }, + start() { + const self = this; + this.timeoutObj = setTimeout(function () { + // 这里发送一个心跳,后端收到后,返回一个心跳消息, + // onmessage拿到返回的心跳就说明连接正常 + console.log("websocket-发送心跳") + _this.sendMsgBeat(); + self.serverTimeoutObj = setTimeout(function () { + console.log("websocket-心跳响应超时") + // 如果超过一定时间还没重置,说明后端主动断开了 + _this.ws.close(); // 如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次 + }, self.timeout); + }, this.timeout); + }, + }; + this.reconnect(); + } + // 重连 + reconnect() { + const self = this; + if (!!this.ws) { // 关闭之前的链接 + this.ws.close() + this.ws = null + } + this.ws = new WebSocket(this.url); + this.ws.onopen = function () { + console.log("websocket-连接成功") + self.heartCheck.reset().start(); + }; + this.ws.onmessage = function (e) { + // console.log("websocket-收到消息", e) + // 拿到任何消息都说明当前连接是正常的 + const isBeat = e.data == 'pong' + isBeat && self.heartCheck.reset().start(); + const exts = ['sessionId', 'pong'] // 不处理的消息头 + const isEmpty = !e.data + const isExts = exts.some(item => e.data.includes(item)) + if (isEmpty && isExts) return; + // 自定义处理 + self.onmessage && self.onmessage(e.data, e); + }; + this.ws.onerror = function (e) { + console.log("websocket-连接异常", e) + self.connectSocket() // 重连 + }; + this.ws.onclose = function (e) { + console.log("websocket-连接断开", e) + self.connectSocket() // 重连 + }; + } + connectSocket() { + this.heartCheck.reset() // 重置心跳 + if (self.closed) return; // 关闭状态不重连 + // if(self.errCount <= 0) return; // 超过重连次数 + // self.errCount--; // 重连次数减1 + if (this.errTime) { + const nowTime = Date.now(); + const bool = nowTime - this.errTime < 1000 // 1s内zhi间内不重连 + if (bool) return; // 1s内不重连 + } + this.errTime = Date.now(); + // 延时5s 后重连 + console.log('重连中...') + this.sleep(5000).then(_ => {this.reconnect()}) + } + // 发送消息 + send(msg) { + if (!msg) throw new Error("msg is not null") + if (!this.ws) throw new Error("ws is not null") + if (typeof msg === "object") msg = JSON.stringify(msg) + if (!msg.includes('"msg":')) throw new Error("msg 格式错误请重试") + this.ws.send(msg) + } + // 发送消息-带消息头(key) + sendMsg(head, content, option = {}) { + if (!head) throw new Error("head is not null") + if (!content) throw new Error("content is not null") + let msg = { head, content, ...option } + // 发送消息 + this.send(this.getMsgObj(msg)) + } + // 发送心跳 + sendMsgBeat() { + // this.send(this.getMsgObj('ping', this.TYPES.beat)) + this.ws.send('ping') + } + /** + * @description 获取消息对象 + * @param {*} msg 消息内容 + * @param {*} chatType 群发 group| 单发 single| 心态 heart_beat + * @param {*} id 群聊id || 单聊id-用户id(userId) + */ + getMsgObj(msg, chatType = 'group', id) { + if (typeof msg === "object") msg = JSON.stringify(msg) + const res = {msg, chatType} + // if (!id) throw new Error(`${type=='group'?'群ID':'用户ID'} is not null`) + if (chatType == 'group') res.groupId = id || this.id || '' + else if (chatType == 'single') res.to = id || this.id || '' + return res + } + // 监听 + watch(callback) { + callback && (this.onmessage = callback); + } + // 关闭链接 + close() { + this.closed = true; + this.ws.close(); + } + // 延时 ms 毫秒 + sleep(ms){ + return new Promise(resolve => setTimeout(resolve, ms)) + } +} +// 连接socket +export const connect = () => new ChatWs() +// 默认实例 +export default new ChatWs() \ No newline at end of file diff --git a/src/renderer/src/utils/tool.js b/src/renderer/src/utils/tool.js index a89be59..02aeae2 100644 --- a/src/renderer/src/utils/tool.js +++ b/src/renderer/src/utils/tool.js @@ -208,6 +208,10 @@ export const createWindow = async (type, data) => { autoHideMenuBar: true, maximizable: false, } + // pptlist的时候可以选择最大化 + if (data.url == '/pptist'){ + defOption.maximizable = true; + } data.isConsole = true // 是否开启控制台 data.option = {...defOption, ...option} const win = await toolWindow(type, data) diff --git a/src/renderer/src/views/classTask/newClassTaskAssign/searchQuestion/index.vue b/src/renderer/src/views/classTask/newClassTaskAssign/searchQuestion/index.vue index d09d913..87f529b 100644 --- a/src/renderer/src/views/classTask/newClassTaskAssign/searchQuestion/index.vue +++ b/src/renderer/src/views/classTask/newClassTaskAssign/searchQuestion/index.vue @@ -1,5 +1,5 @@