Compare commits
32 Commits
c94563fd43
...
7fc7135d73
Author | SHA1 | Date |
---|---|---|
yangws | 7fc7135d73 | |
zhengdegang | 6bc2579c2e | |
zdg | 75caf13d8b | |
zdg | 7fb98309fb | |
zdg | a3b7248977 | |
zdg | 6ae7c2c4b7 | |
朱浩 | c19631eb89 | |
朱浩 | b8896bf53d | |
朱浩 | 6f5eee4289 | |
朱浩 | aec8e9a21f | |
朱浩 | de909adccb | |
yangws | f7b00b1ccd | |
zhangxuelin | 2ed62802ce | |
zhangxuelin | 2ee5e24e6f | |
zhangxuelin | 3448a029c6 | |
zhangxuelin | 1dce3f6f70 | |
zhangxuelin | d9ae544022 | |
zhangxuelin | b520858f3f | |
zdg | 758a4b09c9 | |
zdg | 716c16928e | |
zdg | 0da2b25586 | |
zdg | bb1d5da104 | |
zdg | 69f54a217b | |
zdg | 1c67ce1b8f | |
zhangxuelin | 799dbed676 | |
zhangxuelin | f80e7dee2a | |
zhangxuelin | 2bf82dc294 | |
zhangxuelin | cc5c823071 | |
zdg | 8df3871f7d | |
zdg | 0ffa45313b | |
zhangxuelin | 7d3007bbf2 | |
zhangxuelin | bf99c4dd00 |
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "aix-win",
|
"name": "aix-win",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "An Electron application with Vue",
|
"description": "An Electron application with Vue",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "example.com",
|
"author": "example.com",
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
"electron-updater": "^6.1.7",
|
"electron-updater": "^6.1.7",
|
||||||
"element-plus": "^2.7.6",
|
"element-plus": "^2.7.6",
|
||||||
"fabric": "^5.3.0",
|
"fabric": "^5.3.0",
|
||||||
|
"im_electron_sdk": "^8.0.5904",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"jsondiffpatch": "0.6.0",
|
"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 { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||||
import icon from '../../resources/icon.png?asset'
|
import icon from '../../resources/icon.png?asset'
|
||||||
import File from './file'
|
import File from './file'
|
||||||
|
import chat from './chat' // chat封装
|
||||||
// 代理 electron/remote
|
// 代理 electron/remote
|
||||||
// 第一步:引入remote
|
// 第一步:引入remote
|
||||||
import remote from '@electron/remote/main'
|
import remote from '@electron/remote/main'
|
||||||
|
@ -229,12 +230,15 @@ app.on('window-all-closed', () => {
|
||||||
|
|
||||||
// 监听全局事件
|
// 监听全局事件
|
||||||
function handleAll() {
|
function handleAll() {
|
||||||
|
// chat.initialize() // im-chat 实例
|
||||||
|
const chatInstance = chat.initialize() // im-chat 实例
|
||||||
// 新窗口创建-监听
|
// 新窗口创建-监听
|
||||||
ipcMain.on('new-window', (e, data) => {
|
ipcMain.on('new-window', (e, data) => {
|
||||||
const { id, type } = data
|
const { id, type } = data
|
||||||
const win = BrowserWindow.fromId(id)
|
const win = BrowserWindow.fromId(id)
|
||||||
win.type = type // 绑定独立标识
|
win.type = type // 绑定独立标识
|
||||||
remote.enable(win.webContents) // 开启远程服务
|
remote.enable(win.webContents) // 开启远程服务
|
||||||
|
chatInstance.enable(win.webContents) // 开启im-chat
|
||||||
})
|
})
|
||||||
// 用于监听-状态管理变化-同步所有窗口
|
// 用于监听-状态管理变化-同步所有窗口
|
||||||
ipcMain.handle('pinia-state-change', (e, storeName, jsonStr) => {
|
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 { contextBridge } from 'electron'
|
||||||
import { electronAPI } from '@electron-toolkit/preload'
|
import { electronAPI } from '@electron-toolkit/preload'
|
||||||
|
import TimRender from 'im_electron_sdk/dist/renderer' // im渲染部分实例
|
||||||
// Custom APIs for renderer
|
// Custom APIs for renderer
|
||||||
const api = {
|
const api = {
|
||||||
|
preloadPath: __dirname, // 当前preload地址
|
||||||
|
getTimRender: () => new TimRender(), // im渲染部分实例
|
||||||
}
|
}
|
||||||
// Use `contextBridge` APIs to expose Electron APIs to
|
// Use `contextBridge` APIs to expose Electron APIs to
|
||||||
// renderer only if context isolation is enabled, otherwise
|
// 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}
|
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="canvasitem">
|
||||||
<div class="pdfAdnFabric" id="pdfAdnFabric">
|
<div class="pdfAdnFabric" id="pdfAdnFabric" >
|
||||||
<canvas id="pdf-fabric"></canvas>
|
<div :class="ispointer ? 'ispointer' : ''">
|
||||||
<canvas id="pdf-fabric1" v-if="props.pdfObj.numberOfPdf == 2"></canvas>
|
<canvas ref="fabriccanvas" />
|
||||||
|
</div>
|
||||||
|
<div v-if="props.pdfObj.numberOfPdf === 2" :class="ispointer ? 'ispointer' : ''">
|
||||||
|
<canvas ref="fabriccanvas1" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -13,12 +17,20 @@ import {
|
||||||
onMounted,
|
onMounted,
|
||||||
watch,
|
watch,
|
||||||
reactive,
|
reactive,
|
||||||
|
defineProps,
|
||||||
|
defineExpose,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
defineEmits,watchEffect
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { fabric } from 'fabric'
|
// import { fabric } from 'fabric'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { handleevent, savecanvsStore, initcanvasdata, displayData } from '@/utils/pdfAndFabric'
|
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({
|
const props = defineProps({
|
||||||
pdfObj: {
|
pdfObj: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -29,13 +41,16 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const ispointer = ref(false) //是否画笔模式
|
||||||
// canvas的所有数据
|
// canvas的所有数据
|
||||||
const canvsStore = reactive({
|
const canvsStore = reactive({
|
||||||
id: 'xxxx',
|
|
||||||
pageArr: []
|
pageArr: []
|
||||||
})
|
})
|
||||||
const fabriccanvas = ref(null)
|
const fabriccanvas = ref(null)
|
||||||
const fabriccanvas1 = ref(null)
|
const fabriccanvas1 = ref(null)
|
||||||
|
|
||||||
|
const canvasFabricVue = ref(null)
|
||||||
|
const canvas1FabricVue = ref(null)
|
||||||
// 页面总数
|
// 页面总数
|
||||||
const numPagesTotal = ref(0)
|
const numPagesTotal = ref(0)
|
||||||
const imgarr = ref([])
|
const imgarr = ref([])
|
||||||
|
@ -47,9 +62,9 @@ const renderPage = async (canvasobj) => {
|
||||||
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
||||||
// 渲染当前页
|
// 渲染当前页
|
||||||
const page = await pdf.getPage(canvasobj.page)
|
const page = await pdf.getPage(canvasobj.page)
|
||||||
var screenWidth = window.innerWidth/2-100;
|
var screenWidth = window.innerWidth / 2 - 100
|
||||||
var screenHeight = window.innerHeight;
|
var screenHeight = window.innerHeight
|
||||||
const viewport = page.getViewport({ scale:2})
|
const viewport = page.getViewport({ scale: 2 })
|
||||||
|
|
||||||
const canvasElement = canvasobj.canvas
|
const canvasElement = canvasobj.canvas
|
||||||
canvasElement.width = viewport.width
|
canvasElement.width = viewport.width
|
||||||
|
@ -58,6 +73,11 @@ const renderPage = async (canvasobj) => {
|
||||||
canvasContext: canvasobj.context,
|
canvasContext: canvasobj.context,
|
||||||
viewport: viewport
|
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) => {
|
page.render(renderContext).promise.then((res) => {
|
||||||
const img = document.createElement('img')
|
const img = document.createElement('img')
|
||||||
img.src = canvasobj.canvas.toDataURL('image/png')
|
img.src = canvasobj.canvas.toDataURL('image/png')
|
||||||
|
@ -68,33 +88,65 @@ const renderPage = async (canvasobj) => {
|
||||||
// 根据传过来的pdf对象 判断改渲染哪一个fabric
|
// 根据传过来的pdf对象 判断改渲染哪一个fabric
|
||||||
if (props.pdfObj.numberOfPdf == 2) {
|
if (props.pdfObj.numberOfPdf == 2) {
|
||||||
if (canvasobj.index == 0) {
|
if (canvasobj.index == 0) {
|
||||||
|
canvasFabricVue.value.canvas.setWidth(screenWidth)
|
||||||
fabriccanvas.value.setWidth(screenWidth)
|
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||||
fabriccanvas.value.setHeight(screenHeight)
|
// updateCanvasBackgroundImage(canvasFabricVue,img)
|
||||||
displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
|
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||||
} else {
|
} else {
|
||||||
fabriccanvas1.value.setWidth(screenWidth)
|
canvas1FabricVue.value.canvas.setWidth(screenWidth)
|
||||||
fabriccanvas1.value.setHeight(screenHeight)
|
canvas1FabricVue.value.canvas.setHeight(screenHeight)
|
||||||
displayData(fabriccanvas1, canvsStore, canvasobj, fabric, img)
|
displayData(canvas1FabricVue, canvsStore, canvasobj, fabric, img)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fabriccanvas.value.setWidth(screenWidth)
|
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||||
fabriccanvas.value.setHeight(screenHeight)
|
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||||
displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
|
|
||||||
}
|
}
|
||||||
// console.log(imgarr.value)
|
|
||||||
img.remove()
|
img.remove()
|
||||||
}
|
}
|
||||||
// 判断imgarr的JSONdata在canvsStore.pageArr有没有
|
})
|
||||||
canvsStore.pageArr.forEach((item) => {
|
}
|
||||||
if (item.page == canvasobj.page) {
|
// 保存数据
|
||||||
imgarr.value.forEach((img) => {
|
const savaDataStore = () => {
|
||||||
if (img.page == canvasobj.page) {
|
if(!toolState.isToolWin){
|
||||||
img.JSONdata = item.JSONdata
|
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) => {
|
const updatePage = (canvasobj) => {
|
||||||
|
@ -105,16 +157,55 @@ const loadPdf = async (canvasobj) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const initPdf = async (type = 'default') => {
|
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)
|
// savecanvsStore(imgarr, canvsStore)
|
||||||
// initcanvasdata(fabriccanvas)
|
|
||||||
// initcanvasdata(fabriccanvas1)
|
if (props.pdfObj.numberOfPdf == 1) {
|
||||||
// 单页模式
|
// imgarr.value[0]
|
||||||
if (type == 'restone') {
|
canvasFabricVue.value.history.clean()
|
||||||
// 清除 canvas 上的所有对象
|
} else {
|
||||||
fabriccanvas1.value.clear()
|
canvasFabricVue.value.history.clean()
|
||||||
// 释放 canvas 的资源
|
canvas1FabricVue.value.history.clean()
|
||||||
fabriccanvas1.value.dispose()
|
|
||||||
}
|
}
|
||||||
// 清除现有 canvas 元素的内容
|
// 清除现有 canvas 元素的内容
|
||||||
canvasNumbsValue.value.forEach((canvasObj) => {
|
canvasNumbsValue.value.forEach((canvasObj) => {
|
||||||
|
@ -146,7 +237,6 @@ const initPdf = async (type = 'default') => {
|
||||||
} else {
|
} else {
|
||||||
canvasNumbsValue.value[i].page = props.pdfObj.numPages + 1
|
canvasNumbsValue.value[i].page = props.pdfObj.numPages + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
canvasNumbsValue.value[i].index = i
|
canvasNumbsValue.value[i].index = i
|
||||||
// 加载FabricVue
|
// 加载FabricVue
|
||||||
await loadPdf(canvasNumbsValue.value[i])
|
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 () => {
|
const initPdfone = async () => {
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
fabriccanvas1.value = new fabric.Canvas('pdf-fabric1')
|
const option = { freeDrawingCursor: 'default' }
|
||||||
fabriccanvas1.value.isDrawingMode = true
|
const canvas2 = new fabricVue()
|
||||||
fabriccanvas1.value.freeDrawingBrush.color = '#A33AFE'
|
await canvas2.initCanvas(fabriccanvas1.value, option)
|
||||||
fabriccanvas1.value.freeDrawingCursor = 'default'
|
canvas2.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||||
fabriccanvas1.value.setWidth(860)
|
canvas1FabricVue.value = canvas2
|
||||||
handleevent(fabriccanvas1.value, imgarr, 'two')
|
await initPdf('addOnePage')
|
||||||
}, 0)
|
}, 0)
|
||||||
initPdf('addOnePage')
|
|
||||||
}
|
}
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
|
// 创建canvas转化成图片
|
||||||
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
|
||||||
numPagesTotal.value = pdf.numPages
|
numPagesTotal.value = pdf.numPages
|
||||||
// console.log(pdf)
|
|
||||||
// 初始化fabriccanvas
|
// 初始化fabriccanvas
|
||||||
fabriccanvas.value = new fabric.Canvas('pdf-fabric')
|
const option = { freeDrawingCursor: 'default' }
|
||||||
fabriccanvas.value.setWidth(860)
|
const canvas1 = new fabricVue()
|
||||||
fabriccanvas.value.isDrawingMode = true
|
// canvas1.boardConfig.mode= TYPES.ActionMode.OTHER
|
||||||
fabriccanvas.value.freeDrawingBrush.color = '#A33AFE'
|
// canvas1.boardConfig.mode= TYPES.ActionMode.ERASE
|
||||||
fabriccanvas.value.freeDrawingCursor = 'default'
|
await canvas1.initCanvas(fabriccanvas.value, option)
|
||||||
|
canvas1.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||||
fabriccanvas1.value = new fabric.Canvas('pdf-fabric1')
|
canvasFabricVue.value = canvas1
|
||||||
fabriccanvas1.value.isDrawingMode = true
|
const canvas2 = new fabricVue()
|
||||||
fabriccanvas1.value.freeDrawingBrush.color = '#A33AFE'
|
await canvas2.initCanvas(fabriccanvas1.value, option)
|
||||||
fabriccanvas1.value.freeDrawingCursor = 'default'
|
canvas2.canvas.setWidth(window.innerWidth / 2 - 100)
|
||||||
fabriccanvas1.value.setWidth(860)
|
// canvas2.canvas.isDrawingMode=false
|
||||||
|
canvas1FabricVue.value = canvas2
|
||||||
|
window.test = { canvas1, canvas2 }
|
||||||
emit('update:numPagesTotal', pdf.numPages)
|
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) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
ElMessage.error('pdf文件错误')
|
ElMessage.error('pdf文件错误')
|
||||||
}
|
}
|
||||||
|
setToolStatus()
|
||||||
// 监听2个canvas事件
|
// 监听2个canvas事件
|
||||||
handleevent(fabriccanvas.value, imgarr)
|
// handleevent(fabriccanvas.value, imgarr)
|
||||||
handleevent(fabriccanvas1.value, imgarr, 'two')
|
// 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({
|
defineExpose({
|
||||||
initPdf,
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -204,6 +401,8 @@ defineExpose({
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 100vh;
|
||||||
}
|
}
|
||||||
.pdfAdnFabric {
|
.pdfAdnFabric {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -214,4 +413,7 @@ defineExpose({
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ispointer {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,15 +1,9 @@
|
||||||
pdfAdnFabric<template>
|
pdfAdnFabric<template>
|
||||||
<div class="canvasitem">
|
<div class="canvasitem">
|
||||||
<div class="pdfAdnFabric" id="pdfAdnFabric" >
|
<div class="pdfAdnFabric" id="pdfAdnFabric" >
|
||||||
<!-- @touchmove="handleTouchMove"
|
|
||||||
@touchend="handleTouchEnd"
|
|
||||||
@mousedown="handleMouseDown"
|
|
||||||
@mousemove="handleMouseMove"
|
|
||||||
@mouseup="handleMouseUp" -->
|
|
||||||
<div :class="ispointer ? 'ispointer' : ''">
|
<div :class="ispointer ? 'ispointer' : ''">
|
||||||
<canvas ref="fabriccanvas" />
|
<canvas ref="fabriccanvas" />
|
||||||
</div>
|
</div>
|
||||||
<!-- style="pointer-events: none;" -->
|
|
||||||
<div v-if="props.pdfObj.numberOfPdf === 2" :class="ispointer ? 'ispointer' : ''">
|
<div v-if="props.pdfObj.numberOfPdf === 2" :class="ispointer ? 'ispointer' : ''">
|
||||||
<canvas ref="fabriccanvas1" />
|
<canvas ref="fabriccanvas1" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,8 +73,6 @@ const renderPage = async (canvasobj) => {
|
||||||
canvasContext: canvasobj.context,
|
canvasContext: canvasobj.context,
|
||||||
viewport: viewport
|
viewport: viewport
|
||||||
}
|
}
|
||||||
// console.log(renderContext,22222222222222222222)
|
|
||||||
|
|
||||||
page.render(renderContext).promise.then((res) => {
|
page.render(renderContext).promise.then((res) => {
|
||||||
const img = document.createElement('img')
|
const img = document.createElement('img')
|
||||||
img.src = canvasobj.canvas.toDataURL('image/png')
|
img.src = canvasobj.canvas.toDataURL('image/png')
|
||||||
|
@ -93,7 +85,6 @@ const renderPage = async (canvasobj) => {
|
||||||
if (canvasobj.index == 0) {
|
if (canvasobj.index == 0) {
|
||||||
canvasFabricVue.value.canvas.setWidth(screenWidth)
|
canvasFabricVue.value.canvas.setWidth(screenWidth)
|
||||||
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
canvasFabricVue.value.canvas.setHeight(screenHeight)
|
||||||
// updateCanvasBackgroundImage(canvasFabricVue,img)
|
|
||||||
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
displayData(canvasFabricVue, canvsStore, canvasobj, fabric, img)
|
||||||
} else {
|
} else {
|
||||||
canvas1FabricVue.value.canvas.setWidth(screenWidth)
|
canvas1FabricVue.value.canvas.setWidth(screenWidth)
|
||||||
|
@ -106,16 +97,6 @@ const renderPage = async (canvasobj) => {
|
||||||
}
|
}
|
||||||
img.remove()
|
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) {
|
if (props.pdfObj.numberOfPdf == 1) {
|
||||||
// imgarr.value[0]
|
|
||||||
canvasFabricVue.value.history.clean()
|
canvasFabricVue.value.history.clean()
|
||||||
} else {
|
} else {
|
||||||
canvasFabricVue.value.history.clean()
|
canvasFabricVue.value.history.clean()
|
||||||
|
@ -393,8 +371,18 @@ defineExpose({
|
||||||
savaDataStore
|
savaDataStore
|
||||||
})
|
})
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
console.log(toolState.model,'监听')
|
setTimeout(() => {
|
||||||
|
console.log(toolState,'监听')
|
||||||
|
|
||||||
|
}, 300)
|
||||||
|
if(toolState.isPdfWin){
|
||||||
|
// if(toolState.isToolWin){
|
||||||
|
// ispointer.value=false
|
||||||
|
// }else{
|
||||||
|
// ispointer.value=true
|
||||||
|
// }
|
||||||
watchToolState() //监听工具栏
|
watchToolState() //监听工具栏
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</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 isNode = typeof require !== 'undefined' // 是否支持node函数
|
||||||
const { ipcRenderer } = isNode?require('electron'):{} // app使用
|
const { ipcRenderer } = isNode?require('electron'):{} // app使用
|
||||||
|
// const Remote = isNode?require('@electron/remote'):{} // 远程模块
|
||||||
export function shareStorePlugin({store}) {
|
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']
|
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)
|
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 storeName = store.$id
|
||||||
const jsonStr = circularSafeStringify(store.$state)
|
const jsonStr = circularSafeStringify(store.$state)
|
||||||
// 通知主线程更新
|
// 通知主线程更新
|
||||||
ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
|
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) {
|
function stateChange(store) {
|
||||||
const storeName = store.$id
|
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 path = isNode?require('path'):{}
|
||||||
const Remote = isNode?require('@electron/remote'):{}
|
const Remote = isNode?require('@electron/remote'):{}
|
||||||
const { ipcRenderer } = isNode?require('electron'):window.electron || {}
|
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 BaseUrl = isNode?process.env['ELECTRON_RENDERER_URL']+'/#':''
|
||||||
const isDev = isNode?process.env.NODE_ENV !== 'production':''
|
const isDev = isNode?process.env.NODE_ENV !== 'production':''
|
||||||
|
const toolState = useToolState() // 获取store状态
|
||||||
|
|
||||||
// 暴露Remote中的属性
|
// 暴露Remote中的属性
|
||||||
export const ipcMain = Remote?.ipcMain || {}
|
export const ipcMain = Remote?.ipcMain || {}
|
||||||
|
@ -83,11 +85,12 @@ export function ipcHandle(fn,key, cb) {
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
let wins_tool = null
|
let wins_tool = null
|
||||||
|
let winPdf=null
|
||||||
export const createWindow = async (type, data) => {
|
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) {
|
switch(type) {
|
||||||
case 'tool-sphere': { // 创建-悬浮球
|
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 option = data.option||{}
|
||||||
const defOption = {
|
const defOption = {
|
||||||
frame: false, // 要创建无边框窗口
|
frame: false, // 要创建无边框窗口
|
||||||
|
@ -98,6 +101,7 @@ export const createWindow = async (type, data) => {
|
||||||
// autoClose: true, // 关闭窗口后自动关闭
|
// autoClose: true, // 关闭窗口后自动关闭
|
||||||
}
|
}
|
||||||
data.isConsole = true // 是否开启控制台
|
data.isConsole = true // 是否开启控制台
|
||||||
|
data.isWeb = false // 是否开启web安全
|
||||||
data.option = {...defOption, ...option}
|
data.option = {...defOption, ...option}
|
||||||
wins_tool = await toolWindow(data)
|
wins_tool = await toolWindow(data)
|
||||||
wins_tool.type = type // 唯一标识
|
wins_tool.type = type // 唯一标识
|
||||||
|
@ -119,12 +123,19 @@ export const createWindow = async (type, data) => {
|
||||||
}
|
}
|
||||||
data.isConsole = true // 是否开启控制台
|
data.isConsole = true // 是否开启控制台
|
||||||
data.option = {...defOption, ...option}
|
data.option = {...defOption, ...option}
|
||||||
|
if(winPdf){ //判断是否已经打开
|
||||||
|
// if (winPdf.isMinimized()) winPdf.restore();
|
||||||
|
winPdf.focus();
|
||||||
|
// toolState.isPdfWin=true
|
||||||
|
return
|
||||||
|
}
|
||||||
const win = await toolWindow(data)
|
const win = await toolWindow(data)
|
||||||
win.type = type // 唯一标识
|
win.type = type // 唯一标识
|
||||||
win.show()
|
win.show()
|
||||||
win.setFullScreen(true) // 设置窗口为全屏
|
win.setFullScreen(true) // 设置窗口为全屏
|
||||||
// win.webContents.openDevTools() // 打开调试工具
|
// win.webContents.openDevTools() // 打开调试工具
|
||||||
eventHandles(type, win) // 事件监听处理
|
eventHandles(type, win) // 事件监听处理
|
||||||
|
winPdf=win
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -140,7 +151,7 @@ export const createWindow = async (type, data) => {
|
||||||
* @author: zdg
|
* @author: zdg
|
||||||
* @date 2021-07-05 14:07:01
|
* @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
|
// width = window.screen.width
|
||||||
let width = option?.width || 800
|
let width = option?.width || 800
|
||||||
let height = option?.height || 600
|
let height = option?.height || 600
|
||||||
|
@ -152,14 +163,12 @@ export function toolWindow({url, isConsole, option={}}) {
|
||||||
const config = {
|
const config = {
|
||||||
width, height,
|
width, height,
|
||||||
type: 'toolbar', // 创建的窗口类型为工具栏窗口
|
type: 'toolbar', // 创建的窗口类型为工具栏窗口
|
||||||
icon: path.join(__dirname, '../../resources/logo2.ico'),
|
// icon: path.join(__dirname, '../../resources/logo2.ico'),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
// preload: path.join(__dirname, '../preload/index.js'),
|
preload: path.join(API.preloadPath, '/index.js'),
|
||||||
preload: '@root/src/preload/index.js',
|
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
nodeIntegration: true, // nodeApi调用
|
nodeIntegration: true, // nodeApi调用
|
||||||
contextIsolation: false, // 沙箱取消
|
contextIsolation: false, // 沙箱取消
|
||||||
// webSecurity: false // 跨域关闭
|
|
||||||
},
|
},
|
||||||
...option
|
...option
|
||||||
}
|
}
|
||||||
|
@ -167,11 +176,17 @@ export function toolWindow({url, isConsole, option={}}) {
|
||||||
let win = new Remote.BrowserWindow(config)
|
let win = new Remote.BrowserWindow(config)
|
||||||
if (!isDev) win.loadFile(urlAll,{hash: url}) // 加载文件
|
if (!isDev) win.loadFile(urlAll,{hash: url}) // 加载文件
|
||||||
else win.loadURL(urlAll) // 加载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()})
|
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) {
|
if (!!isConsole) {
|
||||||
win.webContents.on('console-message', (e,leve,m,lin,s) => {
|
win.webContents.on('console-message', (e,leve,m,lin,s) => {
|
||||||
|
@ -191,12 +206,13 @@ const eventHandles = (type, win) => {
|
||||||
// 公共方法
|
// 公共方法
|
||||||
const publicMethods = ({onClosed}={}) => {
|
const publicMethods = ({onClosed}={}) => {
|
||||||
// 监听主窗口-关闭事件
|
// 监听主窗口-关闭事件
|
||||||
mainWin.once('close', () => {win.destroy()})
|
mainWin.once('close', () => {winPdf=null;win.destroy();})
|
||||||
win.on('closed', () => {
|
win.on('closed', () => {
|
||||||
if(onClosed) onClosed() // 自定义关闭事件
|
if(onClosed) onClosed() // 自定义关闭事件
|
||||||
win = null
|
win = null
|
||||||
wins_tool = null
|
wins_tool = null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 新窗口-创建事件(如:主进程加载远程服务)
|
// 新窗口-创建事件(如:主进程加载远程服务)
|
||||||
ipcRenderer.send('new-window', {id:win.id, type})
|
ipcRenderer.send('new-window', {id:win.id, type})
|
||||||
}
|
}
|
||||||
|
@ -220,7 +236,11 @@ const eventHandles = (type, win) => {
|
||||||
break}
|
break}
|
||||||
case 'open-PDF': {
|
case 'open-PDF': {
|
||||||
// 最小化窗口 minimize()
|
// 最小化窗口 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() // 加载公共方法
|
publicMethods() // 加载公共方法
|
||||||
break}
|
break}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -40,10 +40,8 @@ import { getStaticUrl } from '@/utils/tool'
|
||||||
const { ipcRenderer } = require('electron')
|
const { ipcRenderer } = require('electron')
|
||||||
import { getBookMarkById } from '@/api/eTextbook/index'
|
import { getBookMarkById } from '@/api/eTextbook/index'
|
||||||
import {useToolState} from '@/store/modules/tool'
|
import {useToolState} from '@/store/modules/tool'
|
||||||
// const getStaticUrl=(url)=>{
|
|
||||||
// return url
|
|
||||||
// }
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = getStaticUrl('/lib/build/pdf.worker.mjs')
|
pdfjsLib.GlobalWorkerOptions.workerSrc = getStaticUrl('/lib/build/pdf.worker.mjs')
|
||||||
|
|
||||||
const toolState = useToolState();
|
const toolState = useToolState();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const isOnLoadShow = ref(false) //加载完毕显示
|
const isOnLoadShow = ref(false) //加载完毕显示
|
||||||
|
@ -73,14 +71,9 @@ const navtopage = (type) => {
|
||||||
if (pdfObj.numPages > numPagesTotal.value) return
|
if (pdfObj.numPages > numPagesTotal.value) return
|
||||||
pdfCanvaslist.value.initPdf('rest')
|
pdfCanvaslist.value.initPdf('rest')
|
||||||
}
|
}
|
||||||
// 最小化窗口
|
// 关闭窗口
|
||||||
const minimize = async () => {
|
const minimize = async () => {
|
||||||
await pdfCanvaslist.value.savaDataStore()
|
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) => {
|
const handleUpdate = (data) => {
|
||||||
numPagesTotal.value = data
|
numPagesTotal.value = data
|
||||||
|
@ -107,10 +100,12 @@ const switchPageMode = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
toolState.isPdfWin=true
|
toolState.isPdfWin=true //设置打开pdf窗口
|
||||||
pdfObj.pdfUrl = getStaticUrl(route.query.path, 'user', 'selfFile', true)
|
pdfObj.pdfUrl = getStaticUrl(route.query.path, 'user', 'selfFile', true) //线上
|
||||||
|
// pdfObj.pdfUrl = getStaticUrl('aaa.pdf', 'user', 'selfFile', true) //本地
|
||||||
textbookId.value = route.query.textbookId
|
textbookId.value = route.query.textbookId
|
||||||
pdfObj.bookId=textbookId.value
|
pdfObj.bookId=textbookId.value
|
||||||
|
//初始化获取接口数据
|
||||||
getBookMarkById(textbookId.value).then(res=>{
|
getBookMarkById(textbookId.value).then(res=>{
|
||||||
pdfObj.allPageData=getUniqueArrayByLastOccurrence(res.data)
|
pdfObj.allPageData=getUniqueArrayByLastOccurrence(res.data)
|
||||||
isOnLoadShow.value=true
|
isOnLoadShow.value=true
|
||||||
|
|
|
@ -55,9 +55,11 @@ const toolStore = useToolState()
|
||||||
watch(
|
watch(
|
||||||
() => [dataList,toolStore.isToolWin],
|
() => [dataList,toolStore.isToolWin],
|
||||||
() => {
|
() => {
|
||||||
|
setTimeout(()=>{
|
||||||
getSelfReserv().then((res) => {
|
getSelfReserv().then((res) => {
|
||||||
dataList.value = [...res.data]
|
dataList.value = [...res.data]
|
||||||
})
|
})
|
||||||
|
},300)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
<ChooseTextbook @change-book="nodeClick" @node-click="nodeClick" />
|
<ChooseTextbook @change-book="nodeClick" @node-click="nodeClick" />
|
||||||
<div class="page-right">
|
<div class="page-right">
|
||||||
<div class="header-top flex">
|
<div class="header-top flex">
|
||||||
<div class="textbook-img">
|
<div class="textbook-img" @click="navtoPdf">
|
||||||
<el-image style="width: 80px; height: 110px" :src="curBookImg" @click="navtoPdf" />
|
<el-image style="width: 80px; height: 110px" :src="curBookImg" />
|
||||||
<el-progress
|
<el-progress
|
||||||
v-if="downloadNum > 0 && downloadNum < 100"
|
v-if="downloadNum > 0 && downloadNum < 100"
|
||||||
style="position: absolute; left: 0; z-index: 999"
|
style="position: absolute; left: 0; z-index: 999"
|
||||||
|
|
|
@ -129,7 +129,7 @@ function submit() {
|
||||||
props.user.avatar = userStore.user.avatar
|
props.user.avatar = userStore.user.avatar
|
||||||
updateUserInfo(props.user).then((response) => {
|
updateUserInfo(props.user).then((response) => {
|
||||||
if(response.code == 200){
|
if(response.code == 200){
|
||||||
userStore.login({username:props.user.phonenumber,password:props.user.plainpwd}).then(() => {
|
userStore.login({username:props.user.userName,password:props.user.plainpwd}).then(() => {
|
||||||
userStore.getInfo().then(res => {
|
userStore.getInfo().then(res => {
|
||||||
if(res.code === 200){
|
if(res.code === 200){
|
||||||
ElMessage.success('修改成功')
|
ElMessage.success('修改成功')
|
||||||
|
|
|
@ -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">
|
<transition name="el-fade-in">
|
||||||
<div class="c-popover" :style="`--top: ${topPos}px;--height:${hPost}px;`" v-show="isVisible">
|
<div class="c-popover" :style="`--top: ${topPos}px;--height:${hPost}px;`" v-show="isVisible">
|
||||||
<div class="content">
|
<div class="content" v-if="isVisible">
|
||||||
<homework/>
|
<slot name="content">
|
||||||
|
<homework v-if="activeObj?.prop === 'resource'" />
|
||||||
|
<span v-else style="color:red;">{{activeObj}}</span>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -135,8 +138,4 @@ const clickHandel = (o, e) => {
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.content{
|
|
||||||
color: red;
|
|
||||||
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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">
|
<div ref="warpRef" class="c-warp">
|
||||||
<template v-for="i in sum">
|
<template v-for="i in sum">
|
||||||
<slot><el-icon><Star /></el-icon></slot>
|
<slot><el-icon><Star /></el-icon></slot>
|
||||||
|
|
|
@ -7,9 +7,12 @@
|
||||||
<side-vue v-ignore @ignore-mounted="sideMouse" @change="sideChange"></side-vue>
|
<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 class="tool-bottom-all" @mouseenter="mouseChange(0)" @mouseleave="mouseChange(1)">
|
||||||
<div v-drag="{handle:'.tool-bottom-all', dragtime}"
|
<div v-drag="{handle:'.tool-bottom-all', dragtime}"
|
||||||
@v-drag-start="dragtime = Date.now()">
|
@v-drag-start="dragtime = Date.now()">
|
||||||
|
@ -36,23 +39,33 @@
|
||||||
// 功能说明:electron 悬浮球
|
// 功能说明:electron 悬浮球
|
||||||
import { onMounted, ref, reactive, watchEffect } from 'vue'
|
import { onMounted, ref, reactive, watchEffect } from 'vue'
|
||||||
import { useRoute } from 'vue-router';
|
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 logo from '@root/resources/icon.png' // logo
|
||||||
import boardVue from './components/board.vue' // 画板-子组件
|
import boardVue from './components/board.vue' // 画板-子组件
|
||||||
import sideVue from './components/side.vue' // 画板-子组件
|
import sideVue from './components/side.vue' // 画板-子组件
|
||||||
import upvoteVue from './components/upvote.vue' // 点赞-子组件
|
import upvoteVue from './components/upvote.vue' // 点赞-子组件
|
||||||
|
import imChat from './components/imChat.vue' // im-chat-子组件
|
||||||
import vDrag from './directive/drag' // 自定义指令-拖拽
|
import vDrag from './directive/drag' // 自定义指令-拖拽
|
||||||
import vIgnore from './directive/ignore' // 自定义指令-穿透
|
import vIgnore from './directive/ignore' // 自定义指令-穿透
|
||||||
import { useToolState } from '@/store/modules/tool' // 数据状态-缓存
|
import { useToolState } from '@/store/modules/tool' // 数据状态-缓存
|
||||||
import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' // 相关工具
|
import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' // 相关工具
|
||||||
|
import MsgEnum from '@/plugins/imChat/msgEnum' // 消息头-相关定义(nuem)
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const tabActive = ref('select') // 工具栏当前选中项
|
const tabActive = ref('select') // 工具栏当前选中项
|
||||||
const isFold = ref(false) // 折叠工具栏
|
const isFold = ref(false) // 折叠工具栏
|
||||||
const isDrag = ref(false) // 开始拖拽
|
const isDrag = ref(false) // 开始拖拽
|
||||||
const dragtime = ref(0) // 拖拽时间-计算点击还是拖动
|
const dragtime = ref(0) // 拖拽时间-计算点击还是拖动
|
||||||
const isShow = ref(false) // 是否显示-画板
|
const isShow = ref(false) // 是否显示-画板
|
||||||
|
const isOver = ref(false) // 是否下课
|
||||||
const toolStore = useToolState() // 状态管理
|
const toolStore = useToolState() // 状态管理
|
||||||
const boardVueRef=ref(null) // 画板ref
|
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 = [ // 工具栏按钮列表
|
const btnList = [ // 工具栏按钮列表
|
||||||
{ label: '选择', value: 'select', icon: 'icon-mouse' },
|
{ label: '选择', value: 'select', icon: 'icon-mouse' },
|
||||||
{ label: '画笔', value: 'brush', icon: 'icon-huabi' },
|
{ label: '画笔', value: 'brush', icon: 'icon-huabi' },
|
||||||
|
@ -64,12 +77,27 @@ const btnList = [ // 工具栏按钮列表
|
||||||
]
|
]
|
||||||
// === 页面加载完毕 ===
|
// === 页面加载完毕 ===
|
||||||
onMounted(async() => {
|
onMounted(async() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
getClassInfo() // 获取课堂详情 ex3
|
||||||
resetStatus() // 开启重置状态-监听
|
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
|
// 切换tab-change
|
||||||
const tabChange = (val) => {
|
const tabChange = (val) => {
|
||||||
const bool = !toolStore.isPdfWin && !toolStore.showBoardAll
|
const bool = !toolStore.isPdfWin && !toolStore.showBoardAll
|
||||||
|
@ -90,6 +118,27 @@ const mouseChange = (bool) => {
|
||||||
if (!isShow.value) resBool = !!bool
|
if (!isShow.value) resBool = !!bool
|
||||||
setIgnore(resBool)
|
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)}
|
const setIgnore = (bool) => {ipcMsgSend('tool-sphere:set:ignore', bool)}
|
||||||
// 重置状态: 鼠标|画板
|
// 重置状态: 鼠标|画板
|
||||||
|
@ -102,10 +151,15 @@ const resetStatus = () => {
|
||||||
}, 500)
|
}, 500)
|
||||||
})
|
})
|
||||||
toolStore.isToolWin = true // 标记状态
|
toolStore.isToolWin = true // 标记状态
|
||||||
|
|
||||||
}
|
}
|
||||||
// 侧边工具栏: 移入移出
|
// 侧边工具栏: 移入移出
|
||||||
const sideMouse = e => {
|
const sideMouse = e => {
|
||||||
const {type} = e.detail
|
const {type} = e.detail
|
||||||
|
if (isOver.value && type == 'mouseleave') {
|
||||||
|
setIgnore(false) // 关闭窗口鼠标-穿透
|
||||||
|
return
|
||||||
|
}
|
||||||
mouseChange(type == 'mouseleave')
|
mouseChange(type == 'mouseleave')
|
||||||
}
|
}
|
||||||
// 侧边工具栏: 操作变化
|
// 侧边工具栏: 操作变化
|
||||||
|
@ -119,9 +173,31 @@ const sideChange = async o => {
|
||||||
case 'win': // 窗口
|
case 'win': // 窗口
|
||||||
break
|
break
|
||||||
case 'over': // 下课
|
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
|
toolStore.isToolWin = false
|
||||||
await endClass(route.query.reservId)
|
await classManageApi.endClass(route.query.reservId)
|
||||||
ipcMsgSend('tool-sphere:close')
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue