zdg #144
|
@ -33,6 +33,7 @@
|
|||
"electron-updater": "^6.1.7",
|
||||
"element-plus": "^2.7.6",
|
||||
"fabric": "^5.3.0",
|
||||
"im_electron_sdk": "^8.0.5904",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jsondiffpatch": "0.6.0",
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* @description 腾讯云-即时通讯-sdkID
|
||||
*/
|
||||
// import { ipcMain } from 'electron'
|
||||
// const TimMain = require('im_electron_sdk/dist/main')
|
||||
import TimMain from 'im_electron_sdk/dist/main'
|
||||
// import {TIMErrCode} from 'im_electron_sdk/dist/enumbers'
|
||||
const sdkappidDef = 1600034736 // 可以去腾讯云即时通信IM控制台申请
|
||||
|
||||
// 初始化
|
||||
function init(sdkappid = sdkappidDef) {
|
||||
return new TimMain({sdkappid})
|
||||
}
|
||||
export function initialize(){
|
||||
// ipcMain.handle('im-chat:init', (event, sdkappid) => {
|
||||
// return init(sdkappid)
|
||||
// })
|
||||
return init()
|
||||
}
|
||||
export default { initialize, init }
|
|
@ -3,6 +3,7 @@ import { join } from 'path'
|
|||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
import File from './file'
|
||||
import chat from './chat' // chat封装
|
||||
// 代理 electron/remote
|
||||
// 第一步:引入remote
|
||||
import remote from '@electron/remote/main'
|
||||
|
@ -229,12 +230,15 @@ app.on('window-all-closed', () => {
|
|||
|
||||
// 监听全局事件
|
||||
function handleAll() {
|
||||
// chat.initialize() // im-chat 实例
|
||||
const chatInstance = chat.initialize() // im-chat 实例
|
||||
// 新窗口创建-监听
|
||||
ipcMain.on('new-window', (e, data) => {
|
||||
const { id, type } = data
|
||||
const win = BrowserWindow.fromId(id)
|
||||
win.type = type // 绑定独立标识
|
||||
remote.enable(win.webContents) // 开启远程服务
|
||||
chatInstance.enable(win.webContents) // 开启im-chat
|
||||
})
|
||||
// 用于监听-状态管理变化-同步所有窗口
|
||||
ipcMain.handle('pinia-state-change', (e, storeName, jsonStr) => {
|
||||
|
@ -246,4 +250,10 @@ function handleAll() {
|
|||
}
|
||||
}
|
||||
})
|
||||
// 用于监听-状态管理变化-初始同步
|
||||
ipcMain.handle('pinia-state-init', (e, wid, storeName, jsonStr) => {
|
||||
// console.log('pinia-state-init', jsonStr)
|
||||
const win = BrowserWindow.fromId(wid)
|
||||
win.webContents.send('pinia-state-set', storeName, jsonStr)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* @description: electron 封装的工具函数
|
||||
* 消息整理
|
||||
* tool-sphere:create 创建-悬浮球-窗口
|
||||
*/
|
||||
import { app, shell, BrowserWindow, ipcMain } from 'electron'
|
||||
import { is } from '@electron-toolkit/utils'
|
||||
|
||||
// const baseUrl = 'http://localhost:5173/#' // 开发环境使用
|
||||
const baseUrl = process.env['ELECTRON_RENDERER_URL']+'/#' // 开发环境使用
|
||||
// 所有窗口
|
||||
let allWindow = {}
|
||||
// 其他已有窗口 wins
|
||||
export function init() {
|
||||
// 创建工具-悬浮球
|
||||
ipcMain.on('tool-sphere:create', async(e, data) => {
|
||||
// console.log('测试xxxx', data)
|
||||
await createTools(data) // 执行逻辑
|
||||
e.reply('tool-sphere:create-reply', {code: 200, msg: 'success'}) // 返回结果
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description: 创建工具
|
||||
* @param {*} url 路由地址
|
||||
* @param {number} [width=800] 窗口宽度
|
||||
* @param {number} [height=600] 窗口高度
|
||||
* @param {{}} [option={}] 自定义选项
|
||||
* @author: zdg
|
||||
* @date 2021-07-05 14:07:01
|
||||
*/
|
||||
export function createTools({url, width = 800, height = 600, option={}}) {
|
||||
const { mainWindow } = allWindow||{} // 获取主窗口
|
||||
const devUrl = `${baseUrl}${url}`
|
||||
const buildUrl = `file://${__dirname}/index.html${url}`
|
||||
const urlAll = is.dev ? devUrl : buildUrl
|
||||
return new Promise((resolve) => {
|
||||
let win = new BrowserWindow({
|
||||
width, height,
|
||||
type: 'toolbar', // 创建的窗口类型为工具栏窗口
|
||||
frame: false, // 要创建无边框窗口
|
||||
resizable: false, // 禁止窗口大小缩放
|
||||
transparent: true, // 设置透明
|
||||
alwaysOnTop: true, // 窗口是否总是显示在其他窗口之前
|
||||
|
||||
parent: mainWindow, // 父窗口
|
||||
autoClose: true, // 关闭窗口后自动关闭
|
||||
webPreferences: {
|
||||
nodeIntegration: true, // nodeApi调用
|
||||
contextIsolation: false, // 沙箱取消
|
||||
webSecurity: false // 跨域关闭
|
||||
},
|
||||
...option
|
||||
})
|
||||
// console.log(urlAll)
|
||||
// url = 'https://www.baidu.com'
|
||||
console.log(urlAll)
|
||||
win.loadURL(urlAll)
|
||||
win.setFullScreen(true) // 设置窗口为全屏
|
||||
win.setIgnoreMouseEvents(true) // 忽略鼠标事件|使窗口不可选中
|
||||
win.once('ready-to-show', () => {
|
||||
win.show()
|
||||
resolve(win)
|
||||
})
|
||||
win.on('closed', () => {
|
||||
win = null
|
||||
})
|
||||
})
|
||||
}
|
||||
// 保存窗口
|
||||
export function setWin(win = {}) {
|
||||
if (win && Object.keys(win).length){
|
||||
Object.keys(win).forEach(key => {
|
||||
if (!allWindow[key]) { // 不存在就保存
|
||||
allWindow[key] = win[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
import { contextBridge } from 'electron'
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
|
||||
import TimRender from 'im_electron_sdk/dist/renderer' // im渲染部分实例
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
preloadPath: __dirname, // 当前preload地址
|
||||
getTimRender: () => new TimRender(), // im渲染部分实例
|
||||
}
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
// renderer only if context isolation is enabled, otherwise
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* @description: 后端接口api
|
||||
* @author zdg
|
||||
* @date 2023-07-03
|
||||
*/
|
||||
import request from '@/utils/request'
|
||||
// /system/user/txCloudSign
|
||||
export class ApiService {
|
||||
// zdg: 公共请求-处理(可进行特殊处理)
|
||||
static publicHttp(url, data, method, option = {}, type) {
|
||||
method = method || 'get' // 默认GET
|
||||
const config = { url, method }
|
||||
if (!!data) config[method=='get'?'params':'data'] = data
|
||||
if (!!option) Object.assign(config, option)
|
||||
// 特殊格式处理
|
||||
if (type == 'file') config.headers = { 'Content-Type': 'multipart/form-data' }
|
||||
else if (type == 'json') config.headers = { 'Content-Type': 'application/json' }
|
||||
else if (type == 'form') config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
return request(config)
|
||||
}
|
||||
}
|
||||
// zdg: 腾讯云-即时通讯
|
||||
export class imChat {
|
||||
// 获取腾讯im-chat appid 签名
|
||||
static getTxCloudSign = data => ApiService.publicHttp('/system/user/txCloudSign', data)
|
||||
}
|
|
@ -173,3 +173,15 @@ export function endClass(id) {
|
|||
params: {id}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 获取课堂信息
|
||||
* @param {*} id
|
||||
* @returns
|
||||
*/
|
||||
export function getClassInfo(id) {
|
||||
return request({
|
||||
url: '/smarttalk/classReserv/selectById',
|
||||
method: 'get',
|
||||
params: {id}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<template>
|
||||
pdfAdnFabric<template>
|
||||
<div class="canvasitem">
|
||||
<div class="pdfAdnFabric" id="pdfAdnFabric" >
|
||||
<canvas id="pdf-fabric"></canvas>
|
||||
<canvas id="pdf-fabric1" v-if="props.pdfObj.numberOfPdf == 2"></canvas>
|
||||
<div :class="ispointer ? 'ispointer' : ''">
|
||||
<canvas ref="fabriccanvas" />
|
||||
</div>
|
||||
<div v-if="props.pdfObj.numberOfPdf === 2" :class="ispointer ? 'ispointer' : ''">
|
||||
<canvas ref="fabriccanvas1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -13,12 +17,20 @@ import {
|
|||
onMounted,
|
||||
watch,
|
||||
reactive,
|
||||
defineProps,
|
||||
defineExpose,
|
||||
nextTick,
|
||||
defineEmits,watchEffect
|
||||
} from 'vue'
|
||||
import { fabric } from 'fabric'
|
||||
// import { fabric } from 'fabric'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { handleevent, savecanvsStore, initcanvasdata, displayData } from '@/utils/pdfAndFabric'
|
||||
import { fabricVue, TYPES } from '@/plugins/fabric'
|
||||
import { updateSmartBookMarkContent, addsmartBookMark,getBookMarkById } from '@/api/eTextbook/index'
|
||||
import {useToolState} from '@/store/modules/tool'
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
const toolState = useToolState();
|
||||
const props = defineProps({
|
||||
pdfObj: {
|
||||
type: Object,
|
||||
|
@ -29,13 +41,16 @@ const props = defineProps({
|
|||
}
|
||||
}
|
||||
})
|
||||
const ispointer = ref(false) //是否画笔模式
|
||||
// canvas的所有数据
|
||||
const canvsStore = reactive({
|
||||
id: 'xxxx',
|
||||
pageArr: []
|
||||
})
|
||||
const fabriccanvas = ref(null)
|
||||
const fabriccanvas1 = ref(null)
|
||||
|
||||
const canvasFabricVue = ref(null)
|
||||
const canvas1FabricVue = ref(null)
|
||||
// 页面总数
|
||||
const numPagesTotal = ref(0)
|
||||
const imgarr = ref([])
|
||||
|
@ -47,8 +62,8 @@ const renderPage = async (canvasobj) => {
|
|||
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
||||
// 渲染当前页
|
||||
const page = await pdf.getPage(canvasobj.page)
|
||||
var screenWidth = window.innerWidth/2-100;
|
||||
var screenHeight = window.innerHeight;
|
||||
var screenWidth = window.innerWidth / 2 - 100
|
||||
var screenHeight = window.innerHeight
|
||||
const viewport = page.getViewport({ scale: 2 })
|
||||
|
||||
const canvasElement = canvasobj.canvas
|
||||
|
@ -58,6 +73,11 @@ const renderPage = async (canvasobj) => {
|
|||
canvasContext: canvasobj.context,
|
||||
viewport: viewport
|
||||
}
|
||||
// console.log(renderContext,22222222222222222222)
|
||||
// const textContent = await page.getTextContent();
|
||||
// console.log(textContent);
|
||||
// const annotations = await page.getAnnotations();
|
||||
// console.log(annotations);
|
||||
page.render(renderContext).promise.then((res) => {
|
||||
const img = document.createElement('img')
|
||||
img.src = canvasobj.canvas.toDataURL('image/png')
|
||||
|
@ -68,33 +88,65 @@ const renderPage = async (canvasobj) => {
|
|||
// 根据传过来的pdf对象 判断改渲染哪一个fabric
|
||||
if (props.pdfObj.numberOfPdf == 2) {
|
||||
if (canvasobj.index == 0) {
|
||||
|
||||
fabriccanvas.value.setWidth(screenWidth)
|
||||
fabriccanvas.value.setHeight(screenHeight)
|
||||
displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
|
||||
canvasFabricVue.value.canvas.setWidth(screenWidth)
|
||||
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||
// updateCanvasBackgroundImage(canvasFabricVue,img)
|
||||
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||
} else {
|
||||
fabriccanvas1.value.setWidth(screenWidth)
|
||||
fabriccanvas1.value.setHeight(screenHeight)
|
||||
displayData(fabriccanvas1, canvsStore, canvasobj, fabric, img)
|
||||
canvas1FabricVue.value.canvas.setWidth(screenWidth)
|
||||
canvas1FabricVue.value.canvas.setHeight(screenHeight)
|
||||
displayData(canvas1FabricVue, canvsStore, canvasobj, fabric, img)
|
||||
}
|
||||
} else {
|
||||
fabriccanvas.value.setWidth(screenWidth)
|
||||
fabriccanvas.value.setHeight(screenHeight)
|
||||
displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
|
||||
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||
}
|
||||
// console.log(imgarr.value)
|
||||
img.remove()
|
||||
}
|
||||
// 判断imgarr的JSONdata在canvsStore.pageArr有没有
|
||||
canvsStore.pageArr.forEach((item) => {
|
||||
if (item.page == canvasobj.page) {
|
||||
imgarr.value.forEach((img) => {
|
||||
if (img.page == canvasobj.page) {
|
||||
img.JSONdata = item.JSONdata
|
||||
}
|
||||
})
|
||||
}
|
||||
// 保存数据
|
||||
const savaDataStore = () => {
|
||||
if(!toolState.isToolWin){
|
||||
toolState.isPdfWin=false
|
||||
toolState.showBoardAll=true //恢复默认值
|
||||
ipcRenderer.invoke('tool-sphere:reset') //重置tool状态
|
||||
ipcRenderer.send('open-PDF:minimize')
|
||||
return
|
||||
}
|
||||
imgarr.value.forEach((a) => {
|
||||
if (a.index == 0) {
|
||||
a.JSONdata = canvasFabricVue.value.canvas.toJSON()
|
||||
} else {
|
||||
a.JSONdata = canvas1FabricVue.value.canvas.toJSON()
|
||||
}
|
||||
})
|
||||
const nameMap = new Map(canvsStore.pageArr.map((item) => [item.page, item.id]))
|
||||
// 创建一个用于存储所有异步操作的数组
|
||||
let promises = []
|
||||
imgarr.value.forEach((item) => {
|
||||
if (nameMap.has(item.page)) {
|
||||
const params = {
|
||||
id: nameMap.get(item.page),
|
||||
contentData: JSON.stringify(item.JSONdata.objects)
|
||||
}
|
||||
promises.push(updateSmartBookMarkContent([params]))
|
||||
} else {
|
||||
promises.push(addsmartBookMark({
|
||||
pageNum: item.page,
|
||||
contentData: JSON.stringify(item.JSONdata.objects),
|
||||
bookId: props.pdfObj.bookId,
|
||||
type: '教材',
|
||||
source: 'smarttalk'
|
||||
}))
|
||||
|
||||
}
|
||||
})
|
||||
Promise.all(promises).then(res=>{
|
||||
toolState.isPdfWin=false
|
||||
toolState.showBoardAll=true //恢复默认值
|
||||
ipcRenderer.invoke('tool-sphere:reset') //重置tool状态
|
||||
ipcRenderer.send('open-PDF:minimize')
|
||||
})
|
||||
}
|
||||
const updatePage = (canvasobj) => {
|
||||
|
@ -105,16 +157,55 @@ const loadPdf = async (canvasobj) => {
|
|||
}
|
||||
|
||||
const initPdf = async (type = 'default') => {
|
||||
imgarr.value.forEach((a) => {
|
||||
if (a.index == 0) {
|
||||
a.JSONdata = canvasFabricVue.value.canvas.toJSON()
|
||||
} else {
|
||||
a.JSONdata = canvas1FabricVue.value.canvas.toJSON()
|
||||
}
|
||||
})
|
||||
// 判断是否翻页以及工具窗口是否打开 满足 翻页+打开工具才能保存数据
|
||||
if (type != 'default' && toolState.isToolWin) {
|
||||
const nameMap = new Map(canvsStore.pageArr.map((item) => [item.page, item.id]))
|
||||
// 创建一个用于存储所有异步操作的数组
|
||||
let promises = []
|
||||
imgarr.value.forEach((item) => {
|
||||
if (nameMap.has(item.page)) {
|
||||
const params = {
|
||||
id: nameMap.get(item.page),
|
||||
contentData: JSON.stringify(item.JSONdata.objects)
|
||||
}
|
||||
promises.push(updateSmartBookMarkContent([params]))
|
||||
} else {
|
||||
promises.push(addsmartBookMark({
|
||||
pageNum: item.page,
|
||||
contentData: JSON.stringify(item.JSONdata.objects),
|
||||
bookId: props.pdfObj.bookId,
|
||||
type: '教材',
|
||||
source: 'smarttalk'
|
||||
}))
|
||||
|
||||
}
|
||||
})
|
||||
Promise.all(promises).then(res=>{
|
||||
getBookMarkById(props.pdfObj.bookId).then(res=>{
|
||||
const pageArr=getUniqueArrayByLastOccurrence(res.data)
|
||||
canvsStore.pageArr=[]
|
||||
pageArr.forEach((a) => {
|
||||
canvsStore.pageArr.push({ page: a.pageNum, id: a.id, JSONdata: a.contentData })
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
// 保存数据
|
||||
savecanvsStore(imgarr, canvsStore)
|
||||
// initcanvasdata(fabriccanvas)
|
||||
// initcanvasdata(fabriccanvas1)
|
||||
// 单页模式
|
||||
if (type == 'restone') {
|
||||
// 清除 canvas 上的所有对象
|
||||
fabriccanvas1.value.clear()
|
||||
// 释放 canvas 的资源
|
||||
fabriccanvas1.value.dispose()
|
||||
// savecanvsStore(imgarr, canvsStore)
|
||||
|
||||
if (props.pdfObj.numberOfPdf == 1) {
|
||||
// imgarr.value[0]
|
||||
canvasFabricVue.value.history.clean()
|
||||
} else {
|
||||
canvasFabricVue.value.history.clean()
|
||||
canvas1FabricVue.value.history.clean()
|
||||
}
|
||||
// 清除现有 canvas 元素的内容
|
||||
canvasNumbsValue.value.forEach((canvasObj) => {
|
||||
|
@ -146,7 +237,6 @@ const initPdf = async (type = 'default') => {
|
|||
} else {
|
||||
canvasNumbsValue.value[i].page = props.pdfObj.numPages + 1
|
||||
}
|
||||
|
||||
canvasNumbsValue.value[i].index = i
|
||||
// 加载FabricVue
|
||||
await loadPdf(canvasNumbsValue.value[i])
|
||||
|
@ -154,47 +244,154 @@ const initPdf = async (type = 'default') => {
|
|||
}
|
||||
}
|
||||
}
|
||||
//根据page去重
|
||||
const getUniqueArrayByLastOccurrence=(array)=> {
|
||||
const uniqueItems = array.reduce((acc, current) => {
|
||||
// 使用 Map 来跟踪最后一个出现的 pageNum 和它的对象
|
||||
acc.set(current.pageNum, current);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
// 将 Map 的值转换回数组
|
||||
const resultArray = Array.from(uniqueItems.values());
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
const initPdfone = async () => {
|
||||
setTimeout(() => {
|
||||
fabriccanvas1.value = new fabric.Canvas('pdf-fabric1')
|
||||
fabriccanvas1.value.isDrawingMode = true
|
||||
fabriccanvas1.value.freeDrawingBrush.color = '#A33AFE'
|
||||
fabriccanvas1.value.freeDrawingCursor = 'default'
|
||||
fabriccanvas1.value.setWidth(860)
|
||||
handleevent(fabriccanvas1.value, imgarr, 'two')
|
||||
setTimeout(async () => {
|
||||
const option = { freeDrawingCursor: 'default' }
|
||||
const canvas2 = new fabricVue()
|
||||
await canvas2.initCanvas(fabriccanvas1.value, option)
|
||||
canvas2.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
canvas1FabricVue.value = canvas2
|
||||
await initPdf('addOnePage')
|
||||
}, 0)
|
||||
initPdf('addOnePage')
|
||||
}
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 创建canvas转化成图片
|
||||
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
||||
numPagesTotal.value = pdf.numPages
|
||||
// console.log(pdf)
|
||||
// 初始化fabriccanvas
|
||||
fabriccanvas.value = new fabric.Canvas('pdf-fabric')
|
||||
fabriccanvas.value.setWidth(860)
|
||||
fabriccanvas.value.isDrawingMode = true
|
||||
fabriccanvas.value.freeDrawingBrush.color = '#A33AFE'
|
||||
fabriccanvas.value.freeDrawingCursor = 'default'
|
||||
|
||||
fabriccanvas1.value = new fabric.Canvas('pdf-fabric1')
|
||||
fabriccanvas1.value.isDrawingMode = true
|
||||
fabriccanvas1.value.freeDrawingBrush.color = '#A33AFE'
|
||||
fabriccanvas1.value.freeDrawingCursor = 'default'
|
||||
fabriccanvas1.value.setWidth(860)
|
||||
const option = { freeDrawingCursor: 'default' }
|
||||
const canvas1 = new fabricVue()
|
||||
// canvas1.boardConfig.mode= TYPES.ActionMode.OTHER
|
||||
// canvas1.boardConfig.mode= TYPES.ActionMode.ERASE
|
||||
await canvas1.initCanvas(fabriccanvas.value, option)
|
||||
canvas1.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
canvasFabricVue.value = canvas1
|
||||
const canvas2 = new fabricVue()
|
||||
await canvas2.initCanvas(fabriccanvas1.value, option)
|
||||
canvas2.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||
// canvas2.canvas.isDrawingMode=false
|
||||
canvas1FabricVue.value = canvas2
|
||||
window.test = { canvas1, canvas2 }
|
||||
emit('update:numPagesTotal', pdf.numPages)
|
||||
initPdf()
|
||||
|
||||
if (props.pdfObj.allPageData.length) {
|
||||
props.pdfObj.allPageData.forEach((a) => {
|
||||
if (a.pageNum == 1 || a.pageNum == 2) {
|
||||
canvsStore.pageArr.push({ page: a.pageNum, id: a.id, JSONdata: a.contentData })
|
||||
}
|
||||
})
|
||||
}
|
||||
await initPdf()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
ElMessage.error('pdf文件错误')
|
||||
}
|
||||
setToolStatus()
|
||||
// 监听2个canvas事件
|
||||
handleevent(fabriccanvas.value, imgarr)
|
||||
handleevent(fabriccanvas1.value, imgarr, 'two')
|
||||
// handleevent(fabriccanvas.value, imgarr)
|
||||
// handleevent(fabriccanvas1.value, imgarr, 'two')
|
||||
})
|
||||
// zdg: 设置-底部工具栏-状态
|
||||
const setToolStatus = () => {
|
||||
toolState.showBoardAll = false
|
||||
}
|
||||
// 判断元素是否加载完成
|
||||
const handleMode = (vale,type)=>{
|
||||
if(vale=='select'){
|
||||
ispointer.value=true
|
||||
}else{
|
||||
ispointer.value=false
|
||||
}
|
||||
switch(vale) {
|
||||
case 'select': // 选择模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.OTHER)
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.OTHER)
|
||||
break
|
||||
case 'brush': // 画笔模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.DRAW)
|
||||
canvasFabricVue.value.canvas.freeDrawingCursor = 'default'
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.DRAW)
|
||||
canvas1FabricVue.value.canvas.freeDrawingCursor = 'default'
|
||||
break
|
||||
case 'erase': // 板擦模式
|
||||
canvasFabricVue.value?.handleMode(TYPES.ActionMode.ERASE)
|
||||
canvas1FabricVue.value?.handleMode(TYPES.ActionMode.ERASE)
|
||||
break
|
||||
case 'clear': // 清空画布
|
||||
clearCanvas()
|
||||
// canvas1FabricVue.value.history?.clean()
|
||||
break
|
||||
}
|
||||
}
|
||||
// 清空canvas
|
||||
const clearCanvas=()=>{
|
||||
if(canvasFabricVue.value){
|
||||
const objects = canvasFabricVue.value.canvas.getObjects();
|
||||
objects.forEach((obj) => {
|
||||
// 检查对象是否是背景
|
||||
if (obj !== canvasFabricVue.value.canvas.backgroundImage) {
|
||||
// 删除背景之外的对象
|
||||
canvasFabricVue.value.canvas.remove(obj);
|
||||
}
|
||||
});
|
||||
|
||||
canvasFabricVue.value.canvas.renderAll();
|
||||
}
|
||||
if(canvas1FabricVue.value){
|
||||
const objects = canvas1FabricVue.value.canvas.getObjects();
|
||||
objects.forEach((obj) => {
|
||||
// 检查对象是否是背景
|
||||
if (obj !== canvas1FabricVue.value.canvas.backgroundImage) {
|
||||
// 删除背景之外的对象
|
||||
canvas1FabricVue.value.canvas.remove(obj);
|
||||
}
|
||||
});
|
||||
|
||||
canvas1FabricVue.value.canvas.renderAll();
|
||||
}
|
||||
}
|
||||
const watchToolState=()=>{
|
||||
if(toolState.showBoardAll){
|
||||
setTimeout(() => {
|
||||
toolState.showBoardAll=false
|
||||
}, 200);
|
||||
}
|
||||
// 加载工具
|
||||
handleMode(toolState.model)
|
||||
|
||||
}
|
||||
defineExpose({
|
||||
initPdf,
|
||||
initPdfone
|
||||
initPdfone,
|
||||
savaDataStore
|
||||
})
|
||||
watchEffect(() => {
|
||||
setTimeout(() => {
|
||||
console.log(toolState,'监听')
|
||||
|
||||
}, 300)
|
||||
if(toolState.isPdfWin){
|
||||
// if(toolState.isToolWin){
|
||||
// ispointer.value=false
|
||||
// }else{
|
||||
// ispointer.value=true
|
||||
// }
|
||||
watchToolState() //监听工具栏
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -204,6 +401,8 @@ defineExpose({
|
|||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
max-height: 100vh;
|
||||
}
|
||||
.pdfAdnFabric {
|
||||
position: relative;
|
||||
|
@ -214,4 +413,7 @@ defineExpose({
|
|||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.ispointer {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
|
@ -1,15 +1,9 @@
|
|||
pdfAdnFabric<template>
|
||||
<div class="canvasitem">
|
||||
<div class="pdfAdnFabric" id="pdfAdnFabric" >
|
||||
<!-- @touchmove="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
@mousedown="handleMouseDown"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseup="handleMouseUp" -->
|
||||
<div :class="ispointer ? 'ispointer' : ''">
|
||||
<canvas ref="fabriccanvas" />
|
||||
</div>
|
||||
<!-- style="pointer-events: none;" -->
|
||||
<div v-if="props.pdfObj.numberOfPdf === 2" :class="ispointer ? 'ispointer' : ''">
|
||||
<canvas ref="fabriccanvas1" />
|
||||
</div>
|
||||
|
@ -79,8 +73,6 @@ const renderPage = async (canvasobj) => {
|
|||
canvasContext: canvasobj.context,
|
||||
viewport: viewport
|
||||
}
|
||||
// console.log(renderContext,22222222222222222222)
|
||||
|
||||
page.render(renderContext).promise.then((res) => {
|
||||
const img = document.createElement('img')
|
||||
img.src = canvasobj.canvas.toDataURL('image/png')
|
||||
|
@ -93,7 +85,6 @@ const renderPage = async (canvasobj) => {
|
|||
if (canvasobj.index == 0) {
|
||||
canvasFabricVue.value.canvas.setWidth(screenWidth)
|
||||
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||
// updateCanvasBackgroundImage(canvasFabricVue,img)
|
||||
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||
} else {
|
||||
canvas1FabricVue.value.canvas.setWidth(screenWidth)
|
||||
|
@ -106,16 +97,6 @@ const renderPage = async (canvasobj) => {
|
|||
}
|
||||
img.remove()
|
||||
}
|
||||
// 判断imgarr的JSONdata在canvsStore.pageArr有没有
|
||||
// canvsStore.pageArr.forEach((item) => {
|
||||
// if (item.page == canvasobj.page) {
|
||||
// imgarr.value.forEach((img) => {
|
||||
// if (img.page == canvasobj.page) {
|
||||
// img.JSONdata = item.JSONdata
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
})
|
||||
}
|
||||
// 保存数据
|
||||
|
@ -211,10 +192,7 @@ const initPdf = async (type = 'default') => {
|
|||
})
|
||||
}
|
||||
// 保存数据
|
||||
// savecanvsStore(imgarr, canvsStore)
|
||||
|
||||
if (props.pdfObj.numberOfPdf == 1) {
|
||||
// imgarr.value[0]
|
||||
canvasFabricVue.value.history.clean()
|
||||
} else {
|
||||
canvasFabricVue.value.history.clean()
|
||||
|
@ -393,8 +371,18 @@ defineExpose({
|
|||
savaDataStore
|
||||
})
|
||||
watchEffect(() => {
|
||||
console.log(toolState.model,'监听')
|
||||
setTimeout(() => {
|
||||
console.log(toolState,'监听')
|
||||
|
||||
}, 300)
|
||||
if(toolState.isPdfWin){
|
||||
// if(toolState.isToolWin){
|
||||
// ispointer.value=false
|
||||
// }else{
|
||||
// ispointer.value=true
|
||||
// }
|
||||
watchToolState() //监听工具栏
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,279 @@
|
|||
/**
|
||||
* @description imChat 腾讯-即时通讯(无ui)
|
||||
* 文档地址:https://cloud.tencent.com/document/product/269/75285
|
||||
* 文档地址:https://cloud.tencent.com/document/product/269/63007 (electron)
|
||||
* @author: zdg
|
||||
* @date 2023-07-03
|
||||
*/
|
||||
// const TimRender = require('im_electron_sdk/dist/render')
|
||||
import * as TYPES from './enumbers' // sdk相关枚举
|
||||
import MsgEnum from './msgEnum' // 消息相关枚举(自定义)
|
||||
const API = window.api
|
||||
// TIM生成签名
|
||||
// import * as GenerateUserSig from './userSig' // 引入签名生成器
|
||||
export class ImChat {
|
||||
timChat // imChat对象
|
||||
SDKAppID // sdkID
|
||||
secretKey // key
|
||||
userID // 用户id
|
||||
timGroupId // 群组id
|
||||
userSig // 签名
|
||||
status = { // 状态
|
||||
isLogin: false, // 是否登录
|
||||
isConnect: false, // 是否连接
|
||||
}
|
||||
defOption = { // 默认配置
|
||||
// 日志等级-全量日志
|
||||
log_level: TYPES.TIMLogLevel.kTIMLog_Test,
|
||||
// 群组类型-会议群(Meeting),成员上限 6000 人
|
||||
group_type: TYPES.TIMGroupType.kTIMGroup_ChatRoom,
|
||||
}
|
||||
|
||||
|
||||
constructor(SDKAppID, userSig, userID, isInit) {
|
||||
this.SDKAppID = SDKAppID
|
||||
this.userSig = userSig
|
||||
// const sig = 'eJwtjN0KgjAYQN9l16Vzcz8I3RhE9J*JV94IW-ZV6nASWfTurfTynAPnjdLNyXvoFkWIeBhN-gxK1x2cYdCMTQnlYmxW3QpjQKEo4BhjGgrKh6KfBlrtPGOMuDTYDqqfE26BWUjEeIHSrW1cL-SulHd5KI7zxDbpdh1cX0nuX7JK7HtroNerZhnnPpYz9PkCe5Mx1w__'
|
||||
// this.userSig = sig
|
||||
this.userID = userID
|
||||
window.test = this
|
||||
// this.timGroupId = '@TGS#3CYWMK2ON' // 测试使用
|
||||
if (isInit) return this.init()
|
||||
}
|
||||
// 设置配置
|
||||
async setConfig() {
|
||||
await this.timChat.TIMSetConfig({ // TIMSetConfigParam
|
||||
json_config: { // JSONCongfig
|
||||
set_config_log_level: this.defOption.log_level,
|
||||
set_config_callback_log_level: this.defOption.log_level,
|
||||
// set_config_is_log_output_console: true,
|
||||
// set_config_user_config: { // 用户配置
|
||||
// user_config_is_read_receipt: true, // true表示要收已读回执事件
|
||||
// user_config_is_sync_report: true, // true表示服务端要删掉已读状态
|
||||
// user_config_is_ignore_grouptips_unread: true, // true表示群tips不计入群消息已读计数
|
||||
// user_config_is_is_disable_storage: false, // 是否禁用本地数据库,true表示禁用,false表示不禁用。默认是false
|
||||
// user_config_group_getinfo_option // 获取群组信息默认选项
|
||||
// user_config_group_member_getinfo_option // 获取群组成员信息默认选项
|
||||
// },
|
||||
// set_config_user_define_data // 自定义数据,如果需要,初始化前设置
|
||||
},
|
||||
user_data: '',
|
||||
})
|
||||
// 日志监听
|
||||
this.timChat.TIMSetLogCallback({
|
||||
callback: data => {
|
||||
// console.log('[im-chat]:', data[1])
|
||||
this.setConsole('%cchat-log ', data[1])
|
||||
},
|
||||
user_data: ''
|
||||
})
|
||||
}
|
||||
// 初始化-imChat
|
||||
init() {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
if(!API) reject('preload api获取失败, 初始化-未完成')
|
||||
this.timChat = await API.getTimRender()
|
||||
await this.timChat.TIMInit()
|
||||
console.log('[im-chat]:初始化成功')
|
||||
this.status.isConnect = true
|
||||
this.setConfig() // 设置日志级别
|
||||
resolve(this)
|
||||
} catch (error) {reject(error)}
|
||||
})
|
||||
}
|
||||
// 生成签名
|
||||
genTestUserSig() {
|
||||
const options = {
|
||||
SDKAppID: this.SDKAppID,
|
||||
secretKey: this.secretKey,
|
||||
userID: this.userID,
|
||||
}
|
||||
const { userSig } = GenerateUserSig.genTestUserSig(options)
|
||||
this.userSig = userSig
|
||||
}
|
||||
// 监听
|
||||
watch(callback) {
|
||||
// 先移除监听
|
||||
this.timChat.TIMRemoveRecvNewMsgCallback()
|
||||
// 消息监听
|
||||
this.timChat.TIMAddRecvNewMsgCallback({
|
||||
callback, user_data: this.toStr('msg')
|
||||
})
|
||||
// 群消息监听
|
||||
// 群组系统消息事件包括 加入群、退出群、踢出群、设置管理员、取消管理员、群资料变更、群成员资料变更。此消息是针对所有群组成员下发的
|
||||
this.timChat.TIMSetGroupTipsEventCallback({
|
||||
// callback, user_data: this.toStr('msg-group')
|
||||
callback: (data) => {
|
||||
// console.log('群消息', group_tips_event)
|
||||
this.setConsole('%c群消息', data)
|
||||
},
|
||||
})
|
||||
}
|
||||
// 登录
|
||||
login() {
|
||||
const fn = async (resolve, reject) => {
|
||||
const option = {
|
||||
userID: this.userID,
|
||||
userSig: this.userSig,
|
||||
}
|
||||
// 获取登录状态
|
||||
// [1,2,3,4] | [已登陆,登录中,未登录,登出中]
|
||||
// console.log('登录', this)
|
||||
const status = await this.timChat.TIMGetLoginStatus()
|
||||
if (status == 3) { // 未登录
|
||||
const res = await this.timChat.TIMLogin(option)
|
||||
if (res && res.code == 0) {
|
||||
// console.log('登录成功', res)
|
||||
this.status.isLogin = true
|
||||
resolve({status:0, msg:'登录成功', data:res})
|
||||
} else reject(res)
|
||||
} else {
|
||||
if (status == 1) { // 已登录
|
||||
console.log('已登录')
|
||||
resolve({status, msg:'已登录'})
|
||||
} else if (status == 2) { // 登录中
|
||||
console.log('登录中')
|
||||
resolve({status, msg:'登录中'})
|
||||
} else if (status == 4) { // 登出中
|
||||
console.log('登出中')
|
||||
resolve({status, msg:'登出中'})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return new Promise(fn)
|
||||
}
|
||||
// 登出
|
||||
logout() {
|
||||
if (!this.timChat) return
|
||||
return this.timChat.TIMLogout().then(res => {
|
||||
console.log('登出成功', res)
|
||||
this.status.isLogin = false
|
||||
return res
|
||||
}).catch(error => {
|
||||
console.log('登出失败', error)
|
||||
return error
|
||||
})
|
||||
}
|
||||
// 创建群组 群名和初始成员 userID
|
||||
createGroup(name, memberList=[]) {
|
||||
if (!this.timChat) return
|
||||
if (!!this.timGroupId) return console.log('群组已存在')
|
||||
// 转换初始成员id,userID 转 group_member_info_identifier
|
||||
if (memberList && memberList.length) {
|
||||
memberList = memberList.map(o => ({group_member_info_identifier:o.userID}))
|
||||
}
|
||||
const option = { // CreateGroupParams
|
||||
params: { // GroupParams
|
||||
// create_group_param_group_name: 群组名称(必填)
|
||||
// create_group_param_group_id: 群组ID,不填时创建成功回调会返回一个后台分配的群ID
|
||||
// create_group_param_group_type 群组类型,默认为Public
|
||||
// create_group_param_group_member_array 群组初始成员数组
|
||||
// create_group_param_notification 群组公告
|
||||
// create_group_param_introduction 群组简介
|
||||
// create_group_param_face_url 群组头像URL
|
||||
// create_group_param_add_option 加群选项,默认为Any
|
||||
// create_group_param_max_member_num 群组最大成员数
|
||||
// create_group_param_custom_info 请参考自定义字段
|
||||
create_group_param_group_name: name,
|
||||
create_group_param_group_type: this.defOption.group_type,
|
||||
create_group_param_max_member_num: 200,
|
||||
create_group_param_group_member_array: memberList
|
||||
},
|
||||
data: '', // 用户自定义数据
|
||||
}
|
||||
// @TGS#3XVNI6ZOG
|
||||
return this.timChat.TIMGroupCreate(option).then(res => {
|
||||
if (res && res.code == 0) {
|
||||
const timGroupId = res?.json_param?.create_group_result_groupid
|
||||
if (!!timGroupId && timGroupId != 'undefined'){
|
||||
this.setConsole('%c创建群组成功', timGroupId)
|
||||
this.timGroupId = timGroupId
|
||||
// this.setGroupMsgReceive()
|
||||
}
|
||||
}
|
||||
return res
|
||||
})
|
||||
}
|
||||
// 删除群组
|
||||
deleteGroup() {
|
||||
if (!this.timGroupId) return
|
||||
return this.timChat.TIMGroupDelete({
|
||||
groupId: this.timGroupId,
|
||||
data: '', // 用户自定义数据
|
||||
})
|
||||
}
|
||||
// 设置群消息接收
|
||||
setGroupMsgReceive(timGroupId) {
|
||||
if (!this.timGroupId) this.timGroupId = timGroupId || ''
|
||||
if (!this.timGroupId) return console.log('timGroupId为空')
|
||||
return this.timChat.TIMMsgSetGroupReceiveMessageOpt({
|
||||
groupId: this.timGroupId,
|
||||
opt: TYPES.TIMReceiveMessageOpt.kTIMRecvMsgOpt_Receive,
|
||||
data: '', // 用户自定义数据
|
||||
})
|
||||
}
|
||||
// 获取群组列表
|
||||
getGroupList() {
|
||||
return this.timChat.getGroupList().then(res => {
|
||||
console.log('获取群组列表', res)
|
||||
return res
|
||||
}).catch(error => {
|
||||
console.log('获取群组列表失败', error)
|
||||
return error
|
||||
})
|
||||
}
|
||||
// 发送消息
|
||||
sendMsg(conv_id, msg) {
|
||||
if (!conv_id) return console.log('conv_id为空')
|
||||
if (typeof msg == 'object') msg = JSON.stringify(msg)
|
||||
const option = {
|
||||
conv_id,
|
||||
conv_type: TYPES.TIMConvType.kTIMConv_Group,
|
||||
params: {
|
||||
message_elem_array: [{
|
||||
elem_type: TYPES.TIMElemType.kTIMElem_Text,
|
||||
text_elem_content: msg
|
||||
}],
|
||||
// message_conv_id: conv_id,
|
||||
// message_conv_type: TYPES.TIMConvType.kTIMConv_Group,
|
||||
// message_sender: this.userID
|
||||
},
|
||||
user_data: '', // 用户自定义数据
|
||||
// callback: (data) => {}
|
||||
}
|
||||
console.log('发送消息', option)
|
||||
return this.timChat.TIMMsgSendMessageV2(option)
|
||||
}
|
||||
// 发送关闭(下课)消息
|
||||
sendMsgClosed(){
|
||||
const msg = this.getMsgObj(MsgEnum.HEADS.MSG_closed, '下课', MsgEnum.TYPES.TEACHER)
|
||||
return this.sendMsg(this.timGroupId, msg)
|
||||
}
|
||||
// 获取消息对象
|
||||
getMsgObj(msgHead, msg, type, sender, option={}) {
|
||||
if (!msgHead) throw new Error('msgHead is required')
|
||||
if (!msg) throw new Error('msg is required')
|
||||
if (typeof msg === 'object') msg = JSON.stringify(msg)
|
||||
return {
|
||||
msgKey: msgHead,
|
||||
msgcontent: msg,
|
||||
msgType: type ?? MsgEnum.TYPES.STUDENT, // 默认为学生
|
||||
senduserid: sender ?? this.userID,
|
||||
...option
|
||||
}
|
||||
}
|
||||
// 设置控制台样式
|
||||
setConsole(hearStr,...args) {
|
||||
const css = 'color: #fff;background-color:#2ccb92;padding:3px 5px;border-radius:3px;'
|
||||
const time = new Date().toLocaleTimeString()
|
||||
if (!hearStr) hearStr = '%c' + time
|
||||
console.log(hearStr, css, ...args)
|
||||
}
|
||||
// 获取数据字符串
|
||||
toStr = (data) => {
|
||||
if (typeof data === 'string') data = {type: data}
|
||||
return JSON.stringify(data)
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
* @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_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_0001: 0x0001,
|
||||
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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
export { MsgEnum as default }
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* @description: 生成签名|客户端计算 UserSig
|
||||
* @author: zdg
|
||||
* @date 2021-07-05 14:07:01
|
||||
*/
|
||||
|
||||
// TIM生成签名
|
||||
import LibGenerateTestUserSig from './lib-generate-test-usersig-es.min.js';
|
||||
/**
|
||||
* Signature expiration time, which should not be too short
|
||||
* Time unit: second
|
||||
* Default time: 7 * 24 * 60 * 60 = 604800 = 7days
|
||||
*/
|
||||
const EXPIRETIME = 604800;
|
||||
|
||||
/**
|
||||
* Module: GenerateTestUserSig
|
||||
*
|
||||
* Description: Generates UserSig for testing. UserSig is a security signature designed by Tencent Cloud for its cloud services.
|
||||
* It is calculated based on `SDKAppID`, `UserID`, and `EXPIRETIME` using the HMAC-SHA256 encryption algorithm.
|
||||
*
|
||||
* Attention: For the following reasons, do not use the code below in your commercial application.
|
||||
*
|
||||
* The code may be able to calculate UserSig correctly, but it is only for quick testing of the SDK’s basic features, not for commercial applications.
|
||||
* `SECRETKEY` in client code can be easily decompiled and reversed, especially on web.
|
||||
* Once your key is disclosed, attackers will be able to steal your Tencent Cloud traffic.
|
||||
*
|
||||
* The correct method is to deploy the `UserSig` calculation code and encryption key on your project server so that your application can request from your server a `UserSig` that is calculated whenever one is needed.
|
||||
* Given that it is more difficult to hack a server than a client application, server-end calculation can better protect your key.
|
||||
*
|
||||
* Reference: https://cloud.tencent.com/document/product/647/17275#Server
|
||||
*/
|
||||
|
||||
function genTestUserSig(options) {
|
||||
const { SDKAppID, secretKey, userID } = options;
|
||||
const generator = new LibGenerateTestUserSig(SDKAppID, secretKey, EXPIRETIME);
|
||||
const userSig = generator.genTestUserSig(userID);
|
||||
return {
|
||||
SDKAppID,
|
||||
userSig,
|
||||
};
|
||||
}
|
||||
|
||||
export { genTestUserSig, EXPIRETIME };
|
|
@ -3,26 +3,60 @@
|
|||
*/
|
||||
const isNode = typeof require !== 'undefined' // 是否支持node函数
|
||||
const { ipcRenderer } = isNode?require('electron'):{} // app使用
|
||||
// const Remote = isNode?require('@electron/remote'):{} // 远程模块
|
||||
export function shareStorePlugin({store}) {
|
||||
store.$subscribe(() => { // 自动同步
|
||||
store.$subscribe((mutation, state) => { // 自动同步
|
||||
// mutation 变量包含了变化前后的状态
|
||||
// mutation.events: key newValue target oldValue oldTarget
|
||||
// state 是变化后的状态
|
||||
// console.log('store.$subscribe', mutation)
|
||||
// 在存储变化的时候执行
|
||||
const storeName = store.$id
|
||||
// const storeName = store.$id
|
||||
// const storeName = mutation.storeId
|
||||
const { storeId: storeName, payload, events, type } = mutation // direct
|
||||
// if (!Object.keys(payload).length) return
|
||||
if (type != 'direct') return
|
||||
// 用于多窗口共享(需要共享的状态名称)
|
||||
const names = ['tool']
|
||||
if (names.includes(storeName)) stateSync(store) // 需要同步
|
||||
if (names.includes(storeName)) stateSync(storeName, events.key, events.newValue) // 需要同步
|
||||
})
|
||||
// 暴露方法-手动同步
|
||||
store.stateSync = () => stateSync(store)
|
||||
store.stateSync = (storeName, key, value) => {
|
||||
if (!storeName && !!key && !!value) stateSync(storeName, key, value)
|
||||
else stateSyncAll(store)
|
||||
}
|
||||
// 暴露方法-发送当前状态-新窗口
|
||||
store.stateSyncInit = wid => stateSyncInit(wid, store)
|
||||
// 监听主线程消息-同步数据
|
||||
stateChange(store)
|
||||
}
|
||||
// 同步数据-发送给主线程
|
||||
function stateSync(store) {
|
||||
|
||||
// 同步数据-发送给主线程-单独
|
||||
function stateSync(storeName, key, value) {
|
||||
// console.log('state-change', storeName, key, value)
|
||||
let jsonStr = ''
|
||||
if (typeof key === 'string') jsonStr = JSON.stringify({[key]:value})
|
||||
else if (typeof value === 'object') jsonStr = JSON.stringify(key)
|
||||
// 通知主线程更新
|
||||
ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
|
||||
}
|
||||
|
||||
// 同步数据-发送给主线程-全量更新
|
||||
function stateSyncAll(store) {
|
||||
const storeName = store.$id
|
||||
const jsonStr = circularSafeStringify(store.$state)
|
||||
// 通知主线程更新
|
||||
ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
|
||||
}
|
||||
|
||||
// 发送当前数据状态
|
||||
function stateSyncInit(wid, store) {
|
||||
const storeName = store.$id
|
||||
const curJson = JSON.stringify(store.$state) // 当前数据
|
||||
// 发送同步数据给新窗口-更新状态
|
||||
ipcRenderer.invoke('pinia-state-init', wid, storeName, curJson)
|
||||
}
|
||||
|
||||
// 同步数据-接收主线程消息
|
||||
function stateChange(store) {
|
||||
const storeName = store.$id
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
// 所有事件
|
||||
export function handleevent(canvas, imgarr, type = 'defalut') {
|
||||
// // 鼠标按下
|
||||
// canvas.on('mouse:down', function (e) {})
|
||||
// // // 监听鼠标移动事件
|
||||
// // canvas.on('mouse:move', (options) => {
|
||||
// // console.log('Mouse move event:', options);
|
||||
// // });
|
||||
|
||||
// // 监听鼠标释放事件
|
||||
// canvas.on('mouse:up', (options) => {
|
||||
// //判断是点击的哪一个
|
||||
// if (type == 'defalut') {
|
||||
// if (imgarr.value[0].index == 0) {
|
||||
// imgarr.value[0].JSONdata = canvas.toJSON()
|
||||
// }
|
||||
// if (imgarr.value[1]?.index == 0) {
|
||||
// imgarr.value[1].JSONdata = canvas.toJSON()
|
||||
// }
|
||||
// } else {
|
||||
// if (imgarr.value[0].index == 1) {
|
||||
// imgarr.value[0].JSONdata = canvas.toJSON()
|
||||
// }
|
||||
// if (imgarr.value[1]?.index == 1) {
|
||||
// imgarr.value[1].JSONdata = canvas.toJSON()
|
||||
// }
|
||||
// }
|
||||
// console.log(imgarr.value)
|
||||
|
||||
// })
|
||||
}
|
||||
// 保存数据
|
||||
export function savecanvsStore(imgarr, canvsStore) {
|
||||
// canvsStore.pageArr = mergeAndReplace(canvsStore.pageArr, imgarr.value)
|
||||
}
|
||||
// 重显数据
|
||||
export function displayData(canvas, canvsStore, canvasobj, fabric, img) {
|
||||
// // 初始化
|
||||
// if (!canvsStore.pageArr.length) {
|
||||
// fabric.Image.fromURL(img.src, (img) => {
|
||||
// img.set({
|
||||
// left: 0,
|
||||
// top: 0,
|
||||
// scaleX: canvas.value.width / img.width,
|
||||
// scaleY: canvas.value.height / img.height
|
||||
// })
|
||||
// canvas.value.setBackgroundImage(img, canvas.value.renderAll.bind(canvas.value))
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// canvsStore.pageArr.forEach((item) => {
|
||||
// //初始化
|
||||
// if (item.page == canvasobj.page) {
|
||||
// // canvas.value.clear() // 清除 Canvas
|
||||
// // console.log(item.JSONdata, '找到一样的数据')
|
||||
// canvas.value.loadFromJSON(item.JSONdata, () => {
|
||||
// // 在所有对象加载完成后重新渲染画布
|
||||
// canvas.value.renderAll.bind(canvas.value)
|
||||
// canvas.value.renderAll()
|
||||
// // requestAnimationFrame(() => {
|
||||
// // // 渲染所有对象
|
||||
|
||||
// // })
|
||||
// })
|
||||
// } else {
|
||||
// // 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
|
||||
// // // 清除 Canvas
|
||||
// canvas.value.clear()
|
||||
// requestAnimationFrame(function () {
|
||||
// fabric.Image.fromURL(img.src, (img) => {
|
||||
// img.set({
|
||||
// left: 0,
|
||||
// top: 0,
|
||||
// scaleX: canvas.value.width / img.width,
|
||||
// scaleY: canvas.value.height / img.height
|
||||
// })
|
||||
// canvas.value.setBackgroundImage(img, canvas.value.renderAll.bind(canvas.value))
|
||||
// })
|
||||
// // 渲染所有对象
|
||||
// canvas.value.renderAll.bind(canvas.value)
|
||||
// canvas.value.renderAll()
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
//page 一样替换
|
||||
const mergeAndReplace = (arr1, arr2) => {
|
||||
// // 用于存储替换后的数组
|
||||
// const resultArray = array1.map(item1 => {
|
||||
// // 在 array2 中查找 page 相同的对象
|
||||
// const replacement = array2.find(item2 => item2.page == item1.page);
|
||||
// // 如果找到替换对象,则返回替换对象,否则返回原对象
|
||||
// return replacement ? replacement : item1;
|
||||
// });
|
||||
|
||||
// // 将 array2 中 page 不在 array1 中的对象追加到结果数组中
|
||||
// array2.forEach(item2 => {
|
||||
// const existsInArray1 = array1.some(item1 => item1.page == item2.page);
|
||||
// if (!existsInArray1) {
|
||||
// resultArray.push(item2);
|
||||
// }
|
||||
// });
|
||||
|
||||
// return resultArray;
|
||||
|
||||
// 创建一个映射,将 arr2 中的对象按 page 属性存储
|
||||
let map = new Map(arr2.map((item) => [item.page, item]))
|
||||
|
||||
// 使用 map 替换 arr1 中相应 page 的对象,并添加 arr2 中的对象
|
||||
arr1 = arr1.map((item) => (map.has(item.page) ? map.get(item.page) : item))
|
||||
|
||||
// 将 map 中存在但 arr1 中不存在的对象添加到 arr1
|
||||
for (let [page, obj] of map) {
|
||||
if (!arr1.some((item) => item.page === page)) {
|
||||
arr1.push(obj)
|
||||
}
|
||||
}
|
||||
return arr1
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
export function initcanvasdata(canvas) {
|
||||
canvas.value.clear() // 清除 Canvas
|
||||
// 设置画布的背景色或其他属性
|
||||
canvas.value.backgroundColor = 'rgba(255, 255, 255, 1)' // 白色背景
|
||||
|
||||
// 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
|
||||
requestAnimationFrame(function () {
|
||||
// 渲染所有对象
|
||||
canvas.value.renderAll.bind(canvas.value)
|
||||
})
|
||||
}
|
|
@ -10,10 +10,12 @@ const isNode = typeof require !== 'undefined' // 是否支持node函数
|
|||
const path = isNode?require('path'):{}
|
||||
const Remote = isNode?require('@electron/remote'):{}
|
||||
const { ipcRenderer } = isNode?require('electron'):window.electron || {}
|
||||
|
||||
const API = isNode?window.api:{} // preload-api
|
||||
import { useToolState } from '@/store/modules/tool' // 获取store状态
|
||||
// 常用变量
|
||||
const BaseUrl = isNode?process.env['ELECTRON_RENDERER_URL']+'/#':''
|
||||
const isDev = isNode?process.env.NODE_ENV !== 'production':''
|
||||
const toolState = useToolState() // 获取store状态
|
||||
|
||||
// 暴露Remote中的属性
|
||||
export const ipcMain = Remote?.ipcMain || {}
|
||||
|
@ -83,11 +85,12 @@ export function ipcHandle(fn,key, cb) {
|
|||
* @returns
|
||||
*/
|
||||
let wins_tool = null
|
||||
let winPdf=null
|
||||
export const createWindow = async (type, data) => {
|
||||
if (wins_tool) return console.error('createWindow: win is have')
|
||||
if (!type) return console.error('createWindow: type is null')
|
||||
switch(type) {
|
||||
case 'tool-sphere': { // 创建-悬浮球
|
||||
if (wins_tool) return console.error('createWindow: win is have')
|
||||
if (!type) return console.error('createWindow: type is null')
|
||||
const option = data.option||{}
|
||||
const defOption = {
|
||||
frame: false, // 要创建无边框窗口
|
||||
|
@ -98,6 +101,7 @@ export const createWindow = async (type, data) => {
|
|||
// autoClose: true, // 关闭窗口后自动关闭
|
||||
}
|
||||
data.isConsole = true // 是否开启控制台
|
||||
data.isWeb = false // 是否开启web安全
|
||||
data.option = {...defOption, ...option}
|
||||
wins_tool = await toolWindow(data)
|
||||
wins_tool.type = type // 唯一标识
|
||||
|
@ -119,12 +123,19 @@ export const createWindow = async (type, data) => {
|
|||
}
|
||||
data.isConsole = true // 是否开启控制台
|
||||
data.option = {...defOption, ...option}
|
||||
if(winPdf){ //判断是否已经打开
|
||||
// if (winPdf.isMinimized()) winPdf.restore();
|
||||
winPdf.focus();
|
||||
// toolState.isPdfWin=true
|
||||
return
|
||||
}
|
||||
const win = await toolWindow(data)
|
||||
win.type = type // 唯一标识
|
||||
win.show()
|
||||
win.setFullScreen(true) // 设置窗口为全屏
|
||||
// win.webContents.openDevTools() // 打开调试工具
|
||||
eventHandles(type, win) // 事件监听处理
|
||||
winPdf=win
|
||||
break
|
||||
}
|
||||
default:
|
||||
|
@ -140,7 +151,7 @@ export const createWindow = async (type, data) => {
|
|||
* @author: zdg
|
||||
* @date 2021-07-05 14:07:01
|
||||
*/
|
||||
export function toolWindow({url, isConsole, option={}}) {
|
||||
export function toolWindow({url, isConsole, isWeb=true, option={}}) {
|
||||
// width = window.screen.width
|
||||
let width = option?.width || 800
|
||||
let height = option?.height || 600
|
||||
|
@ -152,14 +163,12 @@ export function toolWindow({url, isConsole, option={}}) {
|
|||
const config = {
|
||||
width, height,
|
||||
type: 'toolbar', // 创建的窗口类型为工具栏窗口
|
||||
icon: path.join(__dirname, '../../resources/logo2.ico'),
|
||||
// icon: path.join(__dirname, '../../resources/logo2.ico'),
|
||||
webPreferences: {
|
||||
// preload: path.join(__dirname, '../preload/index.js'),
|
||||
preload: '@root/src/preload/index.js',
|
||||
preload: path.join(API.preloadPath, '/index.js'),
|
||||
sandbox: false,
|
||||
nodeIntegration: true, // nodeApi调用
|
||||
contextIsolation: false, // 沙箱取消
|
||||
// webSecurity: false // 跨域关闭
|
||||
},
|
||||
...option
|
||||
}
|
||||
|
@ -167,11 +176,17 @@ export function toolWindow({url, isConsole, option={}}) {
|
|||
let win = new Remote.BrowserWindow(config)
|
||||
if (!isDev) win.loadFile(urlAll,{hash: url}) // 加载文件
|
||||
else win.loadURL(urlAll) // 加载url
|
||||
win.once('ready-to-show', () => {resolve(win)})
|
||||
win.once('ready-to-show', () => { // 窗口加载完成
|
||||
resolve(win)
|
||||
})
|
||||
// 主窗口关闭事件
|
||||
mainWin.once('closed', () => { win.destroy()})
|
||||
// 内部监听器
|
||||
win.webContents.on('did-finish-load', () => {})
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
setTimeout(() => {
|
||||
toolState.stateSyncInit(win.id) // 同步状态
|
||||
}, 200);
|
||||
})
|
||||
// 内部监听器-是否打印
|
||||
if (!!isConsole) {
|
||||
win.webContents.on('console-message', (e,leve,m,lin,s) => {
|
||||
|
@ -191,12 +206,13 @@ const eventHandles = (type, win) => {
|
|||
// 公共方法
|
||||
const publicMethods = ({onClosed}={}) => {
|
||||
// 监听主窗口-关闭事件
|
||||
mainWin.once('close', () => {win.destroy()})
|
||||
mainWin.once('close', () => {winPdf=null;win.destroy();})
|
||||
win.on('closed', () => {
|
||||
if(onClosed) onClosed() // 自定义关闭事件
|
||||
win = null
|
||||
wins_tool = null
|
||||
})
|
||||
|
||||
// 新窗口-创建事件(如:主进程加载远程服务)
|
||||
ipcRenderer.send('new-window', {id:win.id, type})
|
||||
}
|
||||
|
@ -220,7 +236,11 @@ const eventHandles = (type, win) => {
|
|||
break}
|
||||
case 'open-PDF': {
|
||||
// 最小化窗口 minimize()
|
||||
Remote.ipcMain.once('open-PDF:minimize', () => {win&&win.destroy()})
|
||||
Remote.ipcMain.once('open-PDF:minimize', () => {
|
||||
winPdf=null
|
||||
win&&win.destroy()
|
||||
// win&&win.minimize(); //缩小功能
|
||||
})
|
||||
publicMethods() // 加载公共方法
|
||||
break}
|
||||
default:
|
||||
|
|
|
@ -40,10 +40,8 @@ import { getStaticUrl } from '@/utils/tool'
|
|||
const { ipcRenderer } = require('electron')
|
||||
import { getBookMarkById } from '@/api/eTextbook/index'
|
||||
import {useToolState} from '@/store/modules/tool'
|
||||
// const getStaticUrl=(url)=>{
|
||||
// return url
|
||||
// }
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = getStaticUrl('/lib/build/pdf.worker.mjs')
|
||||
|
||||
const toolState = useToolState();
|
||||
const route = useRoute();
|
||||
const isOnLoadShow = ref(false) //加载完毕显示
|
||||
|
@ -73,14 +71,9 @@ const navtopage = (type) => {
|
|||
if (pdfObj.numPages > numPagesTotal.value) return
|
||||
pdfCanvaslist.value.initPdf('rest')
|
||||
}
|
||||
// 最小化窗口
|
||||
// 关闭窗口
|
||||
const minimize = async () => {
|
||||
await pdfCanvaslist.value.savaDataStore()
|
||||
// toolState.isPdfWin=false
|
||||
// toolState.showBoardAll=true //恢复默认值
|
||||
// console.log(toolState.showBoardAll,"关闭")
|
||||
// // ipcRenderer.send('tool-sphere:reset') //重置tool状态
|
||||
// ipcRenderer.send('open-PDF:minimize')
|
||||
}
|
||||
const handleUpdate = (data) => {
|
||||
numPagesTotal.value = data
|
||||
|
@ -107,10 +100,12 @@ const switchPageMode = () => {
|
|||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
toolState.isPdfWin=true
|
||||
pdfObj.pdfUrl = getStaticUrl(route.query.path, 'user', 'selfFile', true)
|
||||
toolState.isPdfWin=true //设置打开pdf窗口
|
||||
// pdfObj.pdfUrl = getStaticUrl(route.query.path, 'user', 'selfFile', true) //线上
|
||||
pdfObj.pdfUrl = getStaticUrl('aaa.pdf', 'user', 'selfFile', true) //本地
|
||||
textbookId.value = route.query.textbookId
|
||||
pdfObj.bookId=textbookId.value
|
||||
//初始化获取接口数据
|
||||
getBookMarkById(textbookId.value).then(res=>{
|
||||
pdfObj.allPageData=getUniqueArrayByLastOccurrence(res.data)
|
||||
isOnLoadShow.value=true
|
||||
|
|
|
@ -55,9 +55,11 @@ const toolStore = useToolState()
|
|||
watch(
|
||||
() => [dataList,toolStore.isToolWin],
|
||||
() => {
|
||||
setTimeout(()=>{
|
||||
getSelfReserv().then((res) => {
|
||||
dataList.value = [...res.data]
|
||||
})
|
||||
},300)
|
||||
}
|
||||
)
|
||||
onMounted(() => {
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<script setup>
|
||||
// 功能说明:im-chat 腾讯im chat聊天室
|
||||
import { onMounted, ref, reactive, watchEffect } from 'vue'
|
||||
import { ImChat } from '@/plugins/imChat'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import * as http from '@/api/apiService' // 自定义api service
|
||||
// import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' // 相关工具
|
||||
const userStore = useUserStore()
|
||||
const emits = defineEmits(['change'])
|
||||
const props = defineProps({
|
||||
isGroup: { type: Boolean, default: false }, // 是否创建群
|
||||
})
|
||||
const imChatObj = reactive({imChat:null})
|
||||
onMounted(() => {
|
||||
// console.log(imChatObj)
|
||||
initImChat()
|
||||
})
|
||||
// 初始化 im-chat
|
||||
const initImChat = async () => {
|
||||
// console.log('im-chat', userStore.user.timuserid)
|
||||
try {
|
||||
const { timuserid, deptId, userId } = userStore.user
|
||||
// 获取腾讯云签名
|
||||
const res = await http.imChat.getTxCloudSign({imUserId: timuserid})
|
||||
if (res && res.code == 200) {
|
||||
const { sdkAppId, sign } = res.data
|
||||
// 群名称
|
||||
const groupName = `${deptId}-classteaching-${userId}`
|
||||
// 注册im-chat
|
||||
// await ipcMsgInvoke('im-chat:init', sdkAppId)
|
||||
imChatObj.imChat = new ImChat(sdkAppId, sign, timuserid)
|
||||
// 初始化 im-chat
|
||||
await imChatObj.imChat.init()
|
||||
// 登录 im-chat
|
||||
await imChatObj.imChat.login()
|
||||
// 监听 im-chat 消息
|
||||
imChatObj.imChat.watch((res) => {
|
||||
const [msg] = res[0]?JSON.parse(res[0]):[]
|
||||
imChatObj.imChat.setConsole('%cchat-msg', msg)
|
||||
// 系统消息
|
||||
if(msg.message_sender == '@TIM#SYSTEM'){
|
||||
emits('change', 'msg-system', null, msg)
|
||||
} else { // 普通消息-数据处理
|
||||
(msg?.message_elem_array||[]).forEach(o => {
|
||||
const msgData = !!o.text_elem_content ? JSON.parse(o.text_elem_content)||'' : ''
|
||||
emits('change', 'msg', msgData, msg)
|
||||
})
|
||||
}
|
||||
})
|
||||
// 创建群
|
||||
if (props.isGroup) await createGroup(groupName)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('im-error: ', error)
|
||||
}
|
||||
}
|
||||
// 创建群组
|
||||
const createGroup = async (groupName) => {
|
||||
if (!imChatObj.imChat) return
|
||||
await imChatObj.imChat.createGroup(groupName)
|
||||
emits('change', 'createGroup', imChatObj.imChat.timGroupId)
|
||||
}
|
||||
// 退出
|
||||
const logout = () => imChatObj.imChat?.logout()
|
||||
// 解散群
|
||||
const deleteGroup = () => imChatObj.imChat?.deleteGroup()
|
||||
|
||||
defineExpose({ logout, deleteGroup, imChatObj })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="im-chat">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
|
@ -17,8 +17,11 @@
|
|||
<!-- 内容部分 -->
|
||||
<transition name="el-fade-in">
|
||||
<div class="c-popover" :style="`--top: ${topPos}px;--height:${hPost}px;`" v-show="isVisible">
|
||||
<div class="content">
|
||||
<homework/>
|
||||
<div class="content" v-if="isVisible">
|
||||
<slot name="content">
|
||||
<homework v-if="activeObj?.prop === 'resource'" />
|
||||
<span v-else style="color:red;">{{activeObj}}</span>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
@ -135,8 +138,4 @@ const clickHandel = (o, e) => {
|
|||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
.content{
|
||||
color: red;
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<button v-if="props.test" @click="trigger">测试</button>
|
||||
<el-button v-if="props.test" type="primary" @click="trigger">测试</el-button>
|
||||
<div ref="warpRef" class="c-warp">
|
||||
<template v-for="i in sum">
|
||||
<slot><el-icon><Star /></el-icon></slot>
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
<side-vue v-ignore @ignore-mounted="sideMouse" @change="sideChange"></side-vue>
|
||||
|
||||
<!-- 点赞组件 -->
|
||||
<upvote-vue></upvote-vue>
|
||||
<upvote-vue ref="upvoteRef"></upvote-vue>
|
||||
|
||||
<!-- 底部工具栏 :style="dataPos.style"-->
|
||||
<!-- im-chat 聊天组件 -->
|
||||
<im-chat ref="imChatRef" @change="chatChange" />
|
||||
|
||||
<!-- 底部工具栏 -->
|
||||
<div class="tool-bottom-all" @mouseenter="mouseChange(0)" @mouseleave="mouseChange(1)">
|
||||
<div v-drag="{handle:'.tool-bottom-all', dragtime}"
|
||||
@v-drag-start="dragtime = Date.now()">
|
||||
|
@ -36,23 +39,33 @@
|
|||
// 功能说明:electron 悬浮球
|
||||
import { onMounted, ref, reactive, watchEffect } from 'vue'
|
||||
import { useRoute } from 'vue-router';
|
||||
import { endClass } from '@/api/classManage'
|
||||
import { ElMessageBox, ElMessage, ElLoading } from 'element-plus'
|
||||
import * as classManageApi from '@/api/classManage'
|
||||
import logo from '@root/resources/icon.png' // logo
|
||||
import boardVue from './components/board.vue' // 画板-子组件
|
||||
import sideVue from './components/side.vue' // 画板-子组件
|
||||
import upvoteVue from './components/upvote.vue' // 点赞-子组件
|
||||
import imChat from './components/imChat.vue' // im-chat-子组件
|
||||
import vDrag from './directive/drag' // 自定义指令-拖拽
|
||||
import vIgnore from './directive/ignore' // 自定义指令-穿透
|
||||
import { useToolState } from '@/store/modules/tool' // 数据状态-缓存
|
||||
import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' // 相关工具
|
||||
import MsgEnum from '@/plugins/imChat/msgEnum' // 消息头-相关定义(nuem)
|
||||
const route = useRoute();
|
||||
const tabActive = ref('select') // 工具栏当前选中项
|
||||
const isFold = ref(false) // 折叠工具栏
|
||||
const isDrag = ref(false) // 开始拖拽
|
||||
const dragtime = ref(0) // 拖拽时间-计算点击还是拖动
|
||||
const isShow = ref(false) // 是否显示-画板
|
||||
const isOver = ref(false) // 是否下课
|
||||
const toolStore = useToolState() // 状态管理
|
||||
const boardVueRef=ref(null) // 画板ref
|
||||
const upvoteRef = ref(null) // 点赞 ref
|
||||
const imChatRef = ref(null) // im-chat ref
|
||||
const classObj = reactive({ // 课程相关
|
||||
id: route.query.reservId, // 课程id
|
||||
data: {} // 课程信息
|
||||
})
|
||||
const btnList = [ // 工具栏按钮列表
|
||||
{ label: '选择', value: 'select', icon: 'icon-mouse' },
|
||||
{ label: '画笔', value: 'brush', icon: 'icon-huabi' },
|
||||
|
@ -64,12 +77,27 @@ const btnList = [ // 工具栏按钮列表
|
|||
]
|
||||
// === 页面加载完毕 ===
|
||||
onMounted(async() => {
|
||||
setTimeout(() => {
|
||||
getClassInfo() // 获取课堂详情 ex3
|
||||
resetStatus() // 开启重置状态-监听
|
||||
}, 200);
|
||||
})
|
||||
|
||||
|
||||
// ==== 方法 ===
|
||||
// const test = (e) => { console.log('test', e) }
|
||||
// 获取课堂信息
|
||||
const getClassInfo = async () => {
|
||||
const { data } = await classManageApi.getClassInfo(classObj.id)
|
||||
classObj.data = data
|
||||
if(!data.ex3 || data.ex3 == 'undefined') { // 无群直接创建
|
||||
await imChatRef.value.imChatObj.imChat.createGroup(data.className)
|
||||
const timGroupId = imChatRef.value.imChatObj.imChat.timGroupId
|
||||
classManageApi.startClass(classObj.id, timGroupId) // 开始上课
|
||||
} else { // 已创建群
|
||||
// console.log('已创建群: ', data.ex3)
|
||||
imChatRef.value.imChatObj.imChat.timGroupId = data.ex3
|
||||
// imChatRef.value.imChatObj.imChat.setGroupMsgReceive(data.ex3)
|
||||
}
|
||||
}
|
||||
// 切换tab-change
|
||||
const tabChange = (val) => {
|
||||
const bool = !toolStore.isPdfWin && !toolStore.showBoardAll
|
||||
|
@ -90,6 +118,27 @@ const mouseChange = (bool) => {
|
|||
if (!isShow.value) resBool = !!bool
|
||||
setIgnore(resBool)
|
||||
}
|
||||
// im-chat: 聊天事件 {type, data}
|
||||
const chatChange = (type, data) => {
|
||||
if (type == 'createGroup') { // 创建群-监听
|
||||
console.log('创建群:', data)
|
||||
!!data && classManageApi.startClass(classObj.id, data)
|
||||
} else if (type == 'msg') { // im-chat 消息监听
|
||||
if (!data) return // 没有msg数据
|
||||
const { msgKey:head, msgcontent:msg, senduserid:sendId, msgType } = data
|
||||
switch(head) {
|
||||
case MsgEnum.HEADS.MSG_0001:
|
||||
// console.log('点赞:', data)
|
||||
upvoteRef.value.trigger()
|
||||
break
|
||||
default:
|
||||
console.log('未知消息:', data)
|
||||
break
|
||||
}
|
||||
}
|
||||
// console.log('im-chat-sphere:', type, data)
|
||||
}
|
||||
|
||||
// 忽略鼠标穿透
|
||||
const setIgnore = (bool) => {ipcMsgSend('tool-sphere:set:ignore', bool)}
|
||||
// 重置状态: 鼠标|画板
|
||||
|
@ -102,10 +151,15 @@ const resetStatus = () => {
|
|||
}, 500)
|
||||
})
|
||||
toolStore.isToolWin = true // 标记状态
|
||||
|
||||
}
|
||||
// 侧边工具栏: 移入移出
|
||||
const sideMouse = e => {
|
||||
const {type} = e.detail
|
||||
if (isOver.value && type == 'mouseleave') {
|
||||
setIgnore(false) // 关闭窗口鼠标-穿透
|
||||
return
|
||||
}
|
||||
mouseChange(type == 'mouseleave')
|
||||
}
|
||||
// 侧边工具栏: 操作变化
|
||||
|
@ -119,9 +173,31 @@ const sideChange = async o => {
|
|||
case 'win': // 窗口
|
||||
break
|
||||
case 'over': // 下课
|
||||
isOver.value = true
|
||||
ElMessageBox.confirm('确认结束课程吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
await imChatRef.value?.imChatObj?.imChat?.sendMsgClosed() // 发送下课消息
|
||||
// const elMsg = ElMessage.warning({duration:0,message:'正在下课...'})
|
||||
const elMsg = ElLoading.service({lock: true, text: '正在下课...', background: 'rgba(0, 0, 0, 0.7)'})
|
||||
// 延迟2秒后关闭窗口,如果马上解散群,会导致群组不存在
|
||||
setTimeout(async() => {
|
||||
elMsg.close()
|
||||
toolStore.isToolWin = false
|
||||
await endClass(route.query.reservId)
|
||||
ipcMsgSend('tool-sphere:close')
|
||||
await classManageApi.endClass(route.query.reservId)
|
||||
await imChatRef.value?.deleteGroup() // 解散群
|
||||
await imChatRef.value?.logout() // 退出im
|
||||
ipcMsgSend('tool-sphere:close') // 关闭窗口
|
||||
}, 2000);
|
||||
|
||||
// isOver.value = false
|
||||
// setIgnore(true) // 开启窗口鼠标-穿透
|
||||
}).catch(() => {
|
||||
isOver.value = false
|
||||
setIgnore(true) // 开启窗口鼠标-穿透
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue