hy-app 0.4.9 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,7 +38,22 @@ defineOptions({});
38
38
 
39
39
  // const props = withDefaults(defineProps<IProps>(), defaultProps)
40
40
  const props = defineProps({
41
- /** 显示内容 */
41
+ /** 显示主标题 */
42
+ title: {
43
+ type: String,
44
+ default: "",
45
+ },
46
+ /** 主标题颜色 */
47
+ titleColor: {
48
+ type: String,
49
+ default: "",
50
+ },
51
+ /** 主标题字体大小,单位px */
52
+ titleSize: {
53
+ type: Number,
54
+ default: 0,
55
+ },
56
+ /** 显示副标题内容 */
42
57
  content: {
43
58
  type: String,
44
59
  default: "",
@@ -264,6 +279,9 @@ function createWaterMark(
264
279
  const contentWidth = width * pixelRatio.value;
265
280
  const contentHeight = height * pixelRatio.value;
266
281
  const fontSize = size * pixelRatio.value;
282
+ // 标题字体大小:如果设置了titleSize则使用titleSize,否则使用size的1.2倍
283
+ const titleFontSize = props.titleSize > 0 ? props.titleSize * pixelRatio.value : fontSize * 1.2;
284
+
267
285
  // #ifndef H5
268
286
  if (canvasOffScreenable.value) {
269
287
  createOffscreenCanvas(
@@ -281,6 +299,8 @@ function createWaterMark(
281
299
  image,
282
300
  imageHeight,
283
301
  imageWidth,
302
+ props.title,
303
+ titleFontSize
284
304
  );
285
305
  } else {
286
306
  createCanvas(
@@ -293,6 +313,8 @@ function createWaterMark(
293
313
  image,
294
314
  imageHeight,
295
315
  imageWidth,
316
+ props.title,
317
+ titleFontSize
296
318
  );
297
319
  }
298
320
  // #endif
@@ -312,6 +334,8 @@ function createWaterMark(
312
334
  image,
313
335
  imageHeight,
314
336
  imageWidth,
337
+ props.title,
338
+ titleFontSize
315
339
  );
316
340
  // #endif
317
341
  }
@@ -348,6 +372,8 @@ function createOffscreenCanvas(
348
372
  image: string,
349
373
  imageHeight: number,
350
374
  imageWidth: number,
375
+ title: string,
376
+ titleFontSize: number
351
377
  ) {
352
378
  // 创建离屏canvas
353
379
  const canvas: any = uni.createOffscreenCanvas({
@@ -357,7 +383,29 @@ function createOffscreenCanvas(
357
383
  });
358
384
  const ctx: any = canvas.getContext("2d");
359
385
  if (ctx) {
360
- if (image) {
386
+ if (image && (title || content)) {
387
+ // 图片和文字同时显示
388
+ const img = canvas.createImage() as HTMLImageElement;
389
+ drawImageAndTextOffScreen(
390
+ ctx,
391
+ img,
392
+ image,
393
+ imageHeight,
394
+ imageWidth,
395
+ title,
396
+ content,
397
+ rotate,
398
+ contentWidth,
399
+ contentHeight,
400
+ fontSize,
401
+ titleFontSize,
402
+ fontFamily,
403
+ fontStyle,
404
+ fontWeight,
405
+ color,
406
+ canvas,
407
+ );
408
+ } else if (image) {
361
409
  const img = canvas.createImage() as HTMLImageElement;
362
410
  drawImageOffScreen(
363
411
  ctx,
@@ -373,7 +421,7 @@ function createOffscreenCanvas(
373
421
  } else {
374
422
  drawTextOffScreen(
375
423
  ctx,
376
- content,
424
+ title,
377
425
  contentWidth,
378
426
  contentHeight,
379
427
  rotate,
@@ -383,6 +431,8 @@ function createOffscreenCanvas(
383
431
  fontWeight,
384
432
  color,
385
433
  canvas,
434
+ content,
435
+ titleFontSize
386
436
  );
387
437
  }
388
438
  } else {
@@ -413,10 +463,28 @@ function createCanvas(
413
463
  image: string,
414
464
  imageHeight: number,
415
465
  imageWidth: number,
466
+ title: string,
467
+ titleFontSize: number
416
468
  ) {
417
469
  const ctx = uni.createCanvasContext(canvasId.value);
418
470
  if (ctx) {
419
- if (image) {
471
+ if (image && (title || content)) {
472
+ // 图片和文字同时显示
473
+ drawImageAndTextOnScreen(
474
+ ctx,
475
+ image,
476
+ imageHeight,
477
+ imageWidth,
478
+ title,
479
+ content,
480
+ rotate,
481
+ contentWidth,
482
+ contentHeight,
483
+ fontSize,
484
+ titleFontSize,
485
+ color
486
+ );
487
+ } else if (image) {
420
488
  drawImageOnScreen(
421
489
  ctx,
422
490
  image,
@@ -427,7 +495,7 @@ function createCanvas(
427
495
  contentHeight,
428
496
  );
429
497
  } else {
430
- drawTextOnScreen(ctx, content, contentWidth, rotate, fontSize, color);
498
+ drawTextOnScreen(ctx, title, contentWidth, rotate, fontSize, color, content, titleFontSize);
431
499
  }
432
500
  } else {
433
501
  console.error("无法获取canvas上下文,请确认当前环境是否支持canvas");
@@ -466,13 +534,37 @@ function createH5Canvas(
466
534
  image: string,
467
535
  imageHeight: number,
468
536
  imageWidth: number,
537
+ title: string,
538
+ titleFontSize: number
469
539
  ) {
470
540
  const canvas = document.createElement("canvas");
471
541
  const ctx = canvas.getContext("2d");
472
542
  canvas.setAttribute("width", `${canvasWidth}px`);
473
543
  canvas.setAttribute("height", `${canvasHeight}px`);
474
544
  if (ctx) {
475
- if (image) {
545
+ if (image && (title || content)) {
546
+ // 图片和文字同时显示
547
+ const img = new Image();
548
+ drawImageAndTextOffScreen(
549
+ ctx,
550
+ img,
551
+ image,
552
+ imageHeight,
553
+ imageWidth,
554
+ title,
555
+ content,
556
+ rotate,
557
+ contentWidth,
558
+ contentHeight,
559
+ fontSize,
560
+ titleFontSize,
561
+ fontFamily,
562
+ fontStyle,
563
+ fontWeight,
564
+ color,
565
+ canvas,
566
+ );
567
+ } else if (image) {
476
568
  const img = new Image();
477
569
  drawImageOffScreen(
478
570
  ctx,
@@ -488,7 +580,7 @@ function createH5Canvas(
488
580
  } else {
489
581
  drawTextOffScreen(
490
582
  ctx,
491
- content,
583
+ title,
492
584
  contentWidth,
493
585
  contentHeight,
494
586
  rotate,
@@ -498,6 +590,8 @@ function createH5Canvas(
498
590
  fontWeight,
499
591
  color,
500
592
  canvas,
593
+ content,
594
+ titleFontSize
501
595
  );
502
596
  }
503
597
  } else {
@@ -519,9 +613,32 @@ function createH5Canvas(
519
613
  * @param color 水印字体颜色
520
614
  * @param canvas canvas实例
521
615
  */
616
+ // 测量文本宽度并自动换行
617
+ function wrapText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number, fontSize: number) {
618
+ const words = text.split('');
619
+ const lines: string[] = [];
620
+ let currentLine = '';
621
+
622
+ for (let i = 0; i < words.length; i++) {
623
+ const testLine = currentLine + words[i];
624
+ const metrics = ctx.measureText(testLine);
625
+ const testWidth = metrics.width;
626
+
627
+ // 当文字宽度超过容器宽度的80%时换行
628
+ if (testWidth > maxWidth * 0.8 && currentLine !== '') {
629
+ lines.push(currentLine);
630
+ currentLine = words[i];
631
+ } else {
632
+ currentLine = testLine;
633
+ }
634
+ }
635
+ lines.push(currentLine);
636
+ return lines;
637
+ }
638
+
522
639
  function drawTextOffScreen(
523
640
  ctx: CanvasRenderingContext2D,
524
- content: string,
641
+ title: string,
525
642
  contentWidth: number,
526
643
  contentHeight: number,
527
644
  rotate: number,
@@ -531,15 +648,50 @@ function drawTextOffScreen(
531
648
  fontWeight: string | number,
532
649
  color: string,
533
650
  canvas: HTMLCanvasElement,
651
+ content: string = '',
652
+ titleFontSize: number = 0
534
653
  ) {
535
- console.log(fontSize, "离屏");
536
654
  ctx.textBaseline = "middle";
537
655
  ctx.textAlign = "center";
538
- ctx.translate(contentWidth / 2, contentWidth / 2);
656
+ ctx.translate(contentWidth / 2, contentHeight / 2);
539
657
  ctx.rotate((Math.PI / 180) * rotate);
540
- ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`;
541
- ctx.fillStyle = color;
542
- ctx.fillText(content, 0, 0);
658
+
659
+ // 计算总高度
660
+ let totalTextHeight = titleFontSize;
661
+ if (content) {
662
+ totalTextHeight += fontSize + 5; // 标题和副标题之间的间距
663
+ }
664
+
665
+ // 起始Y坐标
666
+ let startY = -totalTextHeight / 2;
667
+
668
+ // 绘制主标题(支持自动换行)
669
+ if (title) {
670
+ ctx.font = `${fontStyle} normal ${fontWeight} ${titleFontSize}px/${contentHeight}px ${fontFamily}`;
671
+ // 使用titleColor或默认color
672
+ ctx.fillStyle = props.titleColor || color;
673
+ const titleLines = wrapText(ctx, title, contentWidth, titleFontSize);
674
+ const titleLineHeight = titleFontSize * 1.2;
675
+
676
+ for (let i = 0; i < titleLines.length; i++) {
677
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight);
678
+ }
679
+
680
+ startY += titleLines.length * titleLineHeight + 5;
681
+ }
682
+
683
+ // 绘制副标题(支持自动换行)
684
+ if (content) {
685
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`;
686
+ ctx.fillStyle = color;
687
+ const contentLines = wrapText(ctx, content, contentWidth, fontSize);
688
+ const contentLineHeight = fontSize * 1.2;
689
+
690
+ for (let i = 0; i < contentLines.length; i++) {
691
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight);
692
+ }
693
+ }
694
+
543
695
  ctx.restore();
544
696
  waterMarkUrl.value = canvas.toDataURL();
545
697
  }
@@ -553,21 +705,79 @@ function drawTextOffScreen(
553
705
  * @param fontSize 水印字体大小
554
706
  * @param color 水印字体颜色
555
707
  */
708
+ // 简化版本的文字换行(UniApp CanvasContext不支持measureText)
709
+ function simpleWrapText(text: string, maxLength: number) {
710
+ const lines: string[] = [];
711
+ let currentLine = '';
712
+
713
+ // 基于字符数估算换行(适用于UniApp CanvasContext)
714
+ for (let i = 0; i < text.length; i++) {
715
+ currentLine += text[i];
716
+ if (currentLine.length >= maxLength) {
717
+ lines.push(currentLine);
718
+ currentLine = '';
719
+ }
720
+ }
721
+ if (currentLine) {
722
+ lines.push(currentLine);
723
+ }
724
+ return lines;
725
+ }
726
+
556
727
  function drawTextOnScreen(
557
728
  ctx: UniApp.CanvasContext,
558
- content: string,
729
+ title: string,
559
730
  contentWidth: number,
560
731
  rotate: number,
561
732
  fontSize: number,
562
733
  color: string,
734
+ content: string = '',
735
+ titleFontSize: number = 0
563
736
  ) {
564
737
  ctx.setTextBaseline("middle");
565
738
  ctx.setTextAlign("center");
566
739
  ctx.translate(contentWidth / 2, contentWidth / 2);
567
740
  ctx.rotate((Math.PI / 180) * rotate);
568
- ctx.setFillStyle(color);
569
- ctx.setFontSize(fontSize);
570
- ctx.fillText(content, 0, 0);
741
+
742
+ // 估算每行最大字符数
743
+ const maxChars = Math.floor(contentWidth / (fontSize * 0.5));
744
+
745
+ // 计算总高度
746
+ let totalTextHeight = titleFontSize;
747
+ if (content) {
748
+ totalTextHeight += fontSize + 5;
749
+ }
750
+
751
+ // 起始Y坐标
752
+ let startY = -totalTextHeight / 2;
753
+
754
+ // 绘制主标题(支持自动换行)
755
+ if (title) {
756
+ // 使用titleColor或默认color
757
+ ctx.setFillStyle(props.titleColor || color);
758
+ ctx.setFontSize(titleFontSize);
759
+ const titleLines = simpleWrapText(title, maxChars);
760
+ const titleLineHeight = titleFontSize * 1.2;
761
+
762
+ for (let i = 0; i < titleLines.length; i++) {
763
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight);
764
+ }
765
+
766
+ startY += titleLines.length * titleLineHeight + 5;
767
+ }
768
+
769
+ // 绘制副标题(支持自动换行)
770
+ if (content) {
771
+ ctx.setFillStyle(color);
772
+ ctx.setFontSize(fontSize);
773
+ const contentLines = simpleWrapText(content, maxChars);
774
+ const contentLineHeight = fontSize * 1.2;
775
+
776
+ for (let i = 0; i < contentLines.length; i++) {
777
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight);
778
+ }
779
+ }
780
+
571
781
  ctx.restore();
572
782
  ctx.draw();
573
783
  // #ifdef MP-DINGTALK
@@ -632,6 +842,188 @@ async function drawImageOffScreen(
632
842
  };
633
843
  }
634
844
 
845
+ // 绘制图片和文字(离屏)
846
+ async function drawImageAndTextOffScreen(
847
+ ctx: CanvasRenderingContext2D,
848
+ img: HTMLImageElement,
849
+ image: string,
850
+ imageHeight: number,
851
+ imageWidth: number,
852
+ title: string,
853
+ content: string,
854
+ rotate: number,
855
+ contentWidth: number,
856
+ contentHeight: number,
857
+ fontSize: number,
858
+ titleFontSize: number,
859
+ fontFamily: string,
860
+ fontStyle: string,
861
+ fontWeight: string | number,
862
+ color: string,
863
+ canvas: HTMLCanvasElement,
864
+ ) {
865
+ ctx.translate(contentWidth / 2, contentHeight / 2);
866
+ ctx.rotate((Math.PI / 180) * Number(rotate));
867
+ img.crossOrigin = "anonymous";
868
+ img.referrerPolicy = "no-referrer";
869
+
870
+ const imgHeight = imageHeight * pixelRatio.value;
871
+ const imgWidth = imageWidth * pixelRatio.value;
872
+
873
+ img.src = image;
874
+ img.onload = () => {
875
+ // 计算总高度
876
+ let totalHeight = imgHeight;
877
+ const textSpacing = 10;
878
+
879
+ if (title) totalHeight += textSpacing + titleFontSize;
880
+ if (content) totalHeight += fontSize;
881
+
882
+ // 起始Y坐标
883
+ let startY = -totalHeight / 2;
884
+
885
+ // 绘制图片
886
+ ctx.drawImage(
887
+ img,
888
+ -imgWidth / 2,
889
+ startY,
890
+ imgWidth,
891
+ imgHeight,
892
+ );
893
+
894
+ startY += imgHeight + textSpacing;
895
+
896
+ // 设置文字样式
897
+ ctx.textBaseline = "top";
898
+ ctx.textAlign = "center";
899
+
900
+ // 绘制主标题
901
+ if (title) {
902
+ ctx.font = `${fontStyle} normal ${fontWeight} ${titleFontSize}px/${contentHeight}px ${fontFamily}`;
903
+ // 使用titleColor或默认color
904
+ ctx.fillStyle = props.titleColor || color;
905
+ const titleLines = wrapText(ctx, title, contentWidth * 0.9, titleFontSize);
906
+ const titleLineHeight = titleFontSize * 1.2;
907
+
908
+ for (let i = 0; i < titleLines.length; i++) {
909
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight);
910
+ }
911
+
912
+ startY += titleLines.length * titleLineHeight + 5;
913
+ }
914
+
915
+ // 绘制副标题
916
+ if (content) {
917
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`;
918
+ ctx.fillStyle = color;
919
+ const contentLines = wrapText(ctx, content, contentWidth * 0.9, fontSize);
920
+ const contentLineHeight = fontSize * 1.2;
921
+
922
+ for (let i = 0; i < contentLines.length; i++) {
923
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight);
924
+ }
925
+ }
926
+
927
+ ctx.restore();
928
+ waterMarkUrl.value = canvas.toDataURL();
929
+ };
930
+ }
931
+
932
+ // 绘制图片和文字(在屏)
933
+ function drawImageAndTextOnScreen(
934
+ ctx: UniApp.CanvasContext,
935
+ image: string,
936
+ imageHeight: number,
937
+ imageWidth: number,
938
+ title: string,
939
+ content: string,
940
+ rotate: number,
941
+ contentWidth: number,
942
+ contentHeight: number,
943
+ fontSize: number,
944
+ titleFontSize: number,
945
+ color: string
946
+ ) {
947
+ ctx.setTextBaseline("top");
948
+ ctx.setTextAlign("center");
949
+ ctx.translate(contentWidth / 2, contentWidth / 2);
950
+ ctx.rotate((Math.PI / 180) * Number(rotate));
951
+
952
+ const imgHeight = imageHeight * pixelRatio.value;
953
+ const imgWidth = imageWidth * pixelRatio.value;
954
+ const maxChars = Math.floor(contentWidth / (fontSize * 0.5));
955
+
956
+ // 计算总高度
957
+ let totalHeight = imgHeight;
958
+ const textSpacing = 10;
959
+
960
+ if (title) totalHeight += textSpacing + titleFontSize;
961
+ if (content) totalHeight += fontSize;
962
+
963
+ // 起始Y坐标
964
+ let startY = -totalHeight / 2;
965
+
966
+ // 绘制图片
967
+ ctx.drawImage(
968
+ image,
969
+ -imgWidth / 2,
970
+ startY,
971
+ imgWidth,
972
+ imgHeight,
973
+ );
974
+
975
+ startY += imgHeight + textSpacing;
976
+
977
+ // 绘制主标题
978
+ if (title) {
979
+ // 使用titleColor或默认color
980
+ ctx.setFillStyle(props.titleColor || color);
981
+ ctx.setFontSize(titleFontSize);
982
+ const titleLines = simpleWrapText(title, maxChars);
983
+ const titleLineHeight = titleFontSize * 1.2;
984
+
985
+ for (let i = 0; i < titleLines.length; i++) {
986
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight);
987
+ }
988
+
989
+ startY += titleLines.length * titleLineHeight + 5;
990
+ }
991
+
992
+ // 绘制副标题
993
+ if (content) {
994
+ ctx.setFillStyle(color);
995
+ ctx.setFontSize(fontSize);
996
+ const contentLines = simpleWrapText(content, maxChars);
997
+ const contentLineHeight = fontSize * 1.2;
998
+
999
+ for (let i = 0; i < contentLines.length; i++) {
1000
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight);
1001
+ }
1002
+ }
1003
+
1004
+ ctx.restore();
1005
+ ctx.draw(false, () => {
1006
+ // #ifdef MP-DINGTALK
1007
+ // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
1008
+ (ctx as any).toTempFilePath({
1009
+ success(res: any) {
1010
+ showCanvas.value = false;
1011
+ waterMarkUrl.value = res.filePath;
1012
+ },
1013
+ });
1014
+ // #endif
1015
+ // #ifndef MP-DINGTALK
1016
+ uni.canvasToTempFilePath({
1017
+ canvasId: canvasId.value,
1018
+ success: (res) => {
1019
+ showCanvas.value = false;
1020
+ waterMarkUrl.value = res.tempFilePath;
1021
+ },
1022
+ });
1023
+ // #endif
1024
+ });
1025
+ }
1026
+
635
1027
  /**
636
1028
  * 绘制在屏图片canvas
637
1029
  * @param ctx canvas上下文
@@ -2,9 +2,21 @@
2
2
  * 全局分享hooks
3
3
  * */
4
4
  interface ShareConfig {
5
+ /**
6
+ * 标题名称
7
+ * */
5
8
  title?: string;
9
+ /**
10
+ * 页面路径
11
+ * */
6
12
  path?: string;
13
+ /**
14
+ * 分享朋友的封面图片
15
+ * */
7
16
  friendImageUrl?: string;
17
+ /**
18
+ * 分享朋友圈的封面图片
19
+ * */
8
20
  timelineImageUrl?: string;
9
21
  }
10
22