特易招,招聘小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1054 lines
28 KiB

5 months ago
4 months ago
5 months ago
4 months ago
5 months ago
4 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
4 months ago
5 months ago
4 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
  1. <template>
  2. <canvas canvas-id="canvas-drag" disable-scroll="true" @touchstart="start" @touchmove="move" @touchend="end"
  3. :style="'width: ' + width + 'rpx; height: ' + height + 'rpx;'"></canvas>
  4. </template>
  5. <script>
  6. // components/canvas-drag/index.js
  7. let DELETE_ICON = '../../static/components/canvas-drag/close.png'; // 删除按钮
  8. // 删除按钮
  9. let DRAG_ICON = '../../static/components/canvas-drag/scale.png'; // 缩放按钮
  10. // 缩放按钮
  11. const STROKE_COLOR = '#fff';
  12. const ROTATE_ENABLED = false;
  13. let isMove = false; // 标识触摸后是否有移动,用来判断是否需要增加操作历史
  14. // 标识触摸后是否有移动,用来判断是否需要增加操作历史
  15. const DEBUG_MODE = false; // 打开调试后会渲染操作区域边框(无背景时有效)
  16. // 打开调试后会渲染操作区域边框(无背景时有效)
  17. const dragGraph = function({
  18. id,
  19. x = 30,
  20. y = 30,
  21. w,
  22. h,
  23. type,
  24. text,
  25. fontSize = 20,
  26. color = 'red',
  27. url = null,
  28. rotate = 0,
  29. sourceId = null,
  30. selected = true,
  31. permitSelected = false,
  32. }, canvas, factor) {
  33. if (type === 'text') {
  34. canvas.setFontSize(fontSize);
  35. const textWidth = canvas.measureText(text).width;
  36. const textHeight = fontSize + 10;
  37. this.centerX = x + textWidth / 2;
  38. this.centerY = y + textHeight / 2;
  39. this.w = textWidth;
  40. this.h = textHeight;
  41. } else {
  42. this.centerX = x + w / 2;
  43. this.centerY = y + h / 2;
  44. this.w = w;
  45. this.h = h;
  46. }
  47. this.id = id
  48. this.x = x;
  49. this.y = y; // 4个顶点坐标
  50. this.permitSelected = permitSelected; // 4个顶点坐标
  51. this.square = [
  52. [this.x, this.y],
  53. [this.x + this.w, this.y],
  54. [this.x + this.w, this.y + this.h],
  55. [this.x, this.y + this.h]
  56. ];
  57. this.fileUrl = url;
  58. this.text = text;
  59. this.fontSize = fontSize;
  60. this.color = color;
  61. this.ctx = canvas;
  62. this.rotate = rotate;
  63. this.type = type;
  64. this.selected = selected;
  65. this.factor = factor;
  66. this.sourceId = sourceId;
  67. this.MIN_WIDTH = 20;
  68. this.MIN_FONTSIZE = 10;
  69. };
  70. dragGraph.prototype = {
  71. /**
  72. * 绘制元素
  73. */
  74. paint() {
  75. this.ctx.save(); // 由于measureText获取文字宽度依赖于样式,所以如果是文字元素需要先设置样式
  76. let textWidth = 0;
  77. let textHeight = 0;
  78. if (this.type === 'text') {
  79. this.ctx.setFontSize(this.fontSize);
  80. this.ctx.setTextBaseline('middle');
  81. this.ctx.setTextAlign('center');
  82. this.ctx.setFillStyle(this.color);
  83. textWidth = this.ctx.measureText(this.text).width;
  84. textHeight = this.fontSize + 10; // 字体区域中心点不变,左上角位移
  85. this.x = this.centerX - textWidth / 2;
  86. this.y = this.centerY - textHeight / 2;
  87. } // 旋转元素
  88. this.ctx.translate(this.centerX, this.centerY);
  89. this.ctx.rotate(this.rotate * Math.PI / 180);
  90. this.ctx.translate(-this.centerX, -this.centerY); // 渲染元素
  91. if (this.type === 'text') {
  92. this.ctx.fillText(this.text, this.centerX, this.centerY);
  93. } else if (this.type === 'image') {
  94. this.ctx.drawImage(this.fileUrl, this.x, this.y, this.w, this.h);
  95. } // 如果是选中状态,绘制选择虚线框,和缩放图标、删除图标
  96. if (this.selected && !this.permitSelected) {
  97. this.ctx.setLineDash([2, 5]);
  98. this.ctx.setLineWidth(2);
  99. this.ctx.setStrokeStyle(STROKE_COLOR);
  100. this.ctx.lineDashOffset = 6;
  101. if (this.type === 'text') {
  102. this.ctx.strokeRect(this.x, this.y, textWidth, textHeight);
  103. this.ctx.drawImage(DELETE_ICON, this.x - 15, this.y - 15, 30, 30);
  104. this.ctx.drawImage(DRAG_ICON, this.x + textWidth - 15, this.y + textHeight - 15, 30, 30);
  105. } else {
  106. this.ctx.strokeRect(this.x, this.y, this.w, this.h);
  107. // this.ctx.drawImage(DELETE_ICON, this.x - 15, this.y - 15, 30, 30);
  108. this.ctx.drawImage(DRAG_ICON, this.x + this.w - 15, this.y + this.h - 15, 30, 30);
  109. }
  110. }
  111. this.ctx.restore();
  112. },
  113. /**
  114. * 给矩形描边
  115. * @private
  116. */
  117. _drawBorder() {
  118. let p = this.square;
  119. let ctx = this.ctx;
  120. this.ctx.save();
  121. this.ctx.beginPath();
  122. ctx.setStrokeStyle('orange');
  123. this._draw_line(this.ctx, p[0], p[1]);
  124. this._draw_line(this.ctx, p[1], p[2]);
  125. this._draw_line(this.ctx, p[2], p[3]);
  126. this._draw_line(this.ctx, p[3], p[0]);
  127. ctx.restore();
  128. },
  129. /**
  130. * 画一条线
  131. * @param ctx
  132. * @param a
  133. * @param b
  134. * @private
  135. */
  136. _draw_line(ctx, a, b) {
  137. ctx.moveTo(a[0], a[1]);
  138. ctx.lineTo(b[0], b[1]);
  139. ctx.stroke();
  140. },
  141. /**
  142. * 判断点击的坐标落在哪个区域
  143. * @param {*} x 点击的坐标
  144. * @param {*} y 点击的坐标
  145. */
  146. isInGraph(x, y) {
  147. // 删除区域左上角的坐标和区域的高度宽度
  148. const delW = 30;
  149. const delH = 30; // 旋转后的删除区域坐标
  150. const transformedDelCenter = this._rotatePoint(this.x, this.y, this.centerX, this.centerY, this.rotate);
  151. const transformDelX = transformedDelCenter[0] - delW / 2;
  152. const transformDelY = transformedDelCenter[1] - delH / 2; // 变换区域左上角的坐标和区域的高度宽度
  153. const scaleW = 30;
  154. const scaleH = 30;
  155. const transformedScaleCenter = this._rotatePoint(this.x + this.w, this.y + this.h, this.centerX, this
  156. .centerY, this.rotate); // 旋转后的变换区域坐标
  157. const transformScaleX = transformedScaleCenter[0] - scaleW / 2;
  158. const transformScaleY = transformedScaleCenter[1] - scaleH / 2; // 调试使用,标识可操作区域
  159. if (DEBUG_MODE) {
  160. // 标识删除按钮区域
  161. this.ctx.setLineWidth(1);
  162. this.ctx.setStrokeStyle('red');
  163. this.ctx.strokeRect(transformDelX, transformDelY, delW, delH); // 标识旋转/缩放按钮区域
  164. this.ctx.setLineWidth(1);
  165. this.ctx.setStrokeStyle('black');
  166. this.ctx.strokeRect(transformScaleX, transformScaleY, scaleW, scaleH); // 标识移动区域
  167. this._drawBorder();
  168. }
  169. if (x - transformScaleX >= 0 && y - transformScaleY >= 0 && transformScaleX + scaleW - x >= 0 &&
  170. transformScaleY + scaleH - y >= 0) {
  171. // 缩放区域
  172. return 'transform';
  173. } else if (x - transformDelX >= 0 && y - transformDelY >= 0 && transformDelX + delW - x >= 0 &&
  174. transformDelY + delH - y >= 0) {
  175. // 删除区域
  176. return 'del';
  177. } else if (this.insidePolygon(this.square, [x, y])) {
  178. return 'move';
  179. } // 不在选择区域里面
  180. return false;
  181. },
  182. /**
  183. * 判断一个点是否在多边形内部
  184. * @param points 多边形坐标集合
  185. * @param testPoint 测试点坐标
  186. * 返回true为真false为假
  187. * */
  188. insidePolygon(points, testPoint) {
  189. let x = testPoint[0],
  190. y = testPoint[1];
  191. let inside = false;
  192. for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
  193. let xi = points[i][0],
  194. yi = points[i][1];
  195. let xj = points[j][0],
  196. yj = points[j][1];
  197. let intersect = yi > y != yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
  198. if (intersect) inside = !inside;
  199. }
  200. return inside;
  201. },
  202. /**
  203. * 计算旋转后矩形四个顶点的坐标相对于画布
  204. * @private
  205. */
  206. _rotateSquare() {
  207. this.square = [this._rotatePoint(this.x, this.y, this.centerX, this.centerY, this.rotate), this
  208. ._rotatePoint(this.x + this.w, this.y, this.centerX, this.centerY, this.rotate), this._rotatePoint(
  209. this.x + this.w, this.y + this.h, this.centerX, this.centerY, this.rotate), this._rotatePoint(
  210. this.x, this.y + this.h, this.centerX, this.centerY, this.rotate)
  211. ];
  212. },
  213. /**
  214. * 计算旋转后的新坐标相对于画布
  215. * @param x
  216. * @param y
  217. * @param centerX
  218. * @param centerY
  219. * @param degrees
  220. * @returns {*[]}
  221. * @private
  222. */
  223. _rotatePoint(x, y, centerX, centerY, degrees) {
  224. let newX = (x - centerX) * Math.cos(degrees * Math.PI / 180) - (y - centerY) * Math.sin(degrees * Math.PI /
  225. 180) + centerX;
  226. let newY = (x - centerX) * Math.sin(degrees * Math.PI / 180) + (y - centerY) * Math.cos(degrees * Math.PI /
  227. 180) + centerY;
  228. return [newX, newY];
  229. },
  230. /**
  231. *
  232. * @param {*} px 手指按下去的坐标
  233. * @param {*} py 手指按下去的坐标
  234. * @param {*} x 手指移动到的坐标
  235. * @param {*} y 手指移动到的坐标
  236. * @param {*} currentGraph 当前图层的信息
  237. */
  238. transform(px, py, x, y, currentGraph) {
  239. // 获取选择区域的宽度高度
  240. if (this.type === 'text') {
  241. this.ctx.setFontSize(this.fontSize);
  242. const textWidth = this.ctx.measureText(this.text).width;
  243. const textHeight = this.fontSize + 10;
  244. this.w = textWidth;
  245. this.h = textHeight; // 字体区域中心点不变,左上角位移
  246. this.x = this.centerX - textWidth / 2;
  247. this.y = this.centerY - textHeight / 2;
  248. } else {
  249. this.centerX = this.x + this.w / 2;
  250. this.centerY = this.y + this.h / 2;
  251. }
  252. const diffXBefore = px - this.centerX;
  253. const diffYBefore = py - this.centerY;
  254. const diffXAfter = x - this.centerX;
  255. const diffYAfter = y - this.centerY;
  256. const angleBefore = Math.atan2(diffYBefore, diffXBefore) / Math.PI * 180;
  257. const angleAfter = Math.atan2(diffYAfter, diffXAfter) / Math.PI * 180; // 旋转的角度
  258. if (ROTATE_ENABLED) {
  259. this.rotate = currentGraph.rotate + angleAfter - angleBefore;
  260. }
  261. const lineA = Math.sqrt(Math.pow(this.centerX - px, 2) + Math.pow(this.centerY - py, 2));
  262. const lineB = Math.sqrt(Math.pow(this.centerX - x, 2) + Math.pow(this.centerY - y, 2));
  263. if (this.type === 'image') {
  264. let resize_rito = lineB / lineA;
  265. let new_w = currentGraph.w * resize_rito;
  266. let new_h = currentGraph.h * resize_rito;
  267. if (currentGraph.w < currentGraph.h && new_w < this.MIN_WIDTH) {
  268. new_w = this.MIN_WIDTH;
  269. new_h = this.MIN_WIDTH * currentGraph.h / currentGraph.w;
  270. } else if (currentGraph.h < currentGraph.w && new_h < this.MIN_WIDTH) {
  271. new_h = this.MIN_WIDTH;
  272. new_w = this.MIN_WIDTH * currentGraph.w / currentGraph.h;
  273. }
  274. this.w = new_w;
  275. this.h = new_h;
  276. this.x = currentGraph.x - (new_w - currentGraph.w) / 2;
  277. this.y = currentGraph.y - (new_h - currentGraph.h) / 2;
  278. } else if (this.type === 'text') {
  279. const fontSize = currentGraph.fontSize * ((lineB - lineA) / lineA + 1);
  280. this.fontSize = fontSize <= this.MIN_FONTSIZE ? this.MIN_FONTSIZE : fontSize; // 旋转位移后重新计算坐标
  281. this.ctx.setFontSize(this.fontSize);
  282. const textWidth = this.ctx.measureText(this.text).width;
  283. const textHeight = this.fontSize + 10;
  284. this.w = textWidth;
  285. this.h = textHeight; // 字体区域中心点不变,左上角位移
  286. this.x = this.centerX - textWidth / 2;
  287. this.y = this.centerY - textHeight / 2;
  288. }
  289. },
  290. toPx(rpx) {
  291. return rpx * this.factor;
  292. }
  293. };
  294. export default {
  295. data() {
  296. return {
  297. bgImage: '',
  298. history: []
  299. };
  300. },
  301. components: {},
  302. props: {
  303. graph: {
  304. type: Object,
  305. default: () => ({})
  306. },
  307. bgColor: {
  308. type: String,
  309. default: ''
  310. },
  311. bgSourceId: {
  312. type: String,
  313. default: ''
  314. },
  315. width: {
  316. type: Number,
  317. default: 750
  318. },
  319. height: {
  320. type: Number,
  321. default: 750
  322. },
  323. enableUndo: {
  324. type: Boolean,
  325. default: false
  326. }
  327. },
  328. watch: {
  329. graph: {
  330. handler: 'onGraphChange',
  331. deep: true
  332. }
  333. },
  334. /**
  335. * 绘制元素
  336. */
  337. paint() {
  338. this.ctx.save(); // 由于measureText获取文字宽度依赖于样式,所以如果是文字元素需要先设置样式
  339. let textWidth = 0;
  340. let textHeight = 0;
  341. if (this.type === 'text') {
  342. this.ctx.setFontSize(this.fontSize);
  343. this.ctx.setTextBaseline('middle');
  344. this.ctx.setTextAlign('center');
  345. this.ctx.setFillStyle(this.color);
  346. textWidth = this.ctx.measureText(this.text).width;
  347. textHeight = this.fontSize + 10; // 字体区域中心点不变,左上角位移
  348. this.x = this.centerX - textWidth / 2;
  349. this.y = this.centerY - textHeight / 2;
  350. } // 旋转元素
  351. this.ctx.translate(this.centerX, this.centerY);
  352. this.ctx.rotate(this.rotate * Math.PI / 180);
  353. this.ctx.translate(-this.centerX, -this.centerY); // 渲染元素
  354. if (this.type === 'text') {
  355. this.ctx.fillText(this.text, this.centerX, this.centerY);
  356. } else if (this.type === 'image') {
  357. this.ctx.drawImage(this.fileUrl, this.x, this.y, this.w, this.h);
  358. } // 如果是选中状态,绘制选择虚线框,和缩放图标、删除图标
  359. if (this.selected && !this.permitSelected) {
  360. this.ctx.setLineDash([2, 5]);
  361. this.ctx.setLineWidth(2);
  362. this.ctx.setStrokeStyle(STROKE_COLOR);
  363. this.ctx.lineDashOffset = 6;
  364. if (this.type === 'text') {
  365. this.ctx.strokeRect(this.x, this.y, textWidth, textHeight);
  366. this.ctx.drawImage(DELETE_ICON, this.x - 15, this.y - 15, 30, 30);
  367. this.ctx.drawImage(DRAG_ICON, this.x + textWidth - 15, this.y + textHeight - 15, 30, 30);
  368. } else {
  369. this.ctx.strokeRect(this.x, this.y, this.w, this.h);
  370. this.ctx.drawImage(DELETE_ICON, this.x - 15, this.y - 15, 30, 30);
  371. this.ctx.drawImage(DRAG_ICON, this.x + this.w - 15, this.y + this.h - 15, 30, 30);
  372. }
  373. }
  374. this.ctx.restore();
  375. },
  376. /**
  377. * 给矩形描边
  378. * @private
  379. */
  380. _drawBorder() {
  381. let p = this.square;
  382. let ctx = this.ctx;
  383. this.ctx.save();
  384. this.ctx.beginPath();
  385. ctx.setStrokeStyle('orange');
  386. this._draw_line(this.ctx, p[0], p[1]);
  387. this._draw_line(this.ctx, p[1], p[2]);
  388. this._draw_line(this.ctx, p[2], p[3]);
  389. this._draw_line(this.ctx, p[3], p[0]);
  390. ctx.restore();
  391. },
  392. /**
  393. * 画一条线
  394. * @param ctx
  395. * @param a
  396. * @param b
  397. * @private
  398. */
  399. _draw_line(ctx, a, b) {
  400. ctx.moveTo(a[0], a[1]);
  401. ctx.lineTo(b[0], b[1]);
  402. ctx.stroke();
  403. },
  404. /**
  405. * 判断点击的坐标落在哪个区域
  406. * @param {*} x 点击的坐标
  407. * @param {*} y 点击的坐标
  408. */
  409. isInGraph(x, y) {
  410. // 删除区域左上角的坐标和区域的高度宽度
  411. const delW = 30;
  412. const delH = 30; // 旋转后的删除区域坐标
  413. const transformedDelCenter = this._rotatePoint(this.x, this.y, this.centerX, this.centerY, this.rotate);
  414. const transformDelX = transformedDelCenter[0] - delW / 2;
  415. const transformDelY = transformedDelCenter[1] - delH / 2; // 变换区域左上角的坐标和区域的高度宽度
  416. const scaleW = 30;
  417. const scaleH = 30;
  418. const transformedScaleCenter = this._rotatePoint(this.x + this.w, this.y + this.h, this.centerX, this.centerY,
  419. this.rotate); // 旋转后的变换区域坐标
  420. const transformScaleX = transformedScaleCenter[0] - scaleW / 2;
  421. const transformScaleY = transformedScaleCenter[1] - scaleH / 2; // 调试使用,标识可操作区域
  422. if (DEBUG_MODE) {
  423. // 标识删除按钮区域
  424. this.ctx.setLineWidth(1);
  425. this.ctx.setStrokeStyle('red');
  426. this.ctx.strokeRect(transformDelX, transformDelY, delW, delH); // 标识旋转/缩放按钮区域
  427. this.ctx.setLineWidth(1);
  428. this.ctx.setStrokeStyle('black');
  429. this.ctx.strokeRect(transformScaleX, transformScaleY, scaleW, scaleH); // 标识移动区域
  430. this._drawBorder();
  431. }
  432. if (x - transformScaleX >= 0 && y - transformScaleY >= 0 && transformScaleX + scaleW - x >= 0 &&
  433. transformScaleY + scaleH - y >= 0) {
  434. // 缩放区域
  435. return 'transform';
  436. } else if (x - transformDelX >= 0 && y - transformDelY >= 0 && transformDelX + delW - x >= 0 && transformDelY +
  437. delH - y >= 0) {
  438. // 删除区域
  439. return 'del';
  440. } else if (this.insidePolygon(this.square, [x, y])) {
  441. return 'move';
  442. } // 不在选择区域里面
  443. return false;
  444. },
  445. /**
  446. * 判断一个点是否在多边形内部
  447. * @param points 多边形坐标集合
  448. * @param testPoint 测试点坐标
  449. * 返回true为真false为假
  450. * */
  451. insidePolygon(points, testPoint) {
  452. let x = testPoint[0],
  453. y = testPoint[1];
  454. let inside = false;
  455. for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
  456. let xi = points[i][0],
  457. yi = points[i][1];
  458. let xj = points[j][0],
  459. yj = points[j][1];
  460. let intersect = yi > y != yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
  461. if (intersect) inside = !inside;
  462. }
  463. return inside;
  464. },
  465. /**
  466. * 计算旋转后矩形四个顶点的坐标相对于画布
  467. * @private
  468. */
  469. _rotateSquare() {
  470. this.square = [this._rotatePoint(this.x, this.y, this.centerX, this.centerY, this.rotate), this._rotatePoint(
  471. this.x + this.w, this.y, this.centerX, this.centerY, this.rotate), this._rotatePoint(this.x + this
  472. .w, this.y + this.h, this.centerX, this.centerY, this.rotate), this._rotatePoint(this.x, this.y +
  473. this.h, this.centerX, this.centerY, this.rotate)];
  474. },
  475. /**
  476. * 计算旋转后的新坐标相对于画布
  477. * @param x
  478. * @param y
  479. * @param centerX
  480. * @param centerY
  481. * @param degrees
  482. * @returns {*[]}
  483. * @private
  484. */
  485. _rotatePoint(x, y, centerX, centerY, degrees) {
  486. let newX = (x - centerX) * Math.cos(degrees * Math.PI / 180) - (y - centerY) * Math.sin(degrees * Math.PI /
  487. 180) + centerX;
  488. let newY = (x - centerX) * Math.sin(degrees * Math.PI / 180) + (y - centerY) * Math.cos(degrees * Math.PI /
  489. 180) + centerY;
  490. return [newX, newY];
  491. },
  492. /**
  493. *
  494. * @param {*} px 手指按下去的坐标
  495. * @param {*} py 手指按下去的坐标
  496. * @param {*} x 手指移动到的坐标
  497. * @param {*} y 手指移动到的坐标
  498. * @param {*} currentGraph 当前图层的信息
  499. */
  500. transform(px, py, x, y, currentGraph) {
  501. // 获取选择区域的宽度高度
  502. if (this.type === 'text') {
  503. this.ctx.setFontSize(this.fontSize);
  504. const textWidth = this.ctx.measureText(this.text).width;
  505. const textHeight = this.fontSize + 10;
  506. this.w = textWidth;
  507. this.h = textHeight; // 字体区域中心点不变,左上角位移
  508. this.x = this.centerX - textWidth / 2;
  509. this.y = this.centerY - textHeight / 2;
  510. } else {
  511. this.centerX = this.x + this.w / 2;
  512. this.centerY = this.y + this.h / 2;
  513. }
  514. const diffXBefore = px - this.centerX;
  515. const diffYBefore = py - this.centerY;
  516. const diffXAfter = x - this.centerX;
  517. const diffYAfter = y - this.centerY;
  518. const angleBefore = Math.atan2(diffYBefore, diffXBefore) / Math.PI * 180;
  519. const angleAfter = Math.atan2(diffYAfter, diffXAfter) / Math.PI * 180; // 旋转的角度
  520. if (ROTATE_ENABLED) {
  521. this.rotate = currentGraph.rotate + angleAfter - angleBefore;
  522. }
  523. const lineA = Math.sqrt(Math.pow(this.centerX - px, 2) + Math.pow(this.centerY - py, 2));
  524. const lineB = Math.sqrt(Math.pow(this.centerX - x, 2) + Math.pow(this.centerY - y, 2));
  525. if (this.type === 'image') {
  526. let resize_rito = lineB / lineA;
  527. let new_w = currentGraph.w * resize_rito;
  528. let new_h = currentGraph.h * resize_rito;
  529. if (currentGraph.w < currentGraph.h && new_w < this.MIN_WIDTH) {
  530. new_w = this.MIN_WIDTH;
  531. new_h = this.MIN_WIDTH * currentGraph.h / currentGraph.w;
  532. } else if (currentGraph.h < currentGraph.w && new_h < this.MIN_WIDTH) {
  533. new_h = this.MIN_WIDTH;
  534. new_w = this.MIN_WIDTH * currentGraph.w / currentGraph.h;
  535. }
  536. this.w = new_w;
  537. this.h = new_h;
  538. this.x = currentGraph.x - (new_w - currentGraph.w) / 2;
  539. this.y = currentGraph.y - (new_h - currentGraph.h) / 2;
  540. } else if (this.type === 'text') {
  541. const fontSize = currentGraph.fontSize * ((lineB - lineA) / lineA + 1);
  542. this.fontSize = fontSize <= this.MIN_FONTSIZE ? this.MIN_FONTSIZE : fontSize; // 旋转位移后重新计算坐标
  543. this.ctx.setFontSize(this.fontSize);
  544. const textWidth = this.ctx.measureText(this.text).width;
  545. const textHeight = this.fontSize + 10;
  546. this.w = textWidth;
  547. this.h = textHeight; // 字体区域中心点不变,左上角位移
  548. this.x = this.centerX - textWidth / 2;
  549. this.y = this.centerY - textHeight / 2;
  550. }
  551. },
  552. toPx(rpx) {
  553. return rpx * this.factor;
  554. },
  555. beforeMount() {
  556. const sysInfo = wx.getSystemInfoSync();
  557. const screenWidth = sysInfo.screenWidth;
  558. this.factor = screenWidth / 750;
  559. if (typeof this.drawArr === 'undefined') {
  560. this.drawArr = [];
  561. }
  562. this.ctx = wx.createCanvasContext('canvas-drag', this);
  563. this.draw();
  564. },
  565. created() {
  566. uni.downloadFile({
  567. url: DELETE_ICON, //仅为示例,并非真实的资源
  568. success: (res) => {
  569. if (res.statusCode === 200) {
  570. console.log('下载成功:' + res.tempFilePath);
  571. DELETE_ICON = res.tempFilePath;
  572. }
  573. }
  574. })
  575. uni.downloadFile({
  576. url: DRAG_ICON, //仅为示例,并非真实的资源
  577. success: (res) => {
  578. if (res.statusCode === 200) {
  579. console.log('下载成功:' + res.tempFilePath);
  580. DRAG_ICON = res.tempFilePath;
  581. }
  582. }
  583. })
  584. },
  585. methods: {
  586. toPx(rpx) {
  587. return rpx * this.factor;
  588. },
  589. initBg() {
  590. this.bgColor = '';
  591. this.bgSourceId = '';
  592. this.bgImage = '';
  593. },
  594. initHistory() {
  595. this.history = [];
  596. },
  597. recordHistory() {
  598. if (!this.enableUndo) {
  599. return;
  600. }
  601. this.exportJson().then(imgArr => {
  602. this.history.push(JSON.stringify(imgArr));
  603. }).catch(e => {
  604. console.error(e);
  605. });
  606. },
  607. undo() {
  608. if (!this.enableUndo) {
  609. console.log(`后退功能未启用,请设置enableUndo="{{true}}"`);
  610. return;
  611. }
  612. if (this.history.length > 1) {
  613. this.history.pop();
  614. let newConfigObj = this.history[this.history.length - 1];
  615. this.initByArr(JSON.parse(newConfigObj));
  616. } else {
  617. console.log('已是第一步,不能回退');
  618. }
  619. },
  620. onGraphChange(n, o) {
  621. if (JSON.stringify(n) === '{}') return;
  622. this.drawArr.push(new dragGraph(Object.assign({
  623. x: 30,
  624. y: 30
  625. }, n), this.ctx, this.factor));
  626. this.emitDrawArrChange()
  627. this.draw(); // 参数有变化时记录历史
  628. this.recordHistory();
  629. },
  630. initByArr(newArr) {
  631. this.drawArr = []; // 重置绘画元素
  632. this.initBg(); // 重置绘画背景
  633. // 循环插入 drawArr
  634. newArr.forEach((item, index) => {
  635. switch (item.type) {
  636. case 'bgColor':
  637. this.bgImage = '';
  638. this.bgSourceId = '';
  639. this.bgColor = item.color;
  640. break;
  641. case 'bgImage':
  642. this.bgColor = '';
  643. this.bgImage = item.url;
  644. if (item.sourceId) {
  645. this.bgSourceId = item.sourceId;
  646. }
  647. break;
  648. case 'image':
  649. case 'text':
  650. if (index === newArr.length - 1) {
  651. item.selected = true;
  652. } else {
  653. item.selected = false;
  654. }
  655. this.drawArr.push(new dragGraph(item, this.ctx, this.factor));
  656. break;
  657. }
  658. });
  659. this.draw();
  660. },
  661. draw() {
  662. if (this.bgImage !== '') {
  663. this.ctx.drawImage(this.bgImage, 0, 0, this.toPx(this.width), this.toPx(this.height));
  664. }
  665. if (this.bgColor !== '') {
  666. this.ctx.save();
  667. this.ctx.setFillStyle(this.bgColor);
  668. this.ctx.fillRect(0, 0, this.toPx(this.width), this.toPx(this.height));
  669. this.ctx.restore();
  670. }
  671. this.drawArr.forEach(item => {
  672. item.paint();
  673. });
  674. return new Promise(resolve => {
  675. this.ctx.draw(false, () => {
  676. resolve();
  677. });
  678. });
  679. },
  680. start(e) {
  681. isMove = false; // 重置移动标识
  682. const {
  683. x,
  684. y
  685. } = e.touches[0];
  686. this.tempGraphArr = [];
  687. let lastDelIndex = null; // 记录最后一个需要删除的索引
  688. this.drawArr && this.drawArr.forEach((item, index) => {
  689. const action = item.isInGraph(x, y);
  690. if (action) {
  691. item.action = action;
  692. this.tempGraphArr.push(item); // 保存点击时的坐标
  693. this.currentTouch = {
  694. x,
  695. y
  696. };
  697. if (action === 'del') {
  698. // lastDelIndex = index; // 标记需要删除的元素
  699. item.selected = true;
  700. }
  701. } else {
  702. item.action = false;
  703. item.selected = false;
  704. }
  705. }); // 保存点击时元素的信息
  706. if (this.tempGraphArr.length > 0) {
  707. for (let i = 0; i < this.tempGraphArr.length; i++) {
  708. let lastIndex = this.tempGraphArr.length - 1; // 对最后一个元素做操作
  709. if (i === lastIndex) {
  710. // 未选中的元素,不执行删除和缩放操作
  711. if (lastDelIndex !== null && this.tempGraphArr[i].selected) {
  712. if (this.drawArr[lastDelIndex].action == 'del') {
  713. this.drawArr.splice(lastDelIndex, 1);
  714. this.ctx.clearRect(0, 0, this.toPx(this.width), this.toPx(this.height));
  715. }
  716. } else {
  717. this.tempGraphArr[lastIndex].selected = true;
  718. this.currentGraph = Object.assign({}, this.tempGraphArr[lastIndex]);
  719. }
  720. } else {
  721. // 不是最后一个元素,不需要选中,也不记录状态
  722. this.tempGraphArr[i].action = false;
  723. this.tempGraphArr[i].selected = false;
  724. }
  725. }
  726. }
  727. this.draw();
  728. },
  729. move(e) {
  730. const {
  731. x,
  732. y
  733. } = e.touches[0];
  734. if (this.tempGraphArr && this.tempGraphArr.length > 0) {
  735. isMove = true; // 有选中元素,并且有移动时,设置移动标识
  736. const currentGraph = this.tempGraphArr[this.tempGraphArr.length - 1];
  737. if (currentGraph.action === 'move') {
  738. currentGraph.centerX = this.currentGraph.centerX + (x - this.currentTouch.x);
  739. currentGraph.centerY = this.currentGraph.centerY + (y - this.currentTouch
  740. .y); // 使用中心点坐标计算位移,不使用 x,y 坐标,因为会受旋转影响。
  741. if (currentGraph.type !== 'text') {
  742. currentGraph.x = currentGraph.centerX - this.currentGraph.w / 2;
  743. currentGraph.y = currentGraph.centerY - this.currentGraph.h / 2;
  744. }
  745. } else if (currentGraph.action === 'transform') {
  746. currentGraph.transform(this.currentTouch.x, this.currentTouch.y, x, y, this.currentGraph);
  747. } // 更新4个坐标点(相对于画布的坐标系)
  748. currentGraph._rotateSquare();
  749. this.draw();
  750. }
  751. },
  752. end(e) {
  753. this.emitDrawArrChange()
  754. this.tempGraphArr = [];
  755. if (isMove) {
  756. isMove = false; // 重置移动标识
  757. // 用户操作结束时记录历史
  758. this.recordHistory();
  759. }
  760. },
  761. emitDrawArrChange(){
  762. let map = {}
  763. let arr = this.drawArr.map((item, index) => {
  764. let p = {
  765. x : item.x,
  766. y : item.y,
  767. w : item.w,
  768. h : item.h,
  769. id : item.id || index,
  770. }
  771. map[p.id] = p
  772. return p
  773. })
  774. this.$emit('onDrawArrChange', map)
  775. },
  776. exportFun() {
  777. return new Promise((resolve, reject) => {
  778. this.drawArr = this.drawArr.map(item => {
  779. item.selected = false;
  780. return item;
  781. });
  782. this.draw().then(() => {
  783. wx.canvasToTempFilePath({
  784. canvasId: 'canvas-drag',
  785. success: res => {
  786. resolve(res.tempFilePath);
  787. },
  788. fail: e => {
  789. reject(e);
  790. }
  791. }, this);
  792. });
  793. });
  794. },
  795. exportJson() {
  796. return new Promise((resolve, reject) => {
  797. let exportArr = this.drawArr.map(item => {
  798. item.selected = false;
  799. switch (item.type) {
  800. case 'image':
  801. return {
  802. type: 'image',
  803. url: item.fileUrl,
  804. y: item.y,
  805. x: item.x,
  806. w: item.w,
  807. h: item.h,
  808. rotate: item.rotate,
  809. sourceId: item.sourceId
  810. };
  811. break;
  812. case 'text':
  813. return {
  814. type: 'text',
  815. text: item.text,
  816. color: item.color,
  817. fontSize: item.fontSize,
  818. y: item.y,
  819. x: item.x,
  820. w: item.w,
  821. h: item.h,
  822. rotate: item.rotate
  823. };
  824. break;
  825. }
  826. });
  827. if (this.bgImage) {
  828. let tmp_img_config = {
  829. type: 'bgImage',
  830. url: this.bgImage
  831. };
  832. if (this.bgSourceId) {
  833. tmp_img_config['sourceId'] = this.bgSourceId;
  834. }
  835. exportArr.unshift(tmp_img_config);
  836. } else if (this.bgColor) {
  837. exportArr.unshift({
  838. type: 'bgColor',
  839. color: this.bgColor
  840. });
  841. }
  842. resolve(exportArr);
  843. });
  844. },
  845. changColor(color) {
  846. const selected = this.drawArr.filter(item => item.selected);
  847. if (selected.length > 0) {
  848. selected[0].color = color;
  849. }
  850. this.draw(); // 改变文字颜色时记录历史
  851. this.recordHistory();
  852. },
  853. changeBgColor(color) {
  854. this.bgImage = '';
  855. this.bgColor = color;
  856. this.draw(); // 改变背景颜色时记录历史
  857. this.recordHistory();
  858. },
  859. changeBgImage(newBgImg) {
  860. this.bgColor = '';
  861. if (typeof newBgImg == 'string') {
  862. this.bgSourceId = '';
  863. this.bgImage = newBgImg;
  864. } else {
  865. this.bgSourceId = newBgImg.sourceId;
  866. this.bgImage = newBgImg.url;
  867. }
  868. this.draw(); // 改变背景图片时记录历史
  869. this.recordHistory();
  870. },
  871. clearCanvas() {
  872. this.ctx.clearRect(0, 0, this.toPx(this.width), this.toPx(this.height));
  873. this.ctx.draw();
  874. this.drawArr = [];
  875. this.initBg(); // 重置绘画背景
  876. this.initHistory(); // 清空历史记录
  877. }
  878. }
  879. };
  880. </script>
  881. <style>
  882. @import "./index.css";
  883. </style>