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,640 +1,640 @@
1
- <template>
2
- <view :class="['hy-signature', customClass]" :style="customStyle">
3
- <view class="hy-signature__content" :style="{ borderRadius: addUnit(round) }">
4
- <!-- #ifdef MP-WEIXIN -->
5
- <canvas
6
- class="hy-signature__content-canvas"
7
- :style="canvasStyle"
8
- :width="canvasState.canvasWidth"
9
- :height="canvasState.canvasHeight"
10
- :canvas-id="canvasId"
11
- :id="canvasId"
12
- :disable-scroll="disableScroll"
13
- @touchstart="startDrawing"
14
- @touchend="stopDrawing"
15
- @touchmove="draw"
16
- type="2d"
17
- />
18
- <!-- #endif -->
19
- <!-- #ifndef MP-WEIXIN -->
20
- <canvas
21
- class="hy-signature__content-canvas"
22
- :canvas-id="canvasId"
23
- :style="canvasStyle"
24
- :width="canvasState.canvasWidth"
25
- :height="canvasState.canvasHeight"
26
- :id="canvasId"
27
- :disable-scroll="disableScroll"
28
- @touchstart="startDrawing"
29
- @touchend="stopDrawing"
30
- @touchmove="draw"
31
- />
32
- <!-- #endif -->
33
- </view>
34
- <view class="hy-signature__footer">
35
- <slot
36
- v-if="$slots.footer"
37
- name="footer"
38
- :clear="clear"
39
- :confirm="confirmSignature"
40
- :current-step="currentStep"
41
- :revoke="revoke"
42
- :restore="restore"
43
- :can-undo="lines.length > 0"
44
- :can-redo="redoLines.length > 0"
45
- :history-list="lines"
46
- >
47
- </slot>
48
- <template v-else>
49
- <hy-flex gap="12">
50
- <block v-if="enableHistory">
51
- <hy-button
52
- size="small"
53
- plain
54
- shape="circle"
55
- @click="revoke"
56
- :disabled="lines.length <= 0"
57
- :text="revokeText"
58
- ></hy-button>
59
- <hy-button
60
- size="small"
61
- plain
62
- shape="circle"
63
- @click="restore"
64
- :disabled="redoLines.length <= 0"
65
- :text="restoreText"
66
- ></hy-button>
67
- </block>
68
- <hy-button
69
- size="small"
70
- plain
71
- shape="circle"
72
- @click="clear"
73
- :text="clearText"
74
- ></hy-button>
75
- <hy-button
76
- size="small"
77
- shape="circle"
78
- @click="confirmSignature"
79
- :text="confirmText"
80
- ></hy-button>
81
- </hy-flex>
82
- </template>
83
- </view>
84
- </view>
85
- </template>
86
-
87
- <script lang="ts">
88
- export default {
89
- name: 'hy-signature',
90
- options: {
91
- addGlobalClass: true,
92
- virtualHost: true,
93
- styleIsolation: 'shared'
94
- }
95
- }
96
- </script>
97
-
98
- <script lang="ts" setup>
99
- import { computed, getCurrentInstance, onBeforeMount, onMounted, reactive, ref, watch } from 'vue'
100
- import type { CSSProperties } from 'vue'
101
- import { addUnit, getRect, guid } from '../../libs'
102
- import type { SignatureExpose, SignatureResult, Point, Line, ISignatureEmits } from './typing'
103
- // #ifdef MP-WEIXIN
104
- import { canvas2dAdapter } from './canvasHelper'
105
- // #endif
106
- import signatureProps from './props'
107
-
108
- // 组件
109
- import HyButton from '../hy-button/hy-button.vue'
110
- import HyFlex from '../hy-flex/hy-flex.vue'
111
-
112
- /**
113
- * 用于签名场景,基于 Canvas 实现的签名组件。提供了基础签名、历史记录、笔锋效果等功能。
114
- * @displayName hy-signature
115
- */
116
- defineOptions({})
117
-
118
- const props = defineProps(signatureProps)
119
- const emit = defineEmits<ISignatureEmits>()
120
- const instance = getCurrentInstance() as any
121
- const canvasId = ref<string>(`signature${guid()}`) // canvas 组件的唯一标识符
122
- let canvas: null = null //canvas对象 微信小程序生成图片必须传入
123
- const drawing = ref<boolean>(false) // 是否正在绘制
124
- const pixelRatio = ref<number>(1) // 像素比
125
-
126
- const canvasState = reactive({
127
- canvasWidth: 0,
128
- canvasHeight: 0,
129
- ctx: null as UniApp.CanvasContext | null // canvas上下文
130
- })
131
-
132
- watch(
133
- () => props.penColor,
134
- () => {
135
- setLine()
136
- }
137
- )
138
-
139
- watch(
140
- () => props.lineWidth,
141
- () => {
142
- setLine()
143
- }
144
- )
145
-
146
- const canvasStyle = computed(() => {
147
- const style: CSSProperties = {}
148
- if (props.width) {
149
- style.width = addUnit(props.width)
150
- }
151
-
152
- if (props.height) {
153
- style.height = addUnit(props.height)
154
- }
155
-
156
- return `${style}`
157
- })
158
-
159
- const disableScroll = computed(() => props.disableScroll)
160
- const enableHistory = computed(() => props.enableHistory)
161
-
162
- const lines = ref<Line[]>([]) // 保存所有线条
163
- const redoLines = ref<Line[]>([]) // 保存撤销的线条
164
- const currentLine = ref<Line>() // 当前正在绘制的线
165
- const currentStep = ref(0) // 当前步骤
166
-
167
- /**
168
- * @description 添加计算笔画宽度的方法
169
- * */
170
- function calculateLineWidth(speed: number): number {
171
- if (!props.pressure) return props.lineWidth
172
-
173
- const minSpeed = props.minSpeed || 1.5
174
- const limitedSpeed = Math.min(minSpeed * 10, Math.max(minSpeed, speed))
175
- const addWidth = ((props.maxWidth - props.minWidth) * (limitedSpeed - minSpeed)) / minSpeed
176
- const lineWidth = Math.max(props.maxWidth - addWidth, props.minWidth)
177
- return Math.min(lineWidth, props.maxWidth)
178
- }
179
-
180
- /**
181
- * @description 获取默认笔画宽度
182
- * */
183
- const getDefaultLineWidth = () => {
184
- if (props.pressure) {
185
- // 在压感模式下,使用最大和最小宽度的平均值作为默认值
186
- return (props.maxWidth + props.minWidth) / 2
187
- }
188
- return props.lineWidth
189
- }
190
-
191
- /**
192
- * @description 开始画线
193
- * */
194
- const startDrawing = (e: any) => {
195
- e.preventDefault()
196
- drawing.value = true
197
- setLine()
198
- emit('start', e)
199
-
200
- // 创建新线条,同时保存当前的所有绘制参数
201
- const { x, y } = e.touches[0]
202
- currentLine.value = {
203
- points: [
204
- {
205
- x,
206
- y,
207
- t: Date.now() // 使用 t 替换 width
208
- }
209
- ],
210
- color: props.penColor,
211
- width: getDefaultLineWidth(),
212
- backgroundColor: props.backgroundColor,
213
- isPressure: props.pressure // 添加笔锋模式标记
214
- }
215
-
216
- // 清空重做记录
217
- redoLines.value = []
218
- draw(e)
219
- }
220
-
221
- /**
222
- * @description 结束画线
223
- * */
224
- const stopDrawing = (e: TouchEvent) => {
225
- e.preventDefault()
226
- drawing.value = false
227
- if (currentLine.value) {
228
- // 保存完整的线条信息,包括所有点的参数
229
- lines.value.push({
230
- ...currentLine.value,
231
- points: currentLine.value.points.map((point) => ({
232
- ...point,
233
- t: point.t,
234
- speed: point.speed,
235
- distance: point.distance,
236
- lineWidth: point.lineWidth,
237
- lastX1: point.lastX1,
238
- lastY1: point.lastY1,
239
- lastX2: point.lastX2,
240
- lastY2: point.lastY2,
241
- isFirstPoint: point.isFirstPoint
242
- }))
243
- })
244
- currentStep.value = lines.value.length
245
- }
246
- currentLine.value = undefined
247
- const { ctx } = canvasState
248
- if (ctx) ctx.beginPath()
249
- emit('end', e)
250
- }
251
-
252
- /**
253
- * @description 初始化 canvas
254
- * @param forceUpdate 是否强制更新
255
- */
256
- const initCanvas = (forceUpdate: boolean = false) => {
257
- // 如果不是强制更新,且已经初始化过 canvas,则不再重复初始化
258
- if (!forceUpdate && canvasState.canvasHeight && canvasState.canvasWidth) {
259
- return
260
- }
261
- getContext().then(() => {
262
- const { ctx } = canvasState
263
- if (ctx && props.backgroundColor) {
264
- ctx.setFillStyle(props.backgroundColor)
265
- ctx.fillRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
266
- ctx.draw()
267
- }
268
- })
269
- }
270
-
271
- /**
272
- * @description 清空 canvas
273
- * */
274
- const clear = () => {
275
- lines.value = []
276
- redoLines.value = []
277
- currentStep.value = 0
278
- clearCanvas()
279
- emit('clear')
280
- }
281
-
282
- // 确认签名
283
- const confirmSignature = () => {
284
- canvasToImage()
285
- }
286
-
287
- /**
288
- * @description canvas划线
289
- * */
290
- const draw = (e: any) => {
291
- e.preventDefault()
292
- const { ctx } = canvasState
293
-
294
- if (!drawing.value || props.disabled || !ctx) return
295
- const { x, y } = e.touches[0]
296
-
297
- const point: Point = {
298
- x,
299
- y,
300
- t: Date.now()
301
- }
302
-
303
- if (currentLine.value) {
304
- const points = currentLine.value.points
305
- const prePoint = points[points.length - 1]
306
-
307
- if (prePoint.t === point.t || (prePoint.x === x && prePoint.y === y)) {
308
- return
309
- }
310
-
311
- // 计算点的速度和距离
312
- point.distance = Math.sqrt(
313
- Math.pow(point.x - prePoint.x, 2) + Math.pow(point.y - prePoint.y, 2)
314
- )
315
- point.speed = point.distance / (point.t - prePoint.t || 0.1)
316
-
317
- if (props.pressure) {
318
- point.lineWidth = calculateLineWidth(point.speed)
319
- // 处理线宽变化率限制
320
- if (points.length >= 2) {
321
- const prePoint2 = points[points.length - 2]
322
- if (prePoint2.lineWidth && prePoint.lineWidth) {
323
- const rate = (point.lineWidth - prePoint.lineWidth) / prePoint.lineWidth
324
- const maxRate = 0.2 // 最大变化率20%
325
- if (Math.abs(rate) > maxRate) {
326
- const per = rate > 0 ? maxRate : -maxRate
327
- point.lineWidth = prePoint.lineWidth * (1 + per)
328
- }
329
- }
330
- }
331
- }
332
-
333
- points.push(point)
334
-
335
- // 非笔锋模式直接使用线段连接
336
- if (!props.pressure) {
337
- ctx.beginPath()
338
- ctx.moveTo(prePoint.x, prePoint.y)
339
- ctx.lineTo(point.x, point.y)
340
- ctx.stroke()
341
- ctx.draw(true)
342
- } else if (points.length >= 2) {
343
- // 笔锋模式使用贝塞尔曲线
344
- drawSmoothLine(prePoint, point)
345
- }
346
- }
347
-
348
- emit('signing', e)
349
- }
350
-
351
- /**
352
- * @description 重绘整个画布
353
- * */
354
- const redrawCanvas = () => {
355
- const { ctx } = canvasState
356
- if (!ctx) return
357
-
358
- // 清除画布并设置背景
359
- if (props.backgroundColor) {
360
- ctx.setFillStyle(props.backgroundColor)
361
- ctx.fillRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
362
- } else {
363
- ctx.clearRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
364
- }
365
-
366
- // 如果没有线条,只需要清空画布
367
- if (lines.value.length === 0) {
368
- ctx.draw()
369
- return
370
- }
371
-
372
- // 收集所有绘制操作,最后一次性 draw
373
- lines.value.forEach((line) => {
374
- if (!line.points.length) return
375
-
376
- ctx.setStrokeStyle(line.color)
377
- ctx.setLineJoin('round')
378
- ctx.setLineCap('round')
379
-
380
- if (line.isPressure && props.pressure) {
381
- // 笔锋模式的重绘
382
- line.points.forEach((point, index) => {
383
- if (index === 0) return
384
- const prePoint = line.points[index - 1]
385
- const dis_x = point.x - prePoint.x
386
- const dis_y = point.y - prePoint.y
387
- const distance = Math.sqrt(dis_x * dis_x + dis_y * dis_y)
388
-
389
- if (distance <= 2) {
390
- point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5
391
- point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5
392
- } else {
393
- const speed = point.speed || 0
394
- const minSpeed = props.minSpeed || 1.5
395
- const speedFactor = Math.max(0.1, Math.min(0.9, speed / (minSpeed * 10)))
396
-
397
- point.lastX1 = prePoint.x + dis_x * (0.2 + speedFactor * 0.3)
398
- point.lastY1 = prePoint.y + dis_y * (0.2 + speedFactor * 0.3)
399
- point.lastX2 = prePoint.x + dis_x * (0.8 - speedFactor * 0.3)
400
- point.lastY2 = prePoint.y + dis_y * (0.8 - speedFactor * 0.3)
401
- }
402
-
403
- const lineWidth = point.lineWidth || line.width
404
- point.isFirstPoint = true
405
- })
406
- } else {
407
- // 非笔锋模式的重绘
408
- ctx.setLineWidth(line.width)
409
- line.points.forEach((point, index) => {
410
- if (index === 0) return
411
- const prePoint = line.points[index - 1]
412
- ctx.beginPath()
413
- ctx.moveTo(prePoint.x, prePoint.y)
414
- ctx.lineTo(point.x, point.y)
415
- ctx.stroke()
416
- })
417
- }
418
- })
419
-
420
- // 所有线条绘制完成后,一次性更新画布
421
- ctx.draw()
422
- }
423
-
424
- /**
425
- * @description 修改撤销功能
426
- * */
427
- const revoke = () => {
428
- if (!lines.value.length) return
429
- const step = Math.min(props.step, lines.value.length)
430
- const removedLines = lines.value.splice(lines.value.length - step)
431
- redoLines.value.push(...removedLines)
432
- currentStep.value = Math.max(0, currentStep.value - step)
433
- redrawCanvas()
434
- }
435
-
436
- /**
437
- * @description 修改恢复功能
438
- * */
439
- const restore = () => {
440
- if (!redoLines.value.length) return
441
- const step = Math.min(props.step, redoLines.value.length)
442
- const restoredLines = redoLines.value.splice(redoLines.value.length - step)
443
- lines.value.push(...restoredLines)
444
- currentStep.value = Math.min(lines.value.length, currentStep.value + step)
445
- redrawCanvas()
446
- }
447
-
448
- /**
449
- * @description 添加平滑线条绘制方法
450
- * */
451
- function drawSmoothLine(prePoint: Point, point: Point) {
452
- const { ctx } = canvasState
453
- if (!ctx) return
454
-
455
- // 计算两点间距离
456
- const dis_x = point.x - prePoint.x
457
- const dis_y = point.y - prePoint.y
458
- const distance = Math.sqrt(dis_x * dis_x + dis_y * dis_y)
459
-
460
- if (distance <= 2) {
461
- // 对于非常近的点,直接使用中点
462
- point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5
463
- point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5
464
- } else {
465
- // 根据点的速度计算控制点的偏移程度
466
- const speed = point.speed || 0
467
- const minSpeed = props.minSpeed || 1.5
468
- const speedFactor = Math.max(0.1, Math.min(0.9, speed / (minSpeed * 10)))
469
-
470
- // 计算控制点
471
- point.lastX1 = prePoint.x + dis_x * (0.2 + speedFactor * 0.3)
472
- point.lastY1 = prePoint.y + dis_y * (0.2 + speedFactor * 0.3)
473
- point.lastX2 = prePoint.x + dis_x * (0.8 - speedFactor * 0.3)
474
- point.lastY2 = prePoint.y + dis_y * (0.8 - speedFactor * 0.3)
475
- }
476
-
477
- // 计算线宽
478
- const lineWidth = point.lineWidth || props.lineWidth
479
-
480
- // 绘制贝塞尔曲线
481
- if (typeof prePoint.lastX1 === 'number') {
482
- // 设置线宽
483
- ctx.setLineWidth(lineWidth)
484
- // 绘制第一段曲线
485
- ctx.beginPath()
486
- ctx.moveTo(prePoint.lastX2!, prePoint.lastY2!)
487
- ctx.quadraticCurveTo(prePoint.x, prePoint.y, point.lastX1, point.lastY1)
488
- ctx.stroke()
489
-
490
- if (!prePoint.isFirstPoint) {
491
- // 绘制连接段曲线
492
- ctx.beginPath()
493
- ctx.moveTo(prePoint.lastX1!, prePoint.lastY1!)
494
- ctx.quadraticCurveTo(prePoint.x, prePoint.y, prePoint.lastX2!, prePoint.lastY2!)
495
- ctx.stroke()
496
- }
497
-
498
- // 批量更新绘制内容
499
- ctx.draw(true)
500
- } else {
501
- point.isFirstPoint = true
502
- }
503
- }
504
-
505
- onMounted(() => {
506
- initCanvas()
507
- })
508
-
509
- onBeforeMount(() => {
510
- // #ifdef MP
511
- pixelRatio.value = uni.getSystemInfoSync().pixelRatio
512
- // #endif
513
- })
514
-
515
- /**
516
- * @description 获取canvas上下文
517
- */
518
- function getContext() {
519
- return new Promise<UniApp.CanvasContext>((resolve) => {
520
- const { ctx } = canvasState
521
-
522
- if (ctx) {
523
- return resolve(ctx)
524
- }
525
- // #ifndef MP-WEIXIN
526
- getRect(`#${canvasId.value}`, false, instance).then((canvasRect) => {
527
- setCanvasState(canvasRect.width!, canvasRect.height!)
528
- canvasState.ctx = uni.createCanvasContext(canvasId.value, instance.proxy)
529
- if (canvasState.ctx) {
530
- canvasState.ctx.scale(pixelRatio.value, pixelRatio.value)
531
- }
532
- resolve(canvasState.ctx)
533
- })
534
- // #endif
535
- // #ifdef MP-WEIXIN
536
-
537
- getRect(`#${canvasId.value}`, false, instance, true).then((canvasRect: any) => {
538
- if (canvasRect && canvasRect.node && canvasRect.width && canvasRect.height) {
539
- const canvasInstance = canvasRect.node
540
- canvasState.ctx = canvas2dAdapter(
541
- canvasInstance.getContext('2d') as CanvasRenderingContext2D
542
- )
543
- canvasInstance.width = canvasRect.width * pixelRatio.value
544
- canvasInstance.height = canvasRect.height * pixelRatio.value
545
- canvasState.ctx.scale(pixelRatio.value, pixelRatio.value)
546
- canvas = canvasInstance
547
- setCanvasState(canvasRect.width, canvasRect.height)
548
- resolve(canvasState.ctx)
549
- }
550
- })
551
- // #endif
552
- })
553
- }
554
-
555
- /**
556
- * @description 设置 canvasState
557
- */
558
- function setCanvasState(width: number, height: number) {
559
- canvasState.canvasHeight = height * pixelRatio.value
560
- canvasState.canvasWidth = width * pixelRatio.value
561
- }
562
-
563
- /**
564
- * @description 设置线段
565
- * */
566
- function setLine() {
567
- const { ctx } = canvasState
568
- if (ctx) {
569
- ctx.setLineWidth(getDefaultLineWidth()) // 使用新的默认宽度
570
- ctx.setStrokeStyle(props.penColor)
571
- ctx.setLineJoin('round')
572
- ctx.setLineCap('round')
573
- }
574
- }
575
-
576
- /**
577
- * @description canvas 绘制图片输出成文件类型
578
- */
579
- function canvasToImage() {
580
- const { fileType, quality, exportScale } = props
581
- const { canvasWidth, canvasHeight } = canvasState
582
- uni.canvasToTempFilePath(
583
- {
584
- width: canvasWidth * exportScale,
585
- height: canvasHeight * exportScale,
586
- destWidth: canvasWidth * exportScale,
587
- destHeight: canvasHeight * exportScale,
588
- fileType,
589
- quality,
590
- canvasId: canvasId.value,
591
- canvas: canvas,
592
- success: (res) => {
593
- const result: SignatureResult = {
594
- tempFilePath: res.tempFilePath,
595
- width: (canvasWidth * exportScale) / pixelRatio.value,
596
- height: (canvasHeight * exportScale) / pixelRatio.value,
597
- success: true
598
- }
599
- // #ifdef MP-DINGTALK
600
- result.tempFilePath = (res as any).filePath
601
- // #endif
602
- emit('confirm', result)
603
- },
604
- fail: () => {
605
- const result: SignatureResult = {
606
- tempFilePath: '',
607
- width: (canvasWidth * exportScale) / pixelRatio.value,
608
- height: (canvasHeight * exportScale) / pixelRatio.value,
609
- success: false
610
- }
611
- emit('confirm', result)
612
- }
613
- },
614
- instance.proxy
615
- )
616
- }
617
-
618
- function clearCanvas() {
619
- const { canvasWidth, canvasHeight, ctx } = canvasState
620
- if (ctx) {
621
- ctx.clearRect(0, 0, canvasWidth, canvasHeight)
622
- if (props.backgroundColor) {
623
- ctx.setFillStyle(props.backgroundColor)
624
- ctx.fillRect(0, 0, canvasWidth, canvasHeight)
625
- }
626
- ctx.draw()
627
- }
628
- }
629
-
630
- defineExpose<SignatureExpose>({
631
- init: initCanvas,
632
- clear,
633
- confirm: confirmSignature,
634
- restore,
635
- revoke
636
- })
637
- </script>
638
- <style scoped lang="scss">
639
- @import './index.scss';
640
- </style>
1
+ <template>
2
+ <view :class="['hy-signature', customClass]" :style="customStyle">
3
+ <view class="hy-signature__content" :style="{ borderRadius: addUnit(round) }">
4
+ <!-- #ifdef MP-WEIXIN -->
5
+ <canvas
6
+ class="hy-signature__content-canvas"
7
+ :style="canvasStyle"
8
+ :width="canvasState.canvasWidth"
9
+ :height="canvasState.canvasHeight"
10
+ :canvas-id="canvasId"
11
+ :id="canvasId"
12
+ :disable-scroll="disableScroll"
13
+ @touchstart="startDrawing"
14
+ @touchend="stopDrawing"
15
+ @touchmove="draw"
16
+ type="2d"
17
+ />
18
+ <!-- #endif -->
19
+ <!-- #ifndef MP-WEIXIN -->
20
+ <canvas
21
+ class="hy-signature__content-canvas"
22
+ :canvas-id="canvasId"
23
+ :style="canvasStyle"
24
+ :width="canvasState.canvasWidth"
25
+ :height="canvasState.canvasHeight"
26
+ :id="canvasId"
27
+ :disable-scroll="disableScroll"
28
+ @touchstart="startDrawing"
29
+ @touchend="stopDrawing"
30
+ @touchmove="draw"
31
+ />
32
+ <!-- #endif -->
33
+ </view>
34
+ <view class="hy-signature__footer">
35
+ <slot
36
+ v-if="$slots.footer"
37
+ name="footer"
38
+ :clear="clear"
39
+ :confirm="confirmSignature"
40
+ :current-step="currentStep"
41
+ :revoke="revoke"
42
+ :restore="restore"
43
+ :can-undo="lines.length > 0"
44
+ :can-redo="redoLines.length > 0"
45
+ :history-list="lines"
46
+ >
47
+ </slot>
48
+ <template v-else>
49
+ <hy-flex gap="12">
50
+ <block v-if="enableHistory">
51
+ <hy-button
52
+ size="small"
53
+ plain
54
+ shape="circle"
55
+ @click="revoke"
56
+ :disabled="lines.length <= 0"
57
+ :text="revokeText"
58
+ ></hy-button>
59
+ <hy-button
60
+ size="small"
61
+ plain
62
+ shape="circle"
63
+ @click="restore"
64
+ :disabled="redoLines.length <= 0"
65
+ :text="restoreText"
66
+ ></hy-button>
67
+ </block>
68
+ <hy-button
69
+ size="small"
70
+ plain
71
+ shape="circle"
72
+ @click="clear"
73
+ :text="clearText"
74
+ ></hy-button>
75
+ <hy-button
76
+ size="small"
77
+ shape="circle"
78
+ @click="confirmSignature"
79
+ :text="confirmText"
80
+ ></hy-button>
81
+ </hy-flex>
82
+ </template>
83
+ </view>
84
+ </view>
85
+ </template>
86
+
87
+ <script lang="ts">
88
+ export default {
89
+ name: 'hy-signature',
90
+ options: {
91
+ addGlobalClass: true,
92
+ virtualHost: true,
93
+ styleIsolation: 'shared'
94
+ }
95
+ }
96
+ </script>
97
+
98
+ <script lang="ts" setup>
99
+ import { computed, getCurrentInstance, onBeforeMount, onMounted, reactive, ref, watch } from 'vue'
100
+ import type { CSSProperties } from 'vue'
101
+ import { addUnit, getRect, guid } from '../../libs'
102
+ import type { SignatureExpose, SignatureResult, Point, Line, ISignatureEmits } from './typing'
103
+ // #ifdef MP-WEIXIN
104
+ import { canvas2dAdapter } from './canvasHelper'
105
+ // #endif
106
+ import signatureProps from './props'
107
+
108
+ // 组件
109
+ import HyButton from '../hy-button/hy-button.vue'
110
+ import HyFlex from '../hy-flex/hy-flex.vue'
111
+
112
+ /**
113
+ * 用于签名场景,基于 Canvas 实现的签名组件。提供了基础签名、历史记录、笔锋效果等功能。
114
+ * @displayName hy-signature
115
+ */
116
+ defineOptions({})
117
+
118
+ const props = defineProps(signatureProps)
119
+ const emit = defineEmits<ISignatureEmits>()
120
+ const instance = getCurrentInstance() as any
121
+ const canvasId = ref<string>(`signature${guid()}`) // canvas 组件的唯一标识符
122
+ let canvas: null = null //canvas对象 微信小程序生成图片必须传入
123
+ const drawing = ref<boolean>(false) // 是否正在绘制
124
+ const pixelRatio = ref<number>(1) // 像素比
125
+
126
+ const canvasState = reactive({
127
+ canvasWidth: 0,
128
+ canvasHeight: 0,
129
+ ctx: null as UniApp.CanvasContext | null // canvas上下文
130
+ })
131
+
132
+ watch(
133
+ () => props.penColor,
134
+ () => {
135
+ setLine()
136
+ }
137
+ )
138
+
139
+ watch(
140
+ () => props.lineWidth,
141
+ () => {
142
+ setLine()
143
+ }
144
+ )
145
+
146
+ const canvasStyle = computed(() => {
147
+ const style: CSSProperties = {}
148
+ if (props.width) {
149
+ style.width = addUnit(props.width)
150
+ }
151
+
152
+ if (props.height) {
153
+ style.height = addUnit(props.height)
154
+ }
155
+
156
+ return `${style}`
157
+ })
158
+
159
+ const disableScroll = computed(() => props.disableScroll)
160
+ const enableHistory = computed(() => props.enableHistory)
161
+
162
+ const lines = ref<Line[]>([]) // 保存所有线条
163
+ const redoLines = ref<Line[]>([]) // 保存撤销的线条
164
+ const currentLine = ref<Line>() // 当前正在绘制的线
165
+ const currentStep = ref(0) // 当前步骤
166
+
167
+ /**
168
+ * @description 添加计算笔画宽度的方法
169
+ * */
170
+ function calculateLineWidth(speed: number): number {
171
+ if (!props.pressure) return props.lineWidth
172
+
173
+ const minSpeed = props.minSpeed || 1.5
174
+ const limitedSpeed = Math.min(minSpeed * 10, Math.max(minSpeed, speed))
175
+ const addWidth = ((props.maxWidth - props.minWidth) * (limitedSpeed - minSpeed)) / minSpeed
176
+ const lineWidth = Math.max(props.maxWidth - addWidth, props.minWidth)
177
+ return Math.min(lineWidth, props.maxWidth)
178
+ }
179
+
180
+ /**
181
+ * @description 获取默认笔画宽度
182
+ * */
183
+ const getDefaultLineWidth = () => {
184
+ if (props.pressure) {
185
+ // 在压感模式下,使用最大和最小宽度的平均值作为默认值
186
+ return (props.maxWidth + props.minWidth) / 2
187
+ }
188
+ return props.lineWidth
189
+ }
190
+
191
+ /**
192
+ * @description 开始画线
193
+ * */
194
+ const startDrawing = (e: any) => {
195
+ e.preventDefault()
196
+ drawing.value = true
197
+ setLine()
198
+ emit('start', e)
199
+
200
+ // 创建新线条,同时保存当前的所有绘制参数
201
+ const { x, y } = e.touches[0]
202
+ currentLine.value = {
203
+ points: [
204
+ {
205
+ x,
206
+ y,
207
+ t: Date.now() // 使用 t 替换 width
208
+ }
209
+ ],
210
+ color: props.penColor,
211
+ width: getDefaultLineWidth(),
212
+ backgroundColor: props.backgroundColor,
213
+ isPressure: props.pressure // 添加笔锋模式标记
214
+ }
215
+
216
+ // 清空重做记录
217
+ redoLines.value = []
218
+ draw(e)
219
+ }
220
+
221
+ /**
222
+ * @description 结束画线
223
+ * */
224
+ const stopDrawing = (e: TouchEvent) => {
225
+ e.preventDefault()
226
+ drawing.value = false
227
+ if (currentLine.value) {
228
+ // 保存完整的线条信息,包括所有点的参数
229
+ lines.value.push({
230
+ ...currentLine.value,
231
+ points: currentLine.value.points.map((point) => ({
232
+ ...point,
233
+ t: point.t,
234
+ speed: point.speed,
235
+ distance: point.distance,
236
+ lineWidth: point.lineWidth,
237
+ lastX1: point.lastX1,
238
+ lastY1: point.lastY1,
239
+ lastX2: point.lastX2,
240
+ lastY2: point.lastY2,
241
+ isFirstPoint: point.isFirstPoint
242
+ }))
243
+ })
244
+ currentStep.value = lines.value.length
245
+ }
246
+ currentLine.value = undefined
247
+ const { ctx } = canvasState
248
+ if (ctx) ctx.beginPath()
249
+ emit('end', e)
250
+ }
251
+
252
+ /**
253
+ * @description 初始化 canvas
254
+ * @param forceUpdate 是否强制更新
255
+ */
256
+ const initCanvas = (forceUpdate: boolean = false) => {
257
+ // 如果不是强制更新,且已经初始化过 canvas,则不再重复初始化
258
+ if (!forceUpdate && canvasState.canvasHeight && canvasState.canvasWidth) {
259
+ return
260
+ }
261
+ getContext().then(() => {
262
+ const { ctx } = canvasState
263
+ if (ctx && props.backgroundColor) {
264
+ ctx.setFillStyle(props.backgroundColor)
265
+ ctx.fillRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
266
+ ctx.draw()
267
+ }
268
+ })
269
+ }
270
+
271
+ /**
272
+ * @description 清空 canvas
273
+ * */
274
+ const clear = () => {
275
+ lines.value = []
276
+ redoLines.value = []
277
+ currentStep.value = 0
278
+ clearCanvas()
279
+ emit('clear')
280
+ }
281
+
282
+ // 确认签名
283
+ const confirmSignature = () => {
284
+ canvasToImage()
285
+ }
286
+
287
+ /**
288
+ * @description canvas划线
289
+ * */
290
+ const draw = (e: any) => {
291
+ e.preventDefault()
292
+ const { ctx } = canvasState
293
+
294
+ if (!drawing.value || props.disabled || !ctx) return
295
+ const { x, y } = e.touches[0]
296
+
297
+ const point: Point = {
298
+ x,
299
+ y,
300
+ t: Date.now()
301
+ }
302
+
303
+ if (currentLine.value) {
304
+ const points = currentLine.value.points
305
+ const prePoint = points[points.length - 1]
306
+
307
+ if (prePoint.t === point.t || (prePoint.x === x && prePoint.y === y)) {
308
+ return
309
+ }
310
+
311
+ // 计算点的速度和距离
312
+ point.distance = Math.sqrt(
313
+ Math.pow(point.x - prePoint.x, 2) + Math.pow(point.y - prePoint.y, 2)
314
+ )
315
+ point.speed = point.distance / (point.t - prePoint.t || 0.1)
316
+
317
+ if (props.pressure) {
318
+ point.lineWidth = calculateLineWidth(point.speed)
319
+ // 处理线宽变化率限制
320
+ if (points.length >= 2) {
321
+ const prePoint2 = points[points.length - 2]
322
+ if (prePoint2.lineWidth && prePoint.lineWidth) {
323
+ const rate = (point.lineWidth - prePoint.lineWidth) / prePoint.lineWidth
324
+ const maxRate = 0.2 // 最大变化率20%
325
+ if (Math.abs(rate) > maxRate) {
326
+ const per = rate > 0 ? maxRate : -maxRate
327
+ point.lineWidth = prePoint.lineWidth * (1 + per)
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ points.push(point)
334
+
335
+ // 非笔锋模式直接使用线段连接
336
+ if (!props.pressure) {
337
+ ctx.beginPath()
338
+ ctx.moveTo(prePoint.x, prePoint.y)
339
+ ctx.lineTo(point.x, point.y)
340
+ ctx.stroke()
341
+ ctx.draw(true)
342
+ } else if (points.length >= 2) {
343
+ // 笔锋模式使用贝塞尔曲线
344
+ drawSmoothLine(prePoint, point)
345
+ }
346
+ }
347
+
348
+ emit('signing', e)
349
+ }
350
+
351
+ /**
352
+ * @description 重绘整个画布
353
+ * */
354
+ const redrawCanvas = () => {
355
+ const { ctx } = canvasState
356
+ if (!ctx) return
357
+
358
+ // 清除画布并设置背景
359
+ if (props.backgroundColor) {
360
+ ctx.setFillStyle(props.backgroundColor)
361
+ ctx.fillRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
362
+ } else {
363
+ ctx.clearRect(0, 0, canvasState.canvasWidth, canvasState.canvasHeight)
364
+ }
365
+
366
+ // 如果没有线条,只需要清空画布
367
+ if (lines.value.length === 0) {
368
+ ctx.draw()
369
+ return
370
+ }
371
+
372
+ // 收集所有绘制操作,最后一次性 draw
373
+ lines.value.forEach((line) => {
374
+ if (!line.points.length) return
375
+
376
+ ctx.setStrokeStyle(line.color)
377
+ ctx.setLineJoin('round')
378
+ ctx.setLineCap('round')
379
+
380
+ if (line.isPressure && props.pressure) {
381
+ // 笔锋模式的重绘
382
+ line.points.forEach((point, index) => {
383
+ if (index === 0) return
384
+ const prePoint = line.points[index - 1]
385
+ const dis_x = point.x - prePoint.x
386
+ const dis_y = point.y - prePoint.y
387
+ const distance = Math.sqrt(dis_x * dis_x + dis_y * dis_y)
388
+
389
+ if (distance <= 2) {
390
+ point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5
391
+ point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5
392
+ } else {
393
+ const speed = point.speed || 0
394
+ const minSpeed = props.minSpeed || 1.5
395
+ const speedFactor = Math.max(0.1, Math.min(0.9, speed / (minSpeed * 10)))
396
+
397
+ point.lastX1 = prePoint.x + dis_x * (0.2 + speedFactor * 0.3)
398
+ point.lastY1 = prePoint.y + dis_y * (0.2 + speedFactor * 0.3)
399
+ point.lastX2 = prePoint.x + dis_x * (0.8 - speedFactor * 0.3)
400
+ point.lastY2 = prePoint.y + dis_y * (0.8 - speedFactor * 0.3)
401
+ }
402
+
403
+ const lineWidth = point.lineWidth || line.width
404
+ point.isFirstPoint = true
405
+ })
406
+ } else {
407
+ // 非笔锋模式的重绘
408
+ ctx.setLineWidth(line.width)
409
+ line.points.forEach((point, index) => {
410
+ if (index === 0) return
411
+ const prePoint = line.points[index - 1]
412
+ ctx.beginPath()
413
+ ctx.moveTo(prePoint.x, prePoint.y)
414
+ ctx.lineTo(point.x, point.y)
415
+ ctx.stroke()
416
+ })
417
+ }
418
+ })
419
+
420
+ // 所有线条绘制完成后,一次性更新画布
421
+ ctx.draw()
422
+ }
423
+
424
+ /**
425
+ * @description 修改撤销功能
426
+ * */
427
+ const revoke = () => {
428
+ if (!lines.value.length) return
429
+ const step = Math.min(props.step, lines.value.length)
430
+ const removedLines = lines.value.splice(lines.value.length - step)
431
+ redoLines.value.push(...removedLines)
432
+ currentStep.value = Math.max(0, currentStep.value - step)
433
+ redrawCanvas()
434
+ }
435
+
436
+ /**
437
+ * @description 修改恢复功能
438
+ * */
439
+ const restore = () => {
440
+ if (!redoLines.value.length) return
441
+ const step = Math.min(props.step, redoLines.value.length)
442
+ const restoredLines = redoLines.value.splice(redoLines.value.length - step)
443
+ lines.value.push(...restoredLines)
444
+ currentStep.value = Math.min(lines.value.length, currentStep.value + step)
445
+ redrawCanvas()
446
+ }
447
+
448
+ /**
449
+ * @description 添加平滑线条绘制方法
450
+ * */
451
+ function drawSmoothLine(prePoint: Point, point: Point) {
452
+ const { ctx } = canvasState
453
+ if (!ctx) return
454
+
455
+ // 计算两点间距离
456
+ const dis_x = point.x - prePoint.x
457
+ const dis_y = point.y - prePoint.y
458
+ const distance = Math.sqrt(dis_x * dis_x + dis_y * dis_y)
459
+
460
+ if (distance <= 2) {
461
+ // 对于非常近的点,直接使用中点
462
+ point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5
463
+ point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5
464
+ } else {
465
+ // 根据点的速度计算控制点的偏移程度
466
+ const speed = point.speed || 0
467
+ const minSpeed = props.minSpeed || 1.5
468
+ const speedFactor = Math.max(0.1, Math.min(0.9, speed / (minSpeed * 10)))
469
+
470
+ // 计算控制点
471
+ point.lastX1 = prePoint.x + dis_x * (0.2 + speedFactor * 0.3)
472
+ point.lastY1 = prePoint.y + dis_y * (0.2 + speedFactor * 0.3)
473
+ point.lastX2 = prePoint.x + dis_x * (0.8 - speedFactor * 0.3)
474
+ point.lastY2 = prePoint.y + dis_y * (0.8 - speedFactor * 0.3)
475
+ }
476
+
477
+ // 计算线宽
478
+ const lineWidth = point.lineWidth || props.lineWidth
479
+
480
+ // 绘制贝塞尔曲线
481
+ if (typeof prePoint.lastX1 === 'number') {
482
+ // 设置线宽
483
+ ctx.setLineWidth(lineWidth)
484
+ // 绘制第一段曲线
485
+ ctx.beginPath()
486
+ ctx.moveTo(prePoint.lastX2!, prePoint.lastY2!)
487
+ ctx.quadraticCurveTo(prePoint.x, prePoint.y, point.lastX1, point.lastY1)
488
+ ctx.stroke()
489
+
490
+ if (!prePoint.isFirstPoint) {
491
+ // 绘制连接段曲线
492
+ ctx.beginPath()
493
+ ctx.moveTo(prePoint.lastX1!, prePoint.lastY1!)
494
+ ctx.quadraticCurveTo(prePoint.x, prePoint.y, prePoint.lastX2!, prePoint.lastY2!)
495
+ ctx.stroke()
496
+ }
497
+
498
+ // 批量更新绘制内容
499
+ ctx.draw(true)
500
+ } else {
501
+ point.isFirstPoint = true
502
+ }
503
+ }
504
+
505
+ onMounted(() => {
506
+ initCanvas()
507
+ })
508
+
509
+ onBeforeMount(() => {
510
+ // #ifdef MP
511
+ pixelRatio.value = uni.getSystemInfoSync().pixelRatio
512
+ // #endif
513
+ })
514
+
515
+ /**
516
+ * @description 获取canvas上下文
517
+ */
518
+ function getContext() {
519
+ return new Promise<UniApp.CanvasContext>((resolve) => {
520
+ const { ctx } = canvasState
521
+
522
+ if (ctx) {
523
+ return resolve(ctx)
524
+ }
525
+ // #ifndef MP-WEIXIN
526
+ getRect(`#${canvasId.value}`, false, instance).then((canvasRect) => {
527
+ setCanvasState(canvasRect.width!, canvasRect.height!)
528
+ canvasState.ctx = uni.createCanvasContext(canvasId.value, instance.proxy)
529
+ if (canvasState.ctx) {
530
+ canvasState.ctx.scale(pixelRatio.value, pixelRatio.value)
531
+ }
532
+ resolve(canvasState.ctx)
533
+ })
534
+ // #endif
535
+ // #ifdef MP-WEIXIN
536
+
537
+ getRect(`#${canvasId.value}`, false, instance, true).then((canvasRect: any) => {
538
+ if (canvasRect && canvasRect.node && canvasRect.width && canvasRect.height) {
539
+ const canvasInstance = canvasRect.node
540
+ canvasState.ctx = canvas2dAdapter(
541
+ canvasInstance.getContext('2d') as CanvasRenderingContext2D
542
+ )
543
+ canvasInstance.width = canvasRect.width * pixelRatio.value
544
+ canvasInstance.height = canvasRect.height * pixelRatio.value
545
+ canvasState.ctx.scale(pixelRatio.value, pixelRatio.value)
546
+ canvas = canvasInstance
547
+ setCanvasState(canvasRect.width, canvasRect.height)
548
+ resolve(canvasState.ctx)
549
+ }
550
+ })
551
+ // #endif
552
+ })
553
+ }
554
+
555
+ /**
556
+ * @description 设置 canvasState
557
+ */
558
+ function setCanvasState(width: number, height: number) {
559
+ canvasState.canvasHeight = height * pixelRatio.value
560
+ canvasState.canvasWidth = width * pixelRatio.value
561
+ }
562
+
563
+ /**
564
+ * @description 设置线段
565
+ * */
566
+ function setLine() {
567
+ const { ctx } = canvasState
568
+ if (ctx) {
569
+ ctx.setLineWidth(getDefaultLineWidth()) // 使用新的默认宽度
570
+ ctx.setStrokeStyle(props.penColor)
571
+ ctx.setLineJoin('round')
572
+ ctx.setLineCap('round')
573
+ }
574
+ }
575
+
576
+ /**
577
+ * @description canvas 绘制图片输出成文件类型
578
+ */
579
+ function canvasToImage() {
580
+ const { fileType, quality, exportScale } = props
581
+ const { canvasWidth, canvasHeight } = canvasState
582
+ uni.canvasToTempFilePath(
583
+ {
584
+ width: canvasWidth * exportScale,
585
+ height: canvasHeight * exportScale,
586
+ destWidth: canvasWidth * exportScale,
587
+ destHeight: canvasHeight * exportScale,
588
+ fileType,
589
+ quality,
590
+ canvasId: canvasId.value,
591
+ canvas: canvas,
592
+ success: (res) => {
593
+ const result: SignatureResult = {
594
+ tempFilePath: res.tempFilePath,
595
+ width: (canvasWidth * exportScale) / pixelRatio.value,
596
+ height: (canvasHeight * exportScale) / pixelRatio.value,
597
+ success: true
598
+ }
599
+ // #ifdef MP-DINGTALK
600
+ result.tempFilePath = (res as any).filePath
601
+ // #endif
602
+ emit('confirm', result)
603
+ },
604
+ fail: () => {
605
+ const result: SignatureResult = {
606
+ tempFilePath: '',
607
+ width: (canvasWidth * exportScale) / pixelRatio.value,
608
+ height: (canvasHeight * exportScale) / pixelRatio.value,
609
+ success: false
610
+ }
611
+ emit('confirm', result)
612
+ }
613
+ },
614
+ instance.proxy
615
+ )
616
+ }
617
+
618
+ function clearCanvas() {
619
+ const { canvasWidth, canvasHeight, ctx } = canvasState
620
+ if (ctx) {
621
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight)
622
+ if (props.backgroundColor) {
623
+ ctx.setFillStyle(props.backgroundColor)
624
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight)
625
+ }
626
+ ctx.draw()
627
+ }
628
+ }
629
+
630
+ defineExpose<SignatureExpose>({
631
+ init: initCanvas,
632
+ clear,
633
+ confirm: confirmSignature,
634
+ restore,
635
+ revoke
636
+ })
637
+ </script>
638
+ <style scoped lang="scss">
639
+ @import './index.scss';
640
+ </style>