特易招,招聘小程序
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

11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 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>