hy-app 0.5.9 → 0.5.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.
Files changed (290) hide show
  1. package/components/hy-action-sheet/hy-action-sheet.vue +200 -200
  2. package/components/hy-action-sheet/props.ts +71 -71
  3. package/components/hy-action-sheet/typing.d.ts +64 -64
  4. package/components/hy-address-picker/hy-address-picker.vue +1 -1
  5. package/components/hy-address-picker/props.ts +100 -100
  6. package/components/hy-address-picker/typing.d.ts +16 -16
  7. package/components/hy-avatar/hy-avatar.vue +163 -163
  8. package/components/hy-avatar/props.ts +78 -78
  9. package/components/hy-avatar/typing.d.ts +4 -4
  10. package/components/hy-back-top/hy-back-top.vue +90 -90
  11. package/components/hy-back-top/props.ts +60 -60
  12. package/components/hy-back-top/typing.d.ts +4 -4
  13. package/components/hy-badge/hy-badge.vue +97 -97
  14. package/components/hy-badge/props.ts +82 -82
  15. package/components/hy-badge/typing.d.ts +9 -9
  16. package/components/hy-button/hy-button.vue +275 -275
  17. package/components/hy-button/props.ts +135 -135
  18. package/components/hy-button/typing.d.ts +30 -30
  19. package/components/hy-calendar/header.vue +60 -60
  20. package/components/hy-calendar/hy-calendar.vue +362 -362
  21. package/components/hy-calendar/month.vue +537 -537
  22. package/components/hy-calendar/props.ts +159 -159
  23. package/components/hy-calendar/typing.d.ts +6 -6
  24. package/components/hy-card/hy-card.vue +161 -161
  25. package/components/hy-card/props.ts +122 -122
  26. package/components/hy-card/typing.d.ts +12 -12
  27. package/components/hy-cell/hy-cell.vue +33 -33
  28. package/components/hy-cell/props.ts +54 -54
  29. package/components/hy-cell/typing.d.ts +4 -4
  30. package/components/hy-cell-item/hy-cell-item.vue +161 -161
  31. package/components/hy-cell-item/props.ts +66 -66
  32. package/components/hy-cell-item/typing.d.ts +7 -7
  33. package/components/hy-check-button/hy-check-button.vue +96 -96
  34. package/components/hy-check-button/props.ts +74 -74
  35. package/components/hy-check-button/typing.d.ts +44 -44
  36. package/components/hy-checkbox/hy-checkbox.vue +227 -227
  37. package/components/hy-checkbox/props.ts +96 -96
  38. package/components/hy-checkbox/typing.d.ts +8 -8
  39. package/components/hy-checkbox-group/hy-checkbox-group.vue +45 -45
  40. package/components/hy-checkbox-group/props.ts +80 -80
  41. package/components/hy-checkbox-group/typing.d.ts +6 -6
  42. package/components/hy-checkbox-item/hy-checkbox-item.vue +199 -199
  43. package/components/hy-checkbox-item/props.ts +24 -24
  44. package/components/hy-checkbox-item/typing.d.ts +7 -7
  45. package/components/hy-code-input/hy-code-input.vue +231 -231
  46. package/components/hy-code-input/props.ts +88 -88
  47. package/components/hy-code-input/typing.d.ts +8 -8
  48. package/components/hy-config-provider/hy-config-provider.vue +53 -53
  49. package/components/hy-config-provider/props.ts +28 -28
  50. package/components/hy-count-down/hy-count-down.vue +170 -170
  51. package/components/hy-count-down/index.ts +52 -52
  52. package/components/hy-count-down/props.ts +32 -32
  53. package/components/hy-count-down/typing.d.ts +14 -14
  54. package/components/hy-count-to/hy-count-to.vue +218 -218
  55. package/components/hy-count-to/props.ts +62 -62
  56. package/components/hy-count-to/typing.d.ts +4 -4
  57. package/components/hy-coupon/hy-coupon.vue +172 -172
  58. package/components/hy-coupon/index.scss +171 -171
  59. package/components/hy-coupon/props.ts +103 -103
  60. package/components/hy-coupon/typing.d.ts +14 -14
  61. package/components/hy-datetime-picker/hy-datetime-picker.vue +521 -521
  62. package/components/hy-datetime-picker/props.ts +142 -142
  63. package/components/hy-datetime-picker/typing.d.ts +20 -20
  64. package/components/hy-divider/hy-divider.vue +132 -132
  65. package/components/hy-divider/props.ts +80 -80
  66. package/components/hy-dropdown/hy-dropdown.vue +60 -60
  67. package/components/hy-dropdown/props.ts +40 -40
  68. package/components/hy-dropdown-item/hy-dropdown-item.vue +206 -206
  69. package/components/hy-dropdown-item/props.ts +21 -21
  70. package/components/hy-dropdown-item/typing.d.ts +17 -17
  71. package/components/hy-empty/hy-empty.vue +116 -116
  72. package/components/hy-empty/icon.ts +72 -72
  73. package/components/hy-empty/props.ts +60 -60
  74. package/components/hy-empty/typing.d.ts +38 -38
  75. package/components/hy-flex/hy-flex.vue +53 -53
  76. package/components/hy-flex/index.scss +8 -8
  77. package/components/hy-flex/props.ts +58 -58
  78. package/components/hy-flex/typing.d.ts +21 -21
  79. package/components/hy-float-button/hy-float-button.vue +378 -378
  80. package/components/hy-float-button/props.ts +111 -111
  81. package/components/hy-float-button/typing.d.ts +35 -35
  82. package/components/hy-folding-panel/hy-folding-panel.vue +109 -109
  83. package/components/hy-folding-panel/props.ts +42 -42
  84. package/components/hy-folding-panel/typing.d.ts +19 -19
  85. package/components/hy-folding-panel-item/hy-folding-panel-item.vue +183 -183
  86. package/components/hy-folding-panel-item/props.ts +81 -81
  87. package/components/hy-folding-panel-item/typing.d.ts +37 -37
  88. package/components/hy-form/hy-form.vue +220 -220
  89. package/components/hy-form/props.ts +37 -37
  90. package/components/hy-form/typing.d.ts +41 -41
  91. package/components/hy-form-group/hy-form-group.vue +333 -333
  92. package/components/hy-form-group/props.ts +105 -105
  93. package/components/hy-form-item/hy-form-item.vue +176 -176
  94. package/components/hy-form-item/index.scss +0 -1
  95. package/components/hy-form-item/props.ts +25 -25
  96. package/components/hy-form-item/typing.d.ts +30 -30
  97. package/components/hy-grid/hy-grid.vue +109 -109
  98. package/components/hy-grid/props.ts +60 -60
  99. package/components/hy-grid/typing.d.ts +35 -35
  100. package/components/hy-icon/hy-icon.vue +112 -112
  101. package/components/hy-icon/index.scss +0 -3
  102. package/components/hy-icon/props.ts +79 -79
  103. package/components/hy-icon/typing.d.ts +9 -9
  104. package/components/hy-image/hy-image.vue +192 -192
  105. package/components/hy-image/props.ts +107 -107
  106. package/components/hy-image/typing.d.ts +10 -10
  107. package/components/hy-input/hy-input.vue +333 -333
  108. package/components/hy-input/index.scss +5 -0
  109. package/components/hy-input/props.ts +186 -186
  110. package/components/hy-input/typing.d.ts +31 -31
  111. package/components/hy-line/hy-line.vue +55 -55
  112. package/components/hy-line/props.ts +43 -43
  113. package/components/hy-line-progress/hy-line-progress.vue +102 -102
  114. package/components/hy-line-progress/index.scss +1 -0
  115. package/components/hy-line-progress/props.ts +33 -33
  116. package/components/hy-list/hy-list.vue +226 -226
  117. package/components/hy-list/props.ts +69 -69
  118. package/components/hy-list/typing.d.ts +6 -6
  119. package/components/hy-loading/hy-loading.vue +107 -107
  120. package/components/hy-loading/props.ts +65 -65
  121. package/components/hy-menu/hy-menu.vue +159 -159
  122. package/components/hy-menu/props.ts +44 -44
  123. package/components/hy-menu/typing.d.ts +34 -34
  124. package/components/hy-modal/hy-modal.vue +173 -173
  125. package/components/hy-modal/props.ts +90 -90
  126. package/components/hy-modal/typing.d.ts +11 -11
  127. package/components/hy-navbar/hy-navbar.vue +144 -144
  128. package/components/hy-navbar/props.ts +78 -78
  129. package/components/hy-navbar/typing.d.ts +6 -6
  130. package/components/hy-notice-bar/hy-column-notice.vue +94 -94
  131. package/components/hy-notice-bar/hy-notice-bar.vue +96 -96
  132. package/components/hy-notice-bar/hy-row-notice.vue +121 -121
  133. package/components/hy-notice-bar/props.ts +85 -85
  134. package/components/hy-notice-bar/typing.d.ts +8 -8
  135. package/components/hy-notify/hy-notify.vue +174 -174
  136. package/components/hy-notify/props.ts +51 -51
  137. package/components/hy-number-step/hy-number-step.vue +367 -367
  138. package/components/hy-number-step/props.ts +112 -112
  139. package/components/hy-number-step/typing.d.ts +16 -16
  140. package/components/hy-overlay/hy-overlay.vue +60 -60
  141. package/components/hy-overlay/props.ts +33 -33
  142. package/components/hy-overlay/typing.d.ts +4 -4
  143. package/components/hy-pagination/hy-pagination.vue +135 -135
  144. package/components/hy-pagination/props.ts +55 -55
  145. package/components/hy-pagination/typing.d.ts +10 -10
  146. package/components/hy-picker/hy-picker.vue +7 -5
  147. package/components/hy-picker/props.ts +7 -2
  148. package/components/hy-picker/typing.d.ts +9 -5
  149. package/components/hy-popover/hy-popover.vue +251 -251
  150. package/components/hy-popover/props.ts +51 -51
  151. package/components/hy-popover/typing.d.ts +39 -39
  152. package/components/hy-popup/hy-popup.vue +197 -197
  153. package/components/hy-popup/props.ts +85 -85
  154. package/components/hy-popup/typing.d.ts +10 -10
  155. package/components/hy-price/hy-price.vue +79 -79
  156. package/components/hy-price/props.ts +54 -54
  157. package/components/hy-price/typing.d.ts +4 -4
  158. package/components/hy-qrcode/hy-qrcode.vue +216 -216
  159. package/components/hy-qrcode/props.ts +70 -70
  160. package/components/hy-qrcode/qrcode.js +1304 -1304
  161. package/components/hy-qrcode/typing.d.ts +8 -8
  162. package/components/hy-radio/hy-radio.vue +226 -226
  163. package/components/hy-radio/props.ts +1 -1
  164. package/components/hy-radio/typing.d.ts +8 -8
  165. package/components/hy-rate/hy-rate.vue +239 -239
  166. package/components/hy-rate/props.ts +77 -77
  167. package/components/hy-rate/typing.d.ts +6 -6
  168. package/components/hy-read-more/hy-read-more.vue +130 -130
  169. package/components/hy-read-more/props.ts +45 -45
  170. package/components/hy-read-more/typing.d.ts +6 -6
  171. package/components/hy-rolling-num/hy-rolling-num.vue +188 -188
  172. package/components/hy-rolling-num/props.ts +68 -68
  173. package/components/hy-scroll-list/hy-scroll-list.vue +123 -123
  174. package/components/hy-scroll-list/props.ts +22 -22
  175. package/components/hy-scroll-list/typing.d.ts +6 -6
  176. package/components/hy-search/hy-search.vue +221 -221
  177. package/components/hy-search/props.ts +131 -131
  178. package/components/hy-search/typing.d.ts +22 -22
  179. package/components/hy-signature/hy-signature.vue +640 -640
  180. package/components/hy-signature/props.ts +118 -118
  181. package/components/hy-signature/typing.d.ts +93 -93
  182. package/components/hy-slider/hy-slider.vue +444 -444
  183. package/components/hy-slider/props.ts +77 -77
  184. package/components/hy-slider/typing.d.ts +10 -10
  185. package/components/hy-status-bar/hy-status-bar.vue +41 -41
  186. package/components/hy-status-bar/props.ts +8 -8
  187. package/components/hy-status-bar/typing.d.ts +12 -12
  188. package/components/hy-steps/hy-steps.vue +267 -267
  189. package/components/hy-steps/props.ts +49 -49
  190. package/components/hy-steps/typing.d.ts +21 -21
  191. package/components/hy-sticky/hy-sticky.vue +226 -226
  192. package/components/hy-sticky/props.ts +24 -24
  193. package/components/hy-sticky/typing.d.ts +4 -4
  194. package/components/hy-submit-bar/hy-submit-bar.vue +189 -189
  195. package/components/hy-submit-bar/props.ts +91 -91
  196. package/components/hy-submit-bar/typing.d.ts +24 -24
  197. package/components/hy-subsection/hy-subsection.vue +207 -207
  198. package/components/hy-subsection/props.ts +52 -52
  199. package/components/hy-subsection/typing.d.ts +13 -13
  200. package/components/hy-swipe-action/hy-swipe-action.vue +323 -323
  201. package/components/hy-swipe-action/index.ts +25 -25
  202. package/components/hy-swipe-action/props.ts +47 -47
  203. package/components/hy-swipe-action/typing.d.ts +25 -25
  204. package/components/hy-swiper/hy-swiper-indicator.vue +75 -75
  205. package/components/hy-swiper/hy-swiper.vue +224 -224
  206. package/components/hy-swiper/props.ts +128 -128
  207. package/components/hy-swiper/typing.d.ts +26 -26
  208. package/components/hy-switch/hy-switch.vue +173 -173
  209. package/components/hy-switch/props.ts +61 -61
  210. package/components/hy-switch/typing.d.ts +8 -8
  211. package/components/hy-tabbar/hy-tabbar.vue +136 -136
  212. package/components/hy-tabbar/props.ts +59 -59
  213. package/components/hy-tabbar/typing.d.ts +21 -21
  214. package/components/hy-tabbar-group/hy-tabbar-group.vue +87 -87
  215. package/components/hy-tabbar-group/props.ts +78 -78
  216. package/components/hy-tabbar-group/typing.d.ts +16 -16
  217. package/components/hy-tabbar-item/hy-tabbar-item.vue +103 -103
  218. package/components/hy-tabbar-item/typing.d.ts +10 -10
  219. package/components/hy-table/hy-table.vue +358 -358
  220. package/components/hy-table/props.ts +47 -47
  221. package/components/hy-table/typing.d.ts +34 -34
  222. package/components/hy-tabs/hy-tabs.vue +335 -335
  223. package/components/hy-tabs/props.ts +77 -77
  224. package/components/hy-tabs/typing.d.ts +33 -33
  225. package/components/hy-tag/hy-tag.vue +174 -174
  226. package/components/hy-tag/props.ts +89 -89
  227. package/components/hy-tag/typing.d.ts +13 -13
  228. package/components/hy-text/hy-text.vue +237 -237
  229. package/components/hy-text/props.ts +115 -115
  230. package/components/hy-text/typing.d.ts +6 -6
  231. package/components/hy-textarea/hy-textarea.vue +197 -197
  232. package/components/hy-textarea/index.scss +5 -0
  233. package/components/hy-textarea/props.ts +116 -116
  234. package/components/hy-textarea/typing.d.ts +22 -22
  235. package/components/hy-toast/hy-toast.vue +190 -190
  236. package/components/hy-toast/typing.d.ts +38 -38
  237. package/components/hy-tooltip/hy-tooltip.vue +277 -277
  238. package/components/hy-tooltip/props.ts +78 -78
  239. package/components/hy-tooltip/typing.d.ts +4 -4
  240. package/components/hy-transition/hy-transition.vue +157 -157
  241. package/components/hy-transition/props.ts +32 -32
  242. package/components/hy-transition/typing.d.ts +16 -16
  243. package/components/hy-upload/hy-upload.vue +385 -385
  244. package/components/hy-upload/props.ts +132 -132
  245. package/components/hy-upload/typing.d.ts +65 -65
  246. package/components/hy-warn/hy-warn.vue +115 -115
  247. package/components/hy-warn/props.ts +49 -49
  248. package/components/hy-warn/typing.d.ts +6 -6
  249. package/components/hy-waterfall/hy-waterfall.vue +191 -191
  250. package/components/hy-waterfall/props.ts +21 -21
  251. package/components/hy-watermark/hy-watermark.vue +978 -978
  252. package/components/hy-watermark/props.ts +104 -104
  253. package/components/index.ts +183 -183
  254. package/global.d.ts +91 -91
  255. package/index.ts +1 -1
  256. package/libs/api/http.ts +140 -140
  257. package/libs/api/index.ts +1 -1
  258. package/libs/common/index.ts +2 -2
  259. package/libs/common/queue.ts +28 -28
  260. package/libs/composables/index.ts +6 -6
  261. package/libs/composables/usePopover.ts +241 -241
  262. package/libs/composables/useQueue.ts +53 -53
  263. package/libs/composables/useShakeService.ts +64 -64
  264. package/libs/composables/useShare.ts +42 -42
  265. package/libs/composables/useToast.ts +45 -45
  266. package/libs/composables/useTouch.ts +51 -51
  267. package/libs/config/color.ts +7 -7
  268. package/libs/config/icon.ts +430 -430
  269. package/libs/config/index.ts +2 -2
  270. package/libs/css/iconfont.css +443 -443
  271. package/libs/css/theme.scss +1 -1
  272. package/libs/global/index.ts +6 -6
  273. package/libs/global/register-properties.ts +37 -37
  274. package/libs/index.ts +7 -7
  275. package/libs/typing/index.ts +4 -4
  276. package/libs/typing/modules/common.d.ts +139 -139
  277. package/libs/typing/modules/enum.ts +67 -67
  278. package/libs/typing/modules/form.ts +5 -1
  279. package/libs/typing/modules/http.ts +17 -17
  280. package/libs/typing/modules/icon.d.ts +366 -366
  281. package/libs/typing/modules/rect.ts +10 -10
  282. package/libs/utils/base64.ts +119 -119
  283. package/libs/utils/calendar.js +1021 -1021
  284. package/libs/utils/colorGradient.ts +112 -112
  285. package/libs/utils/index.ts +5 -5
  286. package/libs/utils/inside.ts +350 -361
  287. package/libs/utils/inspect.ts +171 -171
  288. package/libs/utils/utils.ts +521 -521
  289. package/package.json +18 -18
  290. package/web-types.json +1 -1
@@ -1,978 +1,978 @@
1
- <template>
2
- <view :class="rootClass" :style="rootStyle">
3
- <canvas
4
- v-if="!canvasOffScreenable && showCanvas"
5
- type="2d"
6
- :style="{
7
- height: canvasHeight + 'px',
8
- width: canvasWidth + 'px',
9
- visibility: 'hidden'
10
- }"
11
- :canvas-id="canvasId"
12
- :id="canvasId"
13
- />
14
- </view>
15
- </template>
16
-
17
- <script lang="ts">
18
- export default {
19
- name: 'hy-watermark',
20
- options: {
21
- addGlobalClass: true,
22
- virtualHost: true,
23
- styleIsolation: 'shared'
24
- }
25
- }
26
- </script>
27
-
28
- <script lang="ts" setup>
29
- import { computed, onMounted, ref, watch, nextTick } from 'vue'
30
- import type { CSSProperties } from 'vue'
31
- import { addUnit, guid } from '../../libs'
32
- import watermarkProps from './props'
33
-
34
- /**
35
- * 在页面或组件上添加指定的图片或文字,可用于版权保护、品牌宣传等场景。
36
- * @displayName hy-watermark
37
- */
38
- defineOptions({})
39
-
40
- const props = defineProps(watermarkProps)
41
-
42
- watch(
43
- () => props,
44
- () => {
45
- doReset()
46
- },
47
- { deep: true }
48
- )
49
-
50
- const canvasId = ref<string>(`watermark--${guid()}`) // canvas 组件的唯一标识符
51
- const waterMarkUrl = ref<string>('') // canvas生成base64水印
52
- const canvasOffScreenable = ref<boolean>(
53
- uni.canIUse('createOffscreenCanvas') && Boolean(uni.createOffscreenCanvas)
54
- ) // 是否可以使用离屏canvas
55
- const pixelRatio = ref<number>(uni.getSystemInfoSync().pixelRatio) // 像素比
56
- const canvasHeight = ref<number>((props.height + props.gutterY) * pixelRatio.value) // canvas画布高度
57
- const canvasWidth = ref<number>((props.width + props.gutterX) * pixelRatio.value) // canvas画布宽度
58
- const showCanvas = ref<boolean>(true) // 是否展示canvas
59
-
60
- /**
61
- * @description 水印css类
62
- */
63
- const rootClass = computed(() => {
64
- const classes: string[] = ['hy-watermark']
65
- if (props.fullScreen) {
66
- classes.push('is-fullscreen')
67
- }
68
- return classes
69
- })
70
-
71
- /**
72
- * @description 水印样式
73
- */
74
- const rootStyle = computed(() => {
75
- const style: CSSProperties = {
76
- opacity: props.opacity,
77
- backgroundSize: addUnit(props.width + props.gutterX)
78
- }
79
- if (waterMarkUrl.value) {
80
- style['backgroundImage'] = `url('${waterMarkUrl.value}')`
81
- }
82
- return style
83
- })
84
-
85
- onMounted(() => {
86
- doInit()
87
- })
88
-
89
- function doReset() {
90
- showCanvas.value = true
91
- canvasHeight.value = (props.height + props.gutterY) * pixelRatio.value
92
- canvasWidth.value = (props.width + props.gutterX) * pixelRatio.value
93
- nextTick(() => {
94
- doInit()
95
- })
96
- }
97
-
98
- function doInit() {
99
- // #ifdef H5
100
- // h5使用document.createElement创建canvas,不用展示canvas标签
101
- showCanvas.value = false
102
- // #endif
103
- const {
104
- width,
105
- height,
106
- color,
107
- size,
108
- fontStyle,
109
- fontWeight,
110
- fontFamily,
111
- content,
112
- rotate,
113
- gutterX,
114
- gutterY,
115
- image,
116
- imageHeight,
117
- imageWidth,
118
- title
119
- } = props
120
-
121
- // 创建水印
122
- createWaterMark(
123
- width,
124
- height,
125
- color,
126
- size,
127
- fontStyle,
128
- fontWeight,
129
- fontFamily,
130
- content,
131
- rotate,
132
- gutterX,
133
- gutterY,
134
- image,
135
- imageHeight,
136
- imageWidth,
137
- title
138
- )
139
- }
140
-
141
- /**
142
- * 创建水印图片
143
- * @param width canvas宽度
144
- * @param height canvas高度
145
- * @param color canvas字体颜色
146
- * @param size canvas字体大小
147
- * @param fontStyle canvas字体样式
148
- * @param fontWeight canvas字体字重
149
- * @param fontFamily canvas字体系列
150
- * @param content canvas内容
151
- * @param rotate 倾斜角度
152
- * @param gutterX X轴间距
153
- * @param gutterY Y轴间距
154
- * @param image canvas图片
155
- * @param imageHeight canvas图片高度
156
- * @param imageWidth canvas图片宽度
157
- * @param title 标题
158
- */
159
- function createWaterMark(
160
- width: number,
161
- height: number,
162
- color: string,
163
- size: number,
164
- fontStyle: string,
165
- fontWeight: number | string,
166
- fontFamily: string,
167
- content: string,
168
- rotate: number,
169
- gutterX: number,
170
- gutterY: number,
171
- image: string,
172
- imageHeight: number,
173
- imageWidth: number,
174
- title: string
175
- ) {
176
- const canvasHeight = (height + gutterY) * pixelRatio.value
177
- const canvasWidth = (width + gutterX) * pixelRatio.value
178
- const contentWidth = width * pixelRatio.value
179
- const contentHeight = height * pixelRatio.value
180
- const fontSize = size * pixelRatio.value
181
- // 标题字体大小:如果设置了titleSize则使用titleSize,否则使用size的1.2倍
182
- const titleFontSize = props.titleSize > 0 ? props.titleSize * pixelRatio.value : fontSize * 1.2
183
-
184
- // #ifndef H5
185
- if (canvasOffScreenable.value) {
186
- createOffscreenCanvas(
187
- canvasHeight,
188
- canvasWidth,
189
- contentWidth,
190
- contentHeight,
191
- rotate,
192
- fontSize,
193
- fontFamily,
194
- fontStyle,
195
- fontWeight,
196
- color,
197
- content,
198
- image,
199
- imageHeight,
200
- imageWidth,
201
- title,
202
- titleFontSize
203
- )
204
- } else {
205
- createCanvas(
206
- canvasHeight,
207
- contentWidth,
208
- rotate,
209
- fontSize,
210
- color,
211
- content,
212
- image,
213
- imageHeight,
214
- imageWidth,
215
- title,
216
- titleFontSize
217
- )
218
- }
219
- // #endif
220
- // #ifdef H5
221
- createH5Canvas(
222
- canvasHeight,
223
- canvasWidth,
224
- contentWidth,
225
- contentHeight,
226
- rotate,
227
- fontSize,
228
- fontFamily,
229
- fontStyle,
230
- fontWeight,
231
- color,
232
- content,
233
- image,
234
- imageHeight,
235
- imageWidth,
236
- title,
237
- titleFontSize
238
- )
239
- // #endif
240
- }
241
-
242
- /**
243
- * 创建离屏canvas
244
- * @param canvasHeight canvas高度
245
- * @param canvasWidth canvas宽度
246
- * @param contentWidth 内容宽度
247
- * @param contentHeight 内容高度
248
- * @param rotate 内容倾斜角度
249
- * @param fontSize 字体大小
250
- * @param fontFamily 字体系列
251
- * @param fontStyle 字体样式
252
- * @param fontWeight 字体字重
253
- * @param color 字体颜色
254
- * @param content 内容
255
- * @param image canvas图片
256
- * @param imageHeight canvas图片高度
257
- * @param imageWidth canvas图片宽度
258
- */
259
- function createOffscreenCanvas(
260
- canvasHeight: number,
261
- canvasWidth: number,
262
- contentWidth: number,
263
- contentHeight: number,
264
- rotate: number,
265
- fontSize: number,
266
- fontFamily: string,
267
- fontStyle: string,
268
- fontWeight: string | number,
269
- color: string,
270
- content: string,
271
- image: string,
272
- imageHeight: number,
273
- imageWidth: number,
274
- title: string,
275
- titleFontSize: number
276
- ) {
277
- // 创建离屏canvas
278
- const canvas: any = uni.createOffscreenCanvas({
279
- height: canvasHeight,
280
- width: canvasWidth,
281
- type: '2d'
282
- })
283
- const ctx: any = canvas.getContext('2d')
284
- if (ctx) {
285
- if (image && (title || content)) {
286
- // 图片和文字同时显示
287
- const img = canvas.createImage() as HTMLImageElement
288
- drawImageAndTextOffScreen(
289
- ctx,
290
- img,
291
- image,
292
- imageHeight,
293
- imageWidth,
294
- title,
295
- content,
296
- rotate,
297
- contentWidth,
298
- contentHeight,
299
- fontSize,
300
- titleFontSize,
301
- fontFamily,
302
- fontStyle,
303
- fontWeight,
304
- color,
305
- canvas
306
- )
307
- } else if (image) {
308
- const img = canvas.createImage() as HTMLImageElement
309
- drawImageOffScreen(
310
- ctx,
311
- img,
312
- image,
313
- imageHeight,
314
- imageWidth,
315
- rotate,
316
- contentWidth,
317
- contentHeight,
318
- canvas
319
- )
320
- } else {
321
- drawTextOffScreen(
322
- ctx,
323
- title,
324
- contentWidth,
325
- contentHeight,
326
- rotate,
327
- fontSize,
328
- fontFamily,
329
- fontStyle,
330
- fontWeight,
331
- color,
332
- canvas,
333
- content,
334
- titleFontSize
335
- )
336
- }
337
- } else {
338
- console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
339
- }
340
- }
341
-
342
- /**
343
- * 非H5创建canvas
344
- * 不支持创建离屏canvas时调用
345
- * @param contentHeight 内容高度
346
- * @param contentWidth 内容宽度
347
- * @param rotate 内容倾斜角度
348
- * @param fontSize 字体大小
349
- * @param color 字体颜色
350
- * @param content 内容
351
- * @param image canvas图片
352
- * @param imageHeight canvas图片高度
353
- * @param imageWidth canvas图片宽度
354
- */
355
- function createCanvas(
356
- contentHeight: number,
357
- contentWidth: number,
358
- rotate: number,
359
- fontSize: number,
360
- color: string,
361
- content: string,
362
- image: string,
363
- imageHeight: number,
364
- imageWidth: number,
365
- title: string,
366
- titleFontSize: number
367
- ) {
368
- const ctx = uni.createCanvasContext(canvasId.value)
369
- if (ctx) {
370
- if (image && (title || content)) {
371
- // 图片和文字同时显示
372
- drawImageAndTextOnScreen(
373
- ctx,
374
- image,
375
- imageHeight,
376
- imageWidth,
377
- title,
378
- content,
379
- rotate,
380
- contentWidth,
381
- contentHeight,
382
- fontSize,
383
- titleFontSize,
384
- color
385
- )
386
- } else if (image) {
387
- drawImageOnScreen(
388
- ctx,
389
- image,
390
- imageHeight,
391
- imageWidth,
392
- rotate,
393
- contentWidth,
394
- contentHeight
395
- )
396
- } else {
397
- drawTextOnScreen(
398
- ctx,
399
- title,
400
- contentWidth,
401
- rotate,
402
- fontSize,
403
- color,
404
- content,
405
- titleFontSize
406
- )
407
- }
408
- } else {
409
- console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
410
- }
411
- }
412
-
413
- /**
414
- * h5创建canvas
415
- * @param canvasHeight canvas高度
416
- * @param canvasWidth canvas宽度
417
- * @param contentWidth 水印内容宽度
418
- * @param contentHeight 水印内容高度
419
- * @param rotate 水印内容倾斜角度
420
- * @param fontSize 水印字体大小
421
- * @param fontFamily 水印字体系列
422
- * @param fontStyle 水印字体样式
423
- * @param fontWeight 水印字体字重
424
- * @param color 水印字体颜色
425
- * @param content 水印内容
426
- * @param image canvas图片
427
- * @param imageHeight canvas图片高度
428
- * @param imageWidth canvas图片宽度
429
- */
430
- function createH5Canvas(
431
- canvasHeight: number,
432
- canvasWidth: number,
433
- contentWidth: number,
434
- contentHeight: number,
435
- rotate: number,
436
- fontSize: number,
437
- fontFamily: string,
438
- fontStyle: string,
439
- fontWeight: string | number,
440
- color: string,
441
- content: string,
442
- image: string,
443
- imageHeight: number,
444
- imageWidth: number,
445
- title: string,
446
- titleFontSize: number
447
- ) {
448
- const canvas = document.createElement('canvas')
449
- const ctx = canvas.getContext('2d')
450
- canvas.setAttribute('width', `${canvasWidth}px`)
451
- canvas.setAttribute('height', `${canvasHeight}px`)
452
- if (ctx) {
453
- if (image && (title || content)) {
454
- // 图片和文字同时显示
455
- const img = new Image()
456
- drawImageAndTextOffScreen(
457
- ctx,
458
- img,
459
- image,
460
- imageHeight,
461
- imageWidth,
462
- title,
463
- content,
464
- rotate,
465
- contentWidth,
466
- contentHeight,
467
- fontSize,
468
- titleFontSize,
469
- fontFamily,
470
- fontStyle,
471
- fontWeight,
472
- color,
473
- canvas
474
- )
475
- } else if (image) {
476
- const img = new Image()
477
- drawImageOffScreen(
478
- ctx,
479
- img,
480
- image,
481
- imageHeight,
482
- imageWidth,
483
- rotate,
484
- contentWidth,
485
- contentHeight,
486
- canvas
487
- )
488
- } else {
489
- drawTextOffScreen(
490
- ctx,
491
- title,
492
- contentWidth,
493
- contentHeight,
494
- rotate,
495
- fontSize,
496
- fontFamily,
497
- fontStyle,
498
- fontWeight,
499
- color,
500
- canvas,
501
- content,
502
- titleFontSize
503
- )
504
- }
505
- } else {
506
- console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
507
- }
508
- }
509
-
510
- /**
511
- * 绘制离屏文字canvas
512
- * @param ctx canvas上下文
513
- * @param content 水印内容
514
- * @param contentWidth 水印宽度
515
- * @param contentHeight 水印高度
516
- * @param rotate 水印内容倾斜角度
517
- * @param fontSize 水印字体大小
518
- * @param fontFamily 水印字体系列
519
- * @param fontStyle 水印字体样式
520
- * @param fontWeight 水印字体字重
521
- * @param color 水印字体颜色
522
- * @param canvas canvas实例
523
- */
524
- // 测量文本宽度并自动换行
525
- function wrapText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number, fontSize: number) {
526
- const words = text.split('')
527
- const lines: string[] = []
528
- let currentLine = ''
529
-
530
- for (let i = 0; i < words.length; i++) {
531
- const testLine = currentLine + words[i]
532
- const metrics = ctx.measureText(testLine)
533
- const testWidth = metrics.width
534
-
535
- // 当文字宽度超过容器宽度的80%时换行
536
- if (testWidth > maxWidth * 0.8 && currentLine !== '') {
537
- lines.push(currentLine)
538
- currentLine = words[i]
539
- } else {
540
- currentLine = testLine
541
- }
542
- }
543
- lines.push(currentLine)
544
- return lines
545
- }
546
-
547
- function drawTextOffScreen(
548
- ctx: CanvasRenderingContext2D,
549
- title: string,
550
- contentWidth: number,
551
- contentHeight: number,
552
- rotate: number,
553
- fontSize: number,
554
- fontFamily: string,
555
- fontStyle: string,
556
- fontWeight: string | number,
557
- color: string,
558
- canvas: HTMLCanvasElement,
559
- content: string = '',
560
- titleFontSize: number = 0
561
- ) {
562
- ctx.textBaseline = 'middle'
563
- ctx.textAlign = 'center'
564
- ctx.translate(contentWidth / 2, contentHeight / 2)
565
- ctx.rotate((Math.PI / 180) * rotate)
566
-
567
- // 计算总高度
568
- let totalTextHeight = titleFontSize
569
- if (content) {
570
- totalTextHeight += fontSize + 5 // 标题和副标题之间的间距
571
- }
572
-
573
- // 起始Y坐标
574
- let startY = -totalTextHeight / 2
575
-
576
- // 绘制主标题(支持自动换行)
577
- if (title) {
578
- ctx.font = `${fontStyle} normal ${fontWeight} ${titleFontSize}px/${contentHeight}px ${fontFamily}`
579
- // 使用titleColor或默认color
580
- ctx.fillStyle = props.titleColor || color
581
- const titleLines = wrapText(ctx, title, contentWidth, titleFontSize)
582
- const titleLineHeight = titleFontSize * 1.2
583
-
584
- for (let i = 0; i < titleLines.length; i++) {
585
- ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight)
586
- }
587
-
588
- startY += titleLines.length * titleLineHeight + 5
589
- }
590
-
591
- // 绘制副标题(支持自动换行)
592
- if (content) {
593
- ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`
594
- ctx.fillStyle = color
595
- const contentLines = wrapText(ctx, content, contentWidth, fontSize)
596
- const contentLineHeight = fontSize * 1.2
597
-
598
- for (let i = 0; i < contentLines.length; i++) {
599
- ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight)
600
- }
601
- }
602
-
603
- ctx.restore()
604
- waterMarkUrl.value = canvas.toDataURL()
605
- }
606
-
607
- /**
608
- * 绘制在屏文字canvas
609
- * @param ctx canvas上下文
610
- * @param content 水印内容
611
- * @param contentWidth 水印宽度
612
- * @param rotate 水印内容倾斜角度
613
- * @param fontSize 水印字体大小
614
- * @param color 水印字体颜色
615
- */
616
- // 简化版本的文字换行(UniApp CanvasContext不支持measureText)
617
- function simpleWrapText(text: string, maxLength: number) {
618
- const lines: string[] = []
619
- let currentLine = ''
620
-
621
- // 基于字符数估算换行(适用于UniApp CanvasContext)
622
- for (let i = 0; i < text.length; i++) {
623
- currentLine += text[i]
624
- if (currentLine.length >= maxLength) {
625
- lines.push(currentLine)
626
- currentLine = ''
627
- }
628
- }
629
- if (currentLine) {
630
- lines.push(currentLine)
631
- }
632
- return lines
633
- }
634
-
635
- function drawTextOnScreen(
636
- ctx: UniApp.CanvasContext,
637
- title: string,
638
- contentWidth: number,
639
- rotate: number,
640
- fontSize: number,
641
- color: string,
642
- content: string = '',
643
- titleFontSize: number = 0
644
- ) {
645
- ctx.setTextBaseline('middle')
646
- ctx.setTextAlign('center')
647
- ctx.translate(contentWidth / 2, contentWidth / 2)
648
- ctx.rotate((Math.PI / 180) * rotate)
649
-
650
- // 估算每行最大字符数
651
- const maxChars = Math.floor(contentWidth / (fontSize * 0.5))
652
-
653
- // 计算总高度
654
- let totalTextHeight = titleFontSize
655
- if (content) {
656
- totalTextHeight += fontSize + 5
657
- }
658
-
659
- // 起始Y坐标
660
- let startY = -totalTextHeight / 2
661
-
662
- // 绘制主标题(支持自动换行)
663
- if (title) {
664
- // 使用titleColor或默认color
665
- ctx.setFillStyle(props.titleColor || color)
666
- ctx.setFontSize(titleFontSize)
667
- const titleLines = simpleWrapText(title, maxChars)
668
- const titleLineHeight = titleFontSize * 1.2
669
-
670
- for (let i = 0; i < titleLines.length; i++) {
671
- ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight)
672
- }
673
-
674
- startY += titleLines.length * titleLineHeight + 5
675
- }
676
-
677
- // 绘制副标题(支持自动换行)
678
- if (content) {
679
- ctx.setFillStyle(color)
680
- ctx.setFontSize(fontSize)
681
- const contentLines = simpleWrapText(content, maxChars)
682
- const contentLineHeight = fontSize * 1.2
683
-
684
- for (let i = 0; i < contentLines.length; i++) {
685
- ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight)
686
- }
687
- }
688
-
689
- ctx.restore()
690
- ctx.draw()
691
- // #ifdef MP-DINGTALK
692
- // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
693
- ;(ctx as any).toTempFilePath({
694
- success(res: any) {
695
- showCanvas.value = false
696
- waterMarkUrl.value = res.filePath
697
- }
698
- })
699
- // #endif
700
- // #ifndef MP-DINGTALK
701
- uni.canvasToTempFilePath({
702
- canvasId: canvasId.value,
703
- success: (res) => {
704
- showCanvas.value = false
705
- waterMarkUrl.value = res.tempFilePath
706
- }
707
- })
708
- // #endif
709
- }
710
-
711
- /**
712
- * 绘制离屏图片canvas
713
- * @param ctx canvas上下文
714
- * @param img 水印图片对象
715
- * @param image 水印图片地址
716
- * @param imageHeight 水印图片高度
717
- * @param imageWidth 水印图片宽度
718
- * @param rotate 水印内容倾斜角度
719
- * @param contentWidth 水印宽度
720
- * @param contentHeight 水印高度
721
- * @param canvas canvas实例
722
- */
723
- async function drawImageOffScreen(
724
- ctx: CanvasRenderingContext2D,
725
- img: HTMLImageElement,
726
- image: string,
727
- imageHeight: number,
728
- imageWidth: number,
729
- rotate: number,
730
- contentWidth: number,
731
- contentHeight: number,
732
- canvas: HTMLCanvasElement
733
- ) {
734
- ctx.translate(contentWidth / 2, contentHeight / 2)
735
- ctx.rotate((Math.PI / 180) * Number(rotate))
736
- img.crossOrigin = 'anonymous'
737
- img.referrerPolicy = 'no-referrer'
738
-
739
- img.src = image
740
- img.onload = () => {
741
- ctx.drawImage(
742
- img,
743
- (-imageWidth * pixelRatio.value) / 2,
744
- (-imageHeight * pixelRatio.value) / 2,
745
- imageWidth * pixelRatio.value,
746
- imageHeight * pixelRatio.value
747
- )
748
- ctx.restore()
749
- waterMarkUrl.value = canvas.toDataURL()
750
- }
751
- }
752
-
753
- // 绘制图片和文字(离屏)
754
- async function drawImageAndTextOffScreen(
755
- ctx: CanvasRenderingContext2D,
756
- img: HTMLImageElement,
757
- image: string,
758
- imageHeight: number,
759
- imageWidth: number,
760
- title: string,
761
- content: string,
762
- rotate: number,
763
- contentWidth: number,
764
- contentHeight: number,
765
- fontSize: number,
766
- titleFontSize: number,
767
- fontFamily: string,
768
- fontStyle: string,
769
- fontWeight: string | number,
770
- color: string,
771
- canvas: HTMLCanvasElement
772
- ) {
773
- ctx.translate(contentWidth / 2, contentHeight / 2)
774
- ctx.rotate((Math.PI / 180) * Number(rotate))
775
- img.crossOrigin = 'anonymous'
776
- img.referrerPolicy = 'no-referrer'
777
-
778
- const imgHeight = imageHeight * pixelRatio.value
779
- const imgWidth = imageWidth * pixelRatio.value
780
-
781
- img.src = image
782
- img.onload = () => {
783
- // 计算总高度
784
- let totalHeight = imgHeight
785
- const textSpacing = 10
786
-
787
- if (title) totalHeight += textSpacing + titleFontSize
788
- if (content) totalHeight += fontSize
789
-
790
- // 起始Y坐标
791
- let startY = -totalHeight / 2
792
-
793
- // 绘制图片
794
- ctx.drawImage(img, -imgWidth / 2, startY, imgWidth, imgHeight)
795
-
796
- startY += imgHeight + textSpacing
797
-
798
- // 设置文字样式
799
- ctx.textBaseline = 'top'
800
- ctx.textAlign = 'center'
801
-
802
- // 绘制主标题
803
- if (title) {
804
- ctx.font = `${fontStyle} normal ${fontWeight} ${titleFontSize}px/${contentHeight}px ${fontFamily}`
805
- // 使用titleColor或默认color
806
- ctx.fillStyle = props.titleColor || color
807
- const titleLines = wrapText(ctx, title, contentWidth * 0.9, titleFontSize)
808
- const titleLineHeight = titleFontSize * 1.2
809
-
810
- for (let i = 0; i < titleLines.length; i++) {
811
- ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight)
812
- }
813
-
814
- startY += titleLines.length * titleLineHeight + 5
815
- }
816
-
817
- // 绘制副标题
818
- if (content) {
819
- ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`
820
- ctx.fillStyle = color
821
- const contentLines = wrapText(ctx, content, contentWidth * 0.9, fontSize)
822
- const contentLineHeight = fontSize * 1.2
823
-
824
- for (let i = 0; i < contentLines.length; i++) {
825
- ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight)
826
- }
827
- }
828
-
829
- ctx.restore()
830
- waterMarkUrl.value = canvas.toDataURL()
831
- }
832
- }
833
-
834
- // 绘制图片和文字(在屏)
835
- function drawImageAndTextOnScreen(
836
- ctx: UniApp.CanvasContext,
837
- image: string,
838
- imageHeight: number,
839
- imageWidth: number,
840
- title: string,
841
- content: string,
842
- rotate: number,
843
- contentWidth: number,
844
- contentHeight: number,
845
- fontSize: number,
846
- titleFontSize: number,
847
- color: string
848
- ) {
849
- ctx.setTextBaseline('top')
850
- ctx.setTextAlign('center')
851
- ctx.translate(contentWidth / 2, contentWidth / 2)
852
- ctx.rotate((Math.PI / 180) * Number(rotate))
853
-
854
- const imgHeight = imageHeight * pixelRatio.value
855
- const imgWidth = imageWidth * pixelRatio.value
856
- const maxChars = Math.floor(contentWidth / (fontSize * 0.5))
857
-
858
- // 计算总高度
859
- let totalHeight = imgHeight
860
- const textSpacing = 10
861
-
862
- if (title) totalHeight += textSpacing + titleFontSize
863
- if (content) totalHeight += fontSize
864
-
865
- // 起始Y坐标
866
- let startY = -totalHeight / 2
867
-
868
- // 绘制图片
869
- ctx.drawImage(image, -imgWidth / 2, startY, imgWidth, imgHeight)
870
-
871
- startY += imgHeight + textSpacing
872
-
873
- // 绘制主标题
874
- if (title) {
875
- // 使用titleColor或默认color
876
- ctx.setFillStyle(props.titleColor || color)
877
- ctx.setFontSize(titleFontSize)
878
- const titleLines = simpleWrapText(title, maxChars)
879
- const titleLineHeight = titleFontSize * 1.2
880
-
881
- for (let i = 0; i < titleLines.length; i++) {
882
- ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight)
883
- }
884
-
885
- startY += titleLines.length * titleLineHeight + 5
886
- }
887
-
888
- // 绘制副标题
889
- if (content) {
890
- ctx.setFillStyle(color)
891
- ctx.setFontSize(fontSize)
892
- const contentLines = simpleWrapText(content, maxChars)
893
- const contentLineHeight = fontSize * 1.2
894
-
895
- for (let i = 0; i < contentLines.length; i++) {
896
- ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight)
897
- }
898
- }
899
-
900
- ctx.restore()
901
- ctx.draw(false, () => {
902
- // #ifdef MP-DINGTALK
903
- // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
904
- ;(ctx as any).toTempFilePath({
905
- success(res: any) {
906
- showCanvas.value = false
907
- waterMarkUrl.value = res.filePath
908
- }
909
- })
910
- // #endif
911
- // #ifndef MP-DINGTALK
912
- uni.canvasToTempFilePath({
913
- canvasId: canvasId.value,
914
- success: (res) => {
915
- showCanvas.value = false
916
- waterMarkUrl.value = res.tempFilePath
917
- }
918
- })
919
- // #endif
920
- })
921
- }
922
-
923
- /**
924
- * 绘制在屏图片canvas
925
- * @param ctx canvas上下文
926
- * @param image 水印图片地址
927
- * @param imageHeight 水印图片高度
928
- * @param imageWidth 水印图片宽度
929
- * @param rotate 水印内容倾斜角度
930
- * @param contentWidth 水印宽度
931
- * @param contentHeight 水印高度
932
- */
933
- function drawImageOnScreen(
934
- ctx: UniApp.CanvasContext,
935
- image: string,
936
- imageHeight: number,
937
- imageWidth: number,
938
- rotate: number,
939
- contentWidth: number,
940
- contentHeight: number
941
- ) {
942
- ctx.translate(contentWidth / 2, contentHeight / 2)
943
- ctx.rotate((Math.PI / 180) * Number(rotate))
944
-
945
- ctx.drawImage(
946
- image,
947
- (-imageWidth * pixelRatio.value) / 2,
948
- (-imageHeight * pixelRatio.value) / 2,
949
- imageWidth * pixelRatio.value,
950
- imageHeight * pixelRatio.value
951
- )
952
- ctx.restore()
953
- ctx.draw(false, () => {
954
- // #ifdef MP-DINGTALK
955
- // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
956
- ;(ctx as any).toTempFilePath({
957
- success(res: any) {
958
- showCanvas.value = false
959
- waterMarkUrl.value = res.filePath
960
- }
961
- })
962
- // #endif
963
- // #ifndef MP-DINGTALK
964
- uni.canvasToTempFilePath({
965
- canvasId: canvasId.value,
966
- success: (res) => {
967
- showCanvas.value = false
968
- waterMarkUrl.value = res.tempFilePath
969
- }
970
- })
971
- // #endif
972
- })
973
- }
974
- </script>
975
-
976
- <style lang="scss" scoped>
977
- @import './index.scss';
978
- </style>
1
+ <template>
2
+ <view :class="rootClass" :style="rootStyle">
3
+ <canvas
4
+ v-if="!canvasOffScreenable && showCanvas"
5
+ type="2d"
6
+ :style="{
7
+ height: canvasHeight + 'px',
8
+ width: canvasWidth + 'px',
9
+ visibility: 'hidden'
10
+ }"
11
+ :canvas-id="canvasId"
12
+ :id="canvasId"
13
+ />
14
+ </view>
15
+ </template>
16
+
17
+ <script lang="ts">
18
+ export default {
19
+ name: 'hy-watermark',
20
+ options: {
21
+ addGlobalClass: true,
22
+ virtualHost: true,
23
+ styleIsolation: 'shared'
24
+ }
25
+ }
26
+ </script>
27
+
28
+ <script lang="ts" setup>
29
+ import { computed, onMounted, ref, watch, nextTick } from 'vue'
30
+ import type { CSSProperties } from 'vue'
31
+ import { addUnit, guid } from '../../libs'
32
+ import watermarkProps from './props'
33
+
34
+ /**
35
+ * 在页面或组件上添加指定的图片或文字,可用于版权保护、品牌宣传等场景。
36
+ * @displayName hy-watermark
37
+ */
38
+ defineOptions({})
39
+
40
+ const props = defineProps(watermarkProps)
41
+
42
+ watch(
43
+ () => props,
44
+ () => {
45
+ doReset()
46
+ },
47
+ { deep: true }
48
+ )
49
+
50
+ const canvasId = ref<string>(`watermark--${guid()}`) // canvas 组件的唯一标识符
51
+ const waterMarkUrl = ref<string>('') // canvas生成base64水印
52
+ const canvasOffScreenable = ref<boolean>(
53
+ uni.canIUse('createOffscreenCanvas') && Boolean(uni.createOffscreenCanvas)
54
+ ) // 是否可以使用离屏canvas
55
+ const pixelRatio = ref<number>(uni.getSystemInfoSync().pixelRatio) // 像素比
56
+ const canvasHeight = ref<number>((props.height + props.gutterY) * pixelRatio.value) // canvas画布高度
57
+ const canvasWidth = ref<number>((props.width + props.gutterX) * pixelRatio.value) // canvas画布宽度
58
+ const showCanvas = ref<boolean>(true) // 是否展示canvas
59
+
60
+ /**
61
+ * @description 水印css类
62
+ */
63
+ const rootClass = computed(() => {
64
+ const classes: string[] = ['hy-watermark']
65
+ if (props.fullScreen) {
66
+ classes.push('is-fullscreen')
67
+ }
68
+ return classes
69
+ })
70
+
71
+ /**
72
+ * @description 水印样式
73
+ */
74
+ const rootStyle = computed(() => {
75
+ const style: CSSProperties = {
76
+ opacity: props.opacity,
77
+ backgroundSize: addUnit(props.width + props.gutterX)
78
+ }
79
+ if (waterMarkUrl.value) {
80
+ style['backgroundImage'] = `url('${waterMarkUrl.value}')`
81
+ }
82
+ return style
83
+ })
84
+
85
+ onMounted(() => {
86
+ doInit()
87
+ })
88
+
89
+ function doReset() {
90
+ showCanvas.value = true
91
+ canvasHeight.value = (props.height + props.gutterY) * pixelRatio.value
92
+ canvasWidth.value = (props.width + props.gutterX) * pixelRatio.value
93
+ nextTick(() => {
94
+ doInit()
95
+ })
96
+ }
97
+
98
+ function doInit() {
99
+ // #ifdef H5
100
+ // h5使用document.createElement创建canvas,不用展示canvas标签
101
+ showCanvas.value = false
102
+ // #endif
103
+ const {
104
+ width,
105
+ height,
106
+ color,
107
+ size,
108
+ fontStyle,
109
+ fontWeight,
110
+ fontFamily,
111
+ content,
112
+ rotate,
113
+ gutterX,
114
+ gutterY,
115
+ image,
116
+ imageHeight,
117
+ imageWidth,
118
+ title
119
+ } = props
120
+
121
+ // 创建水印
122
+ createWaterMark(
123
+ width,
124
+ height,
125
+ color,
126
+ size,
127
+ fontStyle,
128
+ fontWeight,
129
+ fontFamily,
130
+ content,
131
+ rotate,
132
+ gutterX,
133
+ gutterY,
134
+ image,
135
+ imageHeight,
136
+ imageWidth,
137
+ title
138
+ )
139
+ }
140
+
141
+ /**
142
+ * 创建水印图片
143
+ * @param width canvas宽度
144
+ * @param height canvas高度
145
+ * @param color canvas字体颜色
146
+ * @param size canvas字体大小
147
+ * @param fontStyle canvas字体样式
148
+ * @param fontWeight canvas字体字重
149
+ * @param fontFamily canvas字体系列
150
+ * @param content canvas内容
151
+ * @param rotate 倾斜角度
152
+ * @param gutterX X轴间距
153
+ * @param gutterY Y轴间距
154
+ * @param image canvas图片
155
+ * @param imageHeight canvas图片高度
156
+ * @param imageWidth canvas图片宽度
157
+ * @param title 标题
158
+ */
159
+ function createWaterMark(
160
+ width: number,
161
+ height: number,
162
+ color: string,
163
+ size: number,
164
+ fontStyle: string,
165
+ fontWeight: number | string,
166
+ fontFamily: string,
167
+ content: string,
168
+ rotate: number,
169
+ gutterX: number,
170
+ gutterY: number,
171
+ image: string,
172
+ imageHeight: number,
173
+ imageWidth: number,
174
+ title: string
175
+ ) {
176
+ const canvasHeight = (height + gutterY) * pixelRatio.value
177
+ const canvasWidth = (width + gutterX) * pixelRatio.value
178
+ const contentWidth = width * pixelRatio.value
179
+ const contentHeight = height * pixelRatio.value
180
+ const fontSize = size * pixelRatio.value
181
+ // 标题字体大小:如果设置了titleSize则使用titleSize,否则使用size的1.2倍
182
+ const titleFontSize = props.titleSize > 0 ? props.titleSize * pixelRatio.value : fontSize * 1.2
183
+
184
+ // #ifndef H5
185
+ if (canvasOffScreenable.value) {
186
+ createOffscreenCanvas(
187
+ canvasHeight,
188
+ canvasWidth,
189
+ contentWidth,
190
+ contentHeight,
191
+ rotate,
192
+ fontSize,
193
+ fontFamily,
194
+ fontStyle,
195
+ fontWeight,
196
+ color,
197
+ content,
198
+ image,
199
+ imageHeight,
200
+ imageWidth,
201
+ title,
202
+ titleFontSize
203
+ )
204
+ } else {
205
+ createCanvas(
206
+ canvasHeight,
207
+ contentWidth,
208
+ rotate,
209
+ fontSize,
210
+ color,
211
+ content,
212
+ image,
213
+ imageHeight,
214
+ imageWidth,
215
+ title,
216
+ titleFontSize
217
+ )
218
+ }
219
+ // #endif
220
+ // #ifdef H5
221
+ createH5Canvas(
222
+ canvasHeight,
223
+ canvasWidth,
224
+ contentWidth,
225
+ contentHeight,
226
+ rotate,
227
+ fontSize,
228
+ fontFamily,
229
+ fontStyle,
230
+ fontWeight,
231
+ color,
232
+ content,
233
+ image,
234
+ imageHeight,
235
+ imageWidth,
236
+ title,
237
+ titleFontSize
238
+ )
239
+ // #endif
240
+ }
241
+
242
+ /**
243
+ * 创建离屏canvas
244
+ * @param canvasHeight canvas高度
245
+ * @param canvasWidth canvas宽度
246
+ * @param contentWidth 内容宽度
247
+ * @param contentHeight 内容高度
248
+ * @param rotate 内容倾斜角度
249
+ * @param fontSize 字体大小
250
+ * @param fontFamily 字体系列
251
+ * @param fontStyle 字体样式
252
+ * @param fontWeight 字体字重
253
+ * @param color 字体颜色
254
+ * @param content 内容
255
+ * @param image canvas图片
256
+ * @param imageHeight canvas图片高度
257
+ * @param imageWidth canvas图片宽度
258
+ */
259
+ function createOffscreenCanvas(
260
+ canvasHeight: number,
261
+ canvasWidth: number,
262
+ contentWidth: number,
263
+ contentHeight: number,
264
+ rotate: number,
265
+ fontSize: number,
266
+ fontFamily: string,
267
+ fontStyle: string,
268
+ fontWeight: string | number,
269
+ color: string,
270
+ content: string,
271
+ image: string,
272
+ imageHeight: number,
273
+ imageWidth: number,
274
+ title: string,
275
+ titleFontSize: number
276
+ ) {
277
+ // 创建离屏canvas
278
+ const canvas: any = uni.createOffscreenCanvas({
279
+ height: canvasHeight,
280
+ width: canvasWidth,
281
+ type: '2d'
282
+ })
283
+ const ctx: any = canvas.getContext('2d')
284
+ if (ctx) {
285
+ if (image && (title || content)) {
286
+ // 图片和文字同时显示
287
+ const img = canvas.createImage() as HTMLImageElement
288
+ drawImageAndTextOffScreen(
289
+ ctx,
290
+ img,
291
+ image,
292
+ imageHeight,
293
+ imageWidth,
294
+ title,
295
+ content,
296
+ rotate,
297
+ contentWidth,
298
+ contentHeight,
299
+ fontSize,
300
+ titleFontSize,
301
+ fontFamily,
302
+ fontStyle,
303
+ fontWeight,
304
+ color,
305
+ canvas
306
+ )
307
+ } else if (image) {
308
+ const img = canvas.createImage() as HTMLImageElement
309
+ drawImageOffScreen(
310
+ ctx,
311
+ img,
312
+ image,
313
+ imageHeight,
314
+ imageWidth,
315
+ rotate,
316
+ contentWidth,
317
+ contentHeight,
318
+ canvas
319
+ )
320
+ } else {
321
+ drawTextOffScreen(
322
+ ctx,
323
+ title,
324
+ contentWidth,
325
+ contentHeight,
326
+ rotate,
327
+ fontSize,
328
+ fontFamily,
329
+ fontStyle,
330
+ fontWeight,
331
+ color,
332
+ canvas,
333
+ content,
334
+ titleFontSize
335
+ )
336
+ }
337
+ } else {
338
+ console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
339
+ }
340
+ }
341
+
342
+ /**
343
+ * 非H5创建canvas
344
+ * 不支持创建离屏canvas时调用
345
+ * @param contentHeight 内容高度
346
+ * @param contentWidth 内容宽度
347
+ * @param rotate 内容倾斜角度
348
+ * @param fontSize 字体大小
349
+ * @param color 字体颜色
350
+ * @param content 内容
351
+ * @param image canvas图片
352
+ * @param imageHeight canvas图片高度
353
+ * @param imageWidth canvas图片宽度
354
+ */
355
+ function createCanvas(
356
+ contentHeight: number,
357
+ contentWidth: number,
358
+ rotate: number,
359
+ fontSize: number,
360
+ color: string,
361
+ content: string,
362
+ image: string,
363
+ imageHeight: number,
364
+ imageWidth: number,
365
+ title: string,
366
+ titleFontSize: number
367
+ ) {
368
+ const ctx = uni.createCanvasContext(canvasId.value)
369
+ if (ctx) {
370
+ if (image && (title || content)) {
371
+ // 图片和文字同时显示
372
+ drawImageAndTextOnScreen(
373
+ ctx,
374
+ image,
375
+ imageHeight,
376
+ imageWidth,
377
+ title,
378
+ content,
379
+ rotate,
380
+ contentWidth,
381
+ contentHeight,
382
+ fontSize,
383
+ titleFontSize,
384
+ color
385
+ )
386
+ } else if (image) {
387
+ drawImageOnScreen(
388
+ ctx,
389
+ image,
390
+ imageHeight,
391
+ imageWidth,
392
+ rotate,
393
+ contentWidth,
394
+ contentHeight
395
+ )
396
+ } else {
397
+ drawTextOnScreen(
398
+ ctx,
399
+ title,
400
+ contentWidth,
401
+ rotate,
402
+ fontSize,
403
+ color,
404
+ content,
405
+ titleFontSize
406
+ )
407
+ }
408
+ } else {
409
+ console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
410
+ }
411
+ }
412
+
413
+ /**
414
+ * h5创建canvas
415
+ * @param canvasHeight canvas高度
416
+ * @param canvasWidth canvas宽度
417
+ * @param contentWidth 水印内容宽度
418
+ * @param contentHeight 水印内容高度
419
+ * @param rotate 水印内容倾斜角度
420
+ * @param fontSize 水印字体大小
421
+ * @param fontFamily 水印字体系列
422
+ * @param fontStyle 水印字体样式
423
+ * @param fontWeight 水印字体字重
424
+ * @param color 水印字体颜色
425
+ * @param content 水印内容
426
+ * @param image canvas图片
427
+ * @param imageHeight canvas图片高度
428
+ * @param imageWidth canvas图片宽度
429
+ */
430
+ function createH5Canvas(
431
+ canvasHeight: number,
432
+ canvasWidth: number,
433
+ contentWidth: number,
434
+ contentHeight: number,
435
+ rotate: number,
436
+ fontSize: number,
437
+ fontFamily: string,
438
+ fontStyle: string,
439
+ fontWeight: string | number,
440
+ color: string,
441
+ content: string,
442
+ image: string,
443
+ imageHeight: number,
444
+ imageWidth: number,
445
+ title: string,
446
+ titleFontSize: number
447
+ ) {
448
+ const canvas = document.createElement('canvas')
449
+ const ctx = canvas.getContext('2d')
450
+ canvas.setAttribute('width', `${canvasWidth}px`)
451
+ canvas.setAttribute('height', `${canvasHeight}px`)
452
+ if (ctx) {
453
+ if (image && (title || content)) {
454
+ // 图片和文字同时显示
455
+ const img = new Image()
456
+ drawImageAndTextOffScreen(
457
+ ctx,
458
+ img,
459
+ image,
460
+ imageHeight,
461
+ imageWidth,
462
+ title,
463
+ content,
464
+ rotate,
465
+ contentWidth,
466
+ contentHeight,
467
+ fontSize,
468
+ titleFontSize,
469
+ fontFamily,
470
+ fontStyle,
471
+ fontWeight,
472
+ color,
473
+ canvas
474
+ )
475
+ } else if (image) {
476
+ const img = new Image()
477
+ drawImageOffScreen(
478
+ ctx,
479
+ img,
480
+ image,
481
+ imageHeight,
482
+ imageWidth,
483
+ rotate,
484
+ contentWidth,
485
+ contentHeight,
486
+ canvas
487
+ )
488
+ } else {
489
+ drawTextOffScreen(
490
+ ctx,
491
+ title,
492
+ contentWidth,
493
+ contentHeight,
494
+ rotate,
495
+ fontSize,
496
+ fontFamily,
497
+ fontStyle,
498
+ fontWeight,
499
+ color,
500
+ canvas,
501
+ content,
502
+ titleFontSize
503
+ )
504
+ }
505
+ } else {
506
+ console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
507
+ }
508
+ }
509
+
510
+ /**
511
+ * 绘制离屏文字canvas
512
+ * @param ctx canvas上下文
513
+ * @param content 水印内容
514
+ * @param contentWidth 水印宽度
515
+ * @param contentHeight 水印高度
516
+ * @param rotate 水印内容倾斜角度
517
+ * @param fontSize 水印字体大小
518
+ * @param fontFamily 水印字体系列
519
+ * @param fontStyle 水印字体样式
520
+ * @param fontWeight 水印字体字重
521
+ * @param color 水印字体颜色
522
+ * @param canvas canvas实例
523
+ */
524
+ // 测量文本宽度并自动换行
525
+ function wrapText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number, fontSize: number) {
526
+ const words = text.split('')
527
+ const lines: string[] = []
528
+ let currentLine = ''
529
+
530
+ for (let i = 0; i < words.length; i++) {
531
+ const testLine = currentLine + words[i]
532
+ const metrics = ctx.measureText(testLine)
533
+ const testWidth = metrics.width
534
+
535
+ // 当文字宽度超过容器宽度的80%时换行
536
+ if (testWidth > maxWidth * 0.8 && currentLine !== '') {
537
+ lines.push(currentLine)
538
+ currentLine = words[i]
539
+ } else {
540
+ currentLine = testLine
541
+ }
542
+ }
543
+ lines.push(currentLine)
544
+ return lines
545
+ }
546
+
547
+ function drawTextOffScreen(
548
+ ctx: CanvasRenderingContext2D,
549
+ title: string,
550
+ contentWidth: number,
551
+ contentHeight: number,
552
+ rotate: number,
553
+ fontSize: number,
554
+ fontFamily: string,
555
+ fontStyle: string,
556
+ fontWeight: string | number,
557
+ color: string,
558
+ canvas: HTMLCanvasElement,
559
+ content: string = '',
560
+ titleFontSize: number = 0
561
+ ) {
562
+ ctx.textBaseline = 'middle'
563
+ ctx.textAlign = 'center'
564
+ ctx.translate(contentWidth / 2, contentHeight / 2)
565
+ ctx.rotate((Math.PI / 180) * rotate)
566
+
567
+ // 计算总高度
568
+ let totalTextHeight = titleFontSize
569
+ if (content) {
570
+ totalTextHeight += fontSize + 5 // 标题和副标题之间的间距
571
+ }
572
+
573
+ // 起始Y坐标
574
+ let startY = -totalTextHeight / 2
575
+
576
+ // 绘制主标题(支持自动换行)
577
+ if (title) {
578
+ ctx.font = `${fontStyle} normal ${fontWeight} ${titleFontSize}px/${contentHeight}px ${fontFamily}`
579
+ // 使用titleColor或默认color
580
+ ctx.fillStyle = props.titleColor || color
581
+ const titleLines = wrapText(ctx, title, contentWidth, titleFontSize)
582
+ const titleLineHeight = titleFontSize * 1.2
583
+
584
+ for (let i = 0; i < titleLines.length; i++) {
585
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight)
586
+ }
587
+
588
+ startY += titleLines.length * titleLineHeight + 5
589
+ }
590
+
591
+ // 绘制副标题(支持自动换行)
592
+ if (content) {
593
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`
594
+ ctx.fillStyle = color
595
+ const contentLines = wrapText(ctx, content, contentWidth, fontSize)
596
+ const contentLineHeight = fontSize * 1.2
597
+
598
+ for (let i = 0; i < contentLines.length; i++) {
599
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight)
600
+ }
601
+ }
602
+
603
+ ctx.restore()
604
+ waterMarkUrl.value = canvas.toDataURL()
605
+ }
606
+
607
+ /**
608
+ * 绘制在屏文字canvas
609
+ * @param ctx canvas上下文
610
+ * @param content 水印内容
611
+ * @param contentWidth 水印宽度
612
+ * @param rotate 水印内容倾斜角度
613
+ * @param fontSize 水印字体大小
614
+ * @param color 水印字体颜色
615
+ */
616
+ // 简化版本的文字换行(UniApp CanvasContext不支持measureText)
617
+ function simpleWrapText(text: string, maxLength: number) {
618
+ const lines: string[] = []
619
+ let currentLine = ''
620
+
621
+ // 基于字符数估算换行(适用于UniApp CanvasContext)
622
+ for (let i = 0; i < text.length; i++) {
623
+ currentLine += text[i]
624
+ if (currentLine.length >= maxLength) {
625
+ lines.push(currentLine)
626
+ currentLine = ''
627
+ }
628
+ }
629
+ if (currentLine) {
630
+ lines.push(currentLine)
631
+ }
632
+ return lines
633
+ }
634
+
635
+ function drawTextOnScreen(
636
+ ctx: UniApp.CanvasContext,
637
+ title: string,
638
+ contentWidth: number,
639
+ rotate: number,
640
+ fontSize: number,
641
+ color: string,
642
+ content: string = '',
643
+ titleFontSize: number = 0
644
+ ) {
645
+ ctx.setTextBaseline('middle')
646
+ ctx.setTextAlign('center')
647
+ ctx.translate(contentWidth / 2, contentWidth / 2)
648
+ ctx.rotate((Math.PI / 180) * rotate)
649
+
650
+ // 估算每行最大字符数
651
+ const maxChars = Math.floor(contentWidth / (fontSize * 0.5))
652
+
653
+ // 计算总高度
654
+ let totalTextHeight = titleFontSize
655
+ if (content) {
656
+ totalTextHeight += fontSize + 5
657
+ }
658
+
659
+ // 起始Y坐标
660
+ let startY = -totalTextHeight / 2
661
+
662
+ // 绘制主标题(支持自动换行)
663
+ if (title) {
664
+ // 使用titleColor或默认color
665
+ ctx.setFillStyle(props.titleColor || color)
666
+ ctx.setFontSize(titleFontSize)
667
+ const titleLines = simpleWrapText(title, maxChars)
668
+ const titleLineHeight = titleFontSize * 1.2
669
+
670
+ for (let i = 0; i < titleLines.length; i++) {
671
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight)
672
+ }
673
+
674
+ startY += titleLines.length * titleLineHeight + 5
675
+ }
676
+
677
+ // 绘制副标题(支持自动换行)
678
+ if (content) {
679
+ ctx.setFillStyle(color)
680
+ ctx.setFontSize(fontSize)
681
+ const contentLines = simpleWrapText(content, maxChars)
682
+ const contentLineHeight = fontSize * 1.2
683
+
684
+ for (let i = 0; i < contentLines.length; i++) {
685
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight)
686
+ }
687
+ }
688
+
689
+ ctx.restore()
690
+ ctx.draw()
691
+ // #ifdef MP-DINGTALK
692
+ // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
693
+ ;(ctx as any).toTempFilePath({
694
+ success(res: any) {
695
+ showCanvas.value = false
696
+ waterMarkUrl.value = res.filePath
697
+ }
698
+ })
699
+ // #endif
700
+ // #ifndef MP-DINGTALK
701
+ uni.canvasToTempFilePath({
702
+ canvasId: canvasId.value,
703
+ success: (res) => {
704
+ showCanvas.value = false
705
+ waterMarkUrl.value = res.tempFilePath
706
+ }
707
+ })
708
+ // #endif
709
+ }
710
+
711
+ /**
712
+ * 绘制离屏图片canvas
713
+ * @param ctx canvas上下文
714
+ * @param img 水印图片对象
715
+ * @param image 水印图片地址
716
+ * @param imageHeight 水印图片高度
717
+ * @param imageWidth 水印图片宽度
718
+ * @param rotate 水印内容倾斜角度
719
+ * @param contentWidth 水印宽度
720
+ * @param contentHeight 水印高度
721
+ * @param canvas canvas实例
722
+ */
723
+ async function drawImageOffScreen(
724
+ ctx: CanvasRenderingContext2D,
725
+ img: HTMLImageElement,
726
+ image: string,
727
+ imageHeight: number,
728
+ imageWidth: number,
729
+ rotate: number,
730
+ contentWidth: number,
731
+ contentHeight: number,
732
+ canvas: HTMLCanvasElement
733
+ ) {
734
+ ctx.translate(contentWidth / 2, contentHeight / 2)
735
+ ctx.rotate((Math.PI / 180) * Number(rotate))
736
+ img.crossOrigin = 'anonymous'
737
+ img.referrerPolicy = 'no-referrer'
738
+
739
+ img.src = image
740
+ img.onload = () => {
741
+ ctx.drawImage(
742
+ img,
743
+ (-imageWidth * pixelRatio.value) / 2,
744
+ (-imageHeight * pixelRatio.value) / 2,
745
+ imageWidth * pixelRatio.value,
746
+ imageHeight * pixelRatio.value
747
+ )
748
+ ctx.restore()
749
+ waterMarkUrl.value = canvas.toDataURL()
750
+ }
751
+ }
752
+
753
+ // 绘制图片和文字(离屏)
754
+ async function drawImageAndTextOffScreen(
755
+ ctx: CanvasRenderingContext2D,
756
+ img: HTMLImageElement,
757
+ image: string,
758
+ imageHeight: number,
759
+ imageWidth: number,
760
+ title: string,
761
+ content: string,
762
+ rotate: number,
763
+ contentWidth: number,
764
+ contentHeight: number,
765
+ fontSize: number,
766
+ titleFontSize: number,
767
+ fontFamily: string,
768
+ fontStyle: string,
769
+ fontWeight: string | number,
770
+ color: string,
771
+ canvas: HTMLCanvasElement
772
+ ) {
773
+ ctx.translate(contentWidth / 2, contentHeight / 2)
774
+ ctx.rotate((Math.PI / 180) * Number(rotate))
775
+ img.crossOrigin = 'anonymous'
776
+ img.referrerPolicy = 'no-referrer'
777
+
778
+ const imgHeight = imageHeight * pixelRatio.value
779
+ const imgWidth = imageWidth * pixelRatio.value
780
+
781
+ img.src = image
782
+ img.onload = () => {
783
+ // 计算总高度
784
+ let totalHeight = imgHeight
785
+ const textSpacing = 10
786
+
787
+ if (title) totalHeight += textSpacing + titleFontSize
788
+ if (content) totalHeight += fontSize
789
+
790
+ // 起始Y坐标
791
+ let startY = -totalHeight / 2
792
+
793
+ // 绘制图片
794
+ ctx.drawImage(img, -imgWidth / 2, startY, imgWidth, imgHeight)
795
+
796
+ startY += imgHeight + textSpacing
797
+
798
+ // 设置文字样式
799
+ ctx.textBaseline = 'top'
800
+ ctx.textAlign = 'center'
801
+
802
+ // 绘制主标题
803
+ if (title) {
804
+ ctx.font = `${fontStyle} normal ${fontWeight} ${titleFontSize}px/${contentHeight}px ${fontFamily}`
805
+ // 使用titleColor或默认color
806
+ ctx.fillStyle = props.titleColor || color
807
+ const titleLines = wrapText(ctx, title, contentWidth * 0.9, titleFontSize)
808
+ const titleLineHeight = titleFontSize * 1.2
809
+
810
+ for (let i = 0; i < titleLines.length; i++) {
811
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight)
812
+ }
813
+
814
+ startY += titleLines.length * titleLineHeight + 5
815
+ }
816
+
817
+ // 绘制副标题
818
+ if (content) {
819
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`
820
+ ctx.fillStyle = color
821
+ const contentLines = wrapText(ctx, content, contentWidth * 0.9, fontSize)
822
+ const contentLineHeight = fontSize * 1.2
823
+
824
+ for (let i = 0; i < contentLines.length; i++) {
825
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight)
826
+ }
827
+ }
828
+
829
+ ctx.restore()
830
+ waterMarkUrl.value = canvas.toDataURL()
831
+ }
832
+ }
833
+
834
+ // 绘制图片和文字(在屏)
835
+ function drawImageAndTextOnScreen(
836
+ ctx: UniApp.CanvasContext,
837
+ image: string,
838
+ imageHeight: number,
839
+ imageWidth: number,
840
+ title: string,
841
+ content: string,
842
+ rotate: number,
843
+ contentWidth: number,
844
+ contentHeight: number,
845
+ fontSize: number,
846
+ titleFontSize: number,
847
+ color: string
848
+ ) {
849
+ ctx.setTextBaseline('top')
850
+ ctx.setTextAlign('center')
851
+ ctx.translate(contentWidth / 2, contentWidth / 2)
852
+ ctx.rotate((Math.PI / 180) * Number(rotate))
853
+
854
+ const imgHeight = imageHeight * pixelRatio.value
855
+ const imgWidth = imageWidth * pixelRatio.value
856
+ const maxChars = Math.floor(contentWidth / (fontSize * 0.5))
857
+
858
+ // 计算总高度
859
+ let totalHeight = imgHeight
860
+ const textSpacing = 10
861
+
862
+ if (title) totalHeight += textSpacing + titleFontSize
863
+ if (content) totalHeight += fontSize
864
+
865
+ // 起始Y坐标
866
+ let startY = -totalHeight / 2
867
+
868
+ // 绘制图片
869
+ ctx.drawImage(image, -imgWidth / 2, startY, imgWidth, imgHeight)
870
+
871
+ startY += imgHeight + textSpacing
872
+
873
+ // 绘制主标题
874
+ if (title) {
875
+ // 使用titleColor或默认color
876
+ ctx.setFillStyle(props.titleColor || color)
877
+ ctx.setFontSize(titleFontSize)
878
+ const titleLines = simpleWrapText(title, maxChars)
879
+ const titleLineHeight = titleFontSize * 1.2
880
+
881
+ for (let i = 0; i < titleLines.length; i++) {
882
+ ctx.fillText(titleLines[i], 0, startY + i * titleLineHeight)
883
+ }
884
+
885
+ startY += titleLines.length * titleLineHeight + 5
886
+ }
887
+
888
+ // 绘制副标题
889
+ if (content) {
890
+ ctx.setFillStyle(color)
891
+ ctx.setFontSize(fontSize)
892
+ const contentLines = simpleWrapText(content, maxChars)
893
+ const contentLineHeight = fontSize * 1.2
894
+
895
+ for (let i = 0; i < contentLines.length; i++) {
896
+ ctx.fillText(contentLines[i], 0, startY + i * contentLineHeight)
897
+ }
898
+ }
899
+
900
+ ctx.restore()
901
+ ctx.draw(false, () => {
902
+ // #ifdef MP-DINGTALK
903
+ // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
904
+ ;(ctx as any).toTempFilePath({
905
+ success(res: any) {
906
+ showCanvas.value = false
907
+ waterMarkUrl.value = res.filePath
908
+ }
909
+ })
910
+ // #endif
911
+ // #ifndef MP-DINGTALK
912
+ uni.canvasToTempFilePath({
913
+ canvasId: canvasId.value,
914
+ success: (res) => {
915
+ showCanvas.value = false
916
+ waterMarkUrl.value = res.tempFilePath
917
+ }
918
+ })
919
+ // #endif
920
+ })
921
+ }
922
+
923
+ /**
924
+ * 绘制在屏图片canvas
925
+ * @param ctx canvas上下文
926
+ * @param image 水印图片地址
927
+ * @param imageHeight 水印图片高度
928
+ * @param imageWidth 水印图片宽度
929
+ * @param rotate 水印内容倾斜角度
930
+ * @param contentWidth 水印宽度
931
+ * @param contentHeight 水印高度
932
+ */
933
+ function drawImageOnScreen(
934
+ ctx: UniApp.CanvasContext,
935
+ image: string,
936
+ imageHeight: number,
937
+ imageWidth: number,
938
+ rotate: number,
939
+ contentWidth: number,
940
+ contentHeight: number
941
+ ) {
942
+ ctx.translate(contentWidth / 2, contentHeight / 2)
943
+ ctx.rotate((Math.PI / 180) * Number(rotate))
944
+
945
+ ctx.drawImage(
946
+ image,
947
+ (-imageWidth * pixelRatio.value) / 2,
948
+ (-imageHeight * pixelRatio.value) / 2,
949
+ imageWidth * pixelRatio.value,
950
+ imageHeight * pixelRatio.value
951
+ )
952
+ ctx.restore()
953
+ ctx.draw(false, () => {
954
+ // #ifdef MP-DINGTALK
955
+ // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
956
+ ;(ctx as any).toTempFilePath({
957
+ success(res: any) {
958
+ showCanvas.value = false
959
+ waterMarkUrl.value = res.filePath
960
+ }
961
+ })
962
+ // #endif
963
+ // #ifndef MP-DINGTALK
964
+ uni.canvasToTempFilePath({
965
+ canvasId: canvasId.value,
966
+ success: (res) => {
967
+ showCanvas.value = false
968
+ waterMarkUrl.value = res.tempFilePath
969
+ }
970
+ })
971
+ // #endif
972
+ })
973
+ }
974
+ </script>
975
+
976
+ <style lang="scss" scoped>
977
+ @import './index.scss';
978
+ </style>