43 changed files with 14668 additions and 257 deletions
Binary file not shown.
Binary file not shown.
@ -0,0 +1,29 @@ |
|||
@font-face{ |
|||
font-family: '汉体'; |
|||
src : url('./cn/汉体.ttf'); |
|||
} |
|||
|
|||
@font-face{ |
|||
font-family: '华康金刚黑'; |
|||
src : url('./cn/华康金刚黑.ttf'); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,22 @@ |
|||
/* |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-05 22:54:14 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-05 22:59:30 |
|||
* @Description: 字体文件列表 |
|||
*/ |
|||
|
|||
const cnList = [ |
|||
{ |
|||
'name': '汉体', |
|||
'fontFamily': '汉体' |
|||
}, |
|||
{ |
|||
'name': '华康金刚黑', |
|||
'fontFamily': '华康金刚黑' |
|||
} |
|||
] |
|||
|
|||
const enList = [] |
|||
|
|||
export default [...cnList, ...enList] |
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,217 @@ |
|||
/** |
|||
* Should objects be aligned by a bounding box? |
|||
* [Bug] Scaled objects sometimes can not be aligned by edges |
|||
* https://github.com/fabricjs/fabric.js/blob/master/lib/aligning_guidelines.js
|
|||
*/ |
|||
function initAligningGuidelines(canvas) { |
|||
|
|||
var ctx = canvas.getSelectionContext(), |
|||
aligningLineOffset = 5, |
|||
aligningLineMargin = 4, |
|||
aligningLineWidth = 1, |
|||
aligningLineColor = 'rgb(0,255,0)', |
|||
viewportTransform, |
|||
zoom = 1; |
|||
|
|||
function drawVerticalLine(coords) { |
|||
drawLine( |
|||
coords.x + 0.5, |
|||
coords.y1 > coords.y2 ? coords.y2 : coords.y1, |
|||
coords.x + 0.5, |
|||
coords.y2 > coords.y1 ? coords.y2 : coords.y1); |
|||
} |
|||
|
|||
function drawHorizontalLine(coords) { |
|||
drawLine( |
|||
coords.x1 > coords.x2 ? coords.x2 : coords.x1, |
|||
coords.y + 0.5, |
|||
coords.x2 > coords.x1 ? coords.x2 : coords.x1, |
|||
coords.y + 0.5); |
|||
} |
|||
|
|||
function drawLine(x1, y1, x2, y2) { |
|||
ctx.save(); |
|||
ctx.lineWidth = aligningLineWidth; |
|||
ctx.strokeStyle = aligningLineColor; |
|||
ctx.beginPath(); |
|||
ctx.moveTo(((x1 + viewportTransform[4]) * zoom), ((y1 + viewportTransform[5]) * zoom)); |
|||
ctx.lineTo(((x2 + viewportTransform[4]) * zoom), ((y2 + viewportTransform[5]) * zoom)); |
|||
ctx.stroke(); |
|||
ctx.restore(); |
|||
} |
|||
|
|||
function isInRange(value1, value2) { |
|||
value1 = Math.round(value1); |
|||
value2 = Math.round(value2); |
|||
for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) { |
|||
if (i === value2) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
var verticalLines = [], |
|||
horizontalLines = []; |
|||
|
|||
canvas.on('mouse:down', function () { |
|||
viewportTransform = canvas.viewportTransform; |
|||
zoom = canvas.getZoom(); |
|||
}); |
|||
|
|||
canvas.on('object:moving', function (e) { |
|||
|
|||
var activeObject = e.target, |
|||
canvasObjects = canvas.getObjects(), |
|||
activeObjectCenter = activeObject.getCenterPoint(), |
|||
activeObjectLeft = activeObjectCenter.x, |
|||
activeObjectTop = activeObjectCenter.y, |
|||
activeObjectBoundingRect = activeObject.getBoundingRect(), |
|||
activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3], |
|||
activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0], |
|||
horizontalInTheRange = false, |
|||
verticalInTheRange = false, |
|||
transform = canvas._currentTransform; |
|||
|
|||
if (!transform) return; |
|||
|
|||
// It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
|
|||
// but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
|
|||
|
|||
for (var i = canvasObjects.length; i--;) { |
|||
|
|||
if (canvasObjects[i] === activeObject) continue; |
|||
|
|||
var objectCenter = canvasObjects[i].getCenterPoint(), |
|||
objectLeft = objectCenter.x, |
|||
objectTop = objectCenter.y, |
|||
objectBoundingRect = canvasObjects[i].getBoundingRect(), |
|||
objectHeight = objectBoundingRect.height / viewportTransform[3], |
|||
objectWidth = objectBoundingRect.width / viewportTransform[0]; |
|||
|
|||
// snap by the horizontal center line
|
|||
if (isInRange(objectLeft, activeObjectLeft)) { |
|||
verticalInTheRange = true; |
|||
verticalLines.push({ |
|||
x: objectLeft, |
|||
y1: (objectTop < activeObjectTop) |
|||
? (objectTop - objectHeight / 2 - aligningLineOffset) |
|||
: (objectTop + objectHeight / 2 + aligningLineOffset), |
|||
y2: (activeObjectTop > objectTop) |
|||
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) |
|||
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) |
|||
}); |
|||
activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), 'center', 'center'); |
|||
} |
|||
|
|||
// snap by the left edge
|
|||
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) { |
|||
verticalInTheRange = true; |
|||
verticalLines.push({ |
|||
x: objectLeft - objectWidth / 2, |
|||
y1: (objectTop < activeObjectTop) |
|||
? (objectTop - objectHeight / 2 - aligningLineOffset) |
|||
: (objectTop + objectHeight / 2 + aligningLineOffset), |
|||
y2: (activeObjectTop > objectTop) |
|||
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) |
|||
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) |
|||
}); |
|||
activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), 'center', 'center'); |
|||
} |
|||
|
|||
// snap by the right edge
|
|||
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) { |
|||
verticalInTheRange = true; |
|||
verticalLines.push({ |
|||
x: objectLeft + objectWidth / 2, |
|||
y1: (objectTop < activeObjectTop) |
|||
? (objectTop - objectHeight / 2 - aligningLineOffset) |
|||
: (objectTop + objectHeight / 2 + aligningLineOffset), |
|||
y2: (activeObjectTop > objectTop) |
|||
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) |
|||
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) |
|||
}); |
|||
activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), 'center', 'center'); |
|||
} |
|||
|
|||
// snap by the vertical center line
|
|||
if (isInRange(objectTop, activeObjectTop)) { |
|||
horizontalInTheRange = true; |
|||
horizontalLines.push({ |
|||
y: objectTop, |
|||
x1: (objectLeft < activeObjectLeft) |
|||
? (objectLeft - objectWidth / 2 - aligningLineOffset) |
|||
: (objectLeft + objectWidth / 2 + aligningLineOffset), |
|||
x2: (activeObjectLeft > objectLeft) |
|||
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) |
|||
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) |
|||
}); |
|||
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), 'center', 'center'); |
|||
} |
|||
|
|||
// snap by the top edge
|
|||
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) { |
|||
horizontalInTheRange = true; |
|||
horizontalLines.push({ |
|||
y: objectTop - objectHeight / 2, |
|||
x1: (objectLeft < activeObjectLeft) |
|||
? (objectLeft - objectWidth / 2 - aligningLineOffset) |
|||
: (objectLeft + objectWidth / 2 + aligningLineOffset), |
|||
x2: (activeObjectLeft > objectLeft) |
|||
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) |
|||
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) |
|||
}); |
|||
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), 'center', 'center'); |
|||
} |
|||
|
|||
// snap by the bottom edge
|
|||
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) { |
|||
horizontalInTheRange = true; |
|||
horizontalLines.push({ |
|||
y: objectTop + objectHeight / 2, |
|||
x1: (objectLeft < activeObjectLeft) |
|||
? (objectLeft - objectWidth / 2 - aligningLineOffset) |
|||
: (objectLeft + objectWidth / 2 + aligningLineOffset), |
|||
x2: (activeObjectLeft > objectLeft) |
|||
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) |
|||
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) |
|||
}); |
|||
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), 'center', 'center'); |
|||
} |
|||
} |
|||
|
|||
if (!horizontalInTheRange) { |
|||
horizontalLines.length = 0; |
|||
} |
|||
|
|||
if (!verticalInTheRange) { |
|||
verticalLines.length = 0; |
|||
} |
|||
}); |
|||
|
|||
canvas.on('before:render', function () { |
|||
// fix 保存图片时报错
|
|||
try { |
|||
canvas.clearContext(canvas.contextTop); |
|||
} catch (error) { |
|||
|
|||
} |
|||
}); |
|||
|
|||
canvas.on('after:render', function () { |
|||
for (var i = verticalLines.length; i--;) { |
|||
drawVerticalLine(verticalLines[i]); |
|||
} |
|||
for (var i = horizontalLines.length; i--;) { |
|||
drawHorizontalLine(horizontalLines[i]); |
|||
} |
|||
|
|||
verticalLines.length = horizontalLines.length = 0; |
|||
}); |
|||
|
|||
canvas.on('mouse:up', function () { |
|||
verticalLines.length = horizontalLines.length = 0; |
|||
canvas.renderAll(); |
|||
}); |
|||
} |
|||
export default initAligningGuidelines; |
@ -0,0 +1,60 @@ |
|||
/* |
|||
* @Author: 秦少卫 |
|||
* @Date: 2023-01-09 22:49:02 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2023-01-09 23:08:54 |
|||
* @Description: 控制条样式 |
|||
*/ |
|||
|
|||
import { fabric } from 'fabric'; |
|||
|
|||
function initControls(canvas) { |
|||
|
|||
var deleteIcon = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E"; |
|||
var img = document.createElement('img'); |
|||
img.src = deleteIcon; |
|||
fabric.Object.prototype.controls.deleteControl = new fabric.Control({ |
|||
x: 0.5, |
|||
y: -0.5, |
|||
offsetY: -16, |
|||
offsetX: 16, |
|||
cursorStyle: 'pointer', |
|||
mouseUpHandler: deleteObject, |
|||
render: renderIcon, |
|||
cornerSize: 24 |
|||
}); |
|||
|
|||
function deleteObject() { |
|||
const activeObject = canvas.getActiveObjects() |
|||
if (activeObject) { |
|||
activeObject.map(item => canvas.remove(item)) |
|||
canvas.requestRenderAll() |
|||
canvas.discardActiveObject() |
|||
} |
|||
} |
|||
|
|||
function renderIcon(ctx, left, top, styleOverride, fabricObject) { |
|||
var size = this.cornerSize; |
|||
ctx.save(); |
|||
ctx.translate(left, top); |
|||
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); |
|||
ctx.drawImage(img, -size / 2, -size / 2, size, size); |
|||
ctx.restore(); |
|||
} |
|||
|
|||
setControlsStyle() |
|||
} |
|||
|
|||
function setControlsStyle(){ |
|||
fabric.Object.prototype.transparentCorners = false; |
|||
fabric.Object.prototype.cornerSize = 10; |
|||
fabric.Object.prototype.cornerStrokeColor = '#C2C2C2'; |
|||
fabric.Object.prototype.cornerColor = '#ffffff'; |
|||
fabric.Object.prototype.cornerStyle = 'circle'; |
|||
fabric.Object.prototype.borderColor = '#85CCF9'; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
export default initControls |
@ -0,0 +1,89 @@ |
|||
/* |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-12-07 23:50:05 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2023-01-07 02:06:16 |
|||
* @Description: 快捷键功能 |
|||
*/ |
|||
|
|||
import hotkeys from 'hotkeys-js' |
|||
import { cloneDeep } from 'lodash-es' |
|||
import { v4 as uuid } from 'uuid' |
|||
import { Message } from 'view-design' |
|||
|
|||
const keyNames = { |
|||
lrdu: 'left,right,down,up', // 左右上下
|
|||
backspace: 'backspace', // backspace键盘
|
|||
ctrlz: 'ctrl+z', |
|||
ctrlc: 'ctrl+c', |
|||
ctrlv: 'ctrl+v' |
|||
} |
|||
|
|||
function initHotkeys(canvas) { |
|||
// 删除快捷键
|
|||
hotkeys(keyNames.backspace, function () { |
|||
const activeObject = canvas.getActiveObjects() |
|||
if (activeObject) { |
|||
activeObject.map(item => canvas.remove(item)) |
|||
canvas.requestRenderAll() |
|||
canvas.discardActiveObject() |
|||
} |
|||
}) |
|||
|
|||
// 移动快捷键
|
|||
hotkeys(keyNames.lrdu, (event, handler) => { |
|||
const activeObject = canvas.getActiveObject() |
|||
if (activeObject) { |
|||
switch (handler.key) { |
|||
case 'left': |
|||
activeObject.set('left', activeObject.left - 1 ) |
|||
break; |
|||
case 'right': |
|||
activeObject.set('left', activeObject.left + 1 ) |
|||
break; |
|||
case 'down': |
|||
activeObject.set('top', activeObject.top + 1 ) |
|||
break; |
|||
case 'up': |
|||
activeObject.set('top', activeObject.top - 1 ) |
|||
break; |
|||
default: |
|||
} |
|||
canvas.renderAll() |
|||
} |
|||
}) |
|||
|
|||
// 复制粘贴
|
|||
copyElement(canvas) |
|||
|
|||
} |
|||
|
|||
|
|||
function copyElement(canvas){ |
|||
let copyEl = null |
|||
|
|||
// 复制
|
|||
hotkeys(keyNames.ctrlc, (event, handler) => { |
|||
const activeObject = canvas.getActiveObjects() |
|||
if(activeObject.length === 0) return |
|||
copyEl = cloneDeep(activeObject[0]) |
|||
if(copyEl.left === activeObject[0].left) { |
|||
copyEl.left += 10 |
|||
copyEl.top += 10 |
|||
} |
|||
Message.success('复制成功') |
|||
}) |
|||
// 粘贴
|
|||
hotkeys(keyNames.ctrlv, (event, handler) => { |
|||
if(!copyEl) return Message.warning('暂无复制内容') |
|||
const myCopyEl = cloneDeep(copyEl) |
|||
myCopyEl.id = uuid() |
|||
copyEl.left += 10 |
|||
copyEl.top += 10 |
|||
canvas.add(myCopyEl) |
|||
canvas.setActiveObject(myCopyEl) |
|||
}) |
|||
} |
|||
|
|||
export default initHotkeys |
|||
export { keyNames, hotkeys } |
@ -0,0 +1,89 @@ |
|||
/* |
|||
* @Author: 秦少卫 |
|||
* @Date: 2023-01-06 23:40:09 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2023-01-07 01:35:50 |
|||
* @Description: 线条绘制 |
|||
*/ |
|||
|
|||
import { v4 as uuid } from 'uuid'; |
|||
import Arrow from "@/core/objects/Arrow"; |
|||
function initializeLineDrawing (canvas, defaultPosition) { |
|||
let isDrawingLine = false,isDrawingLineMode = false, isArrow = false, |
|||
lineToDraw, pointer, pointerPoints |
|||
canvas.on('mouse:down', (o) => { |
|||
if (!isDrawingLineMode) return |
|||
|
|||
isDrawingLine = true |
|||
pointer = canvas.getPointer(o.e) |
|||
pointerPoints = [pointer.x, pointer.y, pointer.x, pointer.y] |
|||
|
|||
|
|||
const nodeHandler = isArrow ? Arrow : fabric.Line |
|||
lineToDraw = new nodeHandler(pointerPoints, { |
|||
strokeWidth: 2, |
|||
stroke: '#000000', |
|||
...defaultPosition, |
|||
id: uuid(), |
|||
}); |
|||
|
|||
lineToDraw.selectable = false |
|||
lineToDraw.evented = false |
|||
lineToDraw.strokeUniform = true |
|||
canvas.add(lineToDraw) |
|||
}); |
|||
|
|||
canvas.on('mouse:move', (o) => { |
|||
if (!isDrawingLine) return |
|||
|
|||
pointer = canvas.getPointer(o.e) |
|||
|
|||
if (o.e.shiftKey) { |
|||
// calc angle
|
|||
let startX = pointerPoints[0] |
|||
let startY = pointerPoints[1] |
|||
let x2 = pointer.x - startX |
|||
let y2 = pointer.y - startY |
|||
let r = Math.sqrt(x2 * x2 + y2 * y2) |
|||
let angle = (Math.atan2(y2, x2) / Math.PI * 180) |
|||
|
|||
angle = parseInt(((angle + 7.5) % 360) / 15) * 15 |
|||
|
|||
let cosx = r * Math.cos(angle * Math.PI / 180) |
|||
let sinx = r * Math.sin(angle * Math.PI / 180) |
|||
|
|||
lineToDraw.set({ |
|||
x2: cosx + startX, |
|||
y2: sinx + startY |
|||
}) |
|||
|
|||
} else { |
|||
lineToDraw.set({ |
|||
x2: pointer.x, |
|||
y2: pointer.y |
|||
}) |
|||
} |
|||
|
|||
canvas.renderAll() |
|||
|
|||
}); |
|||
|
|||
canvas.on('mouse:up', () => { |
|||
if (!isDrawingLine) return |
|||
lineToDraw.setCoords() |
|||
isDrawingLine = false |
|||
}); |
|||
|
|||
return { |
|||
setArrow(params){ |
|||
isArrow = params |
|||
}, |
|||
setMode(params){ |
|||
isDrawingLineMode = params |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
export default initializeLineDrawing; |
@ -0,0 +1,46 @@ |
|||
/* |
|||
* @Author: 秦少卫 |
|||
* @Date: 2023-01-07 01:15:50 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2023-01-07 01:16:07 |
|||
* @Description: 箭头元素 |
|||
*/ |
|||
import { fabric } from 'fabric'; |
|||
|
|||
const Arrow = fabric.util.createClass(fabric.Line, { |
|||
type: 'arrow', |
|||
superType: 'drawing', |
|||
initialize(points, options) { |
|||
if (!points) { |
|||
const { x1, x2, y1, y2 } = options; |
|||
points = [x1, y1, x2, y2]; |
|||
} |
|||
options = options || {}; |
|||
this.callSuper('initialize', points, options); |
|||
}, |
|||
_render(ctx) { |
|||
this.callSuper('_render', ctx); |
|||
ctx.save(); |
|||
const xDiff = this.x2 - this.x1; |
|||
const yDiff = this.y2 - this.y1; |
|||
const angle = Math.atan2(yDiff, xDiff); |
|||
ctx.translate((this.x2 - this.x1) / 2, (this.y2 - this.y1) / 2); |
|||
ctx.rotate(angle); |
|||
ctx.beginPath(); |
|||
// Move 5px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
|
|||
ctx.moveTo(5, 0); |
|||
ctx.lineTo(-5, 5); |
|||
ctx.lineTo(-5, -5); |
|||
ctx.closePath(); |
|||
ctx.fillStyle = this.stroke; |
|||
ctx.fill(); |
|||
ctx.restore(); |
|||
}, |
|||
}); |
|||
|
|||
Arrow.fromObject = (options, callback) => { |
|||
const { x1, x2, y1, y2 } = options; |
|||
return callback(new Arrow([x1, y1, x2, y2], options)); |
|||
}; |
|||
|
|||
export default Arrow; |
@ -0,0 +1,80 @@ |
|||
|
|||
export default { |
|||
inject: ['canvas', 'fabric', 'event'], |
|||
data() { |
|||
return { |
|||
mSelectMode: '', // one | multiple
|
|||
mSelectOneType: '', // i-text | group ...
|
|||
mSelectId: '', // 选择id
|
|||
mSelectIds: [] // 选择id
|
|||
} |
|||
}, |
|||
created() { |
|||
this.event.on('selectOne', (e) => { |
|||
console.log('selectOne', e) |
|||
this.mSelectMode = 'one' |
|||
this.mSelectId = e[0].id |
|||
this.mSelectOneType = e[0].type |
|||
// console.log(e[0].id)
|
|||
// console.log(e[0].type)
|
|||
this.mSelectIds = e.map(item => item.id) |
|||
}) |
|||
|
|||
this.event.on('selectMultiple', (e) => { |
|||
console.log('selectMultiple') |
|||
this.mSelectMode = 'multiple' |
|||
this.mSelectId = '' |
|||
this.mSelectIds = e.map(item => item.id) |
|||
}) |
|||
|
|||
this.event.on('selectCancel', () => { |
|||
console.log('selectCancel') |
|||
this.mSelectId = '' |
|||
this.mSelectIds = [] |
|||
this.mSelectMode = '' |
|||
this.mSelectOneType = '' |
|||
}) |
|||
}, |
|||
methods: { |
|||
/** |
|||
* @description: 保存data数据 |
|||
* @param {Object} data 房间详情数据 |
|||
*/ |
|||
_mixinSelected({ event, selected }) { |
|||
if (selected.length === 1) { |
|||
const selectItem = selected[0] |
|||
this.mSelectMode = 'one' |
|||
this.mSelectOneType = selectItem.type |
|||
this.mSelectId = [selectItem.id] |
|||
this.mSelectActive = [selectItem] |
|||
} else if (selected.length > 1) { |
|||
this.mSelectMode = 'multiple' |
|||
this.mSelectActive = selected |
|||
this.mSelectId = selected.map(item => item.id) |
|||
} else { |
|||
this._mixinCancel() |
|||
} |
|||
}, |
|||
/** |
|||
* @description: 保存data数据 |
|||
* @param {Object} data 房间详情数据 |
|||
*/ |
|||
_mixinCancel(data) { |
|||
this.mSelectMode = '' |
|||
this.mSelectId = [] |
|||
this.mSelectActive = [] |
|||
this.mSelectOneType = '' |
|||
}, |
|||
/** |
|||
* @description: 复制到剪切板 |
|||
* @param {String} clipboardText 复制内容 |
|||
*/ |
|||
_mixinClipboard(clipboardText) { |
|||
this.$copyText(clipboardText).then(() => { |
|||
this.$Message.success('复制成功') |
|||
}, () => { |
|||
this.$Message.error('复制失败') |
|||
}) |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,248 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2023-01-07 02:19:20 |
|||
* @Description: 组合元素对齐 |
|||
--> |
|||
|
|||
<template> |
|||
<ButtonGroup size="small"> |
|||
<!-- 水平对齐 --> |
|||
<Button :disabled="notMultiple()" @click="left"><svg t="1650442284704" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2345" width="14" height="14"><path d="M80 24h64v976H80zM198 227h448v190H198zM198 607h746v190H198z" p-id="2346" /></svg></Button> |
|||
<Button :disabled="notMultiple()" @click="xcenter"><svg t="1650442754876" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1514" width="14" height="14"><path d="M477.312 576V448H266.688a32 32 0 0 1-32-32v-192a32 32 0 0 1 32-32h210.624V34.688a34.688 34.688 0 0 1 69.376 0V192h210.624a32 32 0 0 1 32 32v192a32 32 0 0 1-32 32H546.688v128H896a32 32 0 0 1 32 32v192a32 32 0 0 1-32 32H546.688v157.312a34.688 34.688 0 1 1-69.376 0V832H128a32 32 0 0 1-32-32v-192A32 32 0 0 1 128 576h349.312z" fill="#666666" p-id="1515" /></svg></Button> |
|||
<Button :disabled="notMultiple()" @click="right"><svg t="1650442299564" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2543" width="14" height="14"><path d="M944 1000h-64V24h64zM826 417H378V227h448zM826 797H80V607h746z" p-id="2544" /></svg></Button> |
|||
<!-- 垂直对齐 --> |
|||
<Button :disabled="notMultiple()" @click="top"><svg t="1650442692910" class="icon" viewBox="0 0 1170 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1118" width="14" height="14"><path d="M1170.285714 36.571429a36.571429 36.571429 0 0 1-36.571428 36.571428H36.571429a36.571429 36.571429 0 0 1 0-73.142857h1097.142857a36.571429 36.571429 0 0 1 36.571428 36.571429z m-219.428571 146.285714v512a36.571429 36.571429 0 0 1-36.571429 36.571428h-219.428571a36.571429 36.571429 0 0 1-36.571429-36.571428v-512a36.571429 36.571429 0 0 1 36.571429-36.571429h219.428571a36.571429 36.571429 0 0 1 36.571429 36.571429z m-438.857143 0v804.571428a36.571429 36.571429 0 0 1-36.571429 36.571429h-219.428571a36.571429 36.571429 0 0 1-36.571429-36.571429v-804.571428a36.571429 36.571429 0 0 1 36.571429-36.571429h219.428571a36.571429 36.571429 0 0 1 36.571429 36.571429z" fill="#666666" p-id="1119" /></svg></Button> |
|||
<Button :disabled="notMultiple()" @click="ycenter"><svg t="1650442732396" class="icon" viewBox="0 0 1243 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1316" width="14" height="14"><path d="M548.571429 472.356571h146.285714V231.643429a36.571429 36.571429 0 0 1 36.571428-36.571429h219.428572a36.571429 36.571429 0 0 1 36.571428 36.571429v240.713142h179.785143a39.643429 39.643429 0 0 1 0 79.286858H987.428571v240.713142a36.571429 36.571429 0 0 1-36.571428 36.571429h-219.428572a36.571429 36.571429 0 0 1-36.571428-36.571429V551.643429h-146.285714V950.857143a36.571429 36.571429 0 0 1-36.571429 36.571428H292.571429a36.571429 36.571429 0 0 1-36.571429-36.571428V551.643429H76.214857a39.643429 39.643429 0 1 1 0-79.286858H256V73.142857A36.571429 36.571429 0 0 1 292.571429 36.571429h219.428571a36.571429 36.571429 0 0 1 36.571429 36.571428v399.213714z" fill="#666666" p-id="1317" /></svg></Button> |
|||
<Button :disabled="notMultiple()" @click="bottom"><svg t="1650442674784" class="icon" viewBox="0 0 1170 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="920" width="14" height="14"><path d="M1170.285714 987.428571a36.571429 36.571429 0 0 0-36.571428-36.571428H36.571429a36.571429 36.571429 0 0 0 0 73.142857h1097.142857a36.571429 36.571429 0 0 0 36.571428-36.571429z m-219.428571-146.285714v-512a36.571429 36.571429 0 0 0-36.571429-36.571428h-219.428571a36.571429 36.571429 0 0 0-36.571429 36.571428v512a36.571429 36.571429 0 0 0 36.571429 36.571429h219.428571a36.571429 36.571429 0 0 0 36.571429-36.571429z m-438.857143 0V36.571429a36.571429 36.571429 0 0 0-36.571429-36.571429h-219.428571a36.571429 36.571429 0 0 0-36.571429 36.571429v804.571428a36.571429 36.571429 0 0 0 36.571429 36.571429h219.428571a36.571429 36.571429 0 0 0 36.571429-36.571429z" fill="#666666" p-id="921" /></svg></Button> |
|||
<!-- 平均对齐 --> |
|||
<Button :disabled="notMultiple()" @click="xequation"><svg t="1650442800956" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1910" width="14" height="14"><path d="M96 0a32 32 0 0 1 32 32v960a32 32 0 0 1-64 0V32A32 32 0 0 1 96 0z m832 0a32 32 0 0 1 32 32v960a32 32 0 0 1-64 0V32a32 32 0 0 1 32-32zM384 800v-576a32 32 0 0 1 32-32h192a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32h-192a32 32 0 0 1-32-32z" fill="#515151" p-id="1911" /></svg></Button> |
|||
<Button :disabled="notMultiple()" @click="yequation"><svg t="1650442784286" class="icon" viewBox="0 0 1170 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1712" width="14" height="14"><path d="M1170.285714 36.571429a36.571429 36.571429 0 0 1-36.571428 36.571428H36.571429a36.571429 36.571429 0 0 1 0-73.142857h1097.142857a36.571429 36.571429 0 0 1 36.571428 36.571429z m0 950.857142a36.571429 36.571429 0 0 1-36.571428 36.571429H36.571429a36.571429 36.571429 0 0 1 0-73.142857h1097.142857a36.571429 36.571429 0 0 1 36.571428 36.571428zM256 365.714286h658.285714a36.571429 36.571429 0 0 1 36.571429 36.571428v219.428572a36.571429 36.571429 0 0 1-36.571429 36.571428h-658.285714a36.571429 36.571429 0 0 1-36.571429-36.571428v-219.428572a36.571429 36.571429 0 0 1 36.571429-36.571428z" fill="#515151" p-id="1713" /></svg></Button> |
|||
</ButtonGroup> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
|
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
id: '', |
|||
list: [] |
|||
} |
|||
}, |
|||
methods: { |
|||
// 非多选时,禁止组合对齐操作 |
|||
notMultiple() { |
|||
return this.mSelectMode !== 'multiple' |
|||
}, |
|||
// 左对齐 |
|||
left() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject && activeObject.type === 'activeSelection') { |
|||
const activeSelection = activeObject |
|||
const activeObjectLeft = -(activeObject.width / 2) |
|||
activeSelection.forEachObject(item => { |
|||
item.set({ |
|||
left: activeObjectLeft |
|||
}) |
|||
item.setCoords() |
|||
this.canvas.c.renderAll() |
|||
}) |
|||
} |
|||
}, |
|||
// 右对齐 |
|||
right() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject && activeObject.type === 'activeSelection') { |
|||
const activeSelection = activeObject |
|||
const activeObjectLeft = (activeObject.width / 2) |
|||
activeSelection.forEachObject(item => { |
|||
item.set({ |
|||
left: activeObjectLeft - (item.width * item.scaleX) |
|||
}) |
|||
item.setCoords() |
|||
this.canvas.c.renderAll() |
|||
}) |
|||
} |
|||
}, |
|||
// 水平居中对齐 |
|||
xcenter() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject && activeObject.type === 'activeSelection') { |
|||
const activeSelection = activeObject |
|||
activeSelection.forEachObject(item => { |
|||
item.set({ |
|||
left: 0 - ((item.width * item.scaleX) / 2) |
|||
}) |
|||
item.setCoords() |
|||
this.canvas.c.renderAll() |
|||
}) |
|||
} |
|||
}, |
|||
// 垂直居中对齐 |
|||
ycenter() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject && activeObject.type === 'activeSelection') { |
|||
const activeSelection = activeObject |
|||
activeSelection.forEachObject(item => { |
|||
item.set({ |
|||
top: 0 - ((item.height * item.scaleY) / 2) |
|||
}) |
|||
item.setCoords() |
|||
this.canvas.c.renderAll() |
|||
}) |
|||
} |
|||
}, |
|||
// 顶部对齐 |
|||
top() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject && activeObject.type === 'activeSelection') { |
|||
const activeSelection = activeObject |
|||
const activeObjectTop = -(activeObject.height / 2) |
|||
activeSelection.forEachObject(item => { |
|||
item.set({ |
|||
top: activeObjectTop |
|||
}) |
|||
item.setCoords() |
|||
this.canvas.c.renderAll() |
|||
}) |
|||
} |
|||
}, |
|||
// 底部对齐 |
|||
bottom() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject && activeObject.type === 'activeSelection') { |
|||
const activeSelection = activeObject |
|||
const activeObjectTop = (activeObject.height / 2) |
|||
activeSelection.forEachObject(item => { |
|||
item.set({ |
|||
top: activeObjectTop - (item.height * item.scaleY) |
|||
}) |
|||
item.setCoords() |
|||
this.canvas.c.renderAll() |
|||
}) |
|||
} |
|||
}, |
|||
// 水平平均对齐 |
|||
xequation() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject && activeObject.type === 'activeSelection') { |
|||
const activeSelection = activeObject |
|||
// 排序 |
|||
activeSelection._objects.sort((a, b) => { |
|||
return a.left - b.left |
|||
}) |
|||
|
|||
// 平均间距计算 |
|||
const itemSpac = spacWidth() |
|||
// 组原点高度 |
|||
const yHeight = (activeObject.width / 2) |
|||
|
|||
activeObject.forEachObject((item, i) => { |
|||
// 获取当前元素之前所有元素的高度 |
|||
const preHeight = getItemLeft(i) |
|||
// 顶部距离 间距 * 索引 + 之前元素高度 - 原点高度 |
|||
const top = itemSpac * i + preHeight - yHeight |
|||
item.set('left', top) |
|||
}) |
|||
this.canvas.c.renderAll() |
|||
} |
|||
|
|||
// 获取平均间距 |
|||
function spacWidth() { |
|||
const count = getAllItemHeight() |
|||
const allSpac = activeObject.width - count |
|||
return allSpac / (activeObject._objects.length - 1) |
|||
} |
|||
|
|||
// 获取所有元素高度 |
|||
function getAllItemHeight() { |
|||
let count = 0 |
|||
activeObject.forEachObject(item => { |
|||
count += getItemWidth(item) |
|||
}) |
|||
return count |
|||
} |
|||
|
|||
// width属性不准确,需要坐标换算 |
|||
function getItemWidth(item) { |
|||
return item.aCoords.tr.x - item.aCoords.tl.x |
|||
} |
|||
// 获取当前元素之前所有元素的高度 |
|||
function getItemLeft(i) { |
|||
if (i === 0) return 0 |
|||
let width = 0 |
|||
for (let index = 0; index < i; index++) { |
|||
width += getItemWidth(activeObject._objects[index]) |
|||
} |
|||
return width |
|||
} |
|||
}, |
|||
// 垂直平均对齐 |
|||
yequation() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject && activeObject.type === 'activeSelection') { |
|||
const activeSelection = activeObject |
|||
// 排序 |
|||
activeSelection._objects.sort((a, b) => { |
|||
return a.top - b.top |
|||
}) |
|||
|
|||
// 平均间距计算 |
|||
const itemSpac = spacHeight() |
|||
// 组原点高度 |
|||
const yHeight = (activeObject.height / 2) |
|||
|
|||
activeObject.forEachObject((item, i) => { |
|||
// 获取当前元素之前所有元素的高度 |
|||
const preHeight = getItemTop(i) |
|||
// 顶部距离 间距 * 索引 + 之前元素高度 - 原点高度 |
|||
const top = itemSpac * i + preHeight - yHeight |
|||
item.set('top', top) |
|||
}) |
|||
this.canvas.c.renderAll() |
|||
} |
|||
|
|||
// 获取平均间距 |
|||
function spacHeight() { |
|||
const count = getAllItemHeight() |
|||
const allSpac = activeObject.height - count |
|||
return allSpac / (activeObject._objects.length - 1) |
|||
} |
|||
|
|||
// 获取所有元素高度 |
|||
function getAllItemHeight() { |
|||
let count = 0 |
|||
activeObject.forEachObject(item => { |
|||
count += getItemHeight(item) |
|||
}) |
|||
return count |
|||
} |
|||
|
|||
// width属性不准确,需要坐标换算 |
|||
function getItemHeight(item) { |
|||
return item.aCoords.bl.y - item.aCoords.tl.y |
|||
} |
|||
// 获取当前元素之前所有元素的高度 |
|||
function getItemTop(i) { |
|||
if (i === 0) return 0 |
|||
let height = 0 |
|||
for (let index = 0; index < i; index++) { |
|||
height += getItemHeight(activeObject._objects[index]) |
|||
} |
|||
return height |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
/deep/ .ivu-btn[disabled]{ |
|||
svg{ opacity: 0.2;} |
|||
} |
|||
</style> |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,149 @@ |
|||
<template> |
|||
<div> |
|||
<el-divider orientation="left" plain>背景</el-divider> |
|||
<div> |
|||
<img |
|||
ref="od" |
|||
src="@/assets/img/od.png" |
|||
class="img" |
|||
alt="" |
|||
@click="(e) => setBgImg(e.target)" |
|||
> |
|||
<img |
|||
ref="os" |
|||
src="@/assets/img/os.png" |
|||
class="img" |
|||
alt="" |
|||
@click="(e) => setBgImg(e.target)" |
|||
> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getImgStr } from '@/utils/utils' |
|||
export default { |
|||
name: 'BgBar', |
|||
inject: ['canvas', 'fabric'], |
|||
props: { |
|||
isOdOrOs: { |
|||
type: String |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
showModal: false, |
|||
color: '', |
|||
imgFile: '' |
|||
} |
|||
}, |
|||
created() { |
|||
if (this.isOdOrOs === 'OD') { |
|||
this.$nextTick(() => { |
|||
this.setBgImg(this.$refs.od) |
|||
}) |
|||
} else { |
|||
this.$nextTick(() => { |
|||
this.setBgImg(this.$refs.os) |
|||
}) |
|||
} |
|||
}, |
|||
methods: { |
|||
// 设置背景图片 |
|||
setBgImg(target) { |
|||
const imgEl = target.cloneNode(true) |
|||
// console.log(imgEl) |
|||
imgEl.onload = () => { |
|||
// 可跨域设置 |
|||
const imgInstance = new this.fabric.Image(imgEl, { crossOrigin: 'anonymous' }) |
|||
// console.log(imgInstance) |
|||
// 渲染背景 |
|||
this.canvas.c.setBackgroundImage(imgInstance, this.canvas.c.renderAll.bind(this.canvas.c), { |
|||
scaleX: this.canvas.c.width / imgInstance.width, |
|||
scaleY: this.canvas.c.width / imgInstance.width |
|||
}) |
|||
this.canvas.c.renderAll() |
|||
this.canvas.c.requestRenderAll() |
|||
} |
|||
}, |
|||
// 清空文件 展示文件框 |
|||
insert() { |
|||
this.imgFile = '' |
|||
this.showModal = true |
|||
}, |
|||
// 确认插入图片 |
|||
insertImgFile() { |
|||
if (this.imgFile === '') { |
|||
return this.$Message.error('请选择文件') |
|||
} |
|||
const imgEl = document.createElement('img') |
|||
imgEl.src = this.imgFile |
|||
// 插入页面 |
|||
document.body.appendChild(imgEl) |
|||
imgEl.onload = () => { |
|||
this.setBgImg(imgEl) |
|||
imgEl.remove() |
|||
} |
|||
}, |
|||
// 文件选择 |
|||
handleUpload(file) { |
|||
getImgStr(file).then(res => { |
|||
this.imgFile = res |
|||
}) |
|||
}, |
|||
// 背景颜色设置 |
|||
setThisColor() { |
|||
this.setColor(this.color) |
|||
}, |
|||
// 背景颜色设置 |
|||
setColor(color) { |
|||
this.canvas.c.setBackgroundColor(color, this.canvas.c.renderAll.bind(this.canvas.c)) |
|||
this.canvas.c.backgroundImage = '' |
|||
this.canvas.c.renderAll() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<!-- Add "scoped" attribute to limit CSS to this component only --> |
|||
<style lang="less"> |
|||
.ivu-select-dropdown { |
|||
z-index: 9999 !important; |
|||
} |
|||
</style> |
|||
<style scoped lang="less"> |
|||
/deep/ .ivu-form-item { |
|||
margin-bottom: 0; |
|||
} |
|||
.img { |
|||
width: 60px; |
|||
padding: 5px; |
|||
background: #f5f5f5; |
|||
margin-left: 2px; |
|||
height: 70px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.color-list { |
|||
padding: 10px 0; |
|||
.item { |
|||
padding-bottom: 5px; |
|||
} |
|||
span { |
|||
display: inline-block; |
|||
margin-left: 6px; |
|||
background: #f5f5f5; |
|||
height: 20px; |
|||
width: 20px; |
|||
font-size: 12px; |
|||
line-height: 20px; |
|||
vertical-align: middle; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
|
|||
/deep/ .ivu-divider-plain.ivu-divider-with-text-left { |
|||
margin: 10px 0; |
|||
font-weight: bold; |
|||
} |
|||
</style> |
@ -0,0 +1,39 @@ |
|||
<template> |
|||
<div class="canvas-set"> |
|||
<div style="display: inline-block; margin-left: 10px"> |
|||
<button id="drawing-mode" class="btn btn-info">Cancel drawing mode</button><br> |
|||
<button id="clear-canvas" class="btn btn-info">Clear</button><br> |
|||
|
|||
<div id="drawing-mode-options"> |
|||
<label for="drawing-mode-selector">Mode:</label> |
|||
<select id="drawing-mode-selector"> |
|||
<option>Pencil</option> |
|||
<option>Circle</option> |
|||
<option>Spray</option> |
|||
<option>Pattern</option> |
|||
|
|||
<option>hline</option> |
|||
<option>vline</option> |
|||
<option>square</option> |
|||
<option>diamond</option> |
|||
<option>texture</option> |
|||
</select><br> |
|||
|
|||
<label for="drawing-line-width">Line width:</label> |
|||
<span class="info">30</span><input id="drawing-line-width" type="range" value="30" min="0" max="150"><br> |
|||
|
|||
<label for="drawing-color">Line color:</label> |
|||
<input id="drawing-color" type="color" value="#005E7A"><br> |
|||
|
|||
<label for="drawing-shadow-color">Shadow color:</label> |
|||
<input id="drawing-shadow-color" type="color" value="#005E7A"><br> |
|||
|
|||
<label for="drawing-shadow-width">Shadow width:</label> |
|||
<span class="info">0</span><input id="drawing-shadow-width" type="range" value="0" min="0" max="50"><br> |
|||
|
|||
<label for="drawing-shadow-offset">Shadow offset:</label> |
|||
<span class="info">0</span><input id="drawing-shadow-offset" type="range" value="0" min="0" max="50"><br> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,50 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-03 23:55:33 |
|||
* @Description: 多元素或单元素对齐方式 |
|||
--> |
|||
|
|||
<template> |
|||
<ButtonGroup size="small"> |
|||
<!-- 水平集中 --> |
|||
<Button :disabled="!mSelectMode" @click="position('centerH')"><svg t="1650442559691" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3787" width="14" height="14"><path d="M885 607H544V417h192V227H544V24h-64v203H288v190h192v190H139v190h341v203h64V797h341z" p-id="3788" /></svg></Button> |
|||
<!-- 水平垂直居中 --> |
|||
<Button :disabled="!mSelectMode" @click="position('center')"><svg t="1650852784867" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2351" width="14" height="14"><path d="M544 480V64h-64v416H64v64h416v416h64V544h416v-64z" fill="#727272" p-id="2352" /><path d="M123.7 241.1h119.5v64H123.7zM302.9 241.1h119.5v64H302.9zM601.6 241.1h119.5v64H601.6zM780.8 241.1h119.5v64H780.8zM123.7 718.9h119.5v64H123.7zM302.9 718.9h119.5v64H302.9zM601.6 718.9h119.5v64H601.6zM780.8 718.9h119.5v64H780.8z" fill="#B2B2B2" p-id="2353" /></svg></Button> |
|||
<!-- 垂直居中 --> |
|||
<Button :disabled="!mSelectMode" @click="position('centerV')"><svg t="1650442510967" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3412" width="14" height="14"><path d="M859.9 474H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8z m-353.6-74.7c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7 0.4-11.7-5.7-11.7H550V104c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v156h-62.8c-6 0-9.4 7-5.7 11.7l100.8 127.6z m11.4 225.4c-2.9-3.7-8.5-3.7-11.3 0L405.6 752.3c-3.7 4.7-0.4 11.7 5.7 11.7H474v156c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V764h62.8c6 0 9.4-7 5.7-11.7L517.7 624.7z" p-id="3413" /></svg></Button> |
|||
</ButtonGroup> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
|
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
} |
|||
}, |
|||
created() { |
|||
}, |
|||
methods: { |
|||
position(name) { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
if (activeObject) { |
|||
activeObject[name]() |
|||
this.canvas.c.renderAll() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style scoped lang="less"> |
|||
/deep/ .ivu-btn[disabled]{ |
|||
svg{ opacity: 0.2;} |
|||
} |
|||
svg{ |
|||
vertical-align: text-bottom; |
|||
} |
|||
</style> |
@ -0,0 +1,38 @@ |
|||
<template> |
|||
<Button v-if="mSelectMode === 'one'" icon="ios-copy" size="small" @click="clone" /> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
import { v4 as uuid } from 'uuid' |
|||
|
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
} |
|||
}, |
|||
methods: { |
|||
clone() { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
activeObject.clone(cloned => { |
|||
this.canvas.c.discardActiveObject() |
|||
// 间距设置 |
|||
const grid = 10 |
|||
cloned.set({ |
|||
left: cloned.left + grid, |
|||
top: cloned.top + grid, |
|||
id: uuid() |
|||
}) |
|||
this.canvas.c.add(cloned) |
|||
this.canvas.c.setActiveObject(cloned) |
|||
this.canvas.c.requestRenderAll() |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
</style> |
@ -0,0 +1,35 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-03 22:35:59 |
|||
* @Description: 删除元素按钮 |
|||
--> |
|||
|
|||
<template> |
|||
<Button v-if="mSelectMode" icon="ios-trash" size="small" @click="del" /> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
|
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
} |
|||
}, |
|||
methods: { |
|||
del() { |
|||
const activeObject = this.canvas.c.getActiveObjects() |
|||
activeObject && activeObject.map(item => this.canvas.c.remove(item)) |
|||
this.canvas.c.requestRenderAll() |
|||
this.canvas.c.discardActiveObject() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
</style> |
@ -0,0 +1,43 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-12-07 13:28:38 |
|||
* @Description: 元素翻转 |
|||
--> |
|||
|
|||
<template> |
|||
<ButtonGroup size="small"> |
|||
<Button :disabled="notSelectOneMode()" @click="flip('X')"><svg t="1650443094178" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1549" width="14" height="14"><path d="M252.76928 299.904l146.2784 0 0 472.42752-146.2784 0 0-472.42752Z" p-id="1550" /><path d="M477.48096 85.34528l70.87104 0 0 885.80608-70.87104 0 0-885.80608Z" p-id="1551" /><path d="M629.80096 284.8l31.0016 0 0 502.88128-31.0016 0L629.80096 284.8zM776.42752 284.8l31.0016 0 0 502.88128-31.0016 0L776.42752 284.8zM657.09056 315.8016l0-31.0016 123.04896 0 0 31.0016L657.09056 315.8016zM657.27488 787.64544l0-31.0016 123.04896 0 0 31.0016L657.27488 787.64544z" p-id="1552" /></svg></Button> |
|||
<Button :disabled="notSelectOneMode()" @click="flip('Y')"><svg t="1650443104385" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1749" width="14" height="14"><path d="M286.01856 250.91584l472.4224 0 0 146.2784-472.4224 0 0-146.2784Z" p-id="1750" /><path d="M87.19872 475.62752l885.80096 0 0 70.87104-885.80096 0 0-70.87104Z" p-id="1751" /><path d="M773.55008 627.94752l0 31.0016L270.6688 658.94912l0-31.0016L773.55008 627.94752zM773.55008 774.5792l0 31.0016L270.6688 805.5808l0-31.0016L773.55008 774.5792zM742.54848 655.24224l31.0016 0 0 123.04896-31.0016 0L742.54848 655.24224zM270.70464 655.42144l31.0016 0 0 123.04896-31.0016 0L270.70464 655.42144z" p-id="1752" /></svg></Button> |
|||
</ButtonGroup> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
} |
|||
}, |
|||
methods: { |
|||
// 非单选时,禁止镜像操作 |
|||
notSelectOneMode() { |
|||
return this.mSelectMode !== 'one' |
|||
}, |
|||
flip(type) { |
|||
const activeObject = this.canvas.c.getActiveObject() |
|||
activeObject.set('flip' + type, !activeObject['flip' + type]).setCoords() |
|||
this.canvas.c.requestRenderAll() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
/deep/ .ivu-btn[disabled]{ |
|||
svg{ opacity: 0.2;} |
|||
} |
|||
</style> |
@ -0,0 +1,69 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-12-07 13:34:56 |
|||
* @Description: 组合与拆分组合 |
|||
--> |
|||
|
|||
<template> |
|||
<ButtonGroup size="small"> |
|||
<!-- 组合按钮 多选时不可用 --> |
|||
<Button :disabled="!isMultiple" @click="group"><svg t="1650848913991" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17131" width="14" height="14"><path d="M341.333333 341.333333 341.333333 512 554.666667 512 554.666667 341.333333 341.333333 341.333333M42.666667 42.666667 213.333333 42.666667 213.333333 85.333333 810.666667 85.333333 810.666667 42.666667 981.333333 42.666667 981.333333 213.333333 938.666667 213.333333 938.666667 810.666667 981.333333 810.666667 981.333333 981.333333 810.666667 981.333333 810.666667 938.666667 213.333333 938.666667 213.333333 981.333333 42.666667 981.333333 42.666667 810.666667 85.333333 810.666667 85.333333 213.333333 42.666667 213.333333 42.666667 42.666667M213.333333 810.666667 213.333333 853.333333 810.666667 853.333333 810.666667 810.666667 853.333333 810.666667 853.333333 213.333333 810.666667 213.333333 810.666667 170.666667 213.333333 170.666667 213.333333 213.333333 170.666667 213.333333 170.666667 810.666667 213.333333 810.666667M256 256 640 256 640 426.666667 768 426.666667 768 768 341.333333 768 341.333333 597.333333 256 597.333333 256 256M640 597.333333 426.666667 597.333333 426.666667 682.666667 682.666667 682.666667 682.666667 512 640 512 640 597.333333Z" p-id="17132" /></svg></Button> |
|||
<!-- 拆分组合按钮,为单选且组元素时可用 --> |
|||
<Button :disabled="!isGroup" @click="unGroup"><svg t="1650848938557" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17281" width="14" height="14"><path d="M85.333333 85.333333 256 85.333333 256 128 554.666667 128 554.666667 85.333333 725.333333 85.333333 725.333333 256 682.666667 256 682.666667 384 768 384 768 341.333333 938.666667 341.333333 938.666667 512 896 512 896 768 938.666667 768 938.666667 938.666667 768 938.666667 768 896 512 896 512 938.666667 341.333333 938.666667 341.333333 768 384 768 384 682.666667 256 682.666667 256 725.333333 85.333333 725.333333 85.333333 554.666667 128 554.666667 128 256 85.333333 256 85.333333 85.333333M768 512 768 469.333333 682.666667 469.333333 682.666667 554.666667 725.333333 554.666667 725.333333 725.333333 554.666667 725.333333 554.666667 682.666667 469.333333 682.666667 469.333333 768 512 768 512 810.666667 768 810.666667 768 768 810.666667 768 810.666667 512 768 512M554.666667 256 554.666667 213.333333 256 213.333333 256 256 213.333333 256 213.333333 554.666667 256 554.666667 256 597.333333 384 597.333333 384 512 341.333333 512 341.333333 341.333333 512 341.333333 512 384 597.333333 384 597.333333 256 554.666667 256M512 512 469.333333 512 469.333333 597.333333 554.666667 597.333333 554.666667 554.666667 597.333333 554.666667 597.333333 469.333333 512 469.333333 512 512Z" p-id="17282" /></svg></Button> |
|||
</ButtonGroup> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
import { v4 as uuid } from 'uuid' |
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
} |
|||
}, |
|||
computed: { |
|||
// 单选且等于组元素 |
|||
isGroup() { |
|||
return (this.mSelectMode === 'one' && this.mSelectOneType === 'group') |
|||
}, |
|||
// 是否为多选 |
|||
isMultiple() { |
|||
return (this.mSelectMode === 'multiple') |
|||
} |
|||
}, |
|||
methods: { |
|||
// 拆分组 |
|||
unGroup() { |
|||
// 先获取当前选中的对象,然后打散 |
|||
this.canvas.c.getActiveObject().toActiveSelection() |
|||
this.canvas.c.getActiveObject().getObjects().forEach(item => { |
|||
item.set('id', uuid()) |
|||
}) |
|||
this.canvas.c.discardActiveObject().renderAll() |
|||
}, |
|||
group() { |
|||
// 组合元素 |
|||
var activeObj = this.canvas.c.getActiveObject() |
|||
var activegroup = activeObj.toGroup() |
|||
var objectsInGroup = activegroup.getObjects() |
|||
activegroup.clone((newgroup) => { |
|||
this.canvas.c.remove(activegroup) |
|||
objectsInGroup.forEach((object) => { |
|||
this.canvas.c.remove(object) |
|||
}) |
|||
this.canvas.c.add(newgroup) |
|||
this.canvas.c.setActiveObject(newgroup) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style scoped lang="less"> |
|||
/deep/ .ivu-btn[disabled]{ |
|||
svg{ opacity: 0.2;} |
|||
} |
|||
</style> |
@ -0,0 +1,84 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2023-01-07 02:09:06 |
|||
* @Description: 回退重做 |
|||
--> |
|||
|
|||
<template> |
|||
<el-button-group size="small"> |
|||
<!-- 后退 --> |
|||
<el-button :disabled="!list.length" @click="undo"><i class="el-icon-d-arrow-left" />{{ list.length }}</el-button> |
|||
<!-- 重做 --> |
|||
<el-button :disabled="!redoList.length" @click="redo"><i class="el-icon-d-arrow-right" />{{ redoList.length }}</el-button> |
|||
</el-button-group> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
import { keyNames, hotkeys } from '@/core/initHotKeys' |
|||
const maxStep = 10 |
|||
|
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
index: 0, |
|||
redoList: [], |
|||
list: [] |
|||
} |
|||
}, |
|||
created() { |
|||
// 有更新时记录进度 |
|||
this.canvas.c.on({ |
|||
'object:modified': this.save, |
|||
'selection:updated': this.save |
|||
}) |
|||
|
|||
hotkeys(keyNames.ctrlz, this.undo) |
|||
}, |
|||
|
|||
methods: { |
|||
// 保存记录 |
|||
save() { |
|||
const data = this.canvas.c.toJSON(['id']) |
|||
if (this.list.length > maxStep) { |
|||
this.list.shift() |
|||
} |
|||
this.list.push(data) |
|||
}, |
|||
// 后退 |
|||
undo() { |
|||
if (this.list.length) { |
|||
const item = this.list.pop() |
|||
this.redoList.push(item) |
|||
this.renderCanvas(item) |
|||
} |
|||
}, |
|||
// 重做 |
|||
redo() { |
|||
if (this.redoList.length) { |
|||
const item = this.redoList.pop() |
|||
this.list.push(item) |
|||
this.renderCanvas(item) |
|||
} |
|||
}, |
|||
// 根据数据渲染 |
|||
renderCanvas(data) { |
|||
this.canvas.c.clear() |
|||
this.canvas.c.loadFromJSON(data, this.canvas.c.renderAll.bind(this.canvas.c)) |
|||
this.canvas.c.requestRenderAll() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
span.active { |
|||
svg.icon{ |
|||
fill: #2d8cf0; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,76 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-05 22:33:57 |
|||
* @Description: 插入图片 |
|||
--> |
|||
|
|||
<template> |
|||
<div style="display: inline-block"> |
|||
<Button size="small" @click="insert">插入图片</Button> |
|||
<Modal |
|||
v-model="showModal" |
|||
title="请选择" |
|||
:z-index="9999" |
|||
@on-ok="insertImgFile" |
|||
@on-cancel="showModal = false,imgFile = null " |
|||
> |
|||
<Upload :before-upload="handleUpload" action="#"> |
|||
<Button icon="ios-cloud-upload-outline">选择图片文件</Button> |
|||
</Upload> |
|||
</Modal> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
import { getImgStr } from '@/utils/utils' |
|||
import { v4 as uuid } from 'uuid' |
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
showModal: false, |
|||
imgFile: null |
|||
} |
|||
}, |
|||
methods: { |
|||
insert() { |
|||
this.imgFile = '' |
|||
this.showModal = true |
|||
}, |
|||
insertImgFile() { |
|||
const imgEl = document.createElement('img') |
|||
imgEl.src = this.imgFile |
|||
// 插入页面 |
|||
document.body.appendChild(imgEl) |
|||
imgEl.onload = () => { |
|||
// 创建图片对象 |
|||
const imgInstance = new this.fabric.Image(imgEl, { |
|||
id: uuid(), |
|||
name: '图片1', |
|||
left: 100, top: 100 |
|||
}) |
|||
// 设置缩放 |
|||
imgInstance.scale(0.2) |
|||
this.canvas.c.add(imgInstance) |
|||
this.canvas.c.setActiveObject(imgInstance) |
|||
this.canvas.c.renderAll() |
|||
// 删除页面中的图片元素 |
|||
imgEl.remove() |
|||
} |
|||
}, |
|||
// 选择文件 |
|||
handleUpload(file) { |
|||
getImgStr(file).then(res => { |
|||
this.imgFile = res |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
</style> |
@ -0,0 +1,66 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2023-01-07 02:19:24 |
|||
* @Description: 导入JSON文件 |
|||
--> |
|||
|
|||
<template> |
|||
<div style="display: inline-block"> |
|||
<Button size="small" @click="insert">导入文件</Button> |
|||
<Modal |
|||
v-model="showModal" |
|||
title="请选择" |
|||
:z-index="9999" |
|||
@on-ok="insertSvgFile" |
|||
@on-cancel="(showModal = false), (jsonFile = null)" |
|||
> |
|||
<Upload :before-upload="handleUpload" action="#"> |
|||
<Button icon="ios-cloud-upload-outline">选择JSON文件</Button> |
|||
</Upload> |
|||
</Modal> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
import { downFontByJSON } from '@/utils/utils' |
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
showModal: false, |
|||
jsonFile: null |
|||
} |
|||
}, |
|||
methods: { |
|||
insert() { |
|||
this.svg = '' |
|||
this.showModal = true |
|||
}, |
|||
insertSvgFile() { |
|||
if (!this.jsonFile) { |
|||
this.$Message.error('请选择文件') |
|||
return |
|||
} |
|||
// 加载字体后导入 |
|||
downFontByJSON(this.jsonFile).then(() => { |
|||
this.canvas.c.loadFromJSON(this.jsonFile, this.canvas.c.renderAll.bind(this.canvas.c)) |
|||
}) |
|||
}, |
|||
handleUpload(file) { |
|||
const reader = new FileReader() |
|||
reader.readAsText(file, 'UTF-8') |
|||
reader.onload = () => { |
|||
this.jsonFile = reader.result |
|||
} |
|||
return false |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
</style> |
@ -0,0 +1,87 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-05 22:36:02 |
|||
* @Description: 插入SVG元素 |
|||
--> |
|||
|
|||
<template> |
|||
<div style="display:inline-block"> |
|||
<Button size="small" @click="insert">插入SVG元素</Button> |
|||
<Modal |
|||
v-model="showModal" |
|||
title="请选择" |
|||
:z-index="9999" |
|||
@on-ok="insertSvg" |
|||
@on-cancel="showModal = false" |
|||
> |
|||
<RadioGroup v-model="insertType" type="button" button-style="solid" style="padding-bottom: 10px"> |
|||
<Radio label="string">字符串</Radio> |
|||
<Radio label="file">文件</Radio> |
|||
</RadioGroup> |
|||
<!-- 字符串 --> |
|||
<Input v-if="insertType === 'string'" v-model="svgStr" show-word-limit type="textarea" placeholder="请输入SVG字符" /> |
|||
<!-- 文件 --> |
|||
<Upload v-if="insertType === 'file'" :before-upload="handleUpload" action="#"> |
|||
<Button icon="ios-cloud-upload-outline">选择SVG文件</Button> |
|||
</Upload> |
|||
</Modal> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getImgStr } from '@/utils/utils' |
|||
import select from '@/mixins/select' |
|||
import { v4 as uuid } from 'uuid' |
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
insertType: 'string', // 插入类型 file | string |
|||
showModal: false, |
|||
svgStr: '', |
|||
svgFile: null |
|||
} |
|||
}, |
|||
methods: { |
|||
insert() { |
|||
this.svgStr = '' |
|||
this.svgFile = null |
|||
this.showModal = true |
|||
}, |
|||
insertSvg() { |
|||
if (this.insertType === 'string') { |
|||
this.insertSvgStr() |
|||
} else { |
|||
this.insertSvgFile() |
|||
} |
|||
}, |
|||
// 插入字符串元素 |
|||
insertSvgStr() { |
|||
const This = this |
|||
this.fabric.loadSVGFromString(this.svgStr, function(objects, options) { |
|||
const item = This.fabric.util.groupSVGElements(objects, { ...options, name: 'defaultSVG', id: uuid() }) |
|||
This.canvas.c.add(item).centerObject(item).renderAll() |
|||
}) |
|||
}, |
|||
// 插入文件元素 |
|||
insertSvgFile() { |
|||
const This = this |
|||
this.fabric.loadSVGFromURL(this.svgFile, function(objects, options) { |
|||
const item = This.fabric.util.groupSVGElements(objects, { ...options, name: 'defaultSVG', id: uuid() }) |
|||
This.canvas.c.add(item).centerObject(item).renderAll() |
|||
}) |
|||
}, |
|||
handleUpload(file) { |
|||
getImgStr(file).then(res => { |
|||
this.svgFile = res |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
</style> |
@ -0,0 +1,101 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-07 00:17:35 |
|||
* @Description: 导入模板 |
|||
--> |
|||
|
|||
<template> |
|||
<div style="display:inline-block"> |
|||
<el-divider plain orientation="left">标题模板</el-divider> |
|||
<el-tooltip v-for="(item, i) in list" :key="i + '-bai1-button'" :content="item.label" placement="top"> |
|||
<img class="tmpl-img" :alt="item.label" :src="item.src" @click="getTempData(item.tempUrl)"> |
|||
</el-tooltip> |
|||
<!-- <Divider plain orientation="left">形状模板</Divider> --> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
import { downFontByJSON } from '@/utils/utils' |
|||
import { Tooltip, Divider } from 'view-design' |
|||
|
|||
export default { |
|||
name: 'ToolBar', |
|||
components: { |
|||
Tooltip, Divider |
|||
}, |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
jsonFile: null, |
|||
list: [ |
|||
{ |
|||
label: '笔记模板', |
|||
tempUrl: './template/073606d4-22de-491b-8b51-b90d72101d89.json', |
|||
src: './template/073606d4-22de-491b-8b51-b90d72101d89.png' |
|||
}, |
|||
{ |
|||
label: '醒目封面', |
|||
tempUrl: './template/dcebee41-59b5-408b-a65a-c51bc390be3d.json', |
|||
src: './template/dcebee41-59b5-408b-a65a-c51bc390be3d.png' |
|||
}, |
|||
{ |
|||
label: '教师节', |
|||
tempUrl: './template/3a7471f2-b8cf-4939-ad1a-a7d586768640.json', |
|||
src: './template/3a7471f2-b8cf-4939-ad1a-a7d586768640.png' |
|||
}, |
|||
{ |
|||
label: '升职锦囊', |
|||
tempUrl: './template/ef5eb884-28e0-4d79-9e98-a73d759541f8.json', |
|||
src: './template/ef5eb884-28e0-4d79-9e98-a73d759541f8.png' |
|||
}, |
|||
{ |
|||
label: '古风模板', |
|||
tempUrl: './template/ecc3fca2-f66e-465e-b2c7-80b7522fdb3b.json', |
|||
src: './template/ecc3fca2-f66e-465e-b2c7-80b7522fdb3b.png' |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
methods: { |
|||
// 插入文件 |
|||
insertSvgFile() { |
|||
this.$Spin.show({ |
|||
render: (h) => { |
|||
return h('div', '正在加载字体,您耐心等候...') |
|||
} |
|||
}) |
|||
downFontByJSON(this.jsonFile).then(() => { |
|||
this.$Spin.hide() |
|||
this.canvas.c.loadFromJSON(this.jsonFile, this.canvas.c.renderAll.bind(this.canvas.c)) |
|||
}).catch((e) => { |
|||
this.$Spin.hide() |
|||
this.$Message.error('字体加载失败,请重试') |
|||
}) |
|||
}, |
|||
// 获取模板数据 |
|||
getTempData(tmplUrl) { |
|||
this.$Spin.show({ |
|||
render: (h) => { |
|||
return h('div', '加载数据中...') |
|||
} |
|||
}) |
|||
const getTemp = this.$http.get(tmplUrl) |
|||
getTemp.then(res => { |
|||
this.jsonFile = JSON.stringify(res.data) |
|||
this.insertSvgFile() |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
.tmpl-img{ |
|||
width: 77px; |
|||
cursor: pointer; |
|||
margin-right: 5px; |
|||
} |
|||
</style> |
File diff suppressed because one or more lines are too long
@ -0,0 +1,81 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-12-07 23:55:56 |
|||
* @Description: 锁定元素 |
|||
--> |
|||
|
|||
<template> |
|||
<i-switch v-if="mSelectMode === 'one'" v-model="isLock" @on-change="doLock"> |
|||
<Icon slot="open" type="md-lock" /> |
|||
<Icon slot="close" type="md-unlock" /> |
|||
</i-switch> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
const lockAttrs = ['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY'] |
|||
export default { |
|||
name: 'ToolBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
isLock: false |
|||
} |
|||
}, |
|||
created() { |
|||
this.event.on('selectOne', (items) => { |
|||
this.isLock = !items[0].hasControls |
|||
this.mSelectActive = items[0] |
|||
}) |
|||
}, |
|||
methods: { |
|||
doLock(isLock) { |
|||
isLock ? this.lock() : this.unLock() |
|||
}, |
|||
lock() { |
|||
// 修改自定义属性 |
|||
this.mSelectActive.hasControls = false |
|||
// 修改默认属性 |
|||
lockAttrs.forEach(key => { |
|||
this.mSelectActive[key] = true |
|||
}) |
|||
|
|||
this.mSelectActive.selectable = false |
|||
|
|||
this.isLock = true |
|||
this.canvas.c.renderAll() |
|||
}, |
|||
unLock() { |
|||
// 修改自定义属性 |
|||
this.mSelectActive.hasControls = true |
|||
// 修改默认属性 |
|||
lockAttrs.forEach(key => { |
|||
this.mSelectActive[key] = false |
|||
}) |
|||
this.mSelectActive.selectable = true |
|||
|
|||
this.isLock = false |
|||
this.canvas.c.renderAll() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
h3 { |
|||
margin: 40px 0 0; |
|||
} |
|||
ul { |
|||
list-style-type: none; |
|||
padding: 0; |
|||
} |
|||
li { |
|||
display: inline-block; |
|||
margin: 0 10px; |
|||
} |
|||
a { |
|||
color: #42b983; |
|||
} |
|||
</style> |
@ -0,0 +1,38 @@ |
|||
<template> |
|||
<div class="save-box"> |
|||
<el-button type="warning" style="margin-left: 10px" size="small" @click="clear">清空</el-button> |
|||
<el-button type="primary" size="small" @click="saveWith">保存</el-button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
import { v4 as uuid } from 'uuid' |
|||
export default { |
|||
name: 'SaveBar', |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
} |
|||
}, |
|||
methods: { |
|||
saveWith() { |
|||
this.$emit('fullImgBack', this.canvas.c.toDataURL()) |
|||
}, |
|||
clear() { |
|||
this.canvas.c.clear() |
|||
this.canvas.c.setBackgroundColor('#ffffff', this.canvas.c.renderAll.bind(this.canvas.c)) |
|||
}, |
|||
close() { |
|||
this.$emit('closeDialog') |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
.save-box{ |
|||
display: inline-block; |
|||
padding-right: 10px; |
|||
} |
|||
</style> |
@ -0,0 +1,82 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-04 00:21:58 |
|||
* @Description: 尺寸设置 |
|||
--> |
|||
|
|||
<template> |
|||
<div> |
|||
<el-divider plain orientation="left">尺寸</el-divider> |
|||
<div> |
|||
宽度 |
|||
<el-input-number v-model="width" :max="2000" :min="1" size="small" @on-change="setSize" /> |
|||
</div> |
|||
<div style="margin-top: 10px"> |
|||
高度 |
|||
<el-input-number v-model="height" :max="2000" :min="1" size="small" @on-change="setSize" /> |
|||
</div> |
|||
<el-divider plain orientation="left">预设尺寸</el-divider> |
|||
<el-button-group vertical> |
|||
<el-button |
|||
v-for="(item, i) in presetSize" |
|||
:key="i + 'presetSize'" |
|||
size="small" |
|||
style="text-align:left;margin-bottom: 5px" |
|||
@click="setSizeBy(item.width * item.scale, item.height * item.scale)" |
|||
> |
|||
{{ item.label }}:{{ item.width }}x{{ item.height }}*{{ item.scale }} |
|||
</el-button> |
|||
</el-button-group> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'CanvasSize', |
|||
inject: ['canvas', 'fabric'], |
|||
data() { |
|||
return { |
|||
width: 900 * 0.5, |
|||
height: 1200 * 0.5, |
|||
presetSize: [{ |
|||
label: '红书竖版', |
|||
width: 900, |
|||
height: 1200, |
|||
scale: 0.5 |
|||
}, |
|||
{ |
|||
label: '红书横版', |
|||
width: 1200, |
|||
height: 900, |
|||
scale: 0.5 |
|||
}, |
|||
{ |
|||
label: '手机壁纸', |
|||
width: 1080, |
|||
height: 1920, |
|||
scale: 0.4 |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
created() { |
|||
this.setSize() |
|||
}, |
|||
methods: { |
|||
setSizeBy(width, height) { |
|||
this.canvas.c.setWidth(width) |
|||
this.canvas.c.setHeight(height) |
|||
this.canvas.c.renderAll() |
|||
this.width = width |
|||
this.height = height |
|||
}, |
|||
setSize() { |
|||
this.canvas.c.setWidth(this.width) |
|||
this.canvas.c.setHeight(this.height) |
|||
this.canvas.c.renderAll() |
|||
} |
|||
} |
|||
} |
|||
</script> |
@ -0,0 +1,143 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2023-01-17 19:45:19 |
|||
* @Description: 素材面板 |
|||
--> |
|||
|
|||
<template> |
|||
<div> |
|||
<Divider plain orientation="left">卡通</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(460, 489)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem($event,item)"> |
|||
<!-- <div v-for="(item) in getIndex(460, 489)" :key="item">{{ `./svg/${item}.svg` }}</div> --> |
|||
</div> |
|||
<Divider plain orientation="left">卡通水果</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(386, 409)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">体育</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(410, 459)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">秋天</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(40, 49)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">计算机</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(50, 75)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">卡通水果</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(76, 89)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">服饰</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(89, 136)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">旗子</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(137, 151)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">树木</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(152, 181)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">食物</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(182, 201)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">服饰</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(202, 222)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">奖牌</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(223, 252)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">商务</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(253, 261)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">活动</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(262, 270)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">卡通水果</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(271, 300)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">复古</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(301, 350)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
<Divider plain orientation="left">卡通</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(351, 385)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
|
|||
<Divider plain orientation="left">动物</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(490, 519)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
|
|||
<Divider plain orientation="left">手绘</Divider> |
|||
<div class="box"> |
|||
<img v-for="(item) in getIndex(0, 39)" :key="item" v-lazy="`./svg/${item}.svg`" alt="" @click="addItem"> |
|||
</div> |
|||
|
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { v4 as uuid } from 'uuid' |
|||
const defaultPosition = { |
|||
left: 100, top: 100, shadow: '', fontFamily: '1-1' |
|||
} |
|||
export default { |
|||
name: 'ToolBar', |
|||
inject: ['canvas', 'fabric'], |
|||
data() { |
|||
return { |
|||
arr: [] |
|||
} |
|||
}, |
|||
created() { |
|||
}, |
|||
methods: { |
|||
getIndex(start, end) { |
|||
const arr = Array(end - (start - 1)).fill('') |
|||
// console.log(arr.map((item, i) => (i + start))); |
|||
return arr.map((item, i) => (i + start)) |
|||
}, |
|||
// 按照类型渲染 |
|||
addItem(e, item) { |
|||
const url = e.target.src |
|||
// console.log(item) |
|||
this.fabric.loadSVGFromURL(url, (objects, options) => { |
|||
var item = this.fabric.util.groupSVGElements(objects, { ...options, ...defaultPosition, |
|||
id: uuid(), |
|||
name: 'svg元素' |
|||
}) |
|||
this.canvas.c.add(item) |
|||
this.canvas.c.renderAll() |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
|
|||
img { |
|||
display: inline-block; |
|||
width: 53px; |
|||
margin-left: 2px; |
|||
margin-bottom: 2px; |
|||
background: #F5F5F5; |
|||
padding: 6px; |
|||
cursor: pointer; |
|||
} |
|||
</style> |
@ -0,0 +1,15 @@ |
|||
/* |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-04-20 17:13:36 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-04-20 17:19:46 |
|||
* @Description: file content |
|||
*/ |
|||
|
|||
import svgIcon from './index.vue' |
|||
|
|||
export default { |
|||
install(Vue) { |
|||
Vue.component(svgIcon.name, svgIcon) |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
<template> |
|||
<span> |
|||
<svg |
|||
t="1650443094178" |
|||
viewBox="0 0 1024 1024" |
|||
version="1.1" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
p-id="1549" |
|||
:width="size" |
|||
:height="size" |
|||
:fill="color" |
|||
> |
|||
<!-- --> |
|||
<template v-if="name === 'default'"> |
|||
<path d="M252.76928 299.904l146.2784 0 0 472.42752-146.2784 0 0-472.42752Z" p-id="1550" /><path d="M477.48096 85.34528l70.87104 0 0 885.80608-70.87104 0 0-885.80608Z" p-id="1551" /><path d="M629.80096 284.8l31.0016 0 0 502.88128-31.0016 0L629.80096 284.8zM776.42752 284.8l31.0016 0 0 502.88128-31.0016 0L776.42752 284.8zM657.09056 315.8016l0-31.0016 123.04896 0 0 31.0016L657.09056 315.8016zM657.27488 787.64544l0-31.0016 123.04896 0 0 31.0016L657.27488 787.64544z" p-id="1552" /> |
|||
</template> |
|||
<!-- up --> |
|||
<template v-if="name === 'up'"> |
|||
<path d="M876.2 434.3L536.7 94.9c-6.6-6.6-15.5-10.3-24.7-10.3-9.3 0-18.2 3.7-24.7 10.3L147.8 434.3c-13.7 13.7-13.7 35.8 0 49.5 13.7 13.7 35.8 13.7 49.5 0L477 204.1v700.2c0 19.3 15.7 35 35 35s35-15.7 35-35V204.1l279.7 279.7c6.8 6.8 15.8 10.3 24.7 10.3s17.9-3.4 24.7-10.3c13.7-13.7 13.7-35.8 0.1-49.5z" p-id="1800" /> |
|||
</template> |
|||
</svg> |
|||
</span> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'SvgIcon', |
|||
props: { |
|||
name: { |
|||
type: String, |
|||
default: 'default' |
|||
}, |
|||
color: { |
|||
type: String, |
|||
default: '#ea9518' |
|||
}, |
|||
size: { |
|||
type: Number, |
|||
default: 14 |
|||
} |
|||
}, |
|||
data() { |
|||
return {} |
|||
}, |
|||
methods: {} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
</style> |
File diff suppressed because one or more lines are too long
@ -0,0 +1,70 @@ |
|||
<!-- |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-04-21 20:20:20 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-04-25 10:32:58 |
|||
* @Description: file content |
|||
--> |
|||
<template> |
|||
<ButtonGroup> |
|||
<Button icon="el-icon-zoom-in" size="small" @click="big">放大</Button> |
|||
<Button icon="el-icon-zoom-out" size="small" @click="small">缩小</Button> |
|||
<Button size="small" @click="rSet">还原</Button> |
|||
</ButtonGroup> |
|||
</template> |
|||
|
|||
<script> |
|||
import select from '@/mixins/select' |
|||
import { ButtonGroup, Button } from 'element-ui' |
|||
|
|||
export default { |
|||
name: 'ToolBar', |
|||
components: { |
|||
ButtonGroup, |
|||
Button |
|||
}, |
|||
mixins: [select], |
|||
data() { |
|||
return { |
|||
zoom: 0 |
|||
} |
|||
}, |
|||
computed: { |
|||
unShow() { |
|||
return (this.mSelectMode === 'one' && this.mSelectOneType === 'group') |
|||
}, |
|||
createShow() { |
|||
return (this.mSelectMode === 'multiple') |
|||
} |
|||
}, |
|||
methods: { |
|||
rSet() { |
|||
this.canvas.c.zoomToPoint( |
|||
new this.fabric.Point(this.canvas.c.getWidth() / 2, this.canvas.c.getHeight() / 2), |
|||
1 |
|||
) |
|||
this.zoom = `${Math.round(100)}%` |
|||
}, |
|||
big() { |
|||
let zoomRatio = this.canvas.c.getZoom() |
|||
zoomRatio += 0.05 |
|||
this.canvas.c.zoomToPoint( |
|||
new this.fabric.Point(this.canvas.c.getWidth() / 2, this.canvas.c.getHeight() / 2), |
|||
zoomRatio |
|||
) |
|||
this.zoom = `${Math.round(zoomRatio * 100)}%` |
|||
}, |
|||
small() { |
|||
let zoomRatio = this.canvas.c.getZoom() |
|||
zoomRatio -= 0.05 |
|||
this.canvas.c.zoomToPoint( |
|||
new this.fabric.Point(this.canvas.c.getWidth() / 2, this.canvas.c.getHeight() / 2), |
|||
zoomRatio |
|||
) |
|||
this.zoom = `${Math.round(zoomRatio * 100)}%` |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style scoped lang="less"> |
|||
</style> |
@ -1,13 +1,391 @@ |
|||
<template> |
|||
<div /> |
|||
<div class="home"> |
|||
<div> |
|||
<div v-if="show" style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px"> |
|||
<!-- 对齐方式 --> |
|||
<zoom /> |
|||
<div> |
|||
<save @fullImgBack="fullImgBack" /> |
|||
<el-button size="small" @click="closeDialog">关闭</el-button> |
|||
</div> |
|||
</div> |
|||
<div style=" display: flex; height: calc(100vh - 120px);"> |
|||
<div style="width: 380px; height: 100%; background:#fff; display: flex"> |
|||
<el-tabs v-model="menuActive" tab-position="left" style="height: 200px;"> |
|||
<el-tab-pane label="元素" name="2" /> |
|||
<el-tab-pane label="背景" name="3" /> |
|||
<el-tab-pane label="画笔" name="4" /> |
|||
</el-tabs> |
|||
<div class="content"> |
|||
<template v-if="show"> |
|||
<div v-show="menuActive === '2'" class="left-panel"> |
|||
<tools /> |
|||
</div> |
|||
<!-- 背景设置 --> |
|||
<div v-show="menuActive === '3'" class="left-panel"> |
|||
<set-size /> |
|||
<bg-bar ref="bgBar" :is-od-or-os="isOdOrOs" /> |
|||
</div> |
|||
</template> |
|||
<!-- 画笔设置 --> |
|||
<div v-show="menuActive === '4'"> |
|||
<div style="display: inline-block; margin-left: 10px"> |
|||
<button id="drawing-mode" class="btn btn-info">Cancel drawing mode</button><br> |
|||
<button id="clear-canvas" class="btn btn-info">Clear</button><br> |
|||
|
|||
<div id="drawing-mode-options"> |
|||
<label for="drawing-mode-selector">Mode:</label> |
|||
<select id="drawing-mode-selector"> |
|||
<option>Pencil</option> |
|||
<!-- <option>Circle</option>--> |
|||
<!-- <option>Spray</option>--> |
|||
<!-- <option>Pattern</option>--> |
|||
|
|||
<!-- <option>hline</option>--> |
|||
<!-- <option>vline</option>--> |
|||
<!-- <option>square</option>--> |
|||
<option>diamond</option> |
|||
<!-- <option>texture</option>--> |
|||
</select><br> |
|||
|
|||
<label for="drawing-line-width">Line width:</label> |
|||
<span class="info">30</span><input id="drawing-line-width" type="range" value="30" min="30" max="150"><br> |
|||
|
|||
<label for="drawing-color">Line color:</label> |
|||
<input id="drawing-color" type="color" value="#005E7A"><br> |
|||
|
|||
<label for="drawing-shadow-color">Shadow color:</label> |
|||
<input id="drawing-shadow-color" type="color" value="#005E7A"><br> |
|||
|
|||
<label for="drawing-shadow-width">Shadow width:</label> |
|||
<span class="info">0</span><input id="drawing-shadow-width" type="range" value="0" min="0" max="50"><br> |
|||
|
|||
<label for="drawing-shadow-offset">Shadow offset:</label> |
|||
<span class="info">0</span><input id="drawing-shadow-offset" type="range" value="0" min="0" max="50"><br> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<!-- 画布区域 --> |
|||
<div style="width: 100%;position: relative; background:#F1F1F1;"> |
|||
<div class="canvas-box"> |
|||
<canvas id="canvas" /> |
|||
</div> |
|||
</div> |
|||
<!-- 属性区域 --> |
|||
<div style="width: 380px; height: 100%; padding:10px; overflow-y: auto; background:#fff"> |
|||
<layer v-if="show" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
// 顶部组件 |
|||
import save from '@/page-subspecialty/views/modules/imgEditorFabric/img-editor/save.vue' |
|||
import zoom from '@/page-subspecialty/views/modules/imgEditorFabric/img-editor/zoom.vue' |
|||
|
|||
// 左侧组件 |
|||
import tools from '@/page-subspecialty/views/modules/imgEditorFabric/img-editor/tools.vue' |
|||
import bgBar from '@/page-subspecialty/views/modules/imgEditorFabric/img-editor/bgBar.vue' |
|||
import setSize from '@/page-subspecialty/views/modules/imgEditorFabric/img-editor/setSize.vue' |
|||
import brush from '@/page-subspecialty/views/modules/imgEditorFabric/img-editor/brush.vue' |
|||
|
|||
// 右侧组件 |
|||
import layer from '@/page-subspecialty/views/modules/imgEditorFabric/img-editor/layer.vue' |
|||
|
|||
// 功能组件 |
|||
import EventHandle from '@/utils/eventHandler' |
|||
|
|||
import { fabric } from 'fabric' |
|||
|
|||
// 对齐辅助线 |
|||
import initAligningGuidelines from '@/core/initAligningGuidelines' |
|||
import initHotkeys from '@/core/initHotKeys' |
|||
import initControls from '@/core/initControls' |
|||
|
|||
const event = new EventHandle() |
|||
const canvas = {} |
|||
export default { |
|||
name: 'Index' |
|||
name: 'HomeView', |
|||
components: { |
|||
zoom, |
|||
save, |
|||
tools, |
|||
bgBar, |
|||
setSize, |
|||
brush, |
|||
layer |
|||
}, |
|||
props: { |
|||
isOdOrOs: { |
|||
type: String |
|||
} |
|||
}, |
|||
provide: { |
|||
canvas, |
|||
fabric, |
|||
event |
|||
}, |
|||
data() { |
|||
return { |
|||
menuActive: '4', |
|||
show: false, |
|||
select: null |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.canvas = canvas.c = new fabric.Canvas('canvas', { |
|||
isDrawingMode: true |
|||
}) |
|||
// console.log(this.canvas) |
|||
this.canvas.set('backgroundColor', '#fff') |
|||
this.show = true |
|||
event.init(canvas.c) |
|||
initAligningGuidelines(canvas.c) |
|||
initHotkeys(canvas.c) |
|||
initControls(canvas.c) |
|||
this.$nextTick(() => { |
|||
this.setBrush() |
|||
}) |
|||
}, |
|||
methods: { |
|||
fullImgBack(value) { |
|||
this.$emit('fullImgBack', value) |
|||
}, |
|||
closeDialog() { |
|||
this.$emit('closeDialog') |
|||
}, |
|||
setBrush() { |
|||
var $ = function(id) { return document.getElementById(id) } |
|||
var canvas = this.canvas |
|||
fabric.Object.prototype.transparentCorners = false |
|||
|
|||
var drawingModeEl = $('drawing-mode') |
|||
var drawingOptionsEl = $('drawing-mode-options') |
|||
var drawingColorEl = $('drawing-color') |
|||
var drawingShadowColorEl = $('drawing-shadow-color') |
|||
var drawingLineWidthEl = $('drawing-line-width') |
|||
var drawingShadowWidth = $('drawing-shadow-width') |
|||
var drawingShadowOffset = $('drawing-shadow-offset') |
|||
var clearEl = $('clear-canvas') |
|||
|
|||
clearEl.onclick = function() { canvas.clear() } |
|||
|
|||
drawingModeEl.onclick = function() { |
|||
canvas.isDrawingMode = !canvas.isDrawingMode |
|||
if (canvas.isDrawingMode) { |
|||
drawingModeEl.innerHTML = 'Cancel drawing mode' |
|||
drawingOptionsEl.style.display = '' |
|||
} else { |
|||
drawingModeEl.innerHTML = 'Enter drawing mode' |
|||
drawingOptionsEl.style.display = 'none' |
|||
} |
|||
} |
|||
|
|||
if (fabric.PatternBrush) { |
|||
var vLinePatternBrush = new fabric.PatternBrush(canvas) |
|||
vLinePatternBrush.getPatternSrc = function() { |
|||
var patternCanvas = fabric.document.createElement('canvas') |
|||
patternCanvas.width = patternCanvas.height = 10 |
|||
var ctx = patternCanvas.getContext('2d') |
|||
|
|||
ctx.strokeStyle = this.color |
|||
ctx.lineWidth = 5 |
|||
ctx.beginPath() |
|||
ctx.moveTo(0, 5) |
|||
ctx.lineTo(10, 5) |
|||
ctx.closePath() |
|||
ctx.stroke() |
|||
|
|||
return patternCanvas |
|||
} |
|||
|
|||
var hLinePatternBrush = new fabric.PatternBrush(canvas) |
|||
hLinePatternBrush.getPatternSrc = function() { |
|||
var patternCanvas = fabric.document.createElement('canvas') |
|||
patternCanvas.width = patternCanvas.height = 10 |
|||
var ctx = patternCanvas.getContext('2d') |
|||
|
|||
ctx.strokeStyle = this.color |
|||
ctx.lineWidth = 5 |
|||
ctx.beginPath() |
|||
ctx.moveTo(5, 0) |
|||
ctx.lineTo(5, 10) |
|||
ctx.closePath() |
|||
ctx.stroke() |
|||
|
|||
return patternCanvas |
|||
} |
|||
|
|||
var squarePatternBrush = new fabric.PatternBrush(canvas) |
|||
squarePatternBrush.getPatternSrc = function() { |
|||
var squareWidth = 10; var squareDistance = 2 |
|||
|
|||
var patternCanvas = fabric.document.createElement('canvas') |
|||
patternCanvas.width = patternCanvas.height = squareWidth + squareDistance |
|||
var ctx = patternCanvas.getContext('2d') |
|||
|
|||
ctx.fillStyle = this.color |
|||
ctx.fillRect(0, 0, squareWidth, squareWidth) |
|||
|
|||
return patternCanvas |
|||
} |
|||
|
|||
var diamondPatternBrush = new fabric.PatternBrush(canvas) |
|||
diamondPatternBrush.getPatternSrc = function() { |
|||
var squareWidth = 10; var squareDistance = 5 |
|||
var patternCanvas = fabric.document.createElement('canvas') |
|||
var rect = new fabric.Rect({ |
|||
width: squareWidth, |
|||
height: squareWidth, |
|||
angle: 45, |
|||
fill: this.color |
|||
}) |
|||
|
|||
var canvasWidth = rect.getBoundingRect().width |
|||
|
|||
patternCanvas.width = patternCanvas.height = canvasWidth + squareDistance |
|||
rect.set({ left: canvasWidth / 2, top: canvasWidth / 2 }) |
|||
|
|||
var ctx = patternCanvas.getContext('2d') |
|||
rect.render(ctx) |
|||
|
|||
return patternCanvas |
|||
} |
|||
|
|||
var img = new Image() |
|||
img.src = '../assets/honey_im_subtle.png' |
|||
|
|||
var texturePatternBrush = new fabric.PatternBrush(canvas) |
|||
texturePatternBrush.source = img |
|||
} |
|||
|
|||
$('drawing-mode-selector').onchange = function() { |
|||
if (this.value === 'hline') { |
|||
canvas.freeDrawingBrush = vLinePatternBrush |
|||
} else if (this.value === 'vline') { |
|||
canvas.freeDrawingBrush = hLinePatternBrush |
|||
} else if (this.value === 'square') { |
|||
canvas.freeDrawingBrush = squarePatternBrush |
|||
} else if (this.value === 'diamond') { |
|||
canvas.freeDrawingBrush = diamondPatternBrush |
|||
} else if (this.value === 'texture') { |
|||
canvas.freeDrawingBrush = texturePatternBrush |
|||
} else { |
|||
canvas.freeDrawingBrush = new fabric[this.value + 'Brush'](canvas) |
|||
} |
|||
|
|||
if (canvas.freeDrawingBrush) { |
|||
var brush = canvas.freeDrawingBrush |
|||
brush.color = drawingColorEl.value |
|||
if (brush.getPatternSrc) { |
|||
brush.source = brush.getPatternSrc.call(brush) |
|||
} |
|||
brush.width = parseInt(drawingLineWidthEl.value, 10) || 1 |
|||
brush.shadow = new fabric.Shadow({ |
|||
blur: parseInt(drawingShadowWidth.value, 10) || 0, |
|||
offsetX: 0, |
|||
offsetY: 0, |
|||
affectStroke: true, |
|||
color: drawingShadowColorEl.value |
|||
}) |
|||
} |
|||
} |
|||
|
|||
drawingColorEl.onchange = function() { |
|||
var brush = canvas.freeDrawingBrush |
|||
brush.color = this.value |
|||
if (brush.getPatternSrc) { |
|||
brush.source = brush.getPatternSrc.call(brush) |
|||
} |
|||
} |
|||
drawingShadowColorEl.onchange = function() { |
|||
canvas.freeDrawingBrush.shadow.color = this.value |
|||
} |
|||
drawingLineWidthEl.onchange = function() { |
|||
canvas.freeDrawingBrush.width = parseInt(this.value, 10) || 1 |
|||
this.previousSibling.innerHTML = this.value |
|||
} |
|||
drawingShadowWidth.onchange = function() { |
|||
canvas.freeDrawingBrush.shadow.blur = parseInt(this.value, 10) || 0 |
|||
this.previousSibling.innerHTML = this.value |
|||
} |
|||
drawingShadowOffset.onchange = function() { |
|||
canvas.freeDrawingBrush.shadow.offsetX = parseInt(this.value, 10) || 0 |
|||
canvas.freeDrawingBrush.shadow.offsetY = parseInt(this.value, 10) || 0 |
|||
this.previousSibling.innerHTML = this.value |
|||
} |
|||
|
|||
if (canvas.freeDrawingBrush) { |
|||
canvas.freeDrawingBrush.color = drawingColorEl.value |
|||
canvas.freeDrawingBrush.source = canvas.freeDrawingBrush.getPatternSrc.call(this) |
|||
canvas.freeDrawingBrush.width = parseInt(drawingLineWidthEl.value, 10) || 1 |
|||
canvas.freeDrawingBrush.shadow = new fabric.Shadow({ |
|||
blur: parseInt(drawingShadowWidth.value, 10) || 0, |
|||
offsetX: 0, |
|||
offsetY: 0, |
|||
affectStroke: true, |
|||
color: drawingShadowColorEl.value |
|||
}) |
|||
} |
|||
var mainScriptEl = document.getElementById('main') |
|||
if (!mainScriptEl) return |
|||
var preEl = document.createElement('pre') |
|||
var codeEl = document.createElement('code') |
|||
codeEl.innerHTML = mainScriptEl.innerHTML |
|||
codeEl.className = 'language-javascript' |
|||
preEl.appendChild(codeEl) |
|||
document.getElementById('bd-wrapper').appendChild(preEl) |
|||
|
|||
window.addEventListener('load', function() { |
|||
var canvas = this.canvas |
|||
var canvases = this.__canvases || this.canvases |
|||
|
|||
canvas && canvas.calcOffset && canvas.calcOffset() |
|||
|
|||
if (canvases && canvases.length) { |
|||
for (var i = 0, len = canvases.length; i < len; i++) { |
|||
canvases[i].calcOffset() |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
|
|||
<style scoped> |
|||
|
|||
::v-deep .ivu-layout-header { |
|||
padding: 0 10px; |
|||
} |
|||
.home,.ivu-layout{ |
|||
height: calc( 100vh - 70px ); |
|||
} |
|||
.icon{ |
|||
display: block; |
|||
} |
|||
.canvas-box{ |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%, -50%); |
|||
} |
|||
#canvas{ |
|||
width: 300px; |
|||
height: 300px; |
|||
margin: 0 auto; |
|||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHUlEQVQ4jWNgYGAQIYAJglEDhoUBg9+FowbQ2gAARjwKARjtnN8AAAAASUVORK5CYII="); |
|||
background-size: 30px 30px; |
|||
} |
|||
.content{ |
|||
flex: 1; |
|||
width: 200px; |
|||
padding:10px; |
|||
padding-top: 0; |
|||
height: 100%; |
|||
overflow-y: auto; |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,33 @@ |
|||
/* |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-03 19:16:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-04 00:01:00 |
|||
* @Description: 自定义事件 |
|||
*/ |
|||
|
|||
import EventEmitter from 'events' |
|||
|
|||
class EventHandle extends EventEmitter { |
|||
init(handler) { |
|||
this.handler = handler |
|||
this.handler.on('selection:created', (e) => this._selected(e)) |
|||
this.handler.on('selection:updated', (e) => this._selected(e)) |
|||
this.handler.on('selection:cleared', (e) => this._selected(e)) |
|||
} |
|||
|
|||
// 暴露单选多选事件
|
|||
_selected(e) { |
|||
const actives = this.handler.getActiveObjects() |
|||
if (actives && actives.length === 1) { |
|||
this.emit('selectOne', actives) |
|||
} else if (actives && actives.length > 1) { |
|||
this.mSelectMode = 'multiple' |
|||
this.emit('selectMultiple', actives) |
|||
} else { |
|||
this.emit('selectCancel') |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default EventHandle |
@ -0,0 +1,47 @@ |
|||
/* |
|||
* @Author: 秦少卫 |
|||
* @Date: 2022-09-05 22:21:55 |
|||
* @LastEditors: 秦少卫 |
|||
* @LastEditTime: 2022-09-05 23:00:29 |
|||
* @Description: 工具文件 |
|||
*/ |
|||
|
|||
import FontFaceObserver from 'fontfaceobserver' |
|||
|
|||
/** |
|||
* @description: 图片文件转字符串 |
|||
* @param {Blob|File} file 文件 |
|||
* @return {String} |
|||
*/ |
|||
export function getImgStr(file) { |
|||
return new Promise((resolve, reject) => { |
|||
try { |
|||
const reader = new FileReader(); |
|||
reader.readAsDataURL(file); |
|||
reader.onload = () => { |
|||
resolve(reader.result) |
|||
}; |
|||
} catch (error) { |
|||
reject(error) |
|||
} |
|||
}); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @description: 根据json模板下载字体文件 |
|||
* @param {String} str |
|||
* @return {Promise} |
|||
*/ |
|||
export function downFontByJSON(str) { |
|||
const skipFonts = ['arial', 'Microsoft YaHei'] |
|||
const fontFamilys = JSON.parse(str).objects.filter(item => { |
|||
// 为text 并且不为包含字体
|
|||
return (item.type.includes('text') && !skipFonts.includes(item.fontFamily)) |
|||
}).map(item => item.fontFamily) |
|||
const fontFamilysAll = fontFamilys.map(fontName => { |
|||
const font = new FontFaceObserver(fontName); |
|||
return font.load(null, 150000) |
|||
}) |
|||
return Promise.all(fontFamilysAll) |
|||
} |
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue