hy-app 0.4.10 → 0.4.12

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: "",
@@ -205,6 +220,7 @@ function doInit() {
205
220
  image,
206
221
  imageHeight,
207
222
  imageWidth,
223
+ title
208
224
  } = props;
209
225
 
210
226
  // 创建水印
@@ -223,6 +239,7 @@ function doInit() {
223
239
  image,
224
240
  imageHeight,
225
241
  imageWidth,
242
+ title
226
243
  );
227
244
  }
228
245
 
@@ -242,6 +259,7 @@ function doInit() {
242
259
  * @param image canvas图片
243
260
  * @param imageHeight canvas图片高度
244
261
  * @param imageWidth canvas图片宽度
262
+ * @param title 标题
245
263
  */
246
264
  function createWaterMark(
247
265
  width: number,
@@ -258,12 +276,16 @@ function createWaterMark(
258
276
  image: string,
259
277
  imageHeight: number,
260
278
  imageWidth: number,
279
+ title: string,
261
280
  ) {
262
281
  const canvasHeight = (height + gutterY) * pixelRatio.value;
263
282
  const canvasWidth = (width + gutterX) * pixelRatio.value;
264
283
  const contentWidth = width * pixelRatio.value;
265
284
  const contentHeight = height * pixelRatio.value;
266
285
  const fontSize = size * pixelRatio.value;
286
+ // 标题字体大小:如果设置了titleSize则使用titleSize,否则使用size的1.2倍
287
+ const titleFontSize = props.titleSize > 0 ? props.titleSize * pixelRatio.value : fontSize * 1.2;
288
+
267
289
  // #ifndef H5
268
290
  if (canvasOffScreenable.value) {
269
291
  createOffscreenCanvas(
@@ -281,6 +303,8 @@ function createWaterMark(
281
303
  image,
282
304
  imageHeight,
283
305
  imageWidth,
306
+ title,
307
+ titleFontSize
284
308
  );
285
309
  } else {
286
310
  createCanvas(
@@ -293,6 +317,8 @@ function createWaterMark(
293
317
  image,
294
318
  imageHeight,
295
319
  imageWidth,
320
+ title,
321
+ titleFontSize
296
322
  );
297
323
  }
298
324
  // #endif
@@ -312,6 +338,8 @@ function createWaterMark(
312
338
  image,
313
339
  imageHeight,
314
340
  imageWidth,
341
+ title,
342
+ titleFontSize
315
343
  );
316
344
  // #endif
317
345
  }
@@ -348,6 +376,8 @@ function createOffscreenCanvas(
348
376
  image: string,
349
377
  imageHeight: number,
350
378
  imageWidth: number,
379
+ title: string,
380
+ titleFontSize: number
351
381
  ) {
352
382
  // 创建离屏canvas
353
383
  const canvas: any = uni.createOffscreenCanvas({
@@ -357,7 +387,29 @@ function createOffscreenCanvas(
357
387
  });
358
388
  const ctx: any = canvas.getContext("2d");
359
389
  if (ctx) {
360
- if (image) {
390
+ if (image && (title || content)) {
391
+ // 图片和文字同时显示
392
+ const img = canvas.createImage() as HTMLImageElement;
393
+ drawImageAndTextOffScreen(
394
+ ctx,
395
+ img,
396
+ image,
397
+ imageHeight,
398
+ imageWidth,
399
+ title,
400
+ content,
401
+ rotate,
402
+ contentWidth,
403
+ contentHeight,
404
+ fontSize,
405
+ titleFontSize,
406
+ fontFamily,
407
+ fontStyle,
408
+ fontWeight,
409
+ color,
410
+ canvas,
411
+ );
412
+ } else if (image) {
361
413
  const img = canvas.createImage() as HTMLImageElement;
362
414
  drawImageOffScreen(
363
415
  ctx,
@@ -373,7 +425,7 @@ function createOffscreenCanvas(
373
425
  } else {
374
426
  drawTextOffScreen(
375
427
  ctx,
376
- content,
428
+ title,
377
429
  contentWidth,
378
430
  contentHeight,
379
431
  rotate,
@@ -383,6 +435,8 @@ function createOffscreenCanvas(
383
435
  fontWeight,
384
436
  color,
385
437
  canvas,
438
+ content,
439
+ titleFontSize
386
440
  );
387
441
  }
388
442
  } else {
@@ -413,10 +467,28 @@ function createCanvas(
413
467
  image: string,
414
468
  imageHeight: number,
415
469
  imageWidth: number,
470
+ title: string,
471
+ titleFontSize: number
416
472
  ) {
417
473
  const ctx = uni.createCanvasContext(canvasId.value);
418
474
  if (ctx) {
419
- if (image) {
475
+ if (image && (title || content)) {
476
+ // 图片和文字同时显示
477
+ drawImageAndTextOnScreen(
478
+ ctx,
479
+ image,
480
+ imageHeight,
481
+ imageWidth,
482
+ title,
483
+ content,
484
+ rotate,
485
+ contentWidth,
486
+ contentHeight,
487
+ fontSize,
488
+ titleFontSize,
489
+ color
490
+ );
491
+ } else if (image) {
420
492
  drawImageOnScreen(
421
493
  ctx,
422
494
  image,
@@ -427,7 +499,7 @@ function createCanvas(
427
499
  contentHeight,
428
500
  );
429
501
  } else {
430
- drawTextOnScreen(ctx, content, contentWidth, rotate, fontSize, color);
502
+ drawTextOnScreen(ctx, title, contentWidth, rotate, fontSize, color, content, titleFontSize);
431
503
  }
432
504
  } else {
433
505
  console.error("无法获取canvas上下文,请确认当前环境是否支持canvas");
@@ -466,13 +538,37 @@ function createH5Canvas(
466
538
  image: string,
467
539
  imageHeight: number,
468
540
  imageWidth: number,
541
+ title: string,
542
+ titleFontSize: number
469
543
  ) {
470
544
  const canvas = document.createElement("canvas");
471
545
  const ctx = canvas.getContext("2d");
472
546
  canvas.setAttribute("width", `${canvasWidth}px`);
473
547
  canvas.setAttribute("height", `${canvasHeight}px`);
474
548
  if (ctx) {
475
- if (image) {
549
+ if (image && (title || content)) {
550
+ // 图片和文字同时显示
551
+ const img = new Image();
552
+ drawImageAndTextOffScreen(
553
+ ctx,
554
+ img,
555
+ image,
556
+ imageHeight,
557
+ imageWidth,
558
+ title,
559
+ content,
560
+ rotate,
561
+ contentWidth,
562
+ contentHeight,
563
+ fontSize,
564
+ titleFontSize,
565
+ fontFamily,
566
+ fontStyle,
567
+ fontWeight,
568
+ color,
569
+ canvas,
570
+ );
571
+ } else if (image) {
476
572
  const img = new Image();
477
573
  drawImageOffScreen(
478
574
  ctx,
@@ -488,7 +584,7 @@ function createH5Canvas(
488
584
  } else {
489
585
  drawTextOffScreen(
490
586
  ctx,
491
- content,
587
+ title,
492
588
  contentWidth,
493
589
  contentHeight,
494
590
  rotate,
@@ -498,6 +594,8 @@ function createH5Canvas(
498
594
  fontWeight,
499
595
  color,
500
596
  canvas,
597
+ content,
598
+ titleFontSize
501
599
  );
502
600
  }
503
601
  } else {
@@ -519,9 +617,32 @@ function createH5Canvas(
519
617
  * @param color 水印字体颜色
520
618
  * @param canvas canvas实例
521
619
  */
620
+ // 测量文本宽度并自动换行
621
+ function wrapText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number, fontSize: number) {
622
+ const words = text.split('');
623
+ const lines: string[] = [];
624
+ let currentLine = '';
625
+
626
+ for (let i = 0; i < words.length; i++) {
627
+ const testLine = currentLine + words[i];
628
+ const metrics = ctx.measureText(testLine);
629
+ const testWidth = metrics.width;
630
+
631
+ // 当文字宽度超过容器宽度的80%时换行
632
+ if (testWidth > maxWidth * 0.8 && currentLine !== '') {
633
+ lines.push(currentLine);
634
+ currentLine = words[i];
635
+ } else {
636
+ currentLine = testLine;
637
+ }
638
+ }
639
+ lines.push(currentLine);
640
+ return lines;
641
+ }
642
+
522
643
  function drawTextOffScreen(
523
644
  ctx: CanvasRenderingContext2D,
524
- content: string,
645
+ title: string,
525
646
  contentWidth: number,
526
647
  contentHeight: number,
527
648
  rotate: number,
@@ -531,15 +652,50 @@ function drawTextOffScreen(
531
652
  fontWeight: string | number,
532
653
  color: string,
533
654
  canvas: HTMLCanvasElement,
655
+ content: string = '',
656
+ titleFontSize: number = 0
534
657
  ) {
535
- console.log(fontSize, "离屏");
536
658
  ctx.textBaseline = "middle";
537
659
  ctx.textAlign = "center";
538
- ctx.translate(contentWidth / 2, contentWidth / 2);
660
+ ctx.translate(contentWidth / 2, contentHeight / 2);
539
661
  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);
662
+
663
+ // 计算总高度
664
+ let totalTextHeight = titleFontSize;
665
+ if (content) {
666
+ totalTextHeight += fontSize + 5; // 标题和副标题之间的间距
667
+ }
668
+
669
+ // 起始Y坐标
670
+ let startY = -totalTextHeight / 2;
671
+
672
+ // 绘制主标题(支持自动换行)
673
+ if (title) {
674
+ ctx.font = `${fontStyle} normal ${fontWeight} ${titleFontSize}px/${contentHeight}px ${fontFamily}`;
675
+ // 使用titleColor或默认color
676
+ ctx.fillStyle = props.titleColor || color;
677
+ const titleLines = wrapText(ctx, title, contentWidth, titleFontSize);
678
+ const titleLineHeight = titleFontSize * 1.2;
679
+
680
+ for (let i = 0; i < titleLines.length; i++) {
681
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight);
682
+ }
683
+
684
+ startY += titleLines.length * titleLineHeight + 5;
685
+ }
686
+
687
+ // 绘制副标题(支持自动换行)
688
+ if (content) {
689
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`;
690
+ ctx.fillStyle = color;
691
+ const contentLines = wrapText(ctx, content, contentWidth, fontSize);
692
+ const contentLineHeight = fontSize * 1.2;
693
+
694
+ for (let i = 0; i < contentLines.length; i++) {
695
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight);
696
+ }
697
+ }
698
+
543
699
  ctx.restore();
544
700
  waterMarkUrl.value = canvas.toDataURL();
545
701
  }
@@ -553,21 +709,79 @@ function drawTextOffScreen(
553
709
  * @param fontSize 水印字体大小
554
710
  * @param color 水印字体颜色
555
711
  */
712
+ // 简化版本的文字换行(UniApp CanvasContext不支持measureText)
713
+ function simpleWrapText(text: string, maxLength: number) {
714
+ const lines: string[] = [];
715
+ let currentLine = '';
716
+
717
+ // 基于字符数估算换行(适用于UniApp CanvasContext)
718
+ for (let i = 0; i < text.length; i++) {
719
+ currentLine += text[i];
720
+ if (currentLine.length >= maxLength) {
721
+ lines.push(currentLine);
722
+ currentLine = '';
723
+ }
724
+ }
725
+ if (currentLine) {
726
+ lines.push(currentLine);
727
+ }
728
+ return lines;
729
+ }
730
+
556
731
  function drawTextOnScreen(
557
732
  ctx: UniApp.CanvasContext,
558
- content: string,
733
+ title: string,
559
734
  contentWidth: number,
560
735
  rotate: number,
561
736
  fontSize: number,
562
737
  color: string,
738
+ content: string = '',
739
+ titleFontSize: number = 0
563
740
  ) {
564
741
  ctx.setTextBaseline("middle");
565
742
  ctx.setTextAlign("center");
566
743
  ctx.translate(contentWidth / 2, contentWidth / 2);
567
744
  ctx.rotate((Math.PI / 180) * rotate);
568
- ctx.setFillStyle(color);
569
- ctx.setFontSize(fontSize);
570
- ctx.fillText(content, 0, 0);
745
+
746
+ // 估算每行最大字符数
747
+ const maxChars = Math.floor(contentWidth / (fontSize * 0.5));
748
+
749
+ // 计算总高度
750
+ let totalTextHeight = titleFontSize;
751
+ if (content) {
752
+ totalTextHeight += fontSize + 5;
753
+ }
754
+
755
+ // 起始Y坐标
756
+ let startY = -totalTextHeight / 2;
757
+
758
+ // 绘制主标题(支持自动换行)
759
+ if (title) {
760
+ // 使用titleColor或默认color
761
+ ctx.setFillStyle(props.titleColor || color);
762
+ ctx.setFontSize(titleFontSize);
763
+ const titleLines = simpleWrapText(title, maxChars);
764
+ const titleLineHeight = titleFontSize * 1.2;
765
+
766
+ for (let i = 0; i < titleLines.length; i++) {
767
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight);
768
+ }
769
+
770
+ startY += titleLines.length * titleLineHeight + 5;
771
+ }
772
+
773
+ // 绘制副标题(支持自动换行)
774
+ if (content) {
775
+ ctx.setFillStyle(color);
776
+ ctx.setFontSize(fontSize);
777
+ const contentLines = simpleWrapText(content, maxChars);
778
+ const contentLineHeight = fontSize * 1.2;
779
+
780
+ for (let i = 0; i < contentLines.length; i++) {
781
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight);
782
+ }
783
+ }
784
+
571
785
  ctx.restore();
572
786
  ctx.draw();
573
787
  // #ifdef MP-DINGTALK
@@ -632,6 +846,188 @@ async function drawImageOffScreen(
632
846
  };
633
847
  }
634
848
 
849
+ // 绘制图片和文字(离屏)
850
+ async function drawImageAndTextOffScreen(
851
+ ctx: CanvasRenderingContext2D,
852
+ img: HTMLImageElement,
853
+ image: string,
854
+ imageHeight: number,
855
+ imageWidth: number,
856
+ title: string,
857
+ content: string,
858
+ rotate: number,
859
+ contentWidth: number,
860
+ contentHeight: number,
861
+ fontSize: number,
862
+ titleFontSize: number,
863
+ fontFamily: string,
864
+ fontStyle: string,
865
+ fontWeight: string | number,
866
+ color: string,
867
+ canvas: HTMLCanvasElement,
868
+ ) {
869
+ ctx.translate(contentWidth / 2, contentHeight / 2);
870
+ ctx.rotate((Math.PI / 180) * Number(rotate));
871
+ img.crossOrigin = "anonymous";
872
+ img.referrerPolicy = "no-referrer";
873
+
874
+ const imgHeight = imageHeight * pixelRatio.value;
875
+ const imgWidth = imageWidth * pixelRatio.value;
876
+
877
+ img.src = image;
878
+ img.onload = () => {
879
+ // 计算总高度
880
+ let totalHeight = imgHeight;
881
+ const textSpacing = 10;
882
+
883
+ if (title) totalHeight += textSpacing + titleFontSize;
884
+ if (content) totalHeight += fontSize;
885
+
886
+ // 起始Y坐标
887
+ let startY = -totalHeight / 2;
888
+
889
+ // 绘制图片
890
+ ctx.drawImage(
891
+ img,
892
+ -imgWidth / 2,
893
+ startY,
894
+ imgWidth,
895
+ imgHeight,
896
+ );
897
+
898
+ startY += imgHeight + textSpacing;
899
+
900
+ // 设置文字样式
901
+ ctx.textBaseline = "top";
902
+ ctx.textAlign = "center";
903
+
904
+ // 绘制主标题
905
+ if (title) {
906
+ ctx.font = `${fontStyle} normal ${fontWeight} ${titleFontSize}px/${contentHeight}px ${fontFamily}`;
907
+ // 使用titleColor或默认color
908
+ ctx.fillStyle = props.titleColor || color;
909
+ const titleLines = wrapText(ctx, title, contentWidth * 0.9, titleFontSize);
910
+ const titleLineHeight = titleFontSize * 1.2;
911
+
912
+ for (let i = 0; i < titleLines.length; i++) {
913
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight);
914
+ }
915
+
916
+ startY += titleLines.length * titleLineHeight + 5;
917
+ }
918
+
919
+ // 绘制副标题
920
+ if (content) {
921
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`;
922
+ ctx.fillStyle = color;
923
+ const contentLines = wrapText(ctx, content, contentWidth * 0.9, fontSize);
924
+ const contentLineHeight = fontSize * 1.2;
925
+
926
+ for (let i = 0; i < contentLines.length; i++) {
927
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight);
928
+ }
929
+ }
930
+
931
+ ctx.restore();
932
+ waterMarkUrl.value = canvas.toDataURL();
933
+ };
934
+ }
935
+
936
+ // 绘制图片和文字(在屏)
937
+ function drawImageAndTextOnScreen(
938
+ ctx: UniApp.CanvasContext,
939
+ image: string,
940
+ imageHeight: number,
941
+ imageWidth: number,
942
+ title: string,
943
+ content: string,
944
+ rotate: number,
945
+ contentWidth: number,
946
+ contentHeight: number,
947
+ fontSize: number,
948
+ titleFontSize: number,
949
+ color: string
950
+ ) {
951
+ ctx.setTextBaseline("top");
952
+ ctx.setTextAlign("center");
953
+ ctx.translate(contentWidth / 2, contentWidth / 2);
954
+ ctx.rotate((Math.PI / 180) * Number(rotate));
955
+
956
+ const imgHeight = imageHeight * pixelRatio.value;
957
+ const imgWidth = imageWidth * pixelRatio.value;
958
+ const maxChars = Math.floor(contentWidth / (fontSize * 0.5));
959
+
960
+ // 计算总高度
961
+ let totalHeight = imgHeight;
962
+ const textSpacing = 10;
963
+
964
+ if (title) totalHeight += textSpacing + titleFontSize;
965
+ if (content) totalHeight += fontSize;
966
+
967
+ // 起始Y坐标
968
+ let startY = -totalHeight / 2;
969
+
970
+ // 绘制图片
971
+ ctx.drawImage(
972
+ image,
973
+ -imgWidth / 2,
974
+ startY,
975
+ imgWidth,
976
+ imgHeight,
977
+ );
978
+
979
+ startY += imgHeight + textSpacing;
980
+
981
+ // 绘制主标题
982
+ if (title) {
983
+ // 使用titleColor或默认color
984
+ ctx.setFillStyle(props.titleColor || color);
985
+ ctx.setFontSize(titleFontSize);
986
+ const titleLines = simpleWrapText(title, maxChars);
987
+ const titleLineHeight = titleFontSize * 1.2;
988
+
989
+ for (let i = 0; i < titleLines.length; i++) {
990
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight);
991
+ }
992
+
993
+ startY += titleLines.length * titleLineHeight + 5;
994
+ }
995
+
996
+ // 绘制副标题
997
+ if (content) {
998
+ ctx.setFillStyle(color);
999
+ ctx.setFontSize(fontSize);
1000
+ const contentLines = simpleWrapText(content, maxChars);
1001
+ const contentLineHeight = fontSize * 1.2;
1002
+
1003
+ for (let i = 0; i < contentLines.length; i++) {
1004
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight);
1005
+ }
1006
+ }
1007
+
1008
+ ctx.restore();
1009
+ ctx.draw(false, () => {
1010
+ // #ifdef MP-DINGTALK
1011
+ // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
1012
+ (ctx as any).toTempFilePath({
1013
+ success(res: any) {
1014
+ showCanvas.value = false;
1015
+ waterMarkUrl.value = res.filePath;
1016
+ },
1017
+ });
1018
+ // #endif
1019
+ // #ifndef MP-DINGTALK
1020
+ uni.canvasToTempFilePath({
1021
+ canvasId: canvasId.value,
1022
+ success: (res) => {
1023
+ showCanvas.value = false;
1024
+ waterMarkUrl.value = res.tempFilePath;
1025
+ },
1026
+ });
1027
+ // #endif
1028
+ });
1029
+ }
1030
+
635
1031
  /**
636
1032
  * 绘制在屏图片canvas
637
1033
  * @param ctx canvas上下文
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hy-app",
3
- "version": "0.4.10",
4
- "description": "fix: 二维码异步显示不加载问题",
3
+ "version": "0.4.12",
4
+ "description": "fix: 卡片副标题",
5
5
  "main": "./index.ts",
6
6
  "private": false,
7
7
  "scripts": {},