zdg #72
|
@ -29,6 +29,7 @@
|
||||||
"fabric": "5.3.0",
|
"fabric": "5.3.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
|
"jsondiffpatch": "0.6.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pinia-plugin-persistedstate": "^3.2.1",
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
"vue-cropper": "^1.0.3",
|
"vue-cropper": "^1.0.3",
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
/**
|
||||||
|
* @description: 点击事件
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as TYPES from '@/constants/draw'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
export let updateInkHook = null // ((ink: IInk[]) => void) | null
|
||||||
|
|
||||||
|
export class CanvasClickEvent {
|
||||||
|
isMouseDown = false
|
||||||
|
isSpaceKeyDown = false
|
||||||
|
startPoint // fabric.Point | undefined
|
||||||
|
currentElement = null // The current mouse move draws the element|当前鼠标移动会绘制元素
|
||||||
|
|
||||||
|
mouseDownTime = 0
|
||||||
|
autoDrawInk = [[], [], []] // google auto draw ink Array<Array<number>>
|
||||||
|
isDrawBasic = false // 是否默认绘制
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initClickEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
initClickEvent() {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
const useBoardStore = fabricStore.useBoardStore()
|
||||||
|
|
||||||
|
// 事件:按下鼠标
|
||||||
|
canvas?.on('mouse:down', (e) => {
|
||||||
|
this.isMouseDown = true
|
||||||
|
if (this.isSpaceKeyDown) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.startPoint = e.absolutePointer
|
||||||
|
let currentElement = null
|
||||||
|
|
||||||
|
if (useBoardStore.mode === TYPES.ActionMode.DRAW) {
|
||||||
|
if (useBoardStore.drawType === TYPES.DrawType.Shape) {
|
||||||
|
// switch (useShapeStore.shapeStyle) {
|
||||||
|
// case ShapeStyle.Rect:
|
||||||
|
// currentElement = new RectShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Circle:
|
||||||
|
// currentElement = new CircleShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Line:
|
||||||
|
// currentElement = new LineShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Ellipse:
|
||||||
|
// currentElement = new EllipseShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Triangle:
|
||||||
|
// currentElement = new TriangleShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.ArrowLine:
|
||||||
|
// currentElement = new ArrowLineShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.ArrowOutline:
|
||||||
|
// currentElement = new ArrowOutlineShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Cloud:
|
||||||
|
// currentElement = new CloudShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Tooltips:
|
||||||
|
// currentElement = new TooltipsShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Lightning:
|
||||||
|
// currentElement = new LightningShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Close:
|
||||||
|
// currentElement = new CloseShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Check:
|
||||||
|
// currentElement = new CheckShap(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Info:
|
||||||
|
// currentElement = new InfoShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Backspace:
|
||||||
|
// currentElement = new BackspaceShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Block:
|
||||||
|
// currentElement = new BlockShap(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Speaker:
|
||||||
|
// currentElement = new SpeakerShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Search:
|
||||||
|
// currentElement = new SearchShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.InfoOutline:
|
||||||
|
// currentElement = new InfoOutlineShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Heart:
|
||||||
|
// currentElement = new HeartShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Alert:
|
||||||
|
// currentElement = new AlertShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// default:
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
} else if (useBoardStore.drawType === TYPES.DrawType.FreeStyle) {
|
||||||
|
switch (useDrawStore.drawStyle) {
|
||||||
|
// case TYPES.DrawStyle.Shape:
|
||||||
|
// currentElement = new ShapeElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Pixels:
|
||||||
|
// currentElement = new PixelsElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Text:
|
||||||
|
// currentElement = new DrawTextElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.MultiLine:
|
||||||
|
// currentElement = new MultiLineElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Reticulate:
|
||||||
|
// currentElement = new ReticulateElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Rainbow:
|
||||||
|
// currentElement = new RainbowElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Thorn:
|
||||||
|
// currentElement = new ThornElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.MultiPoint:
|
||||||
|
// currentElement = new MultiPointElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Wiggle:
|
||||||
|
// currentElement = new WiggleElement()
|
||||||
|
// break
|
||||||
|
case TYPES.DrawStyle.Basic:
|
||||||
|
// if (useDrawStore.openAutoDraw) {
|
||||||
|
// autoDrawData.resetLoadedSVG()
|
||||||
|
// this.mouseDownTime = new Date().getTime()
|
||||||
|
// }
|
||||||
|
console.log('xxxx')
|
||||||
|
this.isDrawBasic = true
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.currentElement = currentElement
|
||||||
|
})
|
||||||
|
|
||||||
|
// 事件:移动鼠标
|
||||||
|
canvas?.on('mouse:move', (e) => {
|
||||||
|
if (this.isMouseDown) {
|
||||||
|
// Press space, drag the canvas, stop drawing.
|
||||||
|
if (this.isSpaceKeyDown) {
|
||||||
|
canvas.relativePan(new fabric.Point(e.e.movementX, e.e.movementY))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// two touch disabled drawing on mobile
|
||||||
|
if (FabricVue.evnet?.touchEvent?.isTwoTouch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.mouseDownTime &&
|
||||||
|
e.absolutePointer?.x &&
|
||||||
|
e.absolutePointer?.y
|
||||||
|
) {
|
||||||
|
this.autoDrawInk[0].push(e.absolutePointer?.x)
|
||||||
|
this.autoDrawInk[1].push(e.absolutePointer?.y)
|
||||||
|
this.autoDrawInk[2].push(new Date().getTime() - this.mouseDownTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
useBoardStore.mode === TYPES.ActionMode.DRAW && this.currentElement
|
||||||
|
) {
|
||||||
|
this.currentElement.addPosition(e.absolutePointer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 事件:松开鼠标
|
||||||
|
canvas?.on('mouse:up', (e) => {
|
||||||
|
this.isMouseDown = false
|
||||||
|
if (this.autoDrawInk?.[0]?.length > 3) {
|
||||||
|
autoDrawData.addInk([...this.autoDrawInk])
|
||||||
|
updateInkHook?.([...autoDrawData.inks])
|
||||||
|
this.autoDrawInk = [[], [], []]
|
||||||
|
this.mouseDownTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentElement) {
|
||||||
|
let isDestroy = false
|
||||||
|
if (this.startPoint && e.absolutePointer) {
|
||||||
|
const { x: startX, y: startY } = this.startPoint
|
||||||
|
const { x: endX, y: endY } = e.absolutePointer
|
||||||
|
if (startX === endX && startY === endY) {
|
||||||
|
this.currentElement.destroy()
|
||||||
|
isDestroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isDestroy) {
|
||||||
|
if (
|
||||||
|
this.currentElement instanceof LineShape ||
|
||||||
|
this.currentElement instanceof ArrowLineShape
|
||||||
|
) {
|
||||||
|
this.currentElement?.mouseUp()
|
||||||
|
}
|
||||||
|
FabricVue.history?.saveState()
|
||||||
|
}
|
||||||
|
this.currentElement = null
|
||||||
|
}
|
||||||
|
// zdg: 基础画笔 保存数据
|
||||||
|
if (this.isDrawBasic) FabricVue.history?.saveState()
|
||||||
|
window.test = FabricVue
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas?.on('mouse:dblclick', (e) => {
|
||||||
|
if (e?.absolutePointer) {
|
||||||
|
const { x, y } = e.absolutePointer
|
||||||
|
FabricVue.textElement?.loadText(x, y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setSpaceKeyDownState(isSpaceKeyDown) {
|
||||||
|
this.isSpaceKeyDown = isSpaceKeyDown
|
||||||
|
}
|
||||||
|
|
||||||
|
changeInkHookFn(fn) {
|
||||||
|
updateInkHook = fn
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { CanvasClickEvent } from './clickEvent'
|
||||||
|
// import { ObjectEvent } from './objectEvent'
|
||||||
|
// import { CanvasTouchEvent } from './touchEvent'
|
||||||
|
// import { CanvasZoomEvent } from './zoomEvent'
|
||||||
|
// import { WindowEvent } from './windowEvent'
|
||||||
|
|
||||||
|
export class CanvasEvent {
|
||||||
|
clickEvent // CanvasClickEvent
|
||||||
|
zoomEvent // CanvasZoomEvent
|
||||||
|
objectEvent // ObjectEvent
|
||||||
|
windowEvent // WindowEvent
|
||||||
|
touchEvent // CanvasTouchEvent
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const clickEvent = new CanvasClickEvent()
|
||||||
|
this.clickEvent = clickEvent
|
||||||
|
|
||||||
|
// const zoomEvent = new CanvasZoomEvent()
|
||||||
|
// this.zoomEvent = zoomEvent
|
||||||
|
|
||||||
|
// const objectEvent = new ObjectEvent()
|
||||||
|
// this.objectEvent = objectEvent
|
||||||
|
|
||||||
|
// const windowEvent = new WindowEvent()
|
||||||
|
// this.windowEvent = windowEvent
|
||||||
|
|
||||||
|
// const touchEvent = new CanvasTouchEvent()
|
||||||
|
// this.touchEvent = touchEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEvent() {
|
||||||
|
this.windowEvent.removeWindowEvent()
|
||||||
|
this.touchEvent.removeTouchEvent()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* 历史记录
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as commUtils from '@/plugins/fabric/utils'
|
||||||
|
import * as bgUtils from '@/plugins/fabric/utils/bg'
|
||||||
|
import { diff, unpatch, patch } from 'jsondiffpatch'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
const initState = {}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operation History
|
||||||
|
*/
|
||||||
|
export class History {
|
||||||
|
diffs = []
|
||||||
|
canvasData = {}
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const canvasJson = commUtils.getCanvasJSON()
|
||||||
|
this.canvasData = cloneDeep(canvasJson ?? {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存相关数据json
|
||||||
|
saveState() {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
this.diffs = this.diffs.slice(0, this.index)
|
||||||
|
const canvasJson = commUtils.getCanvasJSON()
|
||||||
|
const delta = diff(canvasJson, this.canvasData)
|
||||||
|
this.diffs.push(delta)
|
||||||
|
|
||||||
|
// More than 50 operations, remove initial state
|
||||||
|
if (this.diffs.length > 50) {
|
||||||
|
this.diffs.shift()
|
||||||
|
} else {
|
||||||
|
this.index++
|
||||||
|
}
|
||||||
|
this.canvasData = cloneDeep(canvasJson ?? {})
|
||||||
|
useFileStore.updateBoardData(canvasJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 撤销
|
||||||
|
undo() {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (canvas && this.index > 0) {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
const delta = this.diffs[this.index - 1]
|
||||||
|
this.index--
|
||||||
|
const canvasJson = patch(this.canvasData, delta)
|
||||||
|
canvas.loadFromJSON(canvasJson, () => {
|
||||||
|
commUtils.handleCanvasJSONLoaded(canvas)
|
||||||
|
|
||||||
|
canvas.requestRenderAll()
|
||||||
|
useFileStore.updateBoardData(canvasJson)
|
||||||
|
this.canvasData = cloneDeep(canvasJson ?? {})
|
||||||
|
FabricVue.triggerHook()
|
||||||
|
|
||||||
|
if ((delta)?.backgroundImage) {
|
||||||
|
handleBackgroundImageWhenCanvasSizeChange()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 退回|重做
|
||||||
|
redo() {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (this.index < this.diffs.length && canvas) {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
const delta = this.diffs[this.index]
|
||||||
|
this.index++
|
||||||
|
const canvasJson = unpatch(this.canvasData, delta)
|
||||||
|
canvas.loadFromJSON(canvasJson, () => {
|
||||||
|
handleCanvasJSONLoaded(canvas)
|
||||||
|
canvas.requestRenderAll()
|
||||||
|
|
||||||
|
useFileStore.updateBoardData(canvasJson)
|
||||||
|
this.canvasData = cloneDeep(canvasJson ?? {})
|
||||||
|
FabricVue.triggerHook()
|
||||||
|
|
||||||
|
if ((delta)?.backgroundImage) {
|
||||||
|
bgUtils.handleBackgroundImageWhenCanvasSizeChange()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clean() {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
const useBoardStore = fabricStore.useBoardStore()
|
||||||
|
FabricVue?.canvas?.clear()
|
||||||
|
this.index = 0
|
||||||
|
this.diffs = []
|
||||||
|
this.canvasData = {}
|
||||||
|
useFileStore.updateBoardData(initState)
|
||||||
|
useBoardStore.updateBackgroundColor('#ffffff')
|
||||||
|
useBoardStore.cleanBackgroundImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
initHistory() {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const canvasJson = commUtils.getCanvasJSON()
|
||||||
|
this.canvasData = canvasJson
|
||||||
|
this.index = 0
|
||||||
|
this.diffs = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对象克隆
|
||||||
|
function cloneDeep(obj = {}) {
|
||||||
|
return JSON.parse(JSON.stringify(obj))
|
||||||
|
}
|
|
@ -4,19 +4,21 @@
|
||||||
import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import * as TYPES from '@/constants/draw'
|
import * as TYPES from '@/constants/draw'
|
||||||
import * as fabricUtils from '@/plugins/fabric/utils/draw'
|
import * as fabricUtils from '@/plugins/fabric/utils/draw'
|
||||||
|
import * as commUtils from '@/plugins/fabric/utils'
|
||||||
|
import * as bgUtils from '@/plugins/fabric/utils/bg'
|
||||||
import * as fabricStore from '@/store/modules/draw'
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
import { History } from '@/plugins/fabric/history'
|
||||||
|
import { CanvasEvent } from '@/plugins/fabric/event'
|
||||||
|
|
||||||
// 节点node
|
// 节点node
|
||||||
import { renderPencilBrush } from '@/plugins/fabric/nodes/draw'
|
import { renderPencilBrush } from '@/plugins/fabric/nodes/draw'
|
||||||
|
|
||||||
const useDrawStore = fabricStore.useDrawStore()
|
export class fabricVue {
|
||||||
const useBoardStore = fabricStore.useBoardStore()
|
canvas = null // fabric-canvas 对象
|
||||||
|
evnet = null // 事件对象
|
||||||
export class FabricVue {
|
history = null // 历史记录
|
||||||
static canvas = null // fabric-canvas 对象
|
textElement // 文本节点
|
||||||
static evnet = null // 事件对象
|
hookFn = [] // 钩子
|
||||||
static history = null // 历史记录
|
|
||||||
static textElement // 文本节点
|
|
||||||
static hookFn = [] // 钩子
|
|
||||||
/** 构造函数 */
|
/** 构造函数 */
|
||||||
constructor() {
|
constructor() {
|
||||||
// this.textElement = new TextElement() // 创建文本节点
|
// this.textElement = new TextElement() // 创建文本节点
|
||||||
|
@ -26,7 +28,8 @@ export class FabricVue {
|
||||||
* @param {*} canvasEl canvas元素
|
* @param {*} canvasEl canvas元素
|
||||||
* @returns boolean 是否初始化成功
|
* @returns boolean 是否初始化成功
|
||||||
*/
|
*/
|
||||||
static initCanvas(canvasEl, option = {}) {
|
initCanvas(canvasEl, option = {}) {
|
||||||
|
// const useBoardStore = fabricStore.useBoardStore()
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
this.canvas = new fabric.Canvas(canvasEl, {
|
this.canvas = new fabric.Canvas(canvasEl, {
|
||||||
selectionColor: 'rgba(101, 204, 138, 0.3)',
|
selectionColor: 'rgba(101, 204, 138, 0.3)',
|
||||||
|
@ -51,7 +54,7 @@ export class FabricVue {
|
||||||
// // 创建辅助线
|
// // 创建辅助线
|
||||||
// alignGuideLine.init(this.canvas, useBoardStore.openGuideLine)
|
// alignGuideLine.init(this.canvas, useBoardStore.openGuideLine)
|
||||||
|
|
||||||
// this.evnet = new CanvasEvent() // 创建相关事件
|
this.evnet = new CanvasEvent() // 创建相关事件
|
||||||
this.handleMode()
|
this.handleMode()
|
||||||
|
|
||||||
await this.initCanvasStorage()
|
await this.initCanvasStorage()
|
||||||
|
@ -62,7 +65,7 @@ export class FabricVue {
|
||||||
/**
|
/**
|
||||||
* 销毁canvas
|
* 销毁canvas
|
||||||
*/
|
*/
|
||||||
static removeCanvas() {
|
removeCanvas() {
|
||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
this?.canvas?.dispose()
|
this?.canvas?.dispose()
|
||||||
this.evnet?.removeEvent()
|
this.evnet?.removeEvent()
|
||||||
|
@ -73,21 +76,66 @@ export class FabricVue {
|
||||||
* Initialize the canvas cache
|
* Initialize the canvas cache
|
||||||
* @description 这里可以做一下初始加载json数据的操作
|
* @description 这里可以做一下初始加载json数据的操作
|
||||||
*/
|
*/
|
||||||
static initCanvasStorage() {
|
initCanvasStorage() {
|
||||||
|
const useBoardStore = fabricStore.useBoardStore()
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// TODO: 获取缓存数据
|
const file = useFileStore.currentFile
|
||||||
|
if (file && this.canvas) {
|
||||||
|
this.canvas.clear()
|
||||||
|
this.canvas.loadFromJSON(file.boardData, () => {
|
||||||
|
if (this.canvas) {
|
||||||
|
if (file.viewportTransform) {
|
||||||
|
this.canvas.setViewportTransform(file.viewportTransform)
|
||||||
|
}
|
||||||
|
if (file?.zoom && this.canvas.width && this.canvas.height) {
|
||||||
|
this.canvas.zoomToPoint(
|
||||||
|
new fabric.Point(
|
||||||
|
this.canvas.width / 2,
|
||||||
|
this.canvas.height / 2
|
||||||
|
),
|
||||||
|
file.zoom
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvas.setWidth(window.innerWidth * (file?.canvasWidth || 1))
|
||||||
|
useBoardStore.updateCanvasWidth(file?.canvasWidth || 1)
|
||||||
|
this.canvas.setHeight(window.innerHeight * (file?.canvasHeight || 1))
|
||||||
|
useBoardStore.initBackground()
|
||||||
|
|
||||||
|
const height = file?.canvasHeight || 1
|
||||||
|
useBoardStore.canvasHeight = height
|
||||||
|
this.updateCanvasHeight(height)
|
||||||
|
|
||||||
|
commUtils.handleCanvasJSONLoaded(this.canvas)
|
||||||
|
|
||||||
|
fabric.Object.prototype.set({
|
||||||
|
objectCaching: useBoardStore.isObjectCaching
|
||||||
})
|
})
|
||||||
|
this.canvas.renderAll()
|
||||||
|
this.triggerHook()
|
||||||
|
this.history = new History()
|
||||||
|
}
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* handle mode of operation
|
* handle mode of operation
|
||||||
* @param mode current mode
|
* @param mode current mode
|
||||||
*/
|
*/
|
||||||
static handleMode(mode = useBoardStore.mode) {
|
handleMode(mode) {
|
||||||
if (!this.canvas) {
|
if (!this.canvas) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
const useBoardStore = fabricStore.useBoardStore()
|
||||||
|
mode = mode || useBoardStore.mode
|
||||||
let isDrawingMode = false
|
let isDrawingMode = false
|
||||||
let selection = false
|
let selection = false
|
||||||
const objectSet = {
|
const objectSet = {
|
||||||
|
@ -144,10 +192,11 @@ export class FabricVue {
|
||||||
/**
|
/**
|
||||||
* handle draw style
|
* handle draw style
|
||||||
*/
|
*/
|
||||||
static handleDrawStyle() {
|
handleDrawStyle() {
|
||||||
if (!this.canvas) {
|
if (!this.canvas) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
const drawStyle = useDrawStore.drawStyle
|
const drawStyle = useDrawStore.drawStyle
|
||||||
switch (drawStyle) {
|
switch (drawStyle) {
|
||||||
case TYPES.DrawStyle.Basic:
|
case TYPES.DrawStyle.Basic:
|
||||||
|
@ -165,4 +214,183 @@ export class FabricVue {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* delete active objects
|
||||||
|
*/
|
||||||
|
deleteObject() {
|
||||||
|
// Disable deletion in text input state
|
||||||
|
if (this.textElement.isTextEditing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.canvas) {
|
||||||
|
const activeObjects = this.canvas.getActiveObjects()
|
||||||
|
if (activeObjects?.length) {
|
||||||
|
this.canvas.discardActiveObject()
|
||||||
|
activeObjects?.forEach((obj) => {
|
||||||
|
this.canvas?.remove(obj)
|
||||||
|
})
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* render and save history state
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
if (this.canvas) {
|
||||||
|
this.canvas?.requestRenderAll()
|
||||||
|
this.history?.saveState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* save as Image
|
||||||
|
*/
|
||||||
|
saveImage() {
|
||||||
|
if (this.canvas) {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = this.canvas.toDataURL()
|
||||||
|
link.download = 'paint-board.png'
|
||||||
|
link.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* copy active objects
|
||||||
|
*/
|
||||||
|
copyObject() {
|
||||||
|
const canvas = this.canvas
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const targets = canvas.getActiveObjects()
|
||||||
|
if (targets.length <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
canvas.discardActiveObject()
|
||||||
|
const copys = targets.map((target) => {
|
||||||
|
return new Promise<fabric.Object>((resolve) => {
|
||||||
|
target?.clone((cloned) => {
|
||||||
|
const id = uuidv4()
|
||||||
|
cloned.set({
|
||||||
|
left: (cloned?.left || 0) + 10,
|
||||||
|
top: (cloned?.top || 0) + 10,
|
||||||
|
evented: true,
|
||||||
|
id,
|
||||||
|
perPixelTargetFind: true
|
||||||
|
})
|
||||||
|
resolve(cloned)
|
||||||
|
canvas.add(cloned)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Promise.all(copys).then((objs) => {
|
||||||
|
const activeSelection = new fabric.ActiveSelection(objs, {
|
||||||
|
canvas: canvas
|
||||||
|
})
|
||||||
|
canvas.setActiveObject(activeSelection)
|
||||||
|
this.render()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Moving active objects via fabric's bringForward method
|
||||||
|
* 通过fabric的bringForward方法移动活动对象
|
||||||
|
*/
|
||||||
|
bringForWard() {
|
||||||
|
const canvas = this.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const object = canvas.getActiveObject()
|
||||||
|
if (object) {
|
||||||
|
canvas.bringForward(object, true)
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moving active objects via fabric's sendBackwards method
|
||||||
|
* 通过fabric的sendBackwards方法移动活动对象
|
||||||
|
*/
|
||||||
|
seendBackWard() {
|
||||||
|
const canvas = this.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const object = canvas.getActiveObject()
|
||||||
|
if (object) {
|
||||||
|
canvas.sendBackwards(object, true)
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Moving active objects via fabric's bringToFront method
|
||||||
|
* 通过fabric的bringToFront方法移动活动对象
|
||||||
|
*/
|
||||||
|
bringToFront() {
|
||||||
|
const canvas = this.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const object = canvas.getActiveObject()
|
||||||
|
if (object) {
|
||||||
|
canvas.bringToFront(object)
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Moving active objects via fabric's sendToBack method
|
||||||
|
* 通过fabric的sendToBack方法移动活动对象
|
||||||
|
*/
|
||||||
|
sendToBack() {
|
||||||
|
const canvas = this.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const object = canvas.getActiveObject()
|
||||||
|
if (object) {
|
||||||
|
canvas.sendToBack(object)
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add hook fn to trigger on update|在更新时添加钩钩fn以触发
|
||||||
|
* @param fn hook fn () => void
|
||||||
|
*/
|
||||||
|
addHookFn(fn) {
|
||||||
|
this.hookFn.push(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove trigger hook fn | 移除触发钩fn
|
||||||
|
* @param fn hook fn () => void
|
||||||
|
*/
|
||||||
|
removeHookFn(fn) {
|
||||||
|
const hookIndex = this.hookFn.findIndex((v) => v === fn)
|
||||||
|
if (hookIndex > -1) {
|
||||||
|
this.hookFn.splice(hookIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* trigger hook fn
|
||||||
|
*/
|
||||||
|
triggerHook() {
|
||||||
|
this.hookFn.map((fn) => {
|
||||||
|
fn?.()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCanvasWidth(width) {
|
||||||
|
if (this.canvas) {
|
||||||
|
this.canvas.setWidth(window.innerWidth * width)
|
||||||
|
bgUtils.handleBackgroundImageWhenCanvasSizeChange()
|
||||||
|
// useFileStore.getState().updateCanvasWidth(width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateCanvasHeight(height) {
|
||||||
|
if (this.canvas) {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
this.canvas.setHeight(window.innerHeight * height)
|
||||||
|
useFileStore.updateBoardField('canvasHeight', height)
|
||||||
|
bgUtils.handleBackgroundImageWhenCanvasSizeChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FabricVue = new fabricVue()
|
|
@ -5,7 +5,6 @@
|
||||||
import { FabricVue } from '@/plugins/fabric'
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
import * as TYPES from '@/constants/draw'
|
import * as TYPES from '@/constants/draw'
|
||||||
import * as fabricStore from '@/store/modules/draw'
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
const useDrawStore = fabricStore.useDrawStore()
|
|
||||||
|
|
||||||
export class Material {
|
export class Material {
|
||||||
initPromise = null // Initialize promise
|
initPromise = null // Initialize promise
|
||||||
|
@ -20,6 +19,7 @@ export class Material {
|
||||||
}
|
}
|
||||||
|
|
||||||
async initMaterial() {
|
async initMaterial() {
|
||||||
|
|
||||||
this.initPromise = Promise.all([
|
this.initPromise = Promise.all([
|
||||||
this.loadImage(TYPES.MATERIAL_TYPE.CRAYON),
|
this.loadImage(TYPES.MATERIAL_TYPE.CRAYON),
|
||||||
this.loadImage(TYPES.MATERIAL_TYPE.CARBON),
|
this.loadImage(TYPES.MATERIAL_TYPE.CARBON),
|
||||||
|
@ -29,10 +29,10 @@ export class Material {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
render({
|
render({materialType, color}) {
|
||||||
materialType = useDrawStore.materialType,
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
color = useDrawStore.drawColors[0]
|
materialType = materialType || useDrawStore.materialType
|
||||||
}) {
|
color = color || useDrawStore.drawColors[0]
|
||||||
this.initPromise?.then(() => {
|
this.initPromise?.then(() => {
|
||||||
switch (materialType) {
|
switch (materialType) {
|
||||||
case TYPES.MATERIAL_TYPE.CRAYON:
|
case TYPES.MATERIAL_TYPE.CRAYON:
|
||||||
|
@ -58,6 +58,7 @@ export class Material {
|
||||||
|
|
||||||
renderMaterial(materialImg, color, opacity = 1) {
|
renderMaterial(materialImg, color, opacity = 1) {
|
||||||
if (FabricVue.canvas) {
|
if (FabricVue.canvas) {
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
const patternBrush = new fabric.PatternBrush(FabricVue.canvas)
|
const patternBrush = new fabric.PatternBrush(FabricVue.canvas)
|
||||||
const patternCanvas = document.createElement('canvas')
|
const patternCanvas = document.createElement('canvas')
|
||||||
patternCanvas.width = patternCanvas.height = 100
|
patternCanvas.width = patternCanvas.height = 100
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/**
|
||||||
|
* 多个材质画刷
|
||||||
|
* @param {*} params
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as TYPES from '@/constants/draw'
|
||||||
|
import * as fabricUtils from '@/plugins/fabric/utils/draw'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
const COLOR_WIDTH = 5
|
||||||
|
|
||||||
|
export const renderMultiColor = (params={}) => {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
const colors = params?.colors ?? useDrawStore.drawColors
|
||||||
|
const type = params?.type ?? useDrawStore.multiColorType
|
||||||
|
|
||||||
|
const patternBrush = new fabric.PatternBrush(canvas)
|
||||||
|
const patternCanvas = document.createElement('canvas')
|
||||||
|
const context = patternCanvas.getContext('2d')
|
||||||
|
if (context) {
|
||||||
|
switch (type) {
|
||||||
|
case TYPES.MultiColorType.COL:
|
||||||
|
renderCol(patternCanvas, context, colors)
|
||||||
|
break
|
||||||
|
case TYPES.MultiColorType.ROW:
|
||||||
|
renderRow(patternCanvas, context, colors)
|
||||||
|
break
|
||||||
|
case TYPES.MultiColorType.CIRCLE:
|
||||||
|
renderCircle(patternCanvas, context, colors)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
patternBrush.getPatternSrc = () => {
|
||||||
|
return patternCanvas
|
||||||
|
}
|
||||||
|
patternBrush.getPatternSrcFunction = () => {
|
||||||
|
return patternCanvas
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.isDrawingMode = true
|
||||||
|
canvas.freeDrawingBrush = patternBrush
|
||||||
|
canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth()
|
||||||
|
canvas.freeDrawingBrush.shadow = new fabric.Shadow({
|
||||||
|
blur: fabricUtils.getShadowWidth(),
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
color: useDrawStore.shadowColor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCol(canvas, context, colors=[]) {
|
||||||
|
canvas.width = COLOR_WIDTH * colors.length
|
||||||
|
canvas.height = 20
|
||||||
|
colors.forEach((color, i) => {
|
||||||
|
context.fillStyle = color
|
||||||
|
context.fillRect(COLOR_WIDTH * i, 0, COLOR_WIDTH, 20)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRow(canvas, context, colors=[]) {
|
||||||
|
canvas.width = 20
|
||||||
|
canvas.height = COLOR_WIDTH * colors.length
|
||||||
|
colors.forEach((color, i) => {
|
||||||
|
context.fillStyle = color
|
||||||
|
context.fillRect(0, COLOR_WIDTH * i, 20, COLOR_WIDTH)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCircle(canvas, context, colors=[]) {
|
||||||
|
const radius = 10
|
||||||
|
const padding = 5
|
||||||
|
const n = colors.length
|
||||||
|
|
||||||
|
const canvasWidth = 2 * padding + n * radius * 2 + (n - 1) * padding
|
||||||
|
canvas.width = canvasWidth
|
||||||
|
canvas.height = radius * 2 + 2 * padding
|
||||||
|
|
||||||
|
let x = padding + radius
|
||||||
|
const y = padding + radius
|
||||||
|
|
||||||
|
// render multi circle
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
context.beginPath()
|
||||||
|
context.fillStyle = colors[i]
|
||||||
|
context.arc(x, y, radius, 0, Math.PI * 2)
|
||||||
|
context.closePath()
|
||||||
|
context.fill()
|
||||||
|
x += 2 * radius + padding
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具:背景相关的颜色、背景图片
|
||||||
|
*/
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件: 缩放变化-更新背景图片
|
||||||
|
*/
|
||||||
|
export const handleBackgroundImageWhenCanvasSizeChange = (isRender = true) => {
|
||||||
|
const backgroundImage = FabricVue?.canvas?.backgroundImage
|
||||||
|
if (backgroundImage) {
|
||||||
|
setBackgroundImage(backgroundImage)
|
||||||
|
if (isRender) {
|
||||||
|
FabricVue.canvas?.requestRenderAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新 fabric 加载画布背景图片
|
||||||
|
* @param {*} data string
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateCanvasBackgroundImage = (data) => {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fabric.Image.fromURL(
|
||||||
|
data,
|
||||||
|
(image) => {
|
||||||
|
setBackgroundImage(image)
|
||||||
|
|
||||||
|
canvas.setBackgroundImage(image, () => {
|
||||||
|
FabricVue.render()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{crossOrigin: 'anonymous'}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 设置-更新背景图片
|
||||||
|
* @param {*} image fabric.Image
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const setBackgroundImage = (image) => {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const useBoardStore = fabricStore.useBoardStore()
|
||||||
|
const canvasWidth = canvas.getWidth()
|
||||||
|
const canvasHeight = canvas.getHeight()
|
||||||
|
|
||||||
|
const imgWidth = image.width
|
||||||
|
const imgHeight = image.height
|
||||||
|
|
||||||
|
const scaleWidth = canvasWidth / imgWidth
|
||||||
|
const scaleHeight = canvasHeight / imgHeight
|
||||||
|
|
||||||
|
const scale = Math.min(scaleWidth, scaleHeight)
|
||||||
|
image.scale(scale)
|
||||||
|
|
||||||
|
const imgLeft = canvasWidth / 2 - (imgWidth * scale) / 2
|
||||||
|
const imgTop = canvasHeight / 2 - (imgHeight * scale) / 2
|
||||||
|
|
||||||
|
image.set({
|
||||||
|
left: imgLeft,
|
||||||
|
top: imgTop,
|
||||||
|
originX: 'left',
|
||||||
|
originY: 'top',
|
||||||
|
opacity: useBoardStore.backgroundImageOpacity
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/**
|
||||||
|
* hexToRgba
|
||||||
|
* @param hex hexadecimal color
|
||||||
|
* @param alpha
|
||||||
|
* @returns rgba
|
||||||
|
*/
|
||||||
|
export function hexToRgba(hex, alpha = 1) {
|
||||||
|
const bigint = parseInt(hex.slice(1), 16)
|
||||||
|
const r = (bigint >> 16) & 255
|
||||||
|
const g = (bigint >> 8) & 255
|
||||||
|
const b = bigint & 255
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rgbaToHex(rgba) {
|
||||||
|
if (!rgba) {
|
||||||
|
return rgba
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = rgba.match(/\d+/g)
|
||||||
|
const hex = `#${(
|
||||||
|
(1 << 24) +
|
||||||
|
(parseInt(values[0]) << 16) +
|
||||||
|
(parseInt(values[1]) << 8) +
|
||||||
|
parseInt(values[2])
|
||||||
|
)
|
||||||
|
.toString(16)
|
||||||
|
.slice(1)}`
|
||||||
|
return hex
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get rgba alpha
|
||||||
|
* @param rgbaString rgba color
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getAlphaFromRgba(rgbaString) {
|
||||||
|
const match = rgbaString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
return parseFloat(match[4])
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the rgba of the new alpha
|
||||||
|
* @param rgbaColor
|
||||||
|
* @param newAlpha
|
||||||
|
* @returns newRgbaColor
|
||||||
|
*/
|
||||||
|
export function changeAlpha(rgbaColor, newAlpha) {
|
||||||
|
const match = rgbaColor.match(/rgba?\((.*?)\)/)
|
||||||
|
if (!match) {
|
||||||
|
return rgbaColor
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgbPart = match[1].split(',').map((i) => i.trim())
|
||||||
|
newAlpha = Math.min(1, Math.max(0, newAlpha))
|
||||||
|
|
||||||
|
const newRgbaColor = `rgba(${rgbPart[0]}, ${rgbPart[1]}, ${rgbPart[2]}, ${newAlpha})`
|
||||||
|
return newRgbaColor
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isHexColor(color) {
|
||||||
|
return /^#([0-9A-Fa-f]{3}){1,2}$/.test(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRgbaColor(color) {
|
||||||
|
return /^rgba?\((\d+,\s*){2}\d+,\s*(0(\.\d+)?|1(\.0)?)\)$/.test(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getColorFormat(color) {
|
||||||
|
if (isHexColor(color)) {
|
||||||
|
return 'hex'
|
||||||
|
} else if (isRgbaColor(color)) {
|
||||||
|
return 'rgba'
|
||||||
|
} else {
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,11 @@
|
||||||
|
/**
|
||||||
|
* 工具类
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as TYPES from '@/constants/draw'
|
||||||
|
import * as lineUtils from '@/plugins/fabric/utils/shape/line'
|
||||||
|
import * as arrowLineUtils from '@/plugins/fabric/utils/shape/arrowLine'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* compare version
|
* compare version
|
||||||
* @param v1
|
* @param v1
|
||||||
|
@ -77,3 +85,81 @@ export const isMobile = () => {
|
||||||
navigator.userAgent
|
navigator.userAgent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// 生成UUID
|
||||||
|
export const getUUID = () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
// 时间戳部分
|
||||||
|
const timestampPart = timestamp.toString(16).padStart(12, '0');
|
||||||
|
|
||||||
|
// 生成随机数
|
||||||
|
const rnd = crypto.getRandomValues(new Uint8Array(4));
|
||||||
|
const randomPart = rnd.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
||||||
|
|
||||||
|
// 应用变体和版本标识符
|
||||||
|
const version = (timestamp >= 0x0100000000000000) ? (timestamp & 0x00000000FFFF0000) : 0x4000;
|
||||||
|
const variant = (rnd[0] & 0x0F) | 0x80;
|
||||||
|
|
||||||
|
// 合并所有部分并返回
|
||||||
|
return `${timestampPart}-${randomPart}-${version.toString(16).padStart(4, '0')}-${variant.toString(16).padStart(4, '0')}-${rnd[3].toString(16).padStart(4, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** loadCanvas */
|
||||||
|
export const getCanvasJSON = () => {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (canvas) {
|
||||||
|
return (
|
||||||
|
// 返回额外属性
|
||||||
|
canvas.toDatalessJSON([
|
||||||
|
'id',
|
||||||
|
'_customType',
|
||||||
|
'perPixelTargetFind',
|
||||||
|
'objectCaching'
|
||||||
|
]) ?? {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handling canvas json loaded data
|
||||||
|
* Used to initialize undo redo
|
||||||
|
* @param canvas fabric.Canvas
|
||||||
|
*/
|
||||||
|
export const handleCanvasJSONLoaded = (canvas) => {
|
||||||
|
canvas.getObjects().forEach((obj) => {
|
||||||
|
if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.SHAPE_LINE) {
|
||||||
|
const points = obj.points
|
||||||
|
const lastControl = points.length - 1
|
||||||
|
obj.controls = points.reduce(function (acc, point, index) {
|
||||||
|
acc['p' + index] = new fabric.Control({
|
||||||
|
positionHandler: lineUtils.polygonPositionHandler,
|
||||||
|
actionHandler: lineUtils.anchorWrapper(
|
||||||
|
index > 0 ? index - 1 : lastControl,
|
||||||
|
lineUtils.actionHandler
|
||||||
|
),
|
||||||
|
actionName: 'polylineEndPoint',
|
||||||
|
pointIndex: index
|
||||||
|
})
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.SHAPE_ARROW_LINE) {
|
||||||
|
const paths = (obj).path
|
||||||
|
obj.controls = paths
|
||||||
|
.slice(0, paths.length - 4)
|
||||||
|
.reduce(function (acc, point, index) {
|
||||||
|
acc['p' + index] = new fabric.Control({
|
||||||
|
positionHandler: arrowLineUtils.pathPositionHandler,
|
||||||
|
actionHandler: arrowLineUtils.anchorWrapper(
|
||||||
|
index > 0 ? index - 1 : paths.length - 5,
|
||||||
|
arrowLineUtils.actionHandler
|
||||||
|
),
|
||||||
|
actionName: 'pathEndPoint',
|
||||||
|
pointIndex: index
|
||||||
|
})
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
|
||||||
|
export function calculateArrowSlidePath(
|
||||||
|
paths,
|
||||||
|
startX,
|
||||||
|
startY,
|
||||||
|
endX,
|
||||||
|
endY
|
||||||
|
) {
|
||||||
|
const angleRad = Math.atan2(endY - startY, endX - startX)
|
||||||
|
const arrowWidthHalf = 10
|
||||||
|
|
||||||
|
const arrowLeftX = endX - arrowWidthHalf * Math.cos(angleRad - Math.PI / 6)
|
||||||
|
const arrowLeftY = endY - arrowWidthHalf * Math.sin(angleRad - Math.PI / 6)
|
||||||
|
const arrowRightX = endX - arrowWidthHalf * Math.cos(angleRad + Math.PI / 6)
|
||||||
|
const arrowRightY = endY - arrowWidthHalf * Math.sin(angleRad + Math.PI / 6)
|
||||||
|
|
||||||
|
paths[paths.length - 4][1] = endX
|
||||||
|
paths[paths.length - 4][2] = endY
|
||||||
|
paths[paths.length - 3][1] = arrowLeftX
|
||||||
|
paths[paths.length - 3][2] = arrowLeftY
|
||||||
|
paths[paths.length - 2][1] = endX
|
||||||
|
paths[paths.length - 2][2] = endY
|
||||||
|
paths[paths.length - 1][1] = arrowRightX
|
||||||
|
paths[paths.length - 1][2] = arrowRightY
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} object fabric.Path
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getObjectSizeWithStroke(object) {
|
||||||
|
const stroke = new fabric.Point(
|
||||||
|
object.strokeUniform ? 1 / (object.scaleX) : 1,
|
||||||
|
object.strokeUniform ? 1 / (object.scaleY) : 1
|
||||||
|
).multiply(object.strokeWidth)
|
||||||
|
return new fabric.Point(
|
||||||
|
(object.width) + stroke.x,
|
||||||
|
(object.height) + stroke.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} this fabric.Control
|
||||||
|
* @param {*} dim
|
||||||
|
* @param {*} finalMatrix
|
||||||
|
* @param {*} fabricObject fabric.Path
|
||||||
|
* @returns fabric.Control['positionHandler']
|
||||||
|
*/
|
||||||
|
export const pathPositionHandler = function (
|
||||||
|
fabricControl,
|
||||||
|
dim,
|
||||||
|
finalMatrix,
|
||||||
|
fabricObject
|
||||||
|
) {
|
||||||
|
const paths = fabricObject.path
|
||||||
|
const x = paths[fabricControl.pointIndex][1] - fabricObject.pathOffset.x
|
||||||
|
const y = paths[fabricControl.pointIndex][2] - fabricObject.pathOffset.y
|
||||||
|
return fabric.util.transformPoint(
|
||||||
|
{ x, y },
|
||||||
|
fabric.util.multiplyTransformMatrices(
|
||||||
|
fabricObject.canvas?.viewportTransform,
|
||||||
|
fabricObject.calcTransformMatrix()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} eventData
|
||||||
|
* @param {*} transform
|
||||||
|
* @param {*} x
|
||||||
|
* @param {*} y
|
||||||
|
* @returns fabric.Control['actionHandler']
|
||||||
|
*/
|
||||||
|
export const actionHandler = function (
|
||||||
|
eventData,
|
||||||
|
transform,
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
) {
|
||||||
|
const pathObj = transform.target,
|
||||||
|
currentControl = pathObj.controls[pathObj.__corner],
|
||||||
|
mouseLocalPosition = pathObj.toLocalPoint(
|
||||||
|
new fabric.Point(x, y),
|
||||||
|
'center',
|
||||||
|
'center'
|
||||||
|
),
|
||||||
|
pathBaseSize = getObjectSizeWithStroke(pathObj),
|
||||||
|
size = pathObj._getTransformedDimensions(0, 0),
|
||||||
|
finalPointPosition = {
|
||||||
|
x:
|
||||||
|
(mouseLocalPosition.x * pathBaseSize.x) / size.x + pathObj.pathOffset.x,
|
||||||
|
y: (mouseLocalPosition.y * pathBaseSize.y) / size.y + pathObj.pathOffset.y
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = pathObj.path
|
||||||
|
path[currentControl.pointIndex][1] = finalPointPosition.x
|
||||||
|
path[currentControl.pointIndex][2] = finalPointPosition.y
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentControl.pointIndex === path?.length - 5 ||
|
||||||
|
currentControl.pointIndex === path?.length - 6
|
||||||
|
) {
|
||||||
|
const point1 = path[path.length - 6]
|
||||||
|
const point2 = path[path.length - 5]
|
||||||
|
const newPath = calculateArrowSlidePath(
|
||||||
|
path,
|
||||||
|
point1[1],
|
||||||
|
point1[2],
|
||||||
|
point2[1],
|
||||||
|
point2[2]
|
||||||
|
)
|
||||||
|
pathObj._setPath(newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} anchorIndex
|
||||||
|
* @param {*} fn fabric.Control['actionHandler']
|
||||||
|
* @returns MouseEvent fabric.Transform
|
||||||
|
*/
|
||||||
|
export function anchorWrapper(
|
||||||
|
anchorIndex,
|
||||||
|
fn
|
||||||
|
) {
|
||||||
|
return function (
|
||||||
|
eventData,
|
||||||
|
transform,
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
) {
|
||||||
|
const fabricObject = transform.target
|
||||||
|
const paths = fabricObject.path
|
||||||
|
const absolutePoint = fabric.util.transformPoint(
|
||||||
|
{
|
||||||
|
x: paths[anchorIndex][1] - fabricObject.pathOffset.x,
|
||||||
|
y: paths[anchorIndex][2] - fabricObject.pathOffset.y
|
||||||
|
},
|
||||||
|
fabricObject.calcTransformMatrix()
|
||||||
|
)
|
||||||
|
const actionPerformed = fn(eventData, transform, x, y)
|
||||||
|
fabricObject._setPath(fabricObject.path)
|
||||||
|
const pathBaseSize = getObjectSizeWithStroke(fabricObject)
|
||||||
|
|
||||||
|
const newX =
|
||||||
|
(paths[anchorIndex][1] - fabricObject.pathOffset.x) / pathBaseSize.x
|
||||||
|
const newY =
|
||||||
|
(paths[anchorIndex][2] - fabricObject.pathOffset.y) / pathBaseSize.y
|
||||||
|
fabricObject.setPositionByOrigin(
|
||||||
|
absolutePoint,
|
||||||
|
(newX + 0.5),
|
||||||
|
(newY + 0.5)
|
||||||
|
)
|
||||||
|
return actionPerformed
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 线
|
||||||
|
*/
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} object fabric.Polyline
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getObjectSizeWithStroke(object) {
|
||||||
|
const stroke = new fabric.Point(
|
||||||
|
object.strokeUniform ? 1 / (object.scaleX) : 1,
|
||||||
|
object.strokeUniform ? 1 / (object.scaleY) : 1
|
||||||
|
).multiply(object.strokeWidth)
|
||||||
|
return new fabric.Point(
|
||||||
|
(object.width) + stroke.x,
|
||||||
|
(object.height) + stroke.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} this fabric.Control
|
||||||
|
* @param {*} dim
|
||||||
|
* @param {*} finalMatrix
|
||||||
|
* @param {*} fabricObject fabric.Polyline
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const polygonPositionHandler = function (
|
||||||
|
fabricControl, dim, finalMatrix,fabricObject) {
|
||||||
|
const points = fabricObject.points
|
||||||
|
const x = points[fabricControl.pointIndex].x - fabricObject.pathOffset.x
|
||||||
|
const y = points[fabricControl.pointIndex].y - fabricObject.pathOffset.y
|
||||||
|
return fabric.util.transformPoint(
|
||||||
|
{ x, y },
|
||||||
|
fabric.util.multiplyTransformMatrices(
|
||||||
|
fabricObject.canvas?.viewportTransform,
|
||||||
|
fabricObject.calcTransformMatrix()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} eventData
|
||||||
|
* @param {*} transform
|
||||||
|
* @param {*} x
|
||||||
|
* @param {*} y
|
||||||
|
* @returns fabric.Control['actionHandler']
|
||||||
|
*/
|
||||||
|
export const actionHandler = function (
|
||||||
|
eventData, transform,x, y) {
|
||||||
|
const polygon = transform.target,
|
||||||
|
currentControl = polygon.controls[polygon.__corner],
|
||||||
|
mouseLocalPosition = polygon.toLocalPoint(
|
||||||
|
new fabric.Point(x, y),
|
||||||
|
'center',
|
||||||
|
'center'
|
||||||
|
),
|
||||||
|
polygonBaseSize = getObjectSizeWithStroke(polygon),
|
||||||
|
size = polygon._getTransformedDimensions(0, 0),
|
||||||
|
finalPointPosition = {
|
||||||
|
x:
|
||||||
|
(mouseLocalPosition.x * polygonBaseSize.x) / size.x +
|
||||||
|
polygon.pathOffset.x,
|
||||||
|
y:
|
||||||
|
(mouseLocalPosition.y * polygonBaseSize.y) / size.y +
|
||||||
|
polygon.pathOffset.y
|
||||||
|
}
|
||||||
|
|
||||||
|
const points = polygon.points
|
||||||
|
points[currentControl.pointIndex] = finalPointPosition
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} anchorIndex number
|
||||||
|
* @param {*} fn fabric.Control['actionHandler']
|
||||||
|
* @returns function (eventData, transform, x, y ) | MouseEvent fabric.Transform number number
|
||||||
|
*/
|
||||||
|
export function anchorWrapper(anchorIndex, fn) {
|
||||||
|
return function (eventData, transform, x, y ) {
|
||||||
|
const fabricObject = transform.target
|
||||||
|
const points = fabricObject.points
|
||||||
|
const absolutePoint = fabric.util.transformPoint(
|
||||||
|
{
|
||||||
|
x: points[anchorIndex].x - fabricObject.pathOffset.x,
|
||||||
|
y: points[anchorIndex].y - fabricObject.pathOffset.y
|
||||||
|
},
|
||||||
|
fabricObject.calcTransformMatrix()
|
||||||
|
)
|
||||||
|
const actionPerformed = fn(eventData, transform, x, y)
|
||||||
|
fabricObject._setPositionDimensions({})
|
||||||
|
const polygonBaseSize = getObjectSizeWithStroke(fabricObject)
|
||||||
|
|
||||||
|
const newX =
|
||||||
|
(points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x
|
||||||
|
const newY =
|
||||||
|
(points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y
|
||||||
|
fabricObject.setPositionByOrigin(
|
||||||
|
absolutePoint,
|
||||||
|
(newX + 0.5),
|
||||||
|
(newY + 0.5)
|
||||||
|
)
|
||||||
|
return actionPerformed
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,73 @@ import { defineStore } from "pinia";
|
||||||
import * as TYPES from "@/constants/draw";
|
import * as TYPES from "@/constants/draw";
|
||||||
import { FabricVue } from "@/plugins/fabric";
|
import { FabricVue } from "@/plugins/fabric";
|
||||||
import * as fabricUtils from '@/plugins/fabric/utils/draw'
|
import * as fabricUtils from '@/plugins/fabric/utils/draw'
|
||||||
|
import * as colorUtils from '@/plugins/fabric/utils/color'
|
||||||
|
import * as bgUtils from '@/plugins/fabric/utils/bg'
|
||||||
|
import * as commUtils from '@/plugins/fabric/utils'
|
||||||
|
import { material } from '@/plugins/fabric/nodes/draw/material'
|
||||||
|
import { renderMultiColor } from '@/plugins/fabric/nodes/draw/multiColor'
|
||||||
|
|
||||||
|
// 画板模块-缓存 json相关
|
||||||
|
export const BOARD_VERSION = '1.0.0'
|
||||||
|
|
||||||
|
const getFileObj = (data) => ({
|
||||||
|
id: commUtils.getUUID(),
|
||||||
|
title: 'paint-board',
|
||||||
|
boardVersion: BOARD_VERSION,
|
||||||
|
boardData: {},
|
||||||
|
zoom: 1,
|
||||||
|
canvasWidth: 1,
|
||||||
|
canvasHeight: 1
|
||||||
|
, ...data
|
||||||
|
})
|
||||||
|
const initFileObj = getFileObj()
|
||||||
|
export const useFileStore = defineStore(
|
||||||
|
'file',
|
||||||
|
{
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
currentId: initFileObj.id,
|
||||||
|
currentIndex: 0,
|
||||||
|
currentFile: initFileObj,
|
||||||
|
files: [ initFileObj ],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// update file | 更新文件
|
||||||
|
updateFile(file = {}) {
|
||||||
|
if (file?.id != this.currentId) return
|
||||||
|
this.currentFile = file
|
||||||
|
this.files[this.currentIndex] = data
|
||||||
|
},
|
||||||
|
// update board data | 更新画板数据
|
||||||
|
updateBoardData(data) {
|
||||||
|
this.currentFile.boardData = data
|
||||||
|
this.files[this.currentIndex].boardData = data
|
||||||
|
},
|
||||||
|
// update board zoom | 更新画板缩放
|
||||||
|
updateBoardZoom(zoom){
|
||||||
|
this.currentFile.zoom = zoom
|
||||||
|
this.files[this.currentIndex].zoom = zoom
|
||||||
|
},
|
||||||
|
// update board field | 更新画板字段
|
||||||
|
updateBoardField(key, value) {
|
||||||
|
if(key == 'id') return
|
||||||
|
this.currentFile[key] = value
|
||||||
|
this.files[this.currentIndex][key] = value
|
||||||
|
},
|
||||||
|
// 创建新画板
|
||||||
|
createBoard(option = {}) {
|
||||||
|
delete option?.id
|
||||||
|
let fileObj = getFileObj(option)
|
||||||
|
this.currentFile = fileObj
|
||||||
|
this.currentId = fileObj.id
|
||||||
|
this.files.push(fileObj)
|
||||||
|
this.currentIndex = this.files.length - 1
|
||||||
|
return fileObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 主模块-缓存
|
// 主模块-缓存
|
||||||
const initLanguage = ['en', 'en-US', 'en-us'].includes(navigator.language) ? 'en' : 'zh'
|
const initLanguage = ['en', 'en-US', 'en-us'].includes(navigator.language) ? 'en' : 'zh'
|
||||||
|
@ -26,6 +93,59 @@ export const useBoardStore = defineStore(
|
||||||
isObjectCaching: true,
|
isObjectCaching: true,
|
||||||
openGuideLine: false,
|
openGuideLine: false,
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// update mode | 更新模式
|
||||||
|
updateMode(mode) {
|
||||||
|
if (this.mode != mode) FabricVue.handleMode(mode)
|
||||||
|
this.mode = mode
|
||||||
|
},
|
||||||
|
// update draw type | 更新画笔类型
|
||||||
|
updateDrawType(drawType) {
|
||||||
|
if (this.drawType != drawType){
|
||||||
|
this.drawType = drawType
|
||||||
|
FabricVue.handleMode()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update language | 更新语言
|
||||||
|
updateLanguage(language) {this.language = language},
|
||||||
|
updateCanvasWidth(width) {
|
||||||
|
if (this.canvasWidth !== width){
|
||||||
|
this.canvasHeight = width
|
||||||
|
FabricVue.updateCanvasWidth(width)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// init background | 初始化背景
|
||||||
|
initBackground() {
|
||||||
|
const backgroundColor = FabricVue?.canvas?.backgroundColor
|
||||||
|
if (backgroundColor && typeof backgroundColor === 'string') {
|
||||||
|
const type = colorUtils.getColorFormat(backgroundColor)
|
||||||
|
if (type === 'hex') {
|
||||||
|
const color = colorUtils.hexToRgba(backgroundColor)
|
||||||
|
const opacity = colorUtils.getAlphaFromRgba(color)
|
||||||
|
this.backgroundColor = color
|
||||||
|
this.backgroundOpacity = opacity
|
||||||
|
} else if (type === 'rgba') {
|
||||||
|
const opacity = colorUtils.getAlphaFromRgba(backgroundColor)
|
||||||
|
this.backgroundColor = backgroundColor
|
||||||
|
this.backgroundOpacity = opacity
|
||||||
|
}
|
||||||
|
} else if (FabricVue?.canvas) {
|
||||||
|
FabricVue.canvas.backgroundColor = 'rgba(255, 255, 255, 1)'
|
||||||
|
this.backgroundColor = 'rgba(255, 255, 255, 1)'
|
||||||
|
this.backgroundOpacity = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundImage = FabricVue?.canvas?.backgroundImage
|
||||||
|
if (backgroundImage) {
|
||||||
|
bgUtils.handleBackgroundImageWhenCanvasSizeChange()
|
||||||
|
this.hasBackgroundImage = true
|
||||||
|
this.backgroundOpacity = backgroundImage.opacity
|
||||||
|
} else {
|
||||||
|
this.hasBackgroundImage = true
|
||||||
|
this.backgroundOpacity = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -53,6 +173,7 @@ export const useDrawStore = defineStore(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
// update draw width | 更新画笔宽度
|
||||||
updateDrawWidth(w) {
|
updateDrawWidth(w) {
|
||||||
const oldW = this.drawWidth
|
const oldW = this.drawWidth
|
||||||
if (oldW != w && FabricVue.canvas) {
|
if (oldW != w && FabricVue.canvas) {
|
||||||
|
@ -60,6 +181,93 @@ export const useDrawStore = defineStore(
|
||||||
this.drawWidth = w
|
this.drawWidth = w
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// update draw colors | 更新画笔颜色
|
||||||
|
updateDrawColors(drawColors = []) {
|
||||||
|
const drawStyle = this.drawStyle
|
||||||
|
switch(drawStyle) {
|
||||||
|
case TYPES.DrawStyle.Basic:
|
||||||
|
if (FabricVue.canvas) {
|
||||||
|
FabricVue.canvas.freeDrawingBrush.color = drawColors[0]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case TYPES.DrawStyle.Material:
|
||||||
|
if (this.drawColors[0] != drawColors[0]) {
|
||||||
|
material.render({})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case TYPES.DrawStyle.MultiColor:
|
||||||
|
renderMultiColor({colors: drawColors})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
this.drawColors = drawColors
|
||||||
|
},
|
||||||
|
// update shadow width | 更新画笔阴影宽度
|
||||||
|
updateShadowWidth(shadowWidth) {
|
||||||
|
if (FabricVue.canvas) {
|
||||||
|
(FabricVue.canvas.freeDrawingBrush.shadow).blur = fabricUtils.getShadowWidth(shadowWidth)
|
||||||
|
}
|
||||||
|
this.shadowWidth = shadowWidth
|
||||||
|
},
|
||||||
|
// update shadow color | 更新画笔阴影颜色
|
||||||
|
updateShadowColor(shadowColor) {
|
||||||
|
if (FabricVue.canvas) {
|
||||||
|
(FabricVue.canvas.freeDrawingBrush.shadow).color = shadowColor
|
||||||
|
}
|
||||||
|
this.shadowColor = shadowColor
|
||||||
|
},
|
||||||
|
// update shadow Shape | 更新画笔阴影形状
|
||||||
|
updateDrawShape(drawShape) {
|
||||||
|
this.drawShape = drawShape
|
||||||
|
},
|
||||||
|
// update draw style | 更新画笔阴影样式
|
||||||
|
updateDrawStyle(drawStyle) {
|
||||||
|
this.drawStyle = drawStyle
|
||||||
|
FabricVue.handleDrawStyle()
|
||||||
|
},
|
||||||
|
// update draw shape count | 更新画笔形状数量
|
||||||
|
updateDrawShapeCount(drawShapeCount){
|
||||||
|
this.drawShapeCount = drawShapeCount
|
||||||
|
},
|
||||||
|
// update draw text value | 更新画笔文字
|
||||||
|
updateDrawTextValue(drawTextValue){
|
||||||
|
this.drawTextValue = drawTextValue
|
||||||
|
},
|
||||||
|
// update material type | 更新材质类型
|
||||||
|
updateMaterialType(materialType) {
|
||||||
|
if(this.materialType != materialType) {
|
||||||
|
material.render({materialType})
|
||||||
|
this.materialType = materialType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update eraser width | 更新橡皮擦宽度
|
||||||
|
updateEraserWidth(eraserWidth) {
|
||||||
|
const oldW = this.drawWidth
|
||||||
|
if (oldW != eraserWidth && FabricVue.canvas) {
|
||||||
|
FabricVue.canvas.freeDrawingBrush.width = fabricUtils.getEraserWidth(eraserWidth)
|
||||||
|
this.eraserWidth = eraserWidth
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update multi color type | 更新多色材质类型
|
||||||
|
updateMultiColorType(multiColorType) {
|
||||||
|
if(this.multiColorType != multiColorType) {
|
||||||
|
renderMultiColor({type: multiColorType})
|
||||||
|
this.multiColorType = multiColorType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update text font family | 更新文字字体
|
||||||
|
updateTextFontFamily(fontFamily){
|
||||||
|
this.textFontFamily = fontFamily
|
||||||
|
},
|
||||||
|
// update auto draw state | 更新自动绘图状态
|
||||||
|
updateAutoDrawState() {
|
||||||
|
this.openAutoDraw = !this.openAutoDraw
|
||||||
|
},
|
||||||
|
// update font styles | 更新字体样式
|
||||||
|
updateFontStyles(type) {
|
||||||
|
const ind = this.fontStyles.findIndex(item => item === type)
|
||||||
|
if (ind >= 0) this.fontStyles.splice(ind, 1)
|
||||||
|
else this.fontStyles.push(type)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue