Browse Source

图片编辑组件修改

360view
bianyaqi 2 years ago
parent
commit
c51ee7060c
  1. 6
      package.json
  2. BIN
      src/assets/fonts/cn/华康金刚黑.ttf
  3. BIN
      src/assets/fonts/cn/汉体.ttf
  4. 29
      src/assets/fonts/font.css
  5. 22
      src/assets/fonts/font.js
  6. BIN
      src/assets/img/os.png
  7. 258
      src/components/360View/img-editor.vue
  8. 217
      src/core/initAligningGuidelines.js
  9. 60
      src/core/initControls.js
  10. 89
      src/core/initHotKeys.js
  11. 89
      src/core/initializeLineDrawing.js
  12. 46
      src/core/objects/Arrow.js
  13. 80
      src/mixins/select.js
  14. 11
      src/page-subspecialty/views/modules/formList/laserSurgery.vue
  15. 248
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/align.vue
  16. 404
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/attribute copy.vue
  17. 287
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/attribute.vue
  18. 149
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/bgBar.vue
  19. 39
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/brush.vue
  20. 50
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/centerAlign.vue
  21. 38
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/clone.vue
  22. 35
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/del.vue
  23. 43
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/flip.vue
  24. 69
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/group.vue
  25. 84
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/history.vue
  26. 76
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/importImg.vue
  27. 66
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/importJSON.vue
  28. 87
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/importSvg.vue
  29. 101
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/importTmpl.vue
  30. 191
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/layer.vue
  31. 81
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/lock.vue
  32. 38
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/save.vue
  33. 82
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/setSize.vue
  34. 143
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/svgEl.vue
  35. 15
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/svgIcon/index.js
  36. 51
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/svgIcon/index.vue
  37. 231
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/tools.vue
  38. 70
      src/page-subspecialty/views/modules/imgEditorFabric/img-editor/zoom.vue
  39. 386
      src/page-subspecialty/views/modules/imgEditorFabric/index.vue
  40. 33
      src/utils/eventHandler.js
  41. 47
      src/utils/utils.js
  42. 10872
      static/js/jquery-3.5.1/jquery.js
  43. 2
      static/js/jquery-3.5.1/jquery.min.js

6
package.json

@ -39,9 +39,14 @@
"js-base64": "^3.6.1",
"js-cookie": "^2.2.1",
"jszip-utils": "^0.1.0",
"less": "^4.0.0",
"fontfaceobserver": "^2.1.0",
"less-loader": "^5.0.0",
"lodash": "^4.17.19",
"moment": "^2.29.1",
"node-sass": "^6.0.1",
"hotkeys-js": "^3.8.8",
"lodash-es": "^4.17.21",
"pdfjs-dist": "^2.6.347",
"pizzip": "^3.1.1",
"qs": "^6.9.4",
@ -55,6 +60,7 @@
"tui-color-picker": "^2.2.8",
"tui-image-editor": "^3.15.3",
"v-tooltip": "^2.1.3",
"view-design": "^4.7.0",
"vue": "^2.6.11",
"vue-chat-scroll": "^1.4.0",
"vue-clipboard2": "^0.3.3",

BIN
src/assets/fonts/cn/华康金刚黑.ttf

Binary file not shown.

BIN
src/assets/fonts/cn/汉体.ttf

Binary file not shown.

29
src/assets/fonts/font.css

@ -0,0 +1,29 @@
@font-face{
font-family: '汉体';
src : url('./cn/汉体.ttf');
}
@font-face{
font-family: '华康金刚黑';
src : url('./cn/华康金刚黑.ttf');
}

22
src/assets/fonts/font.js

@ -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]

BIN
src/assets/img/os.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

258
src/components/360View/img-editor.vue

@ -9,178 +9,15 @@
class="img-editor"
@closed="closeDialog"
>
<i class="el-icon-picture-outline replace-picture" />
<input id="inputFile" class="replace-picture" type="file" name="" accept="image/jpeg,image/jpg,image/png" @change="replaceHandle">
<div class="dialog-container">
<div id="tui-image-editor" />
</div>
<template slot="footer">
<el-button @click="visible = false">{{ $t('cancel') }}</el-button>
<el-button type="primary" @click="submit">{{ $t('confirm') }}</el-button>
</template>
<home-view :is-od-or-os="isOdOrOs" @closeDialog="closeDialog" @fullImgBack="fullImgBack" />
</el-dialog>
</template>
<script>
import 'tui-image-editor/dist/tui-image-editor.css'
import 'tui-color-picker/dist/tui-color-picker.css'
import ImageEditor from 'tui-image-editor'
import odOrOs from '@/components/360View/base64/odOrOs'
//
const locale_zh = {
// override default English locale to your custom
Crop: '裁剪',
Resize: '调整大小',
ZoomIn: '放大',
Hand: '拖拽',
History: '历史记录',
ZoomOut: '缩小',
DeleteAll: '全部删除',
Delete: '删除',
Undo: '撤销',
Redo: '反撤销',
Reset: '重置',
Flip: '镜像',
Rotate: '旋转',
Draw: '画',
Shape: '形状标注',
Icon: '图标标注',
Text: '文字标注',
Mask: '遮罩',
Filter: '滤镜',
Bold: '加粗',
Italic: '斜体',
Underline: '下划线',
Left: '左对齐',
Center: '居中',
Right: '右对齐',
Color: '颜色',
'Text size': '字体大小',
Custom: '自定义',
Square: '正方形',
Apply: '应用',
Cancel: '取消',
'Flip X': 'X 轴',
'Flip Y': 'Y 轴',
Range: '区间',
Stroke: '描边',
Fill: '填充',
Circle: '圆',
Triangle: '三角',
Rectangle: '矩形',
Free: '曲线',
Straight: '直线',
Arrow: '箭头',
'Arrow-2': '箭头2',
'Arrow-3': '箭头3',
'Star-1': '星星1',
'Star-2': '星星2',
Polygon: '多边形',
Location: '定位',
Heart: '心形',
Bubble: '气泡',
'Custom icon': '自定义图标',
'Load Mask Image': '加载蒙层图片',
Grayscale: '灰度',
Blur: '模糊',
Sharpen: '锐化',
Emboss: '浮雕',
'Remove White': '除去白色',
Distance: '距离',
Brightness: '亮度',
Noise: '噪音',
'Color Filter': '彩色滤镜',
Sepia: '棕色',
Sepia2: '棕色2',
Invert: '负片',
Pixelate: '像素化',
Threshold: '阈值',
Tint: '色调',
Multiply: '正片叠底',
Blend: '混合色'
// etc...
}
// LoadDownloadlogo
const customTheme = {
// image
'common.bi.image': '', // logo
'common.bisize.width': '0px',
'common.bisize.height': '0px',
'common.backgroundImage': 'none',
'common.backgroundColor': '#f3f4f6',
'common.border': '1px solid #444',
// header
'header.backgroundImage': 'none',
'header.backgroundColor': '#f3f4f6',
'header.border': '0px',
'header.display': 'none',
// load button
'loadButton.backgroundColor': '#fff',
'loadButton.border': '1px solid #ddd',
'loadButton.color': '#222',
'loadButton.fontFamily': 'NotoSans, sans-serif',
'loadButton.fontSize': '12px',
'loadButton.display': 'none', //
// download button
'downloadButton.backgroundColor': '#fdba3b',
'downloadButton.border': '1px solid #fdba3b',
'downloadButton.color': '#fff',
'downloadButton.fontFamily': 'NotoSans, sans-serif',
'downloadButton.fontSize': '12px',
'downloadButton.display': 'none', //
// icons default
'menu.normalIcon.color': '#8a8a8a',
'menu.activeIcon.color': '#555555',
'menu.disabledIcon.color': '#434343',
'menu.hoverIcon.color': '#e9e9e9',
'submenu.normalIcon.color': '#8a8a8a',
'submenu.activeIcon.color': '#e9e9e9',
'menu.iconSize.width': '24px',
'menu.iconSize.height': '24px',
'submenu.iconSize.width': '32px',
'submenu.iconSize.height': '32px',
// submenu primary color
'submenu.backgroundColor': '#1e1e1e',
'submenu.partition.color': '#858585',
// submenu labels
'submenu.normalLabel.color': '#858585',
'submenu.normalLabel.fontWeight': 'lighter',
'submenu.activeLabel.color': '#fff',
'submenu.activeLabel.fontWeight': 'lighter',
// checkbox style
'checkbox.border': '1px solid #ccc',
'checkbox.backgroundColor': '#fff',
// rango style
'range.pointer.color': '#fff',
'range.bar.color': '#666',
'range.subbar.color': '#d1d1d1',
'range.disabledPointer.color': '#414141',
'range.disabledBar.color': '#282828',
'range.disabledSubbar.color': '#414141',
'range.value.color': '#fff',
'range.value.fontWeight': 'lighter',
'range.value.fontSize': '11px',
'range.value.border': '1px solid #353535',
'range.value.backgroundColor': '#151515',
'range.title.color': '#fff',
'range.title.fontWeight': 'lighter',
// colorpicker style
'colorpicker.button.border': '1px solid #1e1e1e',
'colorpicker.title.color': '#fff'
}
import HomeView from '@/page-subspecialty/views/modules/imgEditorFabric/index.vue'
export default {
components: { HomeView },
props: {
bodyStyleShow: {
type: Boolean,
@ -197,97 +34,16 @@ export default {
},
data() {
return {
visible: false,
imgUrl: '',
imgAlt: '',
instance: null,
textOne: '',
textTwo: '',
textThree: '',
editorOptions: {
includeUI: {
loadImage: {
path: '',
name: 'image'
},
// menu: ['crop', 'rotate', 'draw', 'shape', 'icon', 'text', 'filter'], // flipmask
initMenu: 'draw',
selectionStyle: {
lineWidth: 5,
borderColor: '#000000'
},
menuBarPosition: 'bottom',
locale: locale_zh, //
theme: customTheme //
},
fabricConfig: {
//
backgroundColor: 'rgba(255, 255, 255, 1)',
//
scalable: false,
//
customShapes: [
{
name: 'triangle',
icon: 'path://M0,0 L50,50 L100,0 Z',
points: [25, 0, 50, 50, 0, 50],
offset: 25
}
]
}
}
visible: false
}
},
mounted() {
},
methods: {
init(textOne, textTwo, textThree) {
textOne ? this.textOne = textOne : ''
textTwo ? this.textTwo = textTwo : ''
textThree ? this.textThree = textThree : ''
init() {
this.visible = true
this.$nextTick(() => {
if (this.isOdOrOs === 'OD') {
this.imgUrl = odOrOs.od
} else if (this.isOdOrOs === 'OS') {
this.imgUrl = odOrOs.os
}
this.editorOptions.includeUI.loadImage.path = this.imgUrl
this.instance = new ImageEditor(
'#tui-image-editor',
this.editorOptions
)
this.instance.setBrush({ width: 20, color: 'red' })
document.querySelector('.tie-btn-mask').style.display = 'none' //
})
},
submit() {
const base64String = this.instance.toDataURL()
// console.log(base64String)
this.$emit('fullImgBack', base64String, this.imgAlt, this.textOne, this.textTwo, this.textThree)
fullImgBack(value) {
this.$emit('fullImgBack', value)
this.visible = false
// ---
// const data = window.atob(base64String.split(',')[1])
// const ia = new Uint8Array(data.length)
// for (let i = 0; i < data.length; i++) {
// ia[i] = data.charCodeAt(i)
// }
// const blob = new Blob([ia], { type: 'image/png' })
// const fd = new FormData()
// fd.append('image', blob)
// upload fd
},
//
replaceHandle(e) {
var file = e.target.files[0]
var reader = new FileReader()
var that = this
reader.readAsDataURL(file)
reader.onload = function(e) {
that.imgUrl = reader.result
console.log(that.imgUrl)
that.init()
}
},
closeDialog() {
this.$emit('closeDialog')

217
src/core/initAligningGuidelines.js

@ -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;

60
src/core/initControls.js

@ -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

89
src/core/initHotKeys.js

@ -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 }

89
src/core/initializeLineDrawing.js

@ -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;

46
src/core/objects/Arrow.js

@ -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;

80
src/mixins/select.js

@ -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('复制失败')
})
}
}
}

11
src/page-subspecialty/views/modules/formList/laserSurgery.vue

@ -143,7 +143,7 @@
/>
</div>
</div>
<img-editor ref="imgEditorRef" :is-od-or-os="this.confirmData.eyeType" @fullImgBack="fullImgBack" />
<img-editor v-if="editorShow" ref="imgEditorRef" :is-od-or-os="this.confirmData.eyeType" @closeDialog="closeDialog" @fullImgBack="fullImgBack" />
</div>
</div>
</template>
@ -175,6 +175,7 @@ export default {
data() {
return {
origin: '',
editorShow: false,
confirmData: {
patientAddress: '',
patientAge: '',
@ -240,7 +241,13 @@ export default {
this.$store.commit('beginSign', index)
},
editImg() {
this.$refs.imgEditorRef.init()
this.editorShow = true
this.$nextTick(() => {
this.$refs.imgEditorRef.init()
})
},
closeDialog() {
this.editorShow = false
},
// id
async queryProject() {

248
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/align.vue

@ -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>

404
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/attribute copy.vue

File diff suppressed because one or more lines are too long

287
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/attribute.vue

File diff suppressed because one or more lines are too long

149
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/bgBar.vue

@ -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>

39
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/brush.vue

@ -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>

50
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/centerAlign.vue

@ -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>

38
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/clone.vue

@ -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>

35
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/del.vue

@ -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>

43
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/flip.vue

@ -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>

69
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/group.vue

@ -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>

84
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/history.vue

@ -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>

76
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/importImg.vue

@ -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>

66
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/importJSON.vue

@ -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>

87
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/importSvg.vue

@ -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>

101
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/importTmpl.vue

@ -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>

191
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/layer.vue

File diff suppressed because one or more lines are too long

81
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/lock.vue

@ -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>

38
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/save.vue

@ -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>

82
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/setSize.vue

@ -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>

143
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/svgEl.vue

@ -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>

15
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/svgIcon/index.js

@ -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)
}
}

51
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/svgIcon/index.vue

@ -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>

231
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/tools.vue

File diff suppressed because one or more lines are too long

70
src/page-subspecialty/views/modules/imgEditorFabric/img-editor/zoom.vue

@ -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>

386
src/page-subspecialty/views/modules/imgEditorFabric/index.vue

@ -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>

33
src/utils/eventHandler.js

@ -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

47
src/utils/utils.js

@ -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)
}

10872
static/js/jquery-3.5.1/jquery.js

File diff suppressed because it is too large

2
static/js/jquery-3.5.1/jquery.min.js

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save