diff --git a/package.json b/package.json index 899244b..578c277 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron-app", - "version": "1.0.0", + "version": "1.0.1", "description": "An Electron application with Vue", "main": "./out/main/index.js", "author": "example.com", @@ -22,6 +22,7 @@ "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^3.0.0", "@element-plus/icons-vue": "^2.3.1", + "@electron/remote": "^2.1.2", "@vitejs/plugin-vue-jsx": "^4.0.0", "@vueuse/core": "^10.11.0", "crypto-js": "^4.2.0", @@ -44,7 +45,6 @@ }, "devDependencies": { "@electron-toolkit/eslint-config": "^1.0.2", - "@electron/remote": "^2.1.2", "@rushstack/eslint-patch": "^1.10.3", "@vitejs/plugin-vue": "^5.0.5", "@vue/eslint-config-prettier": "^9.0.0", diff --git a/src/main/index.js b/src/main/index.js index 13e692c..cd543a7 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,4 +1,4 @@ -import { app, shell, BrowserWindow, ipcMain, session } from 'electron' +import { app, shell, BrowserWindow, ipcMain, session, BrowserView } from 'electron' import { join } from 'path' import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.png?asset' @@ -31,6 +31,7 @@ function createLoginWindow() { nodeIntegration: true } }) + loginWindow.type = 'login' // 唯一标识 // handleUpdate(loginWindow,ipcMain) // const loginURL = is.dev ? `http://localhost:5173/#/login` : `file://${__dirname}/index.html/#/login` // loginWindow.loadURL(loginURL) @@ -69,12 +70,13 @@ function createMainWindow() { // webSecurity: false // 跨域关闭 } }) - + mainWindow.type = 'main' // 唯一标识 mainWindow.on('ready-to-show', () => { mainWindow.show() }) mainWindow.on('closed', () => { mainWindow = null + app.quit() // 主窗口关闭-结束所有进程 }) mainWindow.webContents.setWindowOpenHandler((details) => { shell.openExternal(details.url) @@ -113,6 +115,7 @@ async function createLinkWin(data) { contextIsolation: true } }) + linkWindow.type = 'link' // 唯一标识 let cookieDetails = { ...data.cookieData } await linkWindow.webContents.session.cookies.set(cookieDetails).then(()=>{ console.log('Cookie is successful'); diff --git a/src/renderer/src/components/pdf/index.vue b/src/renderer/src/components/pdf/index.vue index 3ac1cf6..276aa80 100644 --- a/src/renderer/src/components/pdf/index.vue +++ b/src/renderer/src/components/pdf/index.vue @@ -50,8 +50,10 @@ const renderPage = async (canvasobj) => { const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise // 渲染当前页 const page = await pdf.getPage(canvasobj.page) - const viewport = page.getViewport({ scale: 1 }) - + 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 @@ -67,34 +69,20 @@ const renderPage = async (canvasobj) => { img.onload = () => { // 在这里执行图像加载完成后的操作 // 根据传过来的pdf对象 判断改渲染哪一个fabric - var screenWidth = window.innerWidth/2-10; - var screenHeight = window.innerHeight; - // 计算图像的原始宽度和高度 - var imgWidth = img.width; - var imgHeight = img.height; - // 计算图像的缩放比例以适应屏幕 - var widthRatio = screenWidth / imgWidth; - var heightRatio = screenHeight / imgHeight; - //选择较小的缩放比例以确保图像完全适应屏幕 - var scaleRatio = Math.min(widthRatio, heightRatio); - // 计算缩放后的图像尺寸 - var targetWidth = imgWidth * scaleRatio; - var targetHeight = imgHeight * scaleRatio; - if (props.pdfObj.numberOfPdf == 2) { if (canvasobj.index == 0) { - fabriccanvas.value.setWidth(targetWidth) - fabriccanvas.value.setHeight(targetHeight) + fabriccanvas.value.setWidth(screenWidth) + fabriccanvas.value.setHeight(screenHeight) displayData(fabriccanvas, canvsStore, canvasobj, fabric, img) } else { - fabriccanvas1.value.setWidth(targetWidth) - fabriccanvas1.value.setHeight(targetHeight) + fabriccanvas1.value.setWidth(screenWidth) + fabriccanvas1.value.setHeight(screenHeight) displayData(fabriccanvas1, canvsStore, canvasobj, fabric, img) } } else { - fabriccanvas.value.setWidth(targetWidth) - fabriccanvas.value.setHeight(targetHeight) + fabriccanvas.value.setWidth(screenWidth) + fabriccanvas.value.setHeight(screenHeight) displayData(fabriccanvas, canvsStore, canvasobj, fabric, img) } // console.log(imgarr.value) @@ -121,7 +109,7 @@ const loadPdf = async (canvasobj) => { const initPdf = async (type = 'default') => { // 保存数据 - savecanvsStore(imgarr, canvsStore) + savecanvsStore(imgarr, canvsStore) // initcanvasdata(fabriccanvas) // initcanvasdata(fabriccanvas1) // 单页模式 @@ -173,8 +161,10 @@ const initPdfone = async () => { setTimeout(() => { fabriccanvas1.value = new fabric.Canvas('pdf-fabric1') fabriccanvas1.value.isDrawingMode = true - fabriccanvas1.value.freeDrawingBrush.color = 'red' + fabriccanvas1.value.freeDrawingBrush.color = '#A33AFE' + fabriccanvas1.value.freeDrawingCursor = 'default' fabriccanvas1.value.setWidth(595) + handleevent(fabriccanvas1.value, imgarr, 'two') }, 0) initPdf('addOnePage') } diff --git a/src/renderer/src/plugins/fabric/test.js b/src/renderer/src/plugins/fabric/test.js new file mode 100644 index 0000000..7f49554 --- /dev/null +++ b/src/renderer/src/plugins/fabric/test.js @@ -0,0 +1,223 @@ +/** + * @description 封装fabric js + */ +import { fabric } from 'fabric' + +// 当前使用到的常量|类型(枚举) +export class TYPES { + static ActionMode = { + DRAW: 'draw', // 画笔模式 + ERASE: 'erase', // 橡皮擦模式 + SELECT: 'select', // 选择模式 + Board: 'board' // 画板模式 + } + // 画笔类型 + static DrawType = { + FreeStyle: 'freeStyle', + Shape: 'shape' + } + // 画笔样式 + static DrawStyle = { + Basic: 'basic', + Rainbow: 'rainbow', + Shape: 'shape', + Material: 'material', + Pixels: 'pixels', + MultiColor: 'multiColor', + Text: 'text', + MultiLine: 'multiLine', + Reticulate: 'reticulate', + MultiPoint: 'multiPoint', + Wiggle: 'wiggle', + Thorn: 'thorn' + } + // 各种形状 + static DrawShape = { + Bubble: 'bubble', + Star: 'star', + Love: 'love', + Butterfly: 'butterfly', + Snow: 'snow', + Music: 'music', + Sun: 'sun', + Moon: 'moon', + Leaf: 'leaf', + Flower: 'flower' + } + // 材质类型 + static MATERIAL_TYPE = { + CRAYON: 'crayon', + CARBON: 'carbon', + CLOTH: 'cloth', + OIL: 'oil', + CRAYON_DARK: 'crayonDark' + } + // 多颜色类型 + static MultiColorType = { + COL: 'col', + ROW: 'row', + CIRCLE: 'circle' + } + // 画笔元素 + static FREESTYLE_ELEMENT_CUSTOM_TYPE = { + IMAGE: 'image', + I_TEXT: 'itext', + RAINBOW: 'rainbow', + SHAPE: 'shape', + PIXELS: 'pixels', + DRAW_TEXT: 'drawText', + MULTI_LINE: 'multiLine', + RETICULATE: 'reticulate', + MULTI_POINT: 'multiPoint', + WIGGLE: 'wiggle', + THORN: 'thorn' + } + // 形状元素 + static SHAPE_ELEMENT_CUSTOM_TYPE = { + SHAPE_LINE: 'shapeLine', + SHAPE_RECT: 'shapeRect', + SHAPE_CIRCLE: 'shapeCircle', + SHAPE_ELLIPSE: 'shapeEllipse', + SHAPE_TRIANGLE: 'shapeTriangle', + SHAPE_ARROW_LINE: 'shapeArrowLine', + SHAPE_ARROW_OUTLINE: 'shapeArrowOutline', + SHAPE_CLOUD: 'shapeCloud', + SHAPE_TOOLTIPS: 'shapeTooltips', + SHAPE_LIGHTNING: 'shapeLightning', + SHAPE_CLOSE: 'shapeClose', + SHAPE_CHECK: 'shapeCheck', + SHAPE_INFO: 'shapeInfo', + SHAPE_BACKSPACE: 'shapeBackspace', + SHAPE_BLOCK: 'shapeBlock', + SHAPE_SPEAKER: 'shapeSpeaker', + SHAPE_SEARCH: 'shapeSearch', + SHAPE_INFO_OUTLINE: 'shapeInfoOutline', + SHAPE_HEART: 'shapeHeart', + SHAPE_ALERT: 'shapeAlert' + } + // 元素类型 + static ELEMENT_CUSTOM_TYPE = { + ...this.FREESTYLE_ELEMENT_CUSTOM_TYPE, + ...this.SHAPE_ELEMENT_CUSTOM_TYPE + } +} +// 自由绘画 FreeStyle +// export class FreeStyle { +// } +export const FreeStyle = { + renderPencilBrush: (canvas) => { + const pencilBrush = new fabric.PencilBrush(canvas) + canvas.isDrawingMode = true + canvas.freeDrawingBrush = pencilBrush + canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth() + canvas.freeDrawingBrush.color = useDrawStore.drawColors[0] + canvas.freeDrawingBrush.shadow = new fabric.Shadow({ + blur: fabricUtils.getShadowWidth(), + offsetX: 0, + offsetY: 0, + color: useDrawStore.shadowColor + }) + } +} +// 封装fabric-canvas +export class fabricVue { + canvas = null // fabric-canvas 对象 + evnet = null // 事件对象 + history = null // 历史记录 + textElement // 文本节点 + hookFn = [] // 钩子 + defConfig // 默认配置-canvas + drawConfig // 默认配置-画笔 + boardConfig // 默认配置-画板 + publicConfig // 公共属性-配置 + + /** 构造函数 */ + constructor() { + // this.textElement = new TextElement() // 创建文本节点 + const initLanguage = ['en', 'en-US', 'en-us'].includes(navigator.language) ? 'en' : 'zh' + this.drawConfig = { // 默认配置 + drawWidth: 1, + drawColors: ['#000000'], + shadowWidth: 0, + shadowColor: '#000000', + drawTextValue: 'draw', + drawStyle: TYPES.DrawStyle.Basic, + drawShapeCount: 2, + materialType: TYPES.MATERIAL_TYPE.CRAYON, + drawShape: TYPES.DrawShape.Bubble, + eraserWidth: 20, + multiColorType: TYPES.MultiColorType.COL, + textFontFamily: 'Georgia', + openAutoDraw: false, + fontStyles: [], + } + this.boardConfig = { // 默认配置 + mode: TYPES.ActionMode.DRAW, + drawType: TYPES.DrawType.FreeStyle, + language: initLanguage, + canvasWidth: 1, + canvasHeight: 1, + backgroundColor: 'rgba(255, 255, 255, 1)', + backgroundOpacity: 1, + hasBackgroundImage: false, + backgroundImageOpacity: 1, + isObjectCaching: true, + openGuideLine: false, + } + this.defConfig = { // 默认配置-canvas + // 它用于指定选中对象时显示的选择框的颜色 + selectionColor: 'rgba(101, 204, 138, 0.3)', + // 用于控制在组合(group)对象时是否保留每个对象的堆叠顺序 + preserveObjectStacking: true, + // 它用于控制是否在高分辨率显示器(例如 Retina 显示器)上启用图像的像素级缩放 + enableRetinaScaling: true, + // 锁定背景,不受缩放影响 false + backgroundVpt: false, + // 默认全屏模式 + width: window.innerWidth, + height: window.innerHeight, + } + this.publicConfig = { // 公共属性配置 + Object: { // 默认配置-对象上 + // 设置对象边框的颜色(Rect、Circle、Ellipse、Path) + borderColor: '#65CC8A', + // 设置对象角落的颜色(Rect、RoundRect、Circle 和 Ellipse 类型的对象) + cornerColor: '#65CC8A', + // 设置角落的形状(Rect、RoundRect、Circle 和 Ellipse 类型的对象) + // circle rect round [圆形,直角(默认),圆角] + cornerStyle: 'circle', + // 设置边框虚线的样式(Rect、Circle、Ellipse、Path) + borderDashArray: [3, 3], + // 矩形(Rect)、圆角矩形(RoundRect)、圆形(Circle)和椭圆形(Ellipse)对象的角落是否透明 + transparentCorners: false + }, + Line: { + // 设置边缘连接方式为 miter bevel round|尖角(默认) 斜角切割线 圆形连接 + strokeLineJoin: 'round', + // 设置结束连接方式为 butt round square|直角(默认) 圆形 正方形 + strokeLineCap: 'round' + } + } + } + /** + * 初始化canvas + * @param {*} canvasEl canvas元素 + * @returns boolean 是否初始化成功 + */ + initCanvas(canvasEl, option = {}) { + return new Promise(async(resolve) => { + if (!canvasEl) resolve(false) + this.canvas = new fabric.Canvas(canvasEl, { + ...this.defConfig, ...option + }) + // this.evnet = new CanvasEvent() // 创建相关事件 + // this.history = new History() // 创建历史记录 + // await this.initCanvasStorage() + resolve(true) + }) + } + +} +export const FabricVue = new fabricVue() +export default FabricVue + diff --git a/src/renderer/src/utils/pdfAndFabric.js b/src/renderer/src/utils/pdfAndFabric.js index f4b0ca9..3807034 100644 --- a/src/renderer/src/utils/pdfAndFabric.js +++ b/src/renderer/src/utils/pdfAndFabric.js @@ -14,23 +14,24 @@ export function handleevent(canvas, imgarr, type = 'defalut') { if (imgarr.value[0].index == 0) { imgarr.value[0].JSONdata = canvas.toJSON() } - if (imgarr.value[1].index == 0) { + 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) { + 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) - // console.log(canvsStore.pageArr,22222222222222222222+'存入') } // 重显数据 export function displayData(canvas, canvsStore, canvasobj, fabric, img) { @@ -50,19 +51,21 @@ export function displayData(canvas, canvsStore, canvasobj, fabric, img) { canvsStore.pageArr.forEach((item) => { //初始化 if (item.page == canvasobj.page) { - canvas.value.clear() // 清除 Canvas + // canvas.value.clear() // 清除 Canvas // console.log(item.JSONdata, '找到一样的数据') canvas.value.loadFromJSON(item.JSONdata, () => { // 在所有对象加载完成后重新渲染画布 - requestAnimationFrame(() => { - // 渲染所有对象 - canvas.value.renderAll.bind(canvas.value) - canvas.value.renderAll() - }) + canvas.value.renderAll.bind(canvas.value) + canvas.value.renderAll() + // requestAnimationFrame(() => { + // // 渲染所有对象 + + // }) }) } else { // 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘 - canvas.value.clear() // 清除 Canvas + // // 清除 Canvas + canvas.value.clear() requestAnimationFrame(function () { fabric.Image.fromURL(img.src, (img) => { img.set({ @@ -75,6 +78,7 @@ export function displayData(canvas, canvsStore, canvasobj, fabric, img) { }) // 渲染所有对象 canvas.value.renderAll.bind(canvas.value) + canvas.value.renderAll() }) } }) diff --git a/src/renderer/src/utils/tool.js b/src/renderer/src/utils/tool.js index 2ca72e7..7a56f5e 100644 --- a/src/renderer/src/utils/tool.js +++ b/src/renderer/src/utils/tool.js @@ -5,8 +5,9 @@ // import { ipcRenderer } from 'electron' // 渲染器里面可以使用ipcRenderer -// const path = require('path') +const path = require('path') const Remote = require('@electron/remote') +const { ipcRenderer } = require('electron') // 常用变量 const BaseUrl = process.env['ELECTRON_RENDERER_URL']+'/#' @@ -16,11 +17,11 @@ const isDev = process.env.NODE_ENV !== 'production' /** * @description 消息发送-nodejs 消息发送 * @form src/main/tool.js 来源 - * @param {*} key 消息key + * @param {*} key 消息key * tool-sphere:create 创建-悬浮球 - * @param {*} data 参数 + * @param {*} data 参数 * url:路由地址,width:窗口宽度,height:窗口高度,option:自定义选项 - * @returns + * @returns */ export function ipcMsgSend(key, data) { return new Promise((resolve) => { @@ -37,7 +38,7 @@ export function ipcMsgSend(key, data) { * @param {*} type 类型 * tool-sphere 创建-悬浮球 * @param {*} data 参数 - * @returns + * @returns */ export const createWindow = async (type, data) => { if (!type) return console.error('createWindow: type is null') @@ -54,6 +55,7 @@ export const createWindow = async (type, data) => { } data.option = {...defOption, ...option} const win = await toolWindow(data) + win.type = type // 唯一标识 win.show() win.setFullScreen(true) // 设置窗口为全屏 win.setIgnoreMouseEvents(true, {forward: true}) // 忽略鼠标事件但是事件继续传递给窗口 @@ -72,7 +74,7 @@ export const createWindow = async (type, data) => { } data.option = {...defOption, ...option} const win = await toolWindow(data) - + win.type = type // 唯一标识 win.show() win.setFullScreen(true) // 设置窗口为全屏 eventHandles(type, win) // 事件监听处理 @@ -97,7 +99,7 @@ export function toolWindow({url, isFile, isConsole, option={}}) { let height = option?.height || 600 const mainWin = Remote.getCurrentWindow() // 获取主窗口对象 const devUrl = `${BaseUrl}${url}` - const buildUrl = `file://${__dirname}/index.html${url}` + const buildUrl = path.join(__dirname, 'index.html') const urlAll = isDev ? devUrl : buildUrl return new Promise((resolve) => { const config = { @@ -113,7 +115,7 @@ export function toolWindow({url, isFile, isConsole, option={}}) { } // 创建-新窗口 let win = new Remote.BrowserWindow(config) - if (!!isFile) win.loadFile(urlAll) // 加载文件 + if (!isDev) win.loadFile(urlAll,{hash: url}) // 加载文件 else win.loadURL(urlAll) // 加载url win.once('ready-to-show', () => {resolve(win)}) // 主窗口关闭事件 @@ -134,11 +136,17 @@ export function toolWindow({url, isFile, isConsole, option={}}) { * @param {*} win 窗口对象 */ const eventHandles = (type, win) => { + // const winAll = Remote.BrowserWindow.getAllWindows() + // const mainWin = winAll.find(o => o.type == 'main') // 主窗口对象 // 公共方法 - const publicMethods = ({onClosed}) => { - // 监听关闭事件 - Remote.ipcMain.once('close-window', () => {win.destroy()}) - win.on('closed', () => {!!onClosed && onClosed();win = null}) + const publicMethods = ({onClosed}={}) => { + // 监听主窗口-关闭事件 + // Remote.ipcMain.on('close-window', () => {console.log('关闭窗口');win.destroy()}) + // mainWin.once('closed', () => {console.log('关闭窗口');win.destroy()}) + win.on('closed', () => { + if(onClosed) onClosed() + win = null + }) } switch(type) { case 'tool-sphere': { // 创建-悬浮球 @@ -148,10 +156,7 @@ const eventHandles = (type, win) => { // 关闭窗口 Remote.ipcMain.once('tool-sphere:close', () => { win.destroy() }) // 放大监听-测试 - Remote.ipcMain.once('maximize-window', () => { - win.destroy() - console.log('关闭窗口') - }) + Remote.ipcMain.once('maximize-window', () => {win.destroy()}) const on = { onClosed: () => {Remote.ipcMain.off('tool-sphere:set:ignore', setIgnore)} } @@ -160,6 +165,7 @@ const eventHandles = (type, win) => { case 'open-PDF': { // 最小化窗口 minimize() Remote.ipcMain.once('open-PDF:minimize', () => {win.destroy()}) + publicMethods() // 加载公共方法 break} default: break diff --git a/src/renderer/src/views/classBegins/index.vue b/src/renderer/src/views/classBegins/index.vue index 59a844f..73f62c6 100644 --- a/src/renderer/src/views/classBegins/index.vue +++ b/src/renderer/src/views/classBegins/index.vue @@ -36,7 +36,7 @@ import { ref, onMounted, watch, reactive } from 'vue' import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf' pdfjsLib.GlobalWorkerOptions.workerSrc = '/lib/build/pdf.worker.mjs' import pdfCanvas from '@/components/pdf/index.vue' -const { ipcRenderer } = require('electron') +const { ipcRenderer } = window.electron || {} // 传过去的参数 const pdfObj = reactive({ numberOfPdf: 2, //显示几页 @@ -125,4 +125,4 @@ onMounted(async () => {}) } } } - \ No newline at end of file + diff --git a/src/renderer/src/views/tool/test.vue b/src/renderer/src/views/tool/test.vue index b3e81a9..153d10c 100644 --- a/src/renderer/src/views/tool/test.vue +++ b/src/renderer/src/views/tool/test.vue @@ -1,22 +1,26 @@