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/env.d.ts b/env.d.ts new file mode 100644 index 0000000..32b911d --- /dev/null +++ b/env.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { ComponentOptions } from 'vue' + const componentOptions: ComponentOptions + export default componentOptions +} \ No newline at end of file diff --git a/src/renderer/src/AixPPTist/src/App.vue b/src/renderer/src/AixPPTist/src/App.vue index 5214d9b..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() @@ -81,5 +82,8 @@ const initLoad: Function = () => { \ No newline at end of file + 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 193d60c..eb42165 100644 --- a/src/renderer/src/AixPPTist/src/api/index.ts +++ b/src/renderer/src/AixPPTist/src/api/index.ts @@ -137,13 +137,15 @@ export class PPTApi { id: currentSlide.id, datacontent: JSON.stringify(currentSlide), } - Utils.mxThrottle(() => {this.updateSlide(params)}, 1000, 2) + Utils.mxThrottle(() => {this.updateSlide(params)}, 200, 2) } } // 更新幻灯片 static updateSlide(data: object): Promise { return new Promise(async (resolve, reject) => { const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data) + console.log(data,'data'); + console.log(res,'dresata'); if (res.code === 200) { resolve(true) } else msgUtils.msgError(res.msg || '更新失败');resolve(false) 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/plugins/icon.ts b/src/renderer/src/AixPPTist/src/plugins/icon.ts index 2bffc0e..bc6e035 100644 --- a/src/renderer/src/AixPPTist/src/plugins/icon.ts +++ b/src/renderer/src/AixPPTist/src/plugins/icon.ts @@ -125,7 +125,8 @@ import { User, Switch, More, - Material + Material, + AddPicture } from '@icon-park/vue-next' export interface Icons { @@ -256,7 +257,8 @@ export const icons: Icons = { IconUser: User, IconSwitch: Switch, IconMore: More, - IconMaterial: Material + IconMaterial: Material, + IconAddPicture: AddPicture } export default { 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/CanvasTool/MaterialDialog.vue b/src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/MaterialDialog.vue index 8295fe0..dd648a3 100644 --- a/src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/MaterialDialog.vue +++ b/src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/MaterialDialog.vue @@ -11,6 +11,7 @@ 插入 + @@ -93,11 +94,9 @@ const GetUrlParameters = (parameters) => { } } } - return resData; } - const proxyToBase64 = (url)=> { const dourl = GetUrlParameters(url) console.log(dourl,'dourl') diff --git a/src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/index.vue b/src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/index.vue index 9f03ff0..f83291b 100644 --- a/src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/index.vue +++ b/src/renderer/src/AixPPTist/src/views/Editor/CanvasTool/index.vue @@ -83,6 +83,7 @@ +
@@ -121,14 +122,18 @@ @update="data => { onhtml2canvas(data); classWorkTaskVisible = false }" /> - + - - + + + +
@@ -157,6 +162,8 @@ import PopoverMenuItem from '../../../components/PopoverMenuItem.vue' import QuestToPPTist from '@/views/classTask/newClassTaskAssign/questToPPTist/index.vue' import MaterialDialog from './MaterialDialog.vue' import { PPTApi } from '../../../api' +import TextCreateImg from '@/components/ai-kolors/index.vue' + const mainStore = useMainStore() const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore) const { canUndo, canRedo } = storeToRefs(useSnapshotStore()) @@ -276,8 +283,10 @@ const insertMaterial = (item: MaterialParams) =>{ createImageElement(data) } materiaVisible.value = false - } + +// 文生图 +const imgVisible = ref(false) \ No newline at end of file + 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/index.vue b/src/renderer/src/views/classTask/newClassTaskAssign/index.vue index b1a52f2..577a881 100644 --- a/src/renderer/src/views/classTask/newClassTaskAssign/index.vue +++ b/src/renderer/src/views/classTask/newClassTaskAssign/index.vue @@ -126,7 +126,7 @@