|
@@ -0,0 +1,986 @@
|
|
|
+/* eslint-disable camelcase */
|
|
|
+import React from 'react'
|
|
|
+import { Button, Form, Input, Select, Slider, Switch, InputNumber } from 'antd'
|
|
|
+import fabric from 'fabric'
|
|
|
+import cloneDeep from 'lodash.clonedeep'
|
|
|
+import throttle from 'lodash.throttle'
|
|
|
+import RcColorPicker from 'rc-color-picker'
|
|
|
+import 'rc-color-picker/assets/index.css'
|
|
|
+import jrQrcode from 'jr-qrcode'
|
|
|
+
|
|
|
+import { optionArr, newOptionArr } from './util/data.js'
|
|
|
+
|
|
|
+import styles from './index.less'
|
|
|
+
|
|
|
+fabric = fabric.fabric
|
|
|
+const { Option } = Select
|
|
|
+
|
|
|
+const QRErrorCorrectLevel = {
|
|
|
+ L: 1,
|
|
|
+ M: 0,
|
|
|
+ Q: 3,
|
|
|
+ H: 2
|
|
|
+}
|
|
|
+
|
|
|
+const _config = {
|
|
|
+ canvasState: [],
|
|
|
+ currentStateIndex: -1,
|
|
|
+ undoStatus: false,
|
|
|
+ redoStatus: false,
|
|
|
+ undoFinishedStatus: 1,
|
|
|
+ redoFinishedStatus: 1
|
|
|
+}
|
|
|
+
|
|
|
+export default class Edit extends React.PureComponent {
|
|
|
+ state={
|
|
|
+ name: '模版一',
|
|
|
+ description: '描述',
|
|
|
+ canvas: {},
|
|
|
+
|
|
|
+ currentOptionArr: cloneDeep(newOptionArr), // 当前可设置的数组的值
|
|
|
+ optionArr: cloneDeep(newOptionArr) // 当前可设置的数组的值
|
|
|
+ // currentObjectType: 'text' // 当前要添加对象的类型
|
|
|
+ }
|
|
|
+
|
|
|
+ activeObject={}
|
|
|
+ currentOptionArr=cloneDeep(newOptionArr)
|
|
|
+
|
|
|
+ componentDidMount () {
|
|
|
+ this.canvas_sprite = new fabric.Canvas('canvas', {
|
|
|
+ ...this.state.currentOptionArr[0].css,
|
|
|
+ backgroundColor: this.state.currentOptionArr[0].css.background
|
|
|
+ })
|
|
|
+ this.addEventListener()
|
|
|
+ }
|
|
|
+
|
|
|
+ async addShape (index, action) {
|
|
|
+ const currentOptionArr = this.state.currentOptionArr
|
|
|
+ const { type } = currentOptionArr[index]
|
|
|
+ console.log(type, 1234567890)
|
|
|
+ let Shape
|
|
|
+ switch (type) {
|
|
|
+ case 'text':
|
|
|
+ Shape = await this.addTextObject(index, action)
|
|
|
+ break
|
|
|
+ case 'image':
|
|
|
+ Shape = await this.addImageObject(index, action)
|
|
|
+ break
|
|
|
+ case 'qrcode':
|
|
|
+ Shape = await this.addQrcodeObject(index, action)
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ break
|
|
|
+ }
|
|
|
+ this.canvas_sprite.setActiveObject(Shape)
|
|
|
+ this.activeObject = Shape
|
|
|
+ // this.setState({
|
|
|
+ // visible: true
|
|
|
+ // })
|
|
|
+ this.canvas_sprite.add(Shape)
|
|
|
+ if (action !== 'update') {
|
|
|
+ this.changeActiveObjectValue()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async addTextObject (index, action) {
|
|
|
+ let currentOptionArr
|
|
|
+ if (action === 'update') {
|
|
|
+ currentOptionArr = this.state.currentOptionArr
|
|
|
+ } else {
|
|
|
+ currentOptionArr = this.currentOptionArr
|
|
|
+ }
|
|
|
+ const { css } = currentOptionArr[index]
|
|
|
+ let {
|
|
|
+ width,
|
|
|
+ text,
|
|
|
+ color,
|
|
|
+ fontSize,
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ fontWeight,
|
|
|
+ fontFamily,
|
|
|
+ padding,
|
|
|
+ textDecoration,
|
|
|
+ borderRadius,
|
|
|
+ borderWidth,
|
|
|
+ borderColor,
|
|
|
+ rotate,
|
|
|
+ // align,
|
|
|
+ shadow,
|
|
|
+ lineHeight,
|
|
|
+ textAlign,
|
|
|
+ maxLines,
|
|
|
+ textStyle,
|
|
|
+ background
|
|
|
+ } = css
|
|
|
+ width = width / 1
|
|
|
+ left = left / 1
|
|
|
+ top = top / 1
|
|
|
+ borderRadius = borderRadius / 1
|
|
|
+ borderWidth = borderWidth / 1
|
|
|
+ rotate = rotate / 1
|
|
|
+ fontSize = fontSize / 1
|
|
|
+ maxLines = maxLines / 1
|
|
|
+ padding = 0
|
|
|
+ lineHeight = lineHeight / 1 // 和painter调试得出的值
|
|
|
+ shadow = shadow
|
|
|
+ .trim()
|
|
|
+ .split(/\s+/)
|
|
|
+ .join(' ')
|
|
|
+ // let Shape
|
|
|
+ const config = {
|
|
|
+ width, // 文字的高度随行高
|
|
|
+ fill: color,
|
|
|
+ fontWeight,
|
|
|
+ left: 0, // 距离画布左侧的距离,单位是像素
|
|
|
+ top: 0,
|
|
|
+ fontSize, // 文字大小
|
|
|
+ fontFamily,
|
|
|
+ padding,
|
|
|
+ [textDecoration]: true,
|
|
|
+ textAlign,
|
|
|
+ textStyle,
|
|
|
+ shadow,
|
|
|
+ myshadow: shadow,
|
|
|
+ splitByGrapheme: true, // 文字换行
|
|
|
+ lineHeight,
|
|
|
+ editable: true,
|
|
|
+ maxLines: maxLines,
|
|
|
+ textDecoration: textDecoration,
|
|
|
+ lockScalingY: true,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center'
|
|
|
+ }
|
|
|
+ // 镂空
|
|
|
+ // if (textStyle === 'stroke') {
|
|
|
+ // config = {
|
|
|
+ // ...config,
|
|
|
+ // stroke: color,
|
|
|
+ // fill: 'rgba(0,0,0)'
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ const textBox = new fabric.Textbox(text, config)
|
|
|
+ textBox.toObject = (function (toObject) {
|
|
|
+ return function () {
|
|
|
+ return fabric.util.object.extend(toObject.call(this), {
|
|
|
+ maxLines,
|
|
|
+ textDecoration,
|
|
|
+ textStyle
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })(textBox.toObject)
|
|
|
+ // 通过最大行高计算高度,并删除多余文字,多出文字..表示,三个会换行
|
|
|
+ if (textBox.textLines.length > maxLines) {
|
|
|
+ let text = ''
|
|
|
+ for (let index = 0; index < maxLines; index++) {
|
|
|
+ const element = textBox.textLines[index]
|
|
|
+ if (index === maxLines - 1) {
|
|
|
+ text = text + element + '...'
|
|
|
+ } else {
|
|
|
+ text += element
|
|
|
+ }
|
|
|
+ }
|
|
|
+ textBox.set({
|
|
|
+ text
|
|
|
+ })
|
|
|
+ if (textBox.textLines.length > maxLines) {
|
|
|
+ let text = ''
|
|
|
+ for (let index = 0; index < maxLines; index++) {
|
|
|
+ const element = textBox.textLines[index]
|
|
|
+ if (index === maxLines - 1) {
|
|
|
+ text = text + element.substring(0, element.length - 3) + '...'
|
|
|
+ } else {
|
|
|
+ text += element
|
|
|
+ }
|
|
|
+ }
|
|
|
+ textBox.set({
|
|
|
+ text
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const height = textBox.height / 1 + (textBox.lineHeight / 1 - 1) * textBox.fontSize + padding * 2
|
|
|
+ left = css.left - padding + borderWidth
|
|
|
+ top = css.top - padding + borderWidth
|
|
|
+ textBox.set({
|
|
|
+ top: -((textBox.lineHeight / 1 - 1) * textBox.fontSize) / 2
|
|
|
+ })
|
|
|
+
|
|
|
+ // const Rect = new fabric.Rect({
|
|
|
+ // width: width + borderWidth,
|
|
|
+ // height: height + borderWidth,
|
|
|
+ // left: 0, // 距离画布左侧的距离,单位是像素
|
|
|
+ // top: 0,
|
|
|
+ // originX: 'center',
|
|
|
+ // originY: 'center',
|
|
|
+ // // padding,
|
|
|
+ // rx: borderRadius,
|
|
|
+ // strokeWidth: borderWidth / 1,
|
|
|
+ // stroke: borderColor,
|
|
|
+ // fill: background,
|
|
|
+ // shadow,
|
|
|
+ // selectable: false
|
|
|
+ // })
|
|
|
+ // this.canvas_sprite.add(Rect);
|
|
|
+ // let gradientOption = ''
|
|
|
+ // if (GD.api.isGradient(background)) {
|
|
|
+ // alert(1)
|
|
|
+ // gradientOption = GD.api.doGradient(background, width, height)
|
|
|
+ // } else {
|
|
|
+ // alert(2)
|
|
|
+ // }
|
|
|
+ // if (gradientOption) Rect.setGradient('fill', gradientOption)
|
|
|
+ const Shape = new fabric.Group([], {
|
|
|
+ width: width + borderWidth,
|
|
|
+ height: height + borderWidth,
|
|
|
+ left: left + width / 2, // 距离画布左侧的距离,单位是像素
|
|
|
+ top: top + height / 2,
|
|
|
+ angle: rotate,
|
|
|
+ mytype: 'textGroup',
|
|
|
+ oldText: text,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center',
|
|
|
+ rx: borderRadius,
|
|
|
+ strokeWidth: borderWidth / 1,
|
|
|
+ stroke: borderColor,
|
|
|
+ fill: background,
|
|
|
+ shadow,
|
|
|
+ myshadow: shadow,
|
|
|
+ lockScalingY: true
|
|
|
+ })
|
|
|
+ // Shape.add(Rect)
|
|
|
+ Shape.add(textBox)
|
|
|
+
|
|
|
+ Shape.toObject = (function (toObject) {
|
|
|
+ return function () {
|
|
|
+ return fabric.util.object.extend(toObject.call(this), {
|
|
|
+ mytype: 'textGroup',
|
|
|
+ oldText: text,
|
|
|
+ rx: borderRadius,
|
|
|
+ myshadow: shadow
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })(Shape.toObject)
|
|
|
+ return Shape
|
|
|
+ }
|
|
|
+
|
|
|
+ async addImageObject (index, action) {
|
|
|
+ let currentOptionArr
|
|
|
+ if (action === 'update') {
|
|
|
+ currentOptionArr = this.state.currentOptionArr
|
|
|
+ } else {
|
|
|
+ currentOptionArr = this.currentOptionArr
|
|
|
+ }
|
|
|
+ const { css } = currentOptionArr[index]
|
|
|
+ let {
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ borderRadius,
|
|
|
+ borderWidth,
|
|
|
+ borderColor,
|
|
|
+ background,
|
|
|
+ rotate,
|
|
|
+ // align,
|
|
|
+ shadow,
|
|
|
+ mode,
|
|
|
+ url
|
|
|
+ } = css
|
|
|
+ width = width / 1
|
|
|
+ height = height / 1
|
|
|
+ left = left / 1
|
|
|
+ top = top / 1
|
|
|
+ borderRadius = borderRadius / 1
|
|
|
+ borderWidth = borderWidth / 1
|
|
|
+ rotate = rotate / 1
|
|
|
+ shadow = shadow
|
|
|
+ .trim()
|
|
|
+ .split(/\s+/)
|
|
|
+ .join(' ')
|
|
|
+ const Shape = await this.loadImageUrl(url)
|
|
|
+ const imgWidth = Shape.width
|
|
|
+ const imgHeight = Shape.height
|
|
|
+ Shape.set({
|
|
|
+ url,
|
|
|
+ // align,
|
|
|
+ mode,
|
|
|
+ shadow,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center'
|
|
|
+ })
|
|
|
+ if (mode === 'scaleToFill') {
|
|
|
+ Shape.set({
|
|
|
+ width: imgWidth,
|
|
|
+ height: imgHeight,
|
|
|
+ scaleX: width / imgWidth,
|
|
|
+ scaleY: height / imgHeight,
|
|
|
+ oldScaleX: width / imgWidth,
|
|
|
+ oldScaleY: height / imgHeight
|
|
|
+ })
|
|
|
+ Shape.clipPath = new fabric.Rect({
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center',
|
|
|
+ rx: borderRadius,
|
|
|
+ scaleX: imgWidth / width,
|
|
|
+ scaleY: imgHeight / height
|
|
|
+ })
|
|
|
+ } else if (mode === 'auto') {
|
|
|
+ // 忽略高度会自适应宽度,等比缩放图片
|
|
|
+ Shape.set({
|
|
|
+ width: imgWidth,
|
|
|
+ height: imgHeight,
|
|
|
+ scaleX: width / imgWidth,
|
|
|
+ scaleY: width / imgWidth,
|
|
|
+ oldScaleX: width / imgWidth,
|
|
|
+ oldScaleY: height / imgHeight
|
|
|
+ })
|
|
|
+ Shape.clipPath = new fabric.Rect({
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center',
|
|
|
+ rx: borderRadius,
|
|
|
+ scaleX: imgWidth / width,
|
|
|
+ scaleY: imgHeight / height
|
|
|
+ })
|
|
|
+ } else if (mode === 'aspectFill') {
|
|
|
+ Shape.clipPath = new fabric.Rect({
|
|
|
+ width: width / 1,
|
|
|
+ height: height / 1,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center',
|
|
|
+ rx: borderRadius / 1
|
|
|
+ })
|
|
|
+ Shape.set({
|
|
|
+ width,
|
|
|
+ height
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const group = new fabric.Group([Shape], {
|
|
|
+ left: left + width / 2 + borderWidth,
|
|
|
+ top: top + height / 2 + borderWidth,
|
|
|
+ width: width + borderWidth,
|
|
|
+ height: height + borderWidth,
|
|
|
+ rx: borderRadius / 1,
|
|
|
+ strokeWidth: borderWidth / 1,
|
|
|
+ stroke: borderColor,
|
|
|
+ fill: background,
|
|
|
+ angle: rotate,
|
|
|
+ shadow,
|
|
|
+ myshadow: shadow,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center',
|
|
|
+ mytype: 'image',
|
|
|
+ mode,
|
|
|
+ url,
|
|
|
+ lockUniScaling: true // 只能等比缩放
|
|
|
+ })
|
|
|
+ // 添加边框
|
|
|
+ group.add(
|
|
|
+ new fabric.Rect({
|
|
|
+ width: width + borderWidth,
|
|
|
+ height: height + borderWidth,
|
|
|
+ left: 0,
|
|
|
+ top: 0,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center',
|
|
|
+ // padding,
|
|
|
+ rx: borderRadius + borderWidth / 2,
|
|
|
+ strokeWidth: borderWidth / 1,
|
|
|
+ stroke: borderColor,
|
|
|
+ fill: 'rgba(0,0,0,0)',
|
|
|
+ shadow,
|
|
|
+ selectable: false
|
|
|
+ })
|
|
|
+ )
|
|
|
+ group.toObject = (function (toObject) {
|
|
|
+ return function () {
|
|
|
+ return fabric.util.object.extend(toObject.call(this), {
|
|
|
+ mytype: 'image',
|
|
|
+ mode,
|
|
|
+ url,
|
|
|
+ rx: borderRadius + borderWidth / 2,
|
|
|
+ oldScaleX: width / imgWidth,
|
|
|
+ oldScaleY: height / imgHeight,
|
|
|
+ myshadow: shadow
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })(group.toObject)
|
|
|
+ // console.log('group', group);
|
|
|
+ return group
|
|
|
+ }
|
|
|
+
|
|
|
+ async addQrcodeObject (index, action) {
|
|
|
+ let currentOptionArr
|
|
|
+ if (action === 'update') {
|
|
|
+ currentOptionArr = this.state.currentOptionArr
|
|
|
+ } else {
|
|
|
+ currentOptionArr = this.currentOptionArr
|
|
|
+ }
|
|
|
+ const { css } = currentOptionArr[index]
|
|
|
+ let {
|
|
|
+ width,
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ color,
|
|
|
+ borderRadius,
|
|
|
+ // borderWidth,
|
|
|
+ // borderColor,
|
|
|
+ background,
|
|
|
+ rotate,
|
|
|
+ url
|
|
|
+ // align,
|
|
|
+ } = css
|
|
|
+ width = width / 1
|
|
|
+ left = left / 1 + width / 2
|
|
|
+ top = top / 1 + width / 2
|
|
|
+ rotate = rotate / 1
|
|
|
+ const imgBase64 = jrQrcode.getQrBase64(url, {
|
|
|
+ padding: 0, // 二维码四边空白(默认为10px)
|
|
|
+ width: width / 1, // 二维码图片宽度(默认为256px)
|
|
|
+ height: width / 1, // 二维码图片高度(默认为256px)
|
|
|
+ correctLevel: QRErrorCorrectLevel.H, // 二维码容错level(默认为高)
|
|
|
+ reverse: false, // 反色二维码,二维码颜色为上层容器的背景颜色
|
|
|
+ background: background, // 二维码背景颜色(默认白色)
|
|
|
+ foreground: color // 二维码颜色(默认黑色)
|
|
|
+ })
|
|
|
+ const Shape = await this.loadImageUrl(imgBase64)
|
|
|
+ Shape.set({
|
|
|
+ url,
|
|
|
+ width: width / 1,
|
|
|
+ height: width / 1,
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ color,
|
|
|
+ background,
|
|
|
+ rx: borderRadius / 1,
|
|
|
+ // strokeWidth: borderWidth / 1,
|
|
|
+ // stroke: borderColor,
|
|
|
+ // align,
|
|
|
+ angle: rotate / 1,
|
|
|
+ lockUniScaling: true, // 只能等比缩放
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center',
|
|
|
+ mytype: 'qrcode'
|
|
|
+ })
|
|
|
+ Shape.clipPath = new fabric.Rect({
|
|
|
+ width,
|
|
|
+ height: width / 1,
|
|
|
+ originX: 'center',
|
|
|
+ originY: 'center',
|
|
|
+ rx: borderRadius,
|
|
|
+ angle: rotate / 1
|
|
|
+ })
|
|
|
+ Shape.toObject = (function (toObject) {
|
|
|
+ return function () {
|
|
|
+ return fabric.util.object.extend(toObject.call(this), {
|
|
|
+ mytype: 'qrcode',
|
|
|
+ url,
|
|
|
+ color,
|
|
|
+ background,
|
|
|
+ rx: borderRadius / 1
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })(Shape.toObject)
|
|
|
+ return Shape
|
|
|
+ }
|
|
|
+
|
|
|
+ loadImageUrl = (imgUrl) => {
|
|
|
+ return new Promise(resolve => {
|
|
|
+ fabric.Image.fromURL(imgUrl, function (oImg) {
|
|
|
+ resolve(oImg)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ changeActiveObjectValue = () => {
|
|
|
+ const type = this.activeObject.mytype
|
|
|
+ console.log(type, '99999999')
|
|
|
+ if (!type) return
|
|
|
+ // this.setState({
|
|
|
+ // visible: true
|
|
|
+ // })
|
|
|
+ const item2 = this.activeObject
|
|
|
+ const width = `${(item2.width - item2.strokeWidth) * item2.scaleX}`
|
|
|
+ const height = `${(item2.height - item2.strokeWidth) * item2.scaleY}`
|
|
|
+ /* let left = `${(item2.left / item2.scaleY - (item2.width - item2.strokeWidth) / 2 - item2.strokeWidth).toFixed(2)}`;
|
|
|
+ let top = `${(item2.top / item2.scaleY - (item2.height - item2.strokeWidth) / 2 - item2.strokeWidth).toFixed(2)}`; */
|
|
|
+ const left = `${(item2.left - width / 2).toFixed(2)}`
|
|
|
+ const top = `${(item2.top - height / 2).toFixed(2)}`
|
|
|
+
|
|
|
+ let css = {
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ color: `${item2.color}`,
|
|
|
+ background: `${item2.fill}`,
|
|
|
+ rotate: `${item2.angle}`,
|
|
|
+ borderRadius: `${item2.rx * item2.scaleY}`,
|
|
|
+ borderWidth: `${item2.strokeWidth * item2.scaleY}`,
|
|
|
+ borderColor: `${item2.stroke}`,
|
|
|
+ shadow: `${item2.myshadow}`
|
|
|
+ }
|
|
|
+ let index = ''
|
|
|
+ switch (type) {
|
|
|
+ case 'textGroup':
|
|
|
+ index = 1
|
|
|
+ item2._objects.forEach(ele => {
|
|
|
+ // const css2 = {
|
|
|
+ // text: '',
|
|
|
+ // width,
|
|
|
+ // maxLines: '',
|
|
|
+ // lineHeight: '',
|
|
|
+ // left,
|
|
|
+ // top,
|
|
|
+ // color: `${item2.color}`,
|
|
|
+ // background: `${item2.fill}`,
|
|
|
+ // fontSize: '',
|
|
|
+ // fontWeight: '',
|
|
|
+ // textDecoration: '',
|
|
|
+ // rotate: `${item2.angle}`,
|
|
|
+ // // padding: 0,
|
|
|
+ // borderRadius: `${item2.rx * item2.scaleY}`,
|
|
|
+ // borderWidth: `${item2.strokeWidth * item2.scaleY}`,
|
|
|
+ // borderColor: `${item2.stroke}`,
|
|
|
+ // shadow: `${item2.shadow}`,
|
|
|
+ // textStyle: '',
|
|
|
+ // textAlign: '',
|
|
|
+ // fontFamily: ''
|
|
|
+ // }
|
|
|
+ // if (ele.type === 'rect') {
|
|
|
+ // } else {
|
|
|
+
|
|
|
+ // }
|
|
|
+ css = {
|
|
|
+ // text: '',
|
|
|
+ width,
|
|
|
+ // maxLines: '',
|
|
|
+ // lineHeight: '',
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ // color: `${item2.color}`,
|
|
|
+ background: `${item2.fill}`,
|
|
|
+ // fontSize: '',
|
|
|
+ // fontWeight: '',
|
|
|
+ // textDecoration: '',
|
|
|
+ rotate: `${item2.angle}`,
|
|
|
+ // padding: 0,
|
|
|
+ borderRadius: `${item2.rx * item2.scaleY}`,
|
|
|
+ borderWidth: `${item2.strokeWidth * item2.scaleY}`,
|
|
|
+ borderColor: `${item2.stroke}`,
|
|
|
+ // shadow: `${item2.shadow}`,
|
|
|
+ // textStyle: '',
|
|
|
+ // textAlign: '',
|
|
|
+ // fontFamily: '',
|
|
|
+ text: `${item2.oldText}`,
|
|
|
+ maxLines: `${ele.maxLines}`,
|
|
|
+ lineHeight: `${ele.lineHeight}`,
|
|
|
+ color: ele.fill,
|
|
|
+ // padding: `${ele.padding}`,
|
|
|
+ fontSize: `${ele.fontSize}`,
|
|
|
+ fontWeight: `${ele.fontWeight}`,
|
|
|
+ textStyle: `${ele.textStyle}`,
|
|
|
+ textDecoration: `${ele.textDecoration === 'linethrough' ? 'line-through' : ele.textDecoration}`,
|
|
|
+ fontFamily: `${ele.fontFamily}`,
|
|
|
+ textAlign: `${ele.textAlign}`,
|
|
|
+ shadow: `${item2.myshadow}`
|
|
|
+ }
|
|
|
+ })
|
|
|
+ break
|
|
|
+ case 'image':
|
|
|
+ index = 2
|
|
|
+ delete css.color
|
|
|
+ delete css.background
|
|
|
+ css = {
|
|
|
+ url: item2.url,
|
|
|
+ ...css,
|
|
|
+ mode: `${item2.mode}`,
|
|
|
+ shadow: `${item2.myshadow}`
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'qrcode':
|
|
|
+ index = 3
|
|
|
+ delete css.height
|
|
|
+ delete css.borderWidth
|
|
|
+ delete css.borderColor
|
|
|
+ delete css.shadow
|
|
|
+ css = {
|
|
|
+ url: item2.url,
|
|
|
+ ...css,
|
|
|
+ color: item2.color,
|
|
|
+ background: item2.background
|
|
|
+ }
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ break
|
|
|
+ }
|
|
|
+ const currentOptionArr = cloneDeep(this.state.currentOptionArr)
|
|
|
+ currentOptionArr[index].css = css
|
|
|
+ this.setState({
|
|
|
+ currentOptionArr
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ handleChange = (index, key, value) => {
|
|
|
+ const currentOptionArr = cloneDeep(this.state.currentOptionArr)
|
|
|
+ currentOptionArr[index].css[key] = value
|
|
|
+ this.setState(
|
|
|
+ {
|
|
|
+ currentOptionArr
|
|
|
+ },
|
|
|
+ () => {
|
|
|
+ this.updateObject()
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ async updateObject () {
|
|
|
+ const type = this.activeObject.mytype
|
|
|
+ this.canvas_sprite.remove(this.activeObject)
|
|
|
+ switch (type) {
|
|
|
+ case 'textGroup':
|
|
|
+ await this.addShape(1, 'update')
|
|
|
+ break
|
|
|
+ case 'image':
|
|
|
+ await this.addShape(2, 'update')
|
|
|
+ break
|
|
|
+ case 'qrcode':
|
|
|
+ await this.addShape(3, 'update')
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ break
|
|
|
+ }
|
|
|
+ this.canvas_sprite.renderAll()
|
|
|
+ }
|
|
|
+
|
|
|
+ addEventListener = () => {
|
|
|
+ const throttlechangeActiveObjectValue = throttle(this.changeActiveObjectValue, 100)
|
|
|
+ this.canvas_sprite.on('object:moving', (e) => {
|
|
|
+ console.log('object:moving')
|
|
|
+ var obj = e.target
|
|
|
+ // if object is too big ignore
|
|
|
+ if (obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ obj.setCoords()
|
|
|
+ // top-left corner
|
|
|
+ if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
|
|
|
+ obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top)
|
|
|
+ obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left)
|
|
|
+ }
|
|
|
+ // bot-right corner
|
|
|
+ if (
|
|
|
+ obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height ||
|
|
|
+ obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width
|
|
|
+ ) {
|
|
|
+ obj.top = Math.min(
|
|
|
+ obj.top,
|
|
|
+ obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top
|
|
|
+ )
|
|
|
+ obj.left = Math.min(
|
|
|
+ obj.left,
|
|
|
+ obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ throttlechangeActiveObjectValue()
|
|
|
+ })
|
|
|
+ this.canvas_sprite.on('object:scaling', (e) => {
|
|
|
+ console.log('object:scaling')
|
|
|
+ var obj = e.target
|
|
|
+ // if object is too big ignore
|
|
|
+ if (obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ obj.setCoords()
|
|
|
+ // top-left corner
|
|
|
+ if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
|
|
|
+ obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top)
|
|
|
+ obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left)
|
|
|
+ }
|
|
|
+ // bot-right corner
|
|
|
+ if (
|
|
|
+ obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height ||
|
|
|
+ obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width
|
|
|
+ ) {
|
|
|
+ obj.top = Math.min(
|
|
|
+ obj.top,
|
|
|
+ obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top
|
|
|
+ )
|
|
|
+ obj.left = Math.min(
|
|
|
+ obj.left,
|
|
|
+ obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left
|
|
|
+ )
|
|
|
+ }
|
|
|
+ throttlechangeActiveObjectValue()
|
|
|
+ })
|
|
|
+ this.canvas_sprite.on('mouse:down', (e) => {
|
|
|
+ console.log('mouse:down')
|
|
|
+ if (e.target) {
|
|
|
+ this.activeObject = e.target
|
|
|
+ this.changeActiveObjectValue()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 解决放大缩小元素位置不对的问题
|
|
|
+ this.canvas_sprite.on('object:scaled', (e) => {
|
|
|
+ console.log('object:scaled')
|
|
|
+ if (e.target) {
|
|
|
+ this.activeObject = e.target
|
|
|
+ this.updateObject()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.canvas_sprite.on('object:modified', () => {
|
|
|
+ console.log('object:modified')
|
|
|
+ this.updateCanvasState()
|
|
|
+ })
|
|
|
+
|
|
|
+ this.canvas_sprite.on('object:added', () => {
|
|
|
+ console.log('object:added')
|
|
|
+ this.updateCanvasState()
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ updateCanvasState = () => {
|
|
|
+ const canvas_sprite = this.canvas_sprite
|
|
|
+ if (_config.undoStatus === false && _config.redoStatus === false) {
|
|
|
+ var jsonData = canvas_sprite.toJSON()
|
|
|
+ var canvasAsJson = JSON.stringify(jsonData)
|
|
|
+ if (_config.currentStateIndex < _config.canvasState.length - 1) {
|
|
|
+ var indexToBeInserted = _config.currentStateIndex + 1
|
|
|
+ _config.canvasState[indexToBeInserted] = canvasAsJson
|
|
|
+ var numberOfElementsToRetain = indexToBeInserted + 1
|
|
|
+ _config.canvasState = _config.canvasState.splice(0, numberOfElementsToRetain)
|
|
|
+ } else {
|
|
|
+ _config.canvasState.push(canvasAsJson)
|
|
|
+ }
|
|
|
+ _config.currentStateIndex = _config.canvasState.length - 1
|
|
|
+ if (_config.currentStateIndex === _config.canvasState.length - 1 && _config.currentStateIndex !== -1) {
|
|
|
+ this.setState({
|
|
|
+ redoButtonStatus: 'disabled'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ save = () => {
|
|
|
+ }
|
|
|
+
|
|
|
+ render () {
|
|
|
+ const canvasOption = this.currentOptionArr[0].css
|
|
|
+ return (
|
|
|
+ <React.Fragment>
|
|
|
+ <div className={styles.header}><span>模版:{this.state.name}</span><span>保存于00:00:00 <Button>预览</Button><Button type="primary" onClick={this.save}>保存</Button></span></div>
|
|
|
+ <div className={styles.content}>
|
|
|
+ <div className={styles.left}>
|
|
|
+ <div className={styles.box}>
|
|
|
+ <h2>背景</h2>
|
|
|
+ <Form layout="vertical">
|
|
|
+ <Form.Item label="宽度" >
|
|
|
+ <Input defaultValue={canvasOption.width} onChange={(e) => {
|
|
|
+ this.currentOptionArr[0].css.width = e.target.value
|
|
|
+ this.canvas_sprite.setWidth(e.target.value)
|
|
|
+ }}/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="高度">
|
|
|
+ <Input defaultValue={canvasOption.height} onChange={(e) => {
|
|
|
+ this.currentOptionArr[0].css.height = e.target.value
|
|
|
+ this.canvas_sprite.setHeight(e.target.value)
|
|
|
+ }}/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="背景色">
|
|
|
+ <Input defaultValue={canvasOption.background} onChange={(e) => {
|
|
|
+ this.currentOptionArr[0].css.background = e.target.value
|
|
|
+ this.canvas_sprite.setBackgroundColor(e.target.value)
|
|
|
+ this.canvas_sprite.renderAll()
|
|
|
+ }}/>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+ <div className={styles.box}>
|
|
|
+ <h2>元素</h2>
|
|
|
+ <ul className={styles.elem}>
|
|
|
+ <li onClick={() => this.addShape(1)}>文字</li>
|
|
|
+ <li onClick={() => this.addShape(2)}>图片</li>
|
|
|
+ <li onClick={() => this.addShape(3)}>二维码</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div className={styles.box}>
|
|
|
+ <h2>图层列表</h2>
|
|
|
+ <ul className={styles.layer}>
|
|
|
+ <li className={styles.selected}>xxxx</li>
|
|
|
+ <li>xxxx</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className={styles.middle}>
|
|
|
+ <canvas id="canvas"></canvas>
|
|
|
+ </div>
|
|
|
+ <div className={styles.right}>
|
|
|
+ {
|
|
|
+ this.activeObject.mytype === 'textGroup' && (
|
|
|
+ <div className={styles.box}>
|
|
|
+ <h2>文字</h2>
|
|
|
+ <Form layout="vertical">
|
|
|
+ <Form.Item label="内容">
|
|
|
+ <Input.TextArea rows={3} defaultValue={this.state.currentOptionArr[1].css.text} onChange={(e) => this.handleChange(1, 'text', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ <div style={{ display: 'flex' }}>
|
|
|
+ <Form.Item label="字体" style={{ width: 150 }}>
|
|
|
+ <Select defaultValue={this.state.currentOptionArr[1].css.fontFamily} style={{ width: 145 }} onChange={(value) => this.handleChange(1, 'fontFamily', value)}>
|
|
|
+ <Option value="msyh">msyh</Option>
|
|
|
+ <Option value="msyhbd">msyhbd</Option>
|
|
|
+ <Option value="NotoSerifCJK">NotoSerifCJK</Option>
|
|
|
+ <Option value="PingFang">PingFang</Option>
|
|
|
+ <Option value="pingfang-sc-semibold">pingfang-sc-semibold</Option>
|
|
|
+ <Option value="pingfang_jiancu">pingfang_jiancu</Option>
|
|
|
+ <Option value="pingfang_jianxi">pingfang_jianxi</Option>
|
|
|
+ <Option value="pingfang_regular">pingfang_regular</Option>
|
|
|
+ <Option value="PingFang_SC">PingFang_SC</Option>
|
|
|
+ <Option value="pingfang_sc_medium">pingfang_sc_medium</Option>
|
|
|
+ <Option value="pingfang_sc_semibold">pingfang_sc_semibold</Option>
|
|
|
+ <Option value="simhei">simhei</Option>
|
|
|
+ <Option value="SourceHanSansCN-Normal">SourceHanSansCN-Normal</Option>
|
|
|
+ <Option value="喜鹊招牌体">喜鹊招牌体</Option>
|
|
|
+ <Option value="喜鹊聚珍体regular">喜鹊聚珍体regular</Option>
|
|
|
+ <Option value="思源宋体">思源宋体</Option>
|
|
|
+ <Option value="方正正粗黑简体">方正正粗黑简体</Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="字体大小" style={{ flex: 1 }}>
|
|
|
+ <InputNumber style={{ width: 80 }} min={12} max={100} defaultValue={this.state.currentOptionArr[1].css.fontSize} onChange={(value) => this.handleChange(1, 'fontSize', value)} />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="字体颜色" style={{ flex: 1 }}>
|
|
|
+ <RcColorPicker
|
|
|
+ color={this.state.currentOptionArr[1].css.color}
|
|
|
+ onChange={({ color }) => this.handleChange(1, 'color', color)}
|
|
|
+ >
|
|
|
+ <Input style={{ width: 80 }} />
|
|
|
+ </RcColorPicker>
|
|
|
+ </Form.Item>
|
|
|
+ </div>
|
|
|
+ <div style={{ display: 'flex' }}>
|
|
|
+ <Form.Item label="X偏移" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[1].css.left} onChange={(e) => this.handleChange(1, 'left', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="Y偏移" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[1].css.top} onChange={(e) => this.handleChange(1, 'top', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ </div>
|
|
|
+ <Form.Item label="文字宽度">
|
|
|
+ <Input defaultValue={this.state.currentOptionArr[1].css.width} onChange={(e) => this.handleChange(1, 'width', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="文字行数">
|
|
|
+ <Input defaultValue={this.state.currentOptionArr[1].css.maxLines} onChange={(e) => this.handleChange(1, 'maxLines', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="X居中">
|
|
|
+ <Select defaultValue={this.state.currentOptionArr[1].css.textAlign} style={{ width: 150 }} onChange={(value) => this.handleChange(1, 'textAlign', value)}>
|
|
|
+ <Option value="left">left</Option>
|
|
|
+ <Option value="center">center</Option>
|
|
|
+ <Option value="right">right</Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="文字垂直">
|
|
|
+ <Input/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="不透明">
|
|
|
+ <Slider
|
|
|
+ min={1}
|
|
|
+ max={20}
|
|
|
+ onChange={(value) => console.log(value)}
|
|
|
+ value={10}
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ this.activeObject.mytype === 'qrcode' && (
|
|
|
+ <div className={styles.box}>
|
|
|
+ <h2>二维码</h2>
|
|
|
+ <Form layout="vertical">
|
|
|
+ <Form.Item label="内容">
|
|
|
+ <Input defaultValue={this.state.currentOptionArr[3].css.url} onChange={(e) => this.handleChange(3, 'url', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ <div style={{ display: 'flex' }}>
|
|
|
+ <Form.Item label="X偏移" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[3].css.left} onChange={(e) => this.handleChange(3, 'left', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="Y偏移" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[3].css.top} onChange={(e) => this.handleChange(3, 'top', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ </div>
|
|
|
+ <Form.Item label="LOGO">
|
|
|
+ <Switch defaultChecked onChange={(checked) => console.log(checked)} checkedChildren="开" unCheckedChildren="关" />
|
|
|
+ <Select defaultValue="wpt" style={{ width: 120, marginLeft: '5px' }}>
|
|
|
+ <Option value="wpt">微拍堂</Option>
|
|
|
+ <Option value="youjiang"></Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="大小">
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[3].css.width} onChange={(e) => this.handleChange(3, 'width', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ this.activeObject.mytype === 'image' && (
|
|
|
+ <div className={styles.box}>
|
|
|
+ <h2>图片</h2>
|
|
|
+ <Form layout="vertical">
|
|
|
+ <Form.Item label="路径">
|
|
|
+ <Input/>
|
|
|
+ </Form.Item>
|
|
|
+ <div style={{ display: 'flex' }}>
|
|
|
+ <Form.Item label="宽" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.width} onChange={(e) => this.handleChange(2, 'width', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="高" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.height} onChange={(e) => this.handleChange(2, 'height', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ </div>
|
|
|
+ <div style={{ display: 'flex' }}>
|
|
|
+ <Form.Item label="X偏移" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.left} onChange={(e) => this.handleChange(2, 'left', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="Y偏移" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.top} onChange={(e) => this.handleChange(2, 'top', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ </div>
|
|
|
+ <div style={{ display: 'flex' }}>
|
|
|
+ <Form.Item label="圆形" style={{ flex: 1 }}>
|
|
|
+ <Switch defaultChecked onChange={(checked) => {
|
|
|
+ if (checked) {
|
|
|
+ this.handleChange(2, 'borderRadius', 1000)
|
|
|
+ } else {
|
|
|
+ this.handleChange(2, 'borderRadius', 0)
|
|
|
+ }
|
|
|
+ }} checkedChildren="开" unCheckedChildren="关" />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="圆角" style={{ flex: 1 }}>
|
|
|
+ <Input style={{ width: 100 }} defaultValue={this.state.currentOptionArr[2].css.borderRadius} onChange={(e) => this.handleChange(2, 'borderRadius', e.target.value)}/>
|
|
|
+ </Form.Item>
|
|
|
+ </div>
|
|
|
+ <Form.Item label="高斯模糊">
|
|
|
+ <Input/>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="图片裁剪选项">
|
|
|
+ <Input/>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </React.Fragment>
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|