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.
- package/components/hy-action-sheet/hy-action-sheet.vue +200 -200
- package/components/hy-action-sheet/props.ts +71 -71
- package/components/hy-action-sheet/typing.d.ts +64 -64
- package/components/hy-address-picker/hy-address-picker.vue +1 -1
- package/components/hy-address-picker/props.ts +100 -100
- package/components/hy-address-picker/typing.d.ts +16 -16
- package/components/hy-avatar/hy-avatar.vue +163 -163
- package/components/hy-avatar/props.ts +78 -78
- package/components/hy-avatar/typing.d.ts +4 -4
- package/components/hy-back-top/hy-back-top.vue +90 -90
- package/components/hy-back-top/props.ts +60 -60
- package/components/hy-back-top/typing.d.ts +4 -4
- package/components/hy-badge/hy-badge.vue +97 -97
- package/components/hy-badge/props.ts +82 -82
- package/components/hy-badge/typing.d.ts +9 -9
- package/components/hy-button/hy-button.vue +275 -275
- package/components/hy-button/props.ts +135 -135
- package/components/hy-button/typing.d.ts +30 -30
- package/components/hy-calendar/header.vue +60 -60
- package/components/hy-calendar/hy-calendar.vue +362 -362
- package/components/hy-calendar/month.vue +537 -537
- package/components/hy-calendar/props.ts +159 -159
- package/components/hy-calendar/typing.d.ts +6 -6
- package/components/hy-card/hy-card.vue +161 -161
- package/components/hy-card/props.ts +122 -122
- package/components/hy-card/typing.d.ts +12 -12
- package/components/hy-cell/hy-cell.vue +33 -33
- package/components/hy-cell/props.ts +54 -54
- package/components/hy-cell/typing.d.ts +4 -4
- package/components/hy-cell-item/hy-cell-item.vue +161 -161
- package/components/hy-cell-item/props.ts +66 -66
- package/components/hy-cell-item/typing.d.ts +7 -7
- package/components/hy-check-button/hy-check-button.vue +96 -96
- package/components/hy-check-button/props.ts +74 -74
- package/components/hy-check-button/typing.d.ts +44 -44
- package/components/hy-checkbox/hy-checkbox.vue +227 -227
- package/components/hy-checkbox/props.ts +96 -96
- package/components/hy-checkbox/typing.d.ts +8 -8
- package/components/hy-checkbox-group/hy-checkbox-group.vue +45 -45
- package/components/hy-checkbox-group/props.ts +80 -80
- package/components/hy-checkbox-group/typing.d.ts +6 -6
- package/components/hy-checkbox-item/hy-checkbox-item.vue +199 -199
- package/components/hy-checkbox-item/props.ts +24 -24
- package/components/hy-checkbox-item/typing.d.ts +7 -7
- package/components/hy-code-input/hy-code-input.vue +231 -231
- package/components/hy-code-input/props.ts +88 -88
- package/components/hy-code-input/typing.d.ts +8 -8
- package/components/hy-config-provider/hy-config-provider.vue +53 -53
- package/components/hy-config-provider/props.ts +28 -28
- package/components/hy-count-down/hy-count-down.vue +170 -170
- package/components/hy-count-down/index.ts +52 -52
- package/components/hy-count-down/props.ts +32 -32
- package/components/hy-count-down/typing.d.ts +14 -14
- package/components/hy-count-to/hy-count-to.vue +218 -218
- package/components/hy-count-to/props.ts +62 -62
- package/components/hy-count-to/typing.d.ts +4 -4
- package/components/hy-coupon/hy-coupon.vue +172 -172
- package/components/hy-coupon/index.scss +171 -171
- package/components/hy-coupon/props.ts +103 -103
- package/components/hy-coupon/typing.d.ts +14 -14
- package/components/hy-datetime-picker/hy-datetime-picker.vue +521 -521
- package/components/hy-datetime-picker/props.ts +142 -142
- package/components/hy-datetime-picker/typing.d.ts +20 -20
- package/components/hy-divider/hy-divider.vue +132 -132
- package/components/hy-divider/props.ts +80 -80
- package/components/hy-dropdown/hy-dropdown.vue +60 -60
- package/components/hy-dropdown/props.ts +40 -40
- package/components/hy-dropdown-item/hy-dropdown-item.vue +206 -206
- package/components/hy-dropdown-item/props.ts +21 -21
- package/components/hy-dropdown-item/typing.d.ts +17 -17
- package/components/hy-empty/hy-empty.vue +116 -116
- package/components/hy-empty/icon.ts +72 -72
- package/components/hy-empty/props.ts +60 -60
- package/components/hy-empty/typing.d.ts +38 -38
- package/components/hy-flex/hy-flex.vue +53 -53
- package/components/hy-flex/index.scss +8 -8
- package/components/hy-flex/props.ts +58 -58
- package/components/hy-flex/typing.d.ts +21 -21
- package/components/hy-float-button/hy-float-button.vue +378 -378
- package/components/hy-float-button/props.ts +111 -111
- package/components/hy-float-button/typing.d.ts +35 -35
- package/components/hy-folding-panel/hy-folding-panel.vue +109 -109
- package/components/hy-folding-panel/props.ts +42 -42
- package/components/hy-folding-panel/typing.d.ts +19 -19
- package/components/hy-folding-panel-item/hy-folding-panel-item.vue +183 -183
- package/components/hy-folding-panel-item/props.ts +81 -81
- package/components/hy-folding-panel-item/typing.d.ts +37 -37
- package/components/hy-form/hy-form.vue +220 -220
- package/components/hy-form/props.ts +37 -37
- package/components/hy-form/typing.d.ts +41 -41
- package/components/hy-form-group/hy-form-group.vue +333 -333
- package/components/hy-form-group/props.ts +105 -105
- package/components/hy-form-item/hy-form-item.vue +176 -176
- package/components/hy-form-item/index.scss +0 -1
- package/components/hy-form-item/props.ts +25 -25
- package/components/hy-form-item/typing.d.ts +30 -30
- package/components/hy-grid/hy-grid.vue +109 -109
- package/components/hy-grid/props.ts +60 -60
- package/components/hy-grid/typing.d.ts +35 -35
- package/components/hy-icon/hy-icon.vue +112 -112
- package/components/hy-icon/index.scss +0 -3
- package/components/hy-icon/props.ts +79 -79
- package/components/hy-icon/typing.d.ts +9 -9
- package/components/hy-image/hy-image.vue +192 -192
- package/components/hy-image/props.ts +107 -107
- package/components/hy-image/typing.d.ts +10 -10
- package/components/hy-input/hy-input.vue +333 -333
- package/components/hy-input/index.scss +5 -0
- package/components/hy-input/props.ts +186 -186
- package/components/hy-input/typing.d.ts +31 -31
- package/components/hy-line/hy-line.vue +55 -55
- package/components/hy-line/props.ts +43 -43
- package/components/hy-line-progress/hy-line-progress.vue +102 -102
- package/components/hy-line-progress/index.scss +1 -0
- package/components/hy-line-progress/props.ts +33 -33
- package/components/hy-list/hy-list.vue +226 -226
- package/components/hy-list/props.ts +69 -69
- package/components/hy-list/typing.d.ts +6 -6
- package/components/hy-loading/hy-loading.vue +107 -107
- package/components/hy-loading/props.ts +65 -65
- package/components/hy-menu/hy-menu.vue +159 -159
- package/components/hy-menu/props.ts +44 -44
- package/components/hy-menu/typing.d.ts +34 -34
- package/components/hy-modal/hy-modal.vue +173 -173
- package/components/hy-modal/props.ts +90 -90
- package/components/hy-modal/typing.d.ts +11 -11
- package/components/hy-navbar/hy-navbar.vue +144 -144
- package/components/hy-navbar/props.ts +78 -78
- package/components/hy-navbar/typing.d.ts +6 -6
- package/components/hy-notice-bar/hy-column-notice.vue +94 -94
- package/components/hy-notice-bar/hy-notice-bar.vue +96 -96
- package/components/hy-notice-bar/hy-row-notice.vue +121 -121
- package/components/hy-notice-bar/props.ts +85 -85
- package/components/hy-notice-bar/typing.d.ts +8 -8
- package/components/hy-notify/hy-notify.vue +174 -174
- package/components/hy-notify/props.ts +51 -51
- package/components/hy-number-step/hy-number-step.vue +367 -367
- package/components/hy-number-step/props.ts +112 -112
- package/components/hy-number-step/typing.d.ts +16 -16
- package/components/hy-overlay/hy-overlay.vue +60 -60
- package/components/hy-overlay/props.ts +33 -33
- package/components/hy-overlay/typing.d.ts +4 -4
- package/components/hy-pagination/hy-pagination.vue +135 -135
- package/components/hy-pagination/props.ts +55 -55
- package/components/hy-pagination/typing.d.ts +10 -10
- package/components/hy-picker/hy-picker.vue +7 -5
- package/components/hy-picker/props.ts +7 -2
- package/components/hy-picker/typing.d.ts +9 -5
- package/components/hy-popover/hy-popover.vue +251 -251
- package/components/hy-popover/props.ts +51 -51
- package/components/hy-popover/typing.d.ts +39 -39
- package/components/hy-popup/hy-popup.vue +197 -197
- package/components/hy-popup/props.ts +85 -85
- package/components/hy-popup/typing.d.ts +10 -10
- package/components/hy-price/hy-price.vue +79 -79
- package/components/hy-price/props.ts +54 -54
- package/components/hy-price/typing.d.ts +4 -4
- package/components/hy-qrcode/hy-qrcode.vue +216 -216
- package/components/hy-qrcode/props.ts +70 -70
- package/components/hy-qrcode/qrcode.js +1304 -1304
- package/components/hy-qrcode/typing.d.ts +8 -8
- package/components/hy-radio/hy-radio.vue +226 -226
- package/components/hy-radio/props.ts +1 -1
- package/components/hy-radio/typing.d.ts +8 -8
- package/components/hy-rate/hy-rate.vue +239 -239
- package/components/hy-rate/props.ts +77 -77
- package/components/hy-rate/typing.d.ts +6 -6
- package/components/hy-read-more/hy-read-more.vue +130 -130
- package/components/hy-read-more/props.ts +45 -45
- package/components/hy-read-more/typing.d.ts +6 -6
- package/components/hy-rolling-num/hy-rolling-num.vue +188 -188
- package/components/hy-rolling-num/props.ts +68 -68
- package/components/hy-scroll-list/hy-scroll-list.vue +123 -123
- package/components/hy-scroll-list/props.ts +22 -22
- package/components/hy-scroll-list/typing.d.ts +6 -6
- package/components/hy-search/hy-search.vue +221 -221
- package/components/hy-search/props.ts +131 -131
- package/components/hy-search/typing.d.ts +22 -22
- package/components/hy-signature/hy-signature.vue +640 -640
- package/components/hy-signature/props.ts +118 -118
- package/components/hy-signature/typing.d.ts +93 -93
- package/components/hy-slider/hy-slider.vue +444 -444
- package/components/hy-slider/props.ts +77 -77
- package/components/hy-slider/typing.d.ts +10 -10
- package/components/hy-status-bar/hy-status-bar.vue +41 -41
- package/components/hy-status-bar/props.ts +8 -8
- package/components/hy-status-bar/typing.d.ts +12 -12
- package/components/hy-steps/hy-steps.vue +267 -267
- package/components/hy-steps/props.ts +49 -49
- package/components/hy-steps/typing.d.ts +21 -21
- package/components/hy-sticky/hy-sticky.vue +226 -226
- package/components/hy-sticky/props.ts +24 -24
- package/components/hy-sticky/typing.d.ts +4 -4
- package/components/hy-submit-bar/hy-submit-bar.vue +189 -189
- package/components/hy-submit-bar/props.ts +91 -91
- package/components/hy-submit-bar/typing.d.ts +24 -24
- package/components/hy-subsection/hy-subsection.vue +207 -207
- package/components/hy-subsection/props.ts +52 -52
- package/components/hy-subsection/typing.d.ts +13 -13
- package/components/hy-swipe-action/hy-swipe-action.vue +323 -323
- package/components/hy-swipe-action/index.ts +25 -25
- package/components/hy-swipe-action/props.ts +47 -47
- package/components/hy-swipe-action/typing.d.ts +25 -25
- package/components/hy-swiper/hy-swiper-indicator.vue +75 -75
- package/components/hy-swiper/hy-swiper.vue +224 -224
- package/components/hy-swiper/props.ts +128 -128
- package/components/hy-swiper/typing.d.ts +26 -26
- package/components/hy-switch/hy-switch.vue +173 -173
- package/components/hy-switch/props.ts +61 -61
- package/components/hy-switch/typing.d.ts +8 -8
- package/components/hy-tabbar/hy-tabbar.vue +136 -136
- package/components/hy-tabbar/props.ts +59 -59
- package/components/hy-tabbar/typing.d.ts +21 -21
- package/components/hy-tabbar-group/hy-tabbar-group.vue +87 -87
- package/components/hy-tabbar-group/props.ts +78 -78
- package/components/hy-tabbar-group/typing.d.ts +16 -16
- package/components/hy-tabbar-item/hy-tabbar-item.vue +103 -103
- package/components/hy-tabbar-item/typing.d.ts +10 -10
- package/components/hy-table/hy-table.vue +358 -358
- package/components/hy-table/props.ts +47 -47
- package/components/hy-table/typing.d.ts +34 -34
- package/components/hy-tabs/hy-tabs.vue +335 -335
- package/components/hy-tabs/props.ts +77 -77
- package/components/hy-tabs/typing.d.ts +33 -33
- package/components/hy-tag/hy-tag.vue +174 -174
- package/components/hy-tag/props.ts +89 -89
- package/components/hy-tag/typing.d.ts +13 -13
- package/components/hy-text/hy-text.vue +237 -237
- package/components/hy-text/props.ts +115 -115
- package/components/hy-text/typing.d.ts +6 -6
- package/components/hy-textarea/hy-textarea.vue +197 -197
- package/components/hy-textarea/index.scss +5 -0
- package/components/hy-textarea/props.ts +116 -116
- package/components/hy-textarea/typing.d.ts +22 -22
- package/components/hy-toast/hy-toast.vue +190 -190
- package/components/hy-toast/typing.d.ts +38 -38
- package/components/hy-tooltip/hy-tooltip.vue +277 -277
- package/components/hy-tooltip/props.ts +78 -78
- package/components/hy-tooltip/typing.d.ts +4 -4
- package/components/hy-transition/hy-transition.vue +157 -157
- package/components/hy-transition/props.ts +32 -32
- package/components/hy-transition/typing.d.ts +16 -16
- package/components/hy-upload/hy-upload.vue +385 -385
- package/components/hy-upload/props.ts +132 -132
- package/components/hy-upload/typing.d.ts +65 -65
- package/components/hy-warn/hy-warn.vue +115 -115
- package/components/hy-warn/props.ts +49 -49
- package/components/hy-warn/typing.d.ts +6 -6
- package/components/hy-waterfall/hy-waterfall.vue +191 -191
- package/components/hy-waterfall/props.ts +21 -21
- package/components/hy-watermark/hy-watermark.vue +978 -978
- package/components/hy-watermark/props.ts +104 -104
- package/components/index.ts +183 -183
- package/global.d.ts +91 -91
- package/index.ts +1 -1
- package/libs/api/http.ts +140 -140
- package/libs/api/index.ts +1 -1
- package/libs/common/index.ts +2 -2
- package/libs/common/queue.ts +28 -28
- package/libs/composables/index.ts +6 -6
- package/libs/composables/usePopover.ts +241 -241
- package/libs/composables/useQueue.ts +53 -53
- package/libs/composables/useShakeService.ts +64 -64
- package/libs/composables/useShare.ts +42 -42
- package/libs/composables/useToast.ts +45 -45
- package/libs/composables/useTouch.ts +51 -51
- package/libs/config/color.ts +7 -7
- package/libs/config/icon.ts +430 -430
- package/libs/config/index.ts +2 -2
- package/libs/css/iconfont.css +443 -443
- package/libs/css/theme.scss +1 -1
- package/libs/global/index.ts +6 -6
- package/libs/global/register-properties.ts +37 -37
- package/libs/index.ts +7 -7
- package/libs/typing/index.ts +4 -4
- package/libs/typing/modules/common.d.ts +139 -139
- package/libs/typing/modules/enum.ts +67 -67
- package/libs/typing/modules/form.ts +5 -1
- package/libs/typing/modules/http.ts +17 -17
- package/libs/typing/modules/icon.d.ts +366 -366
- package/libs/typing/modules/rect.ts +10 -10
- package/libs/utils/base64.ts +119 -119
- package/libs/utils/calendar.js +1021 -1021
- package/libs/utils/colorGradient.ts +112 -112
- package/libs/utils/index.ts +5 -5
- package/libs/utils/inside.ts +350 -361
- package/libs/utils/inspect.ts +171 -171
- package/libs/utils/utils.ts +521 -521
- package/package.json +18 -18
- 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>
|