zdg #105

Merged
zhengdegang merged 17 commits from zdg into main 2024-08-07 16:11:14 +08:00
16 changed files with 1173 additions and 238 deletions

View File

@ -0,0 +1,35 @@
import request from '@/utils/request'
// 新增pdf圈点勾画
export const addsmartBookMark = (params) => {
return request({
url: '/smarttalk/bookMark/addSmartBookMark',
method: 'post',
data:params
})
}
// 修改pdf圈点勾画
export const updateSmartBookMarkContent = (params) => {
return request({
url: '/smarttalk/bookMark/updateSmartBookMarkContent',
method: 'post',
data:params
})
}
// 根据书id获取pdf圈点勾画
export const getBookMarkById = (bookId) => {
return request({
url: '/smarttalk/bookMark/' + bookId,
method: 'get'
})
}
//根据id删除对应页数
export function deleteBookMark(ids) {
return request({
url: '/smarttalk/bookMark/' + ids,
method: 'delete'
})
}

View File

@ -0,0 +1,217 @@
<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>
</div>
</template>
<script setup >
import {
ref,
onMounted,
watch,
reactive,
nextTick,
} from 'vue'
import { fabric } from 'fabric'
import { ElMessage } from 'element-plus'
import { handleevent, savecanvsStore, initcanvasdata, displayData } from '@/utils/pdfAndFabric'
const props = defineProps({
pdfObj: {
type: Object,
default: {
numberOfPdf: 2, //pdf 1 2
pdfUrl: null,
numPages: 1
}
}
})
// canvas
const canvsStore = reactive({
id: 'xxxx',
pageArr: []
})
const fabriccanvas = ref(null)
const fabriccanvas1 = ref(null)
//
const numPagesTotal = ref(0)
const imgarr = ref([])
// pdf
const canvasNumbsValue = ref([])
const emit = defineEmits(['update:numPagesTotal'])
const renderPage = async (canvasobj) => {
if (canvasobj.page > numPagesTotal.value) return
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;
const viewport = page.getViewport({ scale:2})
const canvasElement = canvasobj.canvas
canvasElement.width = viewport.width
canvasElement.height = viewport.height
const renderContext = {
canvasContext: canvasobj.context,
viewport: viewport
}
page.render(renderContext).promise.then((res) => {
const img = document.createElement('img')
img.src = canvasobj.canvas.toDataURL('image/png')
canvasobj.canvas.remove()
imgarr.value.push({ src: img.src, page: canvasobj.page, JSONdata: {}, index: canvasobj.index })
img.onload = () => {
//
// 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)
} else {
fabriccanvas1.value.setWidth(screenWidth)
fabriccanvas1.value.setHeight(screenHeight)
displayData(fabriccanvas1, canvsStore, canvasobj, fabric, img)
}
} else {
fabriccanvas.value.setWidth(screenWidth)
fabriccanvas.value.setHeight(screenHeight)
displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
}
// console.log(imgarr.value)
img.remove()
}
// imgarrJSONdatacanvsStore.pageArr
canvsStore.pageArr.forEach((item) => {
if (item.page == canvasobj.page) {
imgarr.value.forEach((img) => {
if (img.page == canvasobj.page) {
img.JSONdata = item.JSONdata
}
})
}
})
})
}
const updatePage = (canvasobj) => {
renderPage(canvasobj)
}
const loadPdf = async (canvasobj) => {
updatePage(canvasobj)
}
const initPdf = async (type = 'default') => {
//
savecanvsStore(imgarr, canvsStore)
// initcanvasdata(fabriccanvas)
// initcanvasdata(fabriccanvas1)
//
if (type == 'restone') {
// canvas
fabriccanvas1.value.clear()
// canvas
fabriccanvas1.value.dispose()
}
// canvas
canvasNumbsValue.value.forEach((canvasObj) => {
const context = canvasObj.context
context.clearRect(0, 0, canvasObj.canvas.width, canvasObj.canvas.height)
})
//
imgarr.value = []
canvasNumbsValue.value = []
if (props.pdfObj.pdfUrl) {
await nextTick() // DOM
if (props.pdfObj.numberOfPdf == 1) {
canvasNumbsValue.value = [{}]
const canvasElement = document.createElement('canvas')
canvasNumbsValue.value[0].canvas = canvasElement
canvasNumbsValue.value[0].context = canvasNumbsValue.value[0].canvas.getContext('2d')
canvasNumbsValue.value[0].page = props.pdfObj.numPages
canvasNumbsValue.value[0].index = 0
await loadPdf(canvasNumbsValue.value[0])
} else {
for (var i = 0; i < props.pdfObj.numberOfPdf; i++) {
canvasNumbsValue.value[i] = {}
const canvasElement = document.createElement('canvas')
canvasNumbsValue.value[i].canvas = canvasElement
canvasNumbsValue.value[i].context = canvasNumbsValue.value[i].canvas.getContext('2d')
//
if (i == 0) {
canvasNumbsValue.value[i].page = props.pdfObj.numPages
} else {
canvasNumbsValue.value[i].page = props.pdfObj.numPages + 1
}
canvasNumbsValue.value[i].index = i
// FabricVue
await loadPdf(canvasNumbsValue.value[i])
}
}
}
}
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')
}, 0)
initPdf('addOnePage')
}
onMounted(async () => {
try {
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)
emit('update:numPagesTotal', pdf.numPages)
initPdf()
} catch (error) {
console.log(error)
ElMessage.error('pdf文件错误')
}
// 2canvas
handleevent(fabriccanvas.value, imgarr)
handleevent(fabriccanvas1.value, imgarr, 'two')
})
defineExpose({
initPdf,
initPdfone
})
</script>
<style lang="scss" scoped>
.canvasitem {
display: flex;
flex-wrap: wrap;
width: 100%;
justify-content: center;
}
.pdfAdnFabric {
position: relative;
display: flex;
height: 100vh;
align-items: center;
:deep(> div:nth-of-type(1)) {
margin-right: 10px;
}
}
</style>

View File

@ -1,8 +1,18 @@
<template> pdfAdnFabric<template>
<div class="canvasitem"> <div class="canvasitem">
<div class="pdfAdnFabric" id="pdfAdnFabric" > <div class="pdfAdnFabric" id="pdfAdnFabric" >
<canvas id="pdf-fabric"></canvas> <!-- @touchmove="handleTouchMove"
<canvas id="pdf-fabric1" v-if="props.pdfObj.numberOfPdf == 2"></canvas> @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>
</div> </div>
</div> </div>
</template> </template>
@ -16,12 +26,17 @@ import {
defineProps, defineProps,
defineExpose, defineExpose,
nextTick, nextTick,
defineEmits 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,
@ -32,13 +47,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([])
@ -50,8 +68,8 @@ 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
@ -61,6 +79,8 @@ 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')
@ -71,33 +91,68 @@ 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()
} }
// imgarrJSONdatacanvsStore.pageArr // imgarrJSONdatacanvsStore.pageArr
canvsStore.pageArr.forEach((item) => { // canvsStore.pageArr.forEach((item) => {
if (item.page == canvasobj.page) { // if (item.page == canvasobj.page) {
imgarr.value.forEach((img) => { // imgarr.value.forEach((img) => {
if (img.page == canvasobj.page) { // if (img.page == canvasobj.page) {
img.JSONdata = item.JSONdata // img.JSONdata = item.JSONdata
} // }
// })
// }
// })
}) })
} }
//
const savaDataStore = () => {
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) => {
@ -108,16 +163,54 @@ 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') {
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) => {
@ -149,7 +242,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])
@ -157,47 +249,144 @@ 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(595) 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(595) 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(595) // 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()
// 2canvas // 2canvas
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(() => {
console.log(toolState.model,'监听')
watchToolState() //
}) })
</script> </script>
@ -207,6 +396,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;
@ -217,4 +408,7 @@ defineExpose({
margin-right: 10px; margin-right: 10px;
} }
} }
.ispointer {
pointer-events: none;
}
</style> </style>

View File

@ -1615,6 +1615,7 @@ export class fabricVue {
break break
case TYPES.ActionMode.OTHER: // 其他(工具选择) case TYPES.ActionMode.OTHER: // 其他(工具选择)
this.canvas.isDrawingMode = false this.canvas.isDrawingMode = false
objectSet.selectable = false
this.canvas.freeDrawingCursor = 'default' this.canvas.freeDrawingCursor = 'default'
break break
default: default:

View File

@ -1,7 +1,8 @@
/** /**
* 共享数据状态-多窗口 * 共享数据状态-多窗口
*/ */
const { ipcRenderer } = require('electron') // app使用 const isNode = typeof require !== 'undefined' // 是否支持node函数
const { ipcRenderer } = isNode?require('electron'):{} // app使用
export function shareStorePlugin({store}) { export function shareStorePlugin({store}) {
store.$subscribe(() => { // 自动同步 store.$subscribe(() => { // 自动同步
// 在存储变化的时候执行 // 在存储变化的时候执行
@ -19,15 +20,16 @@ export function shareStorePlugin({store}) {
function stateSync(store) { function stateSync(store) {
const storeName = store.$id const storeName = store.$id
const jsonStr = JSON.stringify(store.$state) const jsonStr = JSON.stringify(store.$state)
// console.log('state-change', jsonStr, storeName)
// 通知主线程更新 // 通知主线程更新
ipcRenderer.invoke('pinia-state-change', storeName, jsonStr) ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
} }
// 同步数据-接收主线程消息 // 同步数据-接收主线程消息
function stateChange(store) { function stateChange(store) {
const storeName = store.$id const storeName = store.$id
ipcRenderer.on('pinia-state-set', (e, sName, jsonStr) => { ipcRenderer?.on('pinia-state-set', (e, sName, jsonStr) => {
if (sName == storeName) { // 更新对应数据 if (sName == storeName) { // 更新对应数据
console.log('state-set', jsonStr, sName) // console.log('state-set', jsonStr, sName)
const curJson = JSON.stringify(store.$state) // 当前数据 const curJson = JSON.stringify(store.$state) // 当前数据
const isUp = curJson != jsonStr // 不同的时候才写入,不然会导致触发数据变化监听,导致死循环 const isUp = curJson != jsonStr // 不同的时候才写入,不然会导致触发数据变化监听,导致死循环
if (!isUp) return if (!isUp) return

View File

@ -7,6 +7,8 @@ export const useToolState = defineStore('tool', {
state: () => ({ state: () => ({
model: 'select', // 悬浮球-当前模式 model: 'select', // 悬浮球-当前模式
showBoardAll: false, // 全屏画板-是否显示 showBoardAll: false, // 全屏画板-是否显示
isPdfWin: false, // pdf窗口是否打开
isToolWin: false, // 工具窗口是否打开
}), }),
actions: { actions: {
} }

View File

@ -0,0 +1,133 @@
// 所有事件
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)
})
}

View File

@ -1,108 +1,187 @@
// 所有事件 // 所有事件
export function handleevent(canvas, imgarr, type = 'defalut') { export function handleevent(canvas, imgarr, type = 'defalut') {
// 鼠标按下 // // 鼠标按下
canvas.on('mouse:down', function (e) {}) // canvas.on('mouse:down', function (e) {})
// // 监听鼠标移动事件 // // // 监听鼠标移动事件
// canvas.on('mouse:move', (options) => { // // canvas.on('mouse:move', (options) => {
// console.log('Mouse move event:', options); // // console.log('Mouse move event:', options);
// }); // // });
// 监听鼠标释放事件 // // 监听鼠标释放事件
canvas.on('mouse:up', (options) => { // canvas.on('mouse:up', (options) => {
//判断是点击的哪一个 // //判断是点击的哪一个
if (type == 'defalut') { // if (type == 'defalut') {
if (imgarr.value[0].index == 0) { // if (imgarr.value[0].index == 0) {
imgarr.value[0].JSONdata = canvas.toJSON() // imgarr.value[0].JSONdata = canvas.toJSON()
} // }
if (imgarr.value[1]?.index == 0) { // if (imgarr.value[1]?.index == 0) {
imgarr.value[1].JSONdata = canvas.toJSON() // imgarr.value[1].JSONdata = canvas.toJSON()
} // }
} else { // } else {
if (imgarr.value[0].index == 1) { // if (imgarr.value[0].index == 1) {
imgarr.value[0].JSONdata = canvas.toJSON() // imgarr.value[0].JSONdata = canvas.toJSON()
} // }
if (imgarr.value[1]?.index == 1) { // if (imgarr.value[1]?.index == 1) {
imgarr.value[1].JSONdata = canvas.toJSON() // imgarr.value[1].JSONdata = canvas.toJSON()
} // }
} // }
console.log(imgarr.value) // console.log(imgarr.value)
}) // })
} }
// 保存数据 // 保存数据
export function savecanvsStore(imgarr, canvsStore) { export function savecanvsStore(imgarr, canvsStore) {
canvsStore.pageArr = mergeAndReplace(canvsStore.pageArr, imgarr.value) canvsStore.pageArr = mergeAndReplace(canvsStore.pageArr, imgarr.value)
} }
// 重显数据 // 重显数据
export function displayData(canvas, canvsStore, canvasobj, fabric, img) { export function displayData(FabricVue, canvsStore, canvasobj, fabric, img) {
// 初始化 // 初始化
if (!canvsStore.pageArr.length) { const canvas = FabricVue.value.canvas
fabric.Image.fromURL(img.src, (img) => { if (!canvas) {
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 return
} }
canvsStore.pageArr.forEach((item) => { if (!canvsStore.pageArr.length) {
//初始化 fabric.Image.fromURL(
if (item.page == canvasobj.page) { img.src,
// canvas.value.clear() // 清除 Canvas (image) => {
// console.log(item.JSONdata, '找到一样的数据') image.set({
canvas.value.loadFromJSON(item.JSONdata, () => { left: 0,
// 在所有对象加载完成后重新渲染画布 top: 0,
canvas.value.renderAll.bind(canvas.value) scaleX: canvas.width / img.width,
canvas.value.renderAll() scaleY: canvas.height / img.height,
// requestAnimationFrame(() => { erasable: false // 不允许擦拭
// // 渲染所有对象 })
// FabricVue.setBackgroundImage(image, FabricVue.renderAll.bind(FabricVue))
// setBackgroundImage(image,FabricVue)
// canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas));
// }) canvas.setBackgroundImage(image, () => {
canvas.renderAll.bind(canvas)
FabricVue.value.render()
})
},
{crossOrigin: 'anonymous'}
)
return
}
const isfind=findObjectByPage(canvsStore.pageArr,canvasobj.page)
if(isfind){
const canvdata={
objects:JSON.parse(isfind.JSONdata),
version: "5.3.0"
}
requestAnimationFrame(function () {
canvas.loadFromJSON(canvdata, () => {
// 在所有对象加载完成后重新渲染画布
// Utils.handleCanvasJSONLoaded(canvas)
canvas.requestRenderAll() // 批量重绘
})
fabric.Image.fromURL(
img.src,
(image) => {
image.set({
left: 0,
top: 0,
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height,
selectable: false,
evented: false,
erasable: false // 不允许擦拭
})
// canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas));
canvas.setBackgroundImage(image, () => {
canvas.renderAll.bind(canvas)
FabricVue.value.render()
})
},
{crossOrigin: 'anonymous'}
)
// 渲染所有对象
canvas.requestRenderAll() // 批量重绘
}) })
}else{ }else{
// 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘 // 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
// // 清除 Canvas // // 清除 Canvas
canvas.value.clear() // canvas.clear()
requestAnimationFrame(function () { requestAnimationFrame(function () {
fabric.Image.fromURL(img.src, (img) => { fabric.Image.fromURL(
img.set({ img.src,
(image) => {
image.set({
left: 0, left: 0,
top: 0, top: 0,
scaleX: canvas.value.width / img.width, scaleX: canvas.width / img.width,
scaleY: canvas.value.height / img.height scaleY: canvas.height / img.height,
selectable: false,
evented: false,
erasable: false // 不允许擦拭
}) })
canvas.value.setBackgroundImage(img, canvas.value.renderAll.bind(canvas.value)) // canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas));
canvas.setBackgroundImage(image, () => {
canvas.renderAll.bind(canvas)
FabricVue.value.render()
}) })
},
{crossOrigin: 'anonymous'}
)
// 渲染所有对象 // 渲染所有对象
canvas.value.renderAll.bind(canvas.value) canvas.requestRenderAll() // 批量重绘
canvas.value.renderAll()
}) })
} }
})
// canvsStore.pageArr.forEach((item) => {
// //初始化
// if (item.page == canvasobj.page) {
// // item.JSONdata
// canvas.loadFromJSON(JSON.parse(item.JSONdata), () => {
// // 在所有对象加载完成后重新渲染画布
// // Utils.handleCanvasJSONLoaded(canvas)
// canvas.requestRenderAll() // 批量重绘
// })
// } else {
// // 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
// // // 清除 Canvas
// // canvas.clear()
// requestAnimationFrame(function () {
// fabric.Image.fromURL(
// img.src,
// (image) => {
// image.set({
// left: 0,
// top: 0,
// scaleX: canvas.width / img.width,
// scaleY: canvas.height / img.height,
// selectable: false,
// evented: false,
// erasable: false // 不允许擦拭
// })
// // canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas));
// canvas.setBackgroundImage(image, () => {
// canvas.renderAll.bind(canvas)
// FabricVue.value.render()
// })
// },
// {crossOrigin: 'anonymous'}
// )
// // 渲染所有对象
// canvas.requestRenderAll() // 批量重绘
// })
// }
// })
}
// 找到page一样的
const findObjectByPage=(array, page)=> {
const foundObject = array.find(obj => obj.page === page);
return foundObject || false;
} }
//page 一样替换 //page 一样替换
const mergeAndReplace = (arr1, arr2) => { 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 属性存储 // 创建一个映射,将 arr2 中的对象按 page 属性存储
let map = new Map(arr2.map((item) => [item.page, item])) let map = new Map(arr2.map((item) => [item.page, item]))

View File

@ -6,14 +6,17 @@
// Remote.app.getAppPath() E:\njys-work\AIx_Smarttalk\dist\win-unpacked\resources\app.asar // Remote.app.getAppPath() E:\njys-work\AIx_Smarttalk\dist\win-unpacked\resources\app.asar
// path.join(__dirname) 根目录 E:\njys-work\AIx_Smarttalk\dist\win-unpacked\resources\app.asar\out\renderer // path.join(__dirname) 根目录 E:\njys-work\AIx_Smarttalk\dist\win-unpacked\resources\app.asar\out\renderer
const path = require('path') const isNode = typeof require !== 'undefined' // 是否支持node函数
const Remote = require('@electron/remote') const path = isNode?require('path'):{}
const { ipcRenderer } = require('electron') const Remote = isNode?require('@electron/remote'):{}
const { ipcRenderer } = isNode?require('electron'):window.electron || {}
// 常用变量 // 常用变量
const BaseUrl = process.env['ELECTRON_RENDERER_URL']+'/#' const BaseUrl = isNode?process.env['ELECTRON_RENDERER_URL']+'/#':''
const isDev = process.env.NODE_ENV !== 'production' const isDev = isNode?process.env.NODE_ENV !== 'production':''
// 暴露Remote中的属性
export const ipcMain = Remote?.ipcMain || {}
/** /**
* 获取静态资源开发和生产环境 * 获取静态资源开发和生产环境
* @param {*} url * @param {*} url
@ -39,15 +42,32 @@ export const getStaticUrl = (url = '', type = 'app', exitPath = '', isFile = fal
* url:路由地址,width:窗口宽度,height:窗口高度,option:自定义选项 * url:路由地址,width:窗口宽度,height:窗口高度,option:自定义选项
* @returns * @returns
*/ */
export function ipcMsgSend(key, data) { export function ipcMsgSend(key, data, type) {
if (type == 'old') {
return new Promise((resolve) => { return new Promise((resolve) => {
// 返回结果-监听 // 返回结果-监听
ipcRenderer.once(`${key}-reply`, (e, res) => { ipcRenderer?.once(`${key}-reply`, (e, res) => {
resolve(res) resolve(res)
}) })
// 发送消息 // 发送消息
ipcRenderer.send(key, data) ipcRenderer?.send(key, data)
}) })
} else if (type == 'send') { // 单独发送
ipcRenderer?.send(key, data)
} else { // 新版本-默认
return ipcRenderer?.invoke(key, data)
}
}
/**
* @description 封装ipcRenderer事件监听
* @param {*} fn on once handle invoke
* @param {*} key
* @param {Function} cb 回调函数
* @returns
*/
export function ipcHandle(fn,key, cb) {
return ipcRenderer[fn](key, cb)
// return ipcRenderer?.[fn]?.(key, cb)
} }
/** /**
* 创建-窗口 调用该方法 * 创建-窗口 调用该方法
@ -89,11 +109,13 @@ export const createWindow = async (type, data) => {
resizable: true, // 禁止窗口大小缩放 resizable: true, // 禁止窗口大小缩放
alwaysOnTop: false, // 窗口是否总是显示在其他窗口之前 alwaysOnTop: false, // 窗口是否总是显示在其他窗口之前
} }
data.isConsole = true // 是否开启控制台
data.option = {...defOption, ...option} data.option = {...defOption, ...option}
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() // 打开调试工具
eventHandles(type, win) // 事件监听处理 eventHandles(type, win) // 事件监听处理
break break
} }
@ -145,7 +167,7 @@ export function toolWindow({url, isConsole, option={}}) {
// 内部监听器-是否打印 // 内部监听器-是否打印
if (!!isConsole) { if (!!isConsole) {
win.webContents.on('console-message', (e,leve,m,lin,s) => { win.webContents.on('console-message', (e,leve,m,lin,s) => {
console.log(m) console.log(`[${win.type}]`,m)
}) })
} }
}) })
@ -173,13 +195,17 @@ const eventHandles = (type, win) => {
case 'tool-sphere': { // 创建-悬浮球 case 'tool-sphere': { // 创建-悬浮球
// 监听设置穿透 // 监听设置穿透
const setIgnore = (_, ignore) => {win.setIgnoreMouseEvents(ignore, {forward: true})} const setIgnore = (_, ignore) => {win.setIgnoreMouseEvents(ignore, {forward: true})}
Remote.ipcMain.on('tool-sphere:set:ignore', setIgnore) Remote.ipcMain.handle('tool-sphere:set:ignore', setIgnore) // 异步
// 关闭窗口 // 关闭窗口
Remote.ipcMain.once('tool-sphere:close', () => { win&&win.destroy() }) Remote.ipcMain.once('tool-sphere:close', () => { win&&win.destroy() })
// 放大监听-测试 // 放大监听-测试
Remote.ipcMain.once('maximize-window', () => {win&&win.destroy()}) Remote.ipcMain.once('maximize-window', () => {win&&win.destroy()})
const on = { const on = {
onClosed: () => {Remote.ipcMain.off('tool-sphere:set:ignore', setIgnore)} onClosed: () => {
Remote.ipcMain.removeHandler('tool-sphere:set:ignore', setIgnore)
Remote.ipcMain.removeHandler('tool-sphere:reset')
// Remote.ipcMain.removeAllListeners() // 移除所有监听事件
}
} }
publicMethods(on) // 加载公共方法 publicMethods(on) // 加载公共方法
break} break}

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="pdfbox" ref="pdfbox"> <div class="pdfbox" ref="pdfbox">
<pdfCanvas :pdfObj="pdfObj" ref="pdfCanvaslist" @update:numPagesTotal="handleUpdate" /> <pdfCanvas v-if="isOnLoadShow" :pdfObj="pdfObj" ref="pdfCanvaslist" @update:numPagesTotal="handleUpdate" />
<div class="pdf-btn"> <div class="pdf-btn">
<el-button <el-button
style="border-top-left-radius: 8px" style="border-top-left-radius: 8px"
@ -32,20 +32,30 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, reactive } from 'vue' import { ref, onMounted, watch, reactive,watchEffect ,onBeforeUnmount} from 'vue'
import { useRoute } from 'vue-router';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf' import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf'
import pdfCanvas from '@/components/pdf/index.vue' import pdfCanvas from '@/components/pdf/index.vue'
import { getStaticUrl } from '@/utils/tool' import { getStaticUrl } from '@/utils/tool'
const { ipcRenderer } = require('electron') 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') pdfjsLib.GlobalWorkerOptions.workerSrc = getStaticUrl('/lib/build/pdf.worker.mjs')
const toolState = useToolState();
const route = useRoute();
const isOnLoadShow = ref(false) //
// //
const pdfObj = reactive({ const pdfObj = reactive({
numberOfPdf: 2, // numberOfPdf: 2, //
pdfUrl: getStaticUrl('aaa.pdf', 'user', 'selfFile', true), pdfUrl: getStaticUrl('aaa.pdf', 'user', 'selfFile', true),
allPageData:[],
bookId:null,
numPages: 1 // numPages: 1 //
}) })
const textbookId=ref(null) //id
// //
const numPagesTotal = ref(0) const numPagesTotal = ref(0)
const pdfCanvaslist = ref(null) const pdfCanvaslist = ref(null)
@ -60,17 +70,22 @@ const navtopage = (type) => {
pdfObj.numPages += num pdfObj.numPages += num
} }
if (pdfObj.numPages > numPagesTotal.value) return if (pdfObj.numPages > numPagesTotal.value) return
pdfCanvaslist.value.initPdf() pdfCanvaslist.value.initPdf('rest')
} }
// //
const minimize = () => { const minimize = async () => {
ipcRenderer.send('open-PDF:minimize') 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
if (numPagesTotal.value == 1) { if (numPagesTotal.value == 1) {
pdfObj.numberOfPdf = 1 pdfObj.numberOfPdf = 1
pdfCanvaslist.value.initPdf('restone') pdfCanvaslist.value.initPdf('rest')
} }
} }
// //
@ -87,10 +102,35 @@ const switchPageMode = () => {
} else { } else {
// //
pdfObj.numberOfPdf = 1 pdfObj.numberOfPdf = 1
pdfCanvaslist.value.initPdf('restone') pdfCanvaslist.value.initPdf('rest')
} }
} }
onMounted(async () => {}) onMounted(async () => {
toolState.isPdfWin=true
console.log(toolState.showBoardAll,"c初始")
textbookId.value = route.query.textbookId
pdfObj.bookId=textbookId.value
getBookMarkById(textbookId.value).then(res=>{
pdfObj.allPageData=getUniqueArrayByLastOccurrence(res.data)
isOnLoadShow.value=true
})
})
//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;
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -19,7 +19,7 @@
</template> </template>
<script setup> <script setup>
import {ref,defineProps,defineEmits} from "vue"; import {ref} from "vue";
const activeIndex = ref('0') const activeIndex = ref('0')
const props = defineProps({ const props = defineProps({
classList:{ classList:{

View File

@ -72,6 +72,7 @@ import uploadDialog from '@/components/upload-dialog/index.vue'
import { Refresh } from '@element-plus/icons-vue' import { Refresh } from '@element-plus/icons-vue'
import uploaderState from '@/store/modules/uploader' import uploaderState from '@/store/modules/uploader'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { useToolState } from '@/store/modules/tool'
import MoveFile from '@/components/move-file/index.vue' import MoveFile from '@/components/move-file/index.vue'
import FileListItem from '@/views/prepare/container/file-list-item.vue' import FileListItem from '@/views/prepare/container/file-list-item.vue'
import { getSmarttalkPage, moveSmarttalk } from '@/api/file' import { getSmarttalkPage, moveSmarttalk } from '@/api/file'
@ -490,18 +491,23 @@ export default {
// PDF- // PDF-
async navtoPdf() { async navtoPdf() {
let path = await this.getBookPathFromServer() const toolStore = useToolState()
console.log(path) if (toolStore.isPdfWin) return this.$message.error('您当前已打开课本,请勿重复操作')
createWindow('open-PDF', { url: '/classBegins/index' }) // let path = await this.getBookPathFromServer()
// console.log(path)
// console.log(this.uploadData.textbookId)
createWindow('open-PDF', { url: '/classBegins/index?textbookId='+this.uploadData.textbookId })
}, },
// - // -
openLesson() { openLesson() {
const toolStore = useToolState()
if (toolStore.isToolWin) return this.$message.error('您当前已开始上课,请勿重复操作')
createWindow('tool-sphere', { url: '/tool/sphere' }) createWindow('tool-sphere', { url: '/tool/sphere' })
} }
} }
} }
</script> </script>
<style> <style lang="scss">
.prepare-popper { .prepare-popper {
width: 80px !important; width: 80px !important;
min-width: 80px !important; min-width: 80px !important;

View File

@ -6,8 +6,10 @@
</template> </template>
<script setup> <script setup>
// //
import { ref, onMounted, watch } from 'vue' import { ref, onMounted, watch,defineExpose } from 'vue'
import {FabricVue, TYPES} from '@/plugins/fabric' import {FabricVue, TYPES} from '@/plugins/fabric'
import { useToolState } from '@/store/modules/tool'
const toolStore = useToolState()
const canvasRef = ref(null) // const canvasRef = ref(null) //
const props = defineProps({ const props = defineProps({
modelValue: String modelValue: String
@ -23,10 +25,13 @@ onMounted(async() => {
await FabricVue.initCanvas(canvasRef.value, option) await FabricVue.initCanvas(canvasRef.value, option)
} }
}) })
const handleMode = (newVal, oldVal) => {
// if(toolStore.isPdfWin){
watch(() => props.modelValue, (newVal, oldVal) => { if(newVal=='clear'){
// console.log(newVal, oldVal) emit('update:modelValue', oldVal)
}
return
}
switch(newVal) { switch(newVal) {
case 'select': // case 'select': //
FabricVue.handleMode(TYPES.ActionMode.OTHER) FabricVue.handleMode(TYPES.ActionMode.OTHER)
@ -35,14 +40,26 @@ watch(() => props.modelValue, (newVal, oldVal) => {
FabricVue.handleMode(TYPES.ActionMode.DRAW) FabricVue.handleMode(TYPES.ActionMode.DRAW)
FabricVue.canvas.freeDrawingCursor = 'default' FabricVue.canvas.freeDrawingCursor = 'default'
break break
case 'eraser': // case 'erase': //
FabricVue.handleMode(TYPES.ActionMode.ERASE) FabricVue.handleMode(TYPES.ActionMode.ERASE)
break break
case 'clear': // case 'clear': //
if(oldVal){
FabricVue.history?.clean() FabricVue.history?.clean()
emit('update:modelValue', oldVal) emit('update:modelValue', oldVal)
}
break break
} }
}
//
watch(() => props.modelValue, (newVal, oldVal) => {
// console.log(newVal, oldVal)
handleMode(newVal, oldVal)
})
//
defineExpose({
handleMode
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -0,0 +1,137 @@
<template>
<div class="warp" ref="btnRef">
<slot name="start"></slot>
<!-- 工具按钮 -->
<el-space direction="vertical">
<template v-for="(item,index) in list">
<slot :name="item.prop" :item="item" :index="index">
<div class="c-btn flex flex-col items-center gap-2 p-2" @click.stop="clickHandel(item,$event)">
<i class="iconfont" :class="item.icon" :style="item.style" />
<span>{{item.label||item.text||item.name}}</span>
</div>
</slot>
</template>
<slot name="append"></slot>
</el-space>
<slot name="end"></slot>
<!-- 内容部分 -->
<transition name="el-fade-in">
<div class="c-popover" :style="`--top: ${topPos}px;--height:${hPost}px;`" v-show="isVisible">
<div class="content">{{activeObj}}</div>
</div>
</transition>
</div>
</template>
<script setup>
import { computed, defineProps, ref, reactive, watchEffect, onMounted } from 'vue'
// -
const colors = ['#00f389', '#ff7f00', '#ffff00', '#409EFF', '#00baff', '#13b189', '#F56C6C']
const emit = defineEmits(['update:modelValue','change'])
const props = defineProps({
modelValue: { //
type: Boolean,
default: true
},
data: { //
type: Array,
default: () => [
{ label: '资源', prop: 'resource', icon: 'icon-hudong' },
{ label: '互动', prop: 'interact', icon: 'icon-hudong' },
{ label: '窗口', prop: 'win', icon: 'icon-hudong' },
{ label: '下课', prop: 'over', icon: 'icon-hudong' },
]
}
})
const isVisible = ref(false) //
const activeObj = ref(null) //
const btnRef = ref(null) // -ref
const topPos = ref(30) // -
const hPost = ref(0) // -
let posBtnAll = {} //
// === ===
const list = computed(() => props.data.map((o,i) => {
o.style = getStyle(o.style, i)
return o
}))
onMounted(() => {
posBtnAll = btnRef.value.getBoundingClientRect()
hPost.value = posBtnAll.height
})
// === ===
//
const getIndex = i => i % colors.length
//
const getStyle = (style,index) => {
const color = colors[getIndex(index)]
if (!style) return {color}
else {
if (typeof style === 'object') return {...style, color}
return `${style}${style.endsWith(';')?'':';'}color:${color};`
}
}
//
const clickHandel = (o, e) => {
const node = e.target.parentNode.getBoundingClientRect()
const isColse = activeObj.value && activeObj.value.prop === o.prop && isVisible.value
isVisible.value = !isColse //
activeObj.value = o
const nodeH = parseInt(node.height / 2) //
topPos.value = parseInt(node.top) - posBtnAll.top + nodeH
emit('change', o)
}
</script>
<style lang="scss" scoped>
.warp{
border: 1px solid;
position: fixed;
top: 50%;
transform: translateY(-50%);
right: 10px;
min-height: 40vh;
min-width: 4em;
border-radius: 4em;
background-color: #121212;
.el-space{margin: 20px 0;}
.c-btn{
color: #d9dce3;
cursor: pointer;
.iconfont{
font-size: 20px;
color: attr(color);
}
span{font-size: 12px;}
&:hover{
border-radius: 4px;
background: #454545fa;
}
}
}
.c-popover{
--top: 30px;
--height: 40vh;
position: fixed;
inset: 50% 75px auto auto;
transform: translateY(-50%);
background-color: #121212;
padding: 10px;
border-radius: 4px;
min-height: var(--height);
width: 30em;
text-align: left;
&:before{
content: "";
width: 10px;
height: 10px;
background-color: #121212;
position: absolute;
right: -5px;
top: var(--top);
transform: rotate(45deg);
}
}
.content{
color: red;
}
</style>

View File

@ -0,0 +1,45 @@
/**
* 处理元素是否穿透-窗口
*/
import { ipcMsgSend } from '@/utils/tool'
class Ignore {
el // 绑定元素
binding // 绑定对象(参数)
value // 绑定值
isAuto // 是否自动控制
constructor(el, binding) {
this.el = el
this.binding = binding
this.value = binding.value
this.isAuto = !(this.value && this.value instanceof Boolean)
}
// 设置是否穿透
send(bool) {
ipcMsgSend('tool-sphere:set:ignore', bool)
}
// 监听元素移入移出,自动设置
mounted() {
this.el.addEventListener('mouseenter', e => { // 进入
this.send(false) // 元素不穿透,鼠标有效
this.changeHandle(e)
})
this.el.addEventListener('mouseleave', e => { // 离开
this.send(true) // 元素穿透,鼠标无效
this.changeHandle(e)
})
}
changeHandle(e) {
const customEvent = new CustomEvent('ignore-mounted', {detail:{ignore: this, e}})
this.el.dispatchEvent(customEvent)
}
}
export default {
mounted(el, binding) {
const ignore = new Ignore(el, binding)
if (ignore.isAuto) { // 自动控制
ignore.mounted()
} else { // 手动设置
ignore.send(ignore.value)
}
}
}

View File

@ -1,9 +1,11 @@
<template> <template>
<div class="warp-all"> <div class="warp-all">
<board-vue v-model="tabActive" v-show="isShow"></board-vue> <!-- 画板 -->
<board-vue v-model="tabActive" v-show="isShow" ref="boardVueRef"></board-vue>
<!-- 侧边工具栏 -->
<side-vue v-ignore></side-vue>
<!-- 底部工具栏 :style="dataPos.style"--> <!-- 底部工具栏 :style="dataPos.style"-->
<div class="tool-bottom-all" <div class="tool-bottom-all" @mouseenter="mouseChange(0)" @mouseleave="mouseChange(1)">
@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()">
<div class="c-logo" @click="logoHandle" title="拖动 | 折叠 | 展开"> <div class="c-logo" @click="logoHandle" title="拖动 | 折叠 | 展开">
@ -15,7 +17,7 @@
@change="tabChange"> @change="tabChange">
<template #default="{item}"> <template #default="{item}">
<div class="c-btn flex flex-col items-center gap-2 p-2"> <div class="c-btn flex flex-col items-center gap-2 p-2">
<i class="iconfont" :class="item.icon" :style="item.style"></i> <i class="iconfont" :class="item.icon" :style="item.style" />
<span>{{item.label}}</span> <span>{{item.label}}</span>
</div> </div>
</template> </template>
@ -29,55 +31,38 @@
// electron // electron
import { onMounted, ref, reactive, watchEffect } from 'vue' import { onMounted, ref, reactive, watchEffect } from 'vue'
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 vDrag from './directive/drag' // - import vDrag from './directive/drag' // -
import vIgnore from './directive/ignore' // -穿
import { useToolState } from '@/store/modules/tool' import { useToolState } from '@/store/modules/tool'
const { ipcRenderer } = require('electron') // app使 import { ipcMsgSend, ipcHandle, ipcMain } from '@/utils/tool' //
// const ipcRenderer = { send: () => {} } // 使
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 toolStore = useToolState() const toolStore = useToolState() //
const boardVueRef=ref(null) // ref
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' },
{ label: '板擦', value: 'eraser', icon: 'icon-xiangpica' }, { label: '板擦', value: 'erase', icon: 'icon-xiangpica' },
{ label: '清除', value: 'clear', icon: 'icon-xiangpica', style: 'color: #ccc' }, { label: '清除', value: 'clear', icon: 'icon-xiangpica', style: 'color: #ccc' },
// { label: '', value: 'interact', icon: 'icon-hudong' }, // { label: '', value: 'interact', icon: 'icon-hudong' },
// { label: '', value: 'focus', icon: 'icon-jujiao' }, // { label: '', value: 'focus', icon: 'icon-jujiao' },
// { label: '', value: 'more', icon: 'icon-xiazai9' }, // { label: '', value: 'more', icon: 'icon-xiazai9' },
] ]
onMounted(() => { //
// isShow.value = toolStore.showBoardAll // - onMounted(async() => {
// console.log('xxx: ', toolStore.model) resetStatus() // -
// setTimeout(() => {
// toolStore.windowState.test = ''
// }, 2000);
}) })
// const test = (e) => { console.log('test', e) }
// ==== === // ==== ===
const tabChange = (val) => { // tab-change const tabChange = (val) => { // tab-change
// console.log('xxxx:', val) const bool = !toolStore.isPdfWin && !toolStore.showBoardAll
toolStore.showBoardAll = true if(bool) toolStore.showBoardAll = true
switch (val) { // ipcMsgSend('tool-sphere:close')
case 'brush': //
break
case 'eraser': //
break
case 'interact':
break
case 'focus':
break
case 'more':
break
case 'close':
ipcRenderer.send('tool-sphere:close')
break
default:
break
}
toolStore.model = val // tab toolStore.model = val // tab
} }
const logoHandle = (e,t) => { // logo - | const logoHandle = (e,t) => { // logo - |
@ -85,14 +70,30 @@ const logoHandle = (e,t) => { // logo 点击-事件 折叠|展开
isFold.value = !isFold.value isFold.value = !isFold.value
} }
} }
const mouseChange = (bool) => { // 穿 const mouseChange = (bool) => { // 穿
let resBool = false let resBool = false
if (tabActive.value == 'select') resBool = !!bool if (tabActive.value == 'select') resBool = !!bool
ipcRenderer.send('tool-sphere:set:ignore', resBool) if (!isShow.value) resBool = !!bool
setIgnore(resBool)
} }
const setIgnore = (bool) => { // 穿
ipcMsgSend('tool-sphere:set:ignore', bool)
}
const resetStatus = () => { //
if (toolStore.isToolWin) return // -
ipcMain?.handle?.('tool-sphere:reset', () => {
setTimeout(() => {
boardVueRef.value.handleMode(tabActive.value)
mouseChange(1)
}, 500)
})
toolStore.isToolWin = true //
}
watchEffect(() => { // watchEffect(() => { //
isShow.value = toolStore.showBoardAll // - // , : -
const show = !toolStore.isPdfWin && toolStore.showBoardAll
if (show != isShow.value) isShow.value = show
}) })
</script> </script>