uview-plus 3.8.55 → 3.8.106

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 (229) hide show
  1. package/changelog.md +2147 -1828
  2. package/components/u-action-sheet/props.js +3 -1
  3. package/components/u-action-sheet/u-action-sheet.vue +22 -2
  4. package/components/u-album/props.js +3 -1
  5. package/components/u-alert/props.js +4 -2
  6. package/components/u-avatar/props.js +4 -1
  7. package/components/u-avatar-group/props.js +4 -1
  8. package/components/u-back-top/props.js +4 -1
  9. package/components/u-badge/props.js +4 -1
  10. package/components/u-barcode/u-barcode.vue +44 -15
  11. package/components/u-box/props.js +5 -2
  12. package/components/u-button/props.js +4 -1
  13. package/components/u-button/u-button.vue +2 -5
  14. package/components/u-button/vue.scss +3 -3
  15. package/components/u-calendar/calendar.js +8 -0
  16. package/components/u-calendar/props.js +43 -1
  17. package/components/u-calendar/u-calendar.vue +16 -0
  18. package/components/u-calendar-strip/props.js +3 -1
  19. package/components/u-canvas/u-canvas.vue +1028 -423
  20. package/components/u-car-keyboard/props.js +3 -1
  21. package/components/u-card/props.js +3 -1
  22. package/components/u-cell/props.js +4 -1
  23. package/components/u-cell/u-cell.vue +9 -1
  24. package/components/u-cell-group/props.js +4 -1
  25. package/components/u-checkbox/props.js +4 -1
  26. package/components/u-checkbox/u-checkbox.vue +7 -3
  27. package/components/u-checkbox-group/props.js +3 -1
  28. package/components/u-circle-progress/props.js +4 -1
  29. package/components/u-code/props.js +4 -1
  30. package/components/u-code-input/props.js +4 -1
  31. package/components/u-col/props.js +4 -1
  32. package/components/u-collapse/props.js +4 -1
  33. package/components/u-collapse-item/props.js +4 -1
  34. package/components/u-color-picker/u-color-picker.vue +1 -0
  35. package/components/u-column-notice/props.js +4 -1
  36. package/components/u-count-down/props.js +4 -1
  37. package/components/u-count-to/props.js +4 -1
  38. package/components/u-cropper/u-cropper.vue +95 -51
  39. package/components/u-datetime-picker/props.js +4 -1
  40. package/components/u-datetime-picker/u-datetime-picker.vue +53 -4
  41. package/components/u-divider/props.js +4 -1
  42. package/components/u-dragsort/u-dragsort.vue +57 -32
  43. package/components/u-dropdown/props.js +3 -1
  44. package/components/u-dropdown-item/props.js +3 -1
  45. package/components/u-empty/props.js +4 -1
  46. package/components/u-form/props.js +4 -1
  47. package/components/u-form-item/props.js +4 -1
  48. package/components/u-gap/props.js +4 -1
  49. package/components/u-grid/props.js +4 -1
  50. package/components/u-grid-item/props.js +4 -1
  51. package/components/u-guide/props.js +3 -1
  52. package/components/u-icon/props.js +4 -1
  53. package/components/u-icon/u-icon.vue +11 -2
  54. package/components/u-icon/upicon.ttf +0 -0
  55. package/components/u-icon/util.js +103 -10
  56. package/components/u-image/props.js +4 -1
  57. package/components/u-image/u-image.vue +280 -267
  58. package/components/u-index-anchor/props.js +4 -1
  59. package/components/u-index-item/props.js +2 -1
  60. package/components/u-index-item/u-index-item.vue +1 -1
  61. package/components/u-index-list/props.js +4 -1
  62. package/components/u-index-list/u-index-list.vue +1 -1
  63. package/components/u-input/props.js +3 -1
  64. package/components/u-keyboard/props.js +4 -1
  65. package/components/u-line/props.js +4 -1
  66. package/components/u-line-progress/props.js +6 -3
  67. package/components/u-link/props.js +4 -1
  68. package/components/u-list/props.js +4 -1
  69. package/components/u-list-item/props.js +4 -1
  70. package/components/u-loading-icon/props.js +4 -1
  71. package/components/u-loading-page/props.js +3 -1
  72. package/components/u-loadmore/props.js +4 -1
  73. package/components/u-markdown/u-markdown.vue +45 -3
  74. package/components/u-modal/props.js +4 -1
  75. package/components/u-navbar/props.js +4 -2
  76. package/components/u-navbar/u-navbar.vue +15 -9
  77. package/components/u-no-network/props.js +4 -1
  78. package/components/u-notice-bar/props.js +4 -1
  79. package/components/u-notify/props.js +4 -1
  80. package/components/u-novel-reader/content-normalizer.js +80 -0
  81. package/components/u-novel-reader/layout-engine.js +225 -0
  82. package/components/u-novel-reader/measure-adapter.js +69 -0
  83. package/components/u-novel-reader/novelReader.js +35 -0
  84. package/components/u-novel-reader/persistence.js +144 -0
  85. package/components/u-novel-reader/props.js +100 -0
  86. package/components/u-novel-reader/reader-catalog.vue +234 -0
  87. package/components/u-novel-reader/reader-content.vue +226 -0
  88. package/components/u-novel-reader/reader-core.js +151 -0
  89. package/components/u-novel-reader/reader-settings.vue +325 -0
  90. package/components/u-novel-reader/reader-toolbar.vue +313 -0
  91. package/components/u-novel-reader/theme-vars.scss +49 -0
  92. package/components/u-novel-reader/u-novel-reader.vue +836 -0
  93. package/components/u-number-box/props.js +4 -1
  94. package/components/u-number-box/u-number-box.vue +2 -2
  95. package/components/u-number-keyboard/props.js +4 -1
  96. package/components/u-overlay/props.js +4 -1
  97. package/components/u-pagination/u-pagination.vue +60 -39
  98. package/components/u-parse/props.js +4 -1
  99. package/components/u-picker/props.js +4 -1
  100. package/components/u-picker-column/props.js +3 -1
  101. package/components/u-popover/props.js +2 -1
  102. package/components/u-popup/props.js +4 -1
  103. package/components/u-poster/u-poster.vue +246 -214
  104. package/components/u-qrcode/qrcode.js +105 -97
  105. package/components/u-qrcode/u-qrcode.vue +242 -289
  106. package/components/u-radio/props.js +4 -1
  107. package/components/u-radio/u-radio.vue +7 -3
  108. package/components/u-radio-group/props.js +4 -1
  109. package/components/u-rate/props.js +4 -1
  110. package/components/u-read-more/props.js +4 -1
  111. package/components/u-row/props.js +4 -1
  112. package/components/u-row-notice/props.js +4 -1
  113. package/components/u-safe-bottom/props.js +2 -1
  114. package/components/u-scroll-list/props.js +4 -1
  115. package/components/u-scroll-list/scrollWxs.wxs +1 -1
  116. package/components/u-search/props.js +3 -1
  117. package/components/u-skeleton/props.js +4 -1
  118. package/components/u-slider/props.js +3 -1
  119. package/components/u-slider/u-slider.vue +71 -29
  120. package/components/u-status-bar/props.js +4 -1
  121. package/components/u-steps/props.js +4 -1
  122. package/components/u-steps-item/props.js +4 -1
  123. package/components/u-sticky/props.js +4 -1
  124. package/components/u-sticky/u-sticky.vue +11 -0
  125. package/components/u-subsection/props.js +3 -1
  126. package/components/u-swipe-action/props.js +4 -1
  127. package/components/u-swipe-action-item/index.wxs +43 -25
  128. package/components/u-swipe-action-item/nvue.js +9 -0
  129. package/components/u-swipe-action-item/other.js +32 -21
  130. package/components/u-swipe-action-item/props.js +9 -1
  131. package/components/u-swipe-action-item/swipeActionItem.js +1 -0
  132. package/components/u-swipe-action-item/u-swipe-action-item.vue +42 -4
  133. package/components/u-swiper/props.js +9 -1
  134. package/components/u-swiper/swiper.js +1 -0
  135. package/components/u-swiper/u-swiper.vue +2 -0
  136. package/components/u-swiper-indicator/props.js +4 -1
  137. package/components/u-switch/props.js +4 -1
  138. package/components/u-switch/u-switch.vue +1 -1
  139. package/components/u-tabbar/props.js +3 -1
  140. package/components/u-tabbar/u-tabbar.vue +2 -1
  141. package/components/u-tabbar-item/props.js +4 -1
  142. package/components/u-tabbar-item/u-tabbar-item.vue +64 -21
  143. package/components/u-table/props.js +3 -1
  144. package/components/u-table2/tableRow.vue +155 -69
  145. package/components/u-table2/u-table2.vue +327 -137
  146. package/components/u-tabs/props.js +4 -1
  147. package/components/u-tabs/u-tabs.vue +11 -8
  148. package/components/u-tabs-item/props.js +3 -1
  149. package/components/u-tabs-pro/u-tabs-pro.vue +212 -0
  150. package/components/u-tag/props.js +4 -1
  151. package/components/u-td/props.js +3 -1
  152. package/components/u-text/props.js +4 -1
  153. package/components/u-text/text.js +1 -1
  154. package/components/u-text/u-text.vue +18 -6
  155. package/components/u-textarea/props.js +3 -1
  156. package/components/u-textarea/u-textarea.vue +16 -5
  157. package/components/u-th/props.js +3 -1
  158. package/components/u-toolbar/props.js +4 -1
  159. package/components/u-tooltip/props.js +10 -2
  160. package/components/u-tooltip/tooltip.js +2 -1
  161. package/components/u-tooltip/u-tooltip.vue +31 -3
  162. package/components/u-tr/props.js +3 -1
  163. package/components/u-transition/nvue-ani-map.js +75 -68
  164. package/components/u-transition/props.js +4 -1
  165. package/components/u-transition/transitionMixin.js +164 -163
  166. package/components/u-upload/props.js +4 -1
  167. package/components/u-upload/u-upload.vue +48 -41
  168. package/components/u-waterfall/u-waterfall.vue +162 -110
  169. package/index.js +65 -8
  170. package/libs/config/config.js +1 -1
  171. package/libs/config/props.js +180 -204
  172. package/libs/css/components.scss +3 -0
  173. package/libs/function/index.js +66 -0
  174. package/libs/function/uni-api-shims.js +29 -0
  175. package/libs/i18n/index.js +71 -36
  176. package/libs/i18n/locale-packs.js +11 -0
  177. package/libs/i18n/locales/all.js +23 -0
  178. package/libs/i18n/locales/{de.json → de.js} +1 -1
  179. package/libs/i18n/locales/{en.json → en.js} +2 -2
  180. package/libs/i18n/locales/{es.json → es.js} +1 -1
  181. package/libs/i18n/locales/{fr.json → fr.js} +2 -2
  182. package/libs/i18n/locales/{ja.json → ja.js} +1 -1
  183. package/libs/i18n/locales/{ko.json → ko.js} +1 -1
  184. package/libs/i18n/locales/{ru.json → ru.js} +1 -1
  185. package/libs/i18n/locales/{th.json → th.js} +1 -1
  186. package/libs/i18n/locales/{zh-Hans.json → zh-Hans.js} +1 -1
  187. package/libs/i18n/locales/{zh-Hant.json → zh-Hant.js} +1 -1
  188. package/libs/mixin/mixin.js +18 -54
  189. package/libs/root/index.js +78 -5
  190. package/libs/root/page.js +7 -7
  191. package/libs/root/root.js +73 -3
  192. package/libs/util/app-nvue-webview-canvas.js +486 -0
  193. package/libs/util/gcanvas/bridge/bridge-weex.js +10 -7
  194. package/libs/util/gcanvas/context-2d/FillStyleLinearGradient.js +0 -0
  195. package/libs/util/gcanvas/context-2d/FillStylePattern.js +0 -0
  196. package/libs/util/gcanvas/context-2d/FillStyleRadialGradient.js +0 -0
  197. package/libs/util/gcanvas/context-2d/RenderingContext.js +0 -0
  198. package/libs/util/gcanvas/context-webgl/ActiveInfo.js +0 -0
  199. package/libs/util/gcanvas/context-webgl/Buffer.js +0 -0
  200. package/libs/util/gcanvas/context-webgl/Framebuffer.js +0 -0
  201. package/libs/util/gcanvas/context-webgl/GLenum.js +0 -0
  202. package/libs/util/gcanvas/context-webgl/GLmethod.js +0 -0
  203. package/libs/util/gcanvas/context-webgl/GLtype.js +0 -0
  204. package/libs/util/gcanvas/context-webgl/Program.js +0 -0
  205. package/libs/util/gcanvas/context-webgl/Renderbuffer.js +0 -0
  206. package/libs/util/gcanvas/context-webgl/RenderingContext.js +0 -0
  207. package/libs/util/gcanvas/context-webgl/Shader.js +0 -0
  208. package/libs/util/gcanvas/context-webgl/ShaderPrecisionFormat.js +0 -0
  209. package/libs/util/gcanvas/context-webgl/Texture.js +0 -0
  210. package/libs/util/gcanvas/context-webgl/UniformLocation.js +0 -0
  211. package/libs/util/gcanvas/context-webgl/classUtils.js +0 -0
  212. package/libs/util/gcanvas/env/canvas.js +0 -0
  213. package/libs/util/gcanvas/env/image.js +0 -0
  214. package/libs/util/gcanvas/env/tool.js +0 -0
  215. package/libs/util/gcanvas/index.js +3 -3
  216. package/package.json +4 -5
  217. package/tsconfig.json +18 -0
  218. package/types/comps/calendar.d.ts +38 -0
  219. package/types/comps/checkbox.d.ts +12 -0
  220. package/types/comps/lineProgress.d.ts +5 -0
  221. package/types/comps/navbar.d.ts +3 -3
  222. package/types/comps/swipeActionItem.d.ts +13 -0
  223. package/types/comps/swiper.d.ts +5 -0
  224. package/types/comps/tabs.d.ts +2 -1
  225. package/types/comps/text.d.ts +1 -1
  226. package/types/comps/tooltip.d.ts +36 -1
  227. package/types/func.d.ts +23 -0
  228. package/types/index.d.ts +1 -0
  229. /package/components/u-markdown/{marked.esm.js → marked.esm.mjs} +0 -0
@@ -1,423 +1,1028 @@
1
- <template>
2
- <view class="u-canvas"
3
- :id="rootId"
4
- :style="{
5
- width: useRootHeightAndWidth ? '100%' : 'auto',
6
- height: useRootHeightAndWidth ? '100%' : 'auto',
7
- }">
8
- <!-- #ifdef MP || H5 -->
9
- <canvas
10
- class="u-canvas__canvas"
11
- :id="canvasId"
12
- :canvas-id="canvasId"
13
- type="2d"
14
- :style="{ width: width + unit, height: height + unit }"
15
- @touchstart="onTouchStart"
16
- @touchmove="onTouchMove"
17
- @touchend="onTouchEnd"/>
18
- <!-- #endif -->
19
-
20
- <!-- #ifdef APP-PLUS -->
21
- <canvas
22
- class="u-canvas__canvas"
23
- :id="canvasId"
24
- :canvas-id="canvasId"
25
- :style="{ width: width + unit, height: height + unit }"
26
- @touchstart="onTouchStart"
27
- @touchmove="onTouchMove"
28
- @touchend="onTouchEnd"/>
29
- <!-- #endif -->
30
-
31
- <!-- #ifdef APP-NVUE -->
32
- <gcanvas class="u-canvas__canvas" ref="gcanvess"
33
- :style="{ width: width + unit, height: height + unit }"
34
- @touchstart="onTouchStart"
35
- @touchmove="onTouchMove"
36
- @touchend="onTouchEnd">
37
- </gcanvas>
38
- <!-- #endif -->
39
- </view>
40
- </template>
41
-
42
- <script>
43
- // #ifdef APP-NVUE
44
- // https://github.com/dcloudio/NvueCanvasDemo/blob/master/README.md
45
- import {
46
- enable,
47
- WeexBridge,
48
- Image as GImage
49
- } from '../../libs/util/gcanvas/index.js';
50
- // #endif
51
- let canvasNode = null;
52
- export default {
53
- name: "u-canvas",
54
- props: {
55
- canvasId: {
56
- type: String,
57
- default: () => {
58
- return `u-canvas${Math.floor(Math.random() * 1000000)}`
59
- }
60
- },
61
- width: {
62
- type: [String, Number],
63
- default: 300
64
- },
65
- height: {
66
- type: [String, Number],
67
- default: 300
68
- },
69
- unit: {
70
- type: String,
71
- default: 'px'
72
- },
73
- useRootHeightAndWidth: {
74
- type: Boolean,
75
- default: false
76
- },
77
- // 背景色
78
- bgColor: {
79
- type: String,
80
- default: '#ffffff'
81
- }
82
- },
83
- data() {
84
- return {
85
- rootId: `rootId${Number(Math.random() * 100).toFixed(0)}`,
86
- ganvas: null,
87
- canvasContext: null,
88
- widthLocal: this.width,
89
- heightLocal: this.height,
90
- ctx: null
91
- };
92
- },
93
- computed: {
94
- // 计算实际画布尺寸
95
- actualWidth() {
96
- return this.useRootHeightAndWidth ? this.widthLocal : Number(this.width);
97
- },
98
- actualHeight() {
99
- return this.useRootHeightAndWidth ? this.heightLocal : Number(this.height);
100
- }
101
- },
102
- methods: {
103
- // 添加触摸事件处理方法
104
- onTouchStart(e) {
105
- this.$emit('touchstart', e);
106
- },
107
- onTouchMove(e) {
108
- this.$emit('touchmove', e);
109
- },
110
- onTouchEnd(e) {
111
- this.$emit('touchend', e);
112
- },
113
-
114
- /**
115
- * 获取节点
116
- * @param id 节点id
117
- * @param isCanvas 是否为Canvas节点
118
- * @return {Promise<unknown>}
119
- */
120
- async getCanvasNode(id, isCanvas = true) {
121
- let that = this
122
- return new Promise((resolve, reject) => {
123
- try {
124
- // #ifdef APP-NVUE
125
- setTimeout(() => {
126
- /*获取元素引用*/
127
- this.ganvas = this.$refs["gcanvess"]
128
- /*通过元素引用获取canvas对象*/
129
- let canvasNode = enable(this.ganvas, {
130
- bridge: WeexBridge
131
- })
132
- resolve(canvasNode)
133
- }, 200)
134
- // #endif
135
- // #ifndef APP-PLUS-NVUE
136
- const query = uni.createSelectorQuery().in(that).select(`#${id}`);
137
- query.fields({
138
- node: true,
139
- size: true
140
- })
141
- .exec((res) => {
142
- if (isCanvas) {
143
- if (res[0]?.node) {
144
- resolve(res[0].node)
145
- } else {
146
- resolve(false)
147
- console.error("获取节点出错", res)
148
- }
149
- } else {
150
- resolve(res[0])
151
- }
152
- })
153
- // #endif
154
- } catch (e) {
155
- console.error("获取节点失败", e)
156
- }
157
- })
158
- },
159
-
160
- /**
161
- * 获取Canvas上下文
162
- */
163
- getCanvasContext() {
164
- // #ifdef APP-PLUS
165
- return uni.createCanvasContext(this.canvasId, this);
166
- // #endif
167
- // #ifdef APP-PLUS-NVUE || MP || H5
168
- return canvasNode.getContext('2d');
169
- // #endif
170
- },
171
-
172
- /**
173
- * 初始化Canvas
174
- */
175
- async initCanvas() {
176
- try {
177
- canvasNode = await this.getCanvasNode(this.canvasId);
178
-
179
- // #ifdef MP-WEIXIN
180
- // 在微信小程序中,为了提高清晰度,需要考虑设备像素比
181
- const dpr = uni.getSystemInfoSync().pixelRatio;
182
- if(canvasNode) {
183
- // 设置canvas实际绘制尺寸为显示尺寸的dpr倍
184
- canvasNode.width = this.actualWidth * dpr;
185
- canvasNode.height = this.actualHeight * dpr;
186
- }
187
- // #endif
188
-
189
- this.ctx = this.getCanvasContext();
190
-
191
- // #ifdef MP-WEIXIN
192
- if(this.ctx) {
193
- this.ctx.scale(dpr, dpr);
194
- }
195
- // #endif
196
-
197
- // 初始化背景,但不在微信小程序中调用draw
198
- this.clearCanvas();
199
- } catch (error) {
200
- console.error("初始化Canvas失败:", error);
201
- }
202
- },
203
-
204
- /**
205
- * 清空画布
206
- */
207
- clearCanvas() {
208
- if (!this.ctx) return;
209
-
210
- this.clearRect(0, 0, this.actualWidth, this.actualHeight);
211
-
212
- // 填充背景色
213
- this.beginPath();
214
- this.rect(0, 0, this.actualWidth, this.actualHeight);
215
-
216
- this.setFillStyle(this.bgColor);
217
- this.fill();
218
-
219
- this.draw();
220
- },
221
- rect(x, y, width, height) {
222
- if (!this.ctx) return;
223
- this.ctx.rect(x, y, width, height);
224
- },
225
- clearRect(x, y, width, height) {
226
- if (!this.ctx) return;
227
- this.ctx.clearRect(x, y, width, height);
228
- },
229
- fill() {
230
- if (!this.ctx) return;
231
- this.ctx.fill();
232
- },
233
- setFillStyle(color) {
234
- if (!this.ctx) return;
235
-
236
- // #ifndef APP-PLUS-NVUE
237
- if (this.ctx.setFillStyle) {
238
- this.ctx.setFillStyle(color);
239
- } else {
240
- this.ctx.fillStyle = color;
241
- }
242
- // #endif
243
- // #ifdef APP-PLUS-NVUE
244
- this.ctx.setFillStyle(color);
245
- // #endif
246
- },
247
-
248
- /**
249
- * 设置线条样式
250
- */
251
- setLineStyle(lineColor, lineWidth) {
252
- if (!this.ctx) return;
253
- this.setLineCap('round');
254
- this.setLineJoin('round');
255
- this.setStrokeStyle(lineColor);
256
- this.setLineWidth(lineWidth);
257
- },
258
- setLineCap(lineCap = 'round') {
259
- if (!this.ctx) return;
260
- if (this.ctx.setLineCap) {
261
- this.ctx.setLineCap(lineCap);
262
- } else {
263
- this.ctx.lineCap = lineCap;
264
- }
265
- },
266
- setLineJoin(lineJoin = 'round') {
267
- if (!this.ctx) return;
268
- if (this.ctx.setLineJoin) {
269
- this.ctx.setLineJoin(lineJoin);
270
- } else {
271
- this.ctx.lineJoin = lineJoin;
272
- }
273
- },
274
- setStrokeStyle(color) {
275
- if (!this.ctx) return;
276
- if (this.ctx.setStrokeStyle) {
277
- this.ctx.setStrokeStyle(color);
278
- } else {
279
- this.ctx.strokeStyle = color;
280
- }
281
- },
282
- setLineWidth(width) {
283
- if (!this.ctx) return;
284
- if (this.ctx.setLineWidth) {
285
- this.ctx.setLineWidth(width);
286
- } else {
287
- this.ctx.lineWidth = width;
288
- }
289
- },
290
-
291
- /**
292
- * 开始路径
293
- */
294
- beginPath() {
295
- if (!this.ctx) return;
296
- this.ctx.beginPath();
297
- },
298
-
299
- /**
300
- * 移动到某点
301
- */
302
- moveTo(x, y) {
303
- if (!this.ctx) return;
304
- this.ctx.moveTo(x, y);
305
- },
306
-
307
- /**
308
- * 画线到某点
309
- */
310
- lineTo(x, y) {
311
- if (!this.ctx) return;
312
- this.ctx.lineTo(x, y);
313
- },
314
-
315
- /**
316
- * 描边
317
- */
318
- stroke() {
319
- if (!this.ctx) return;
320
- this.ctx.stroke();
321
- },
322
-
323
- /**
324
- * 关闭路径
325
- */
326
- closePath() {
327
- if (!this.ctx) return;
328
- this.ctx.closePath();
329
- },
330
-
331
- /**
332
- * 绘制操作
333
- */
334
- draw(isLastDraw = false) {
335
- // #ifndef MP-WEIXIN
336
- if (this.ctx && typeof this.ctx.draw === 'function') {
337
- this.ctx.draw(isLastDraw);
338
- }
339
- // #endif
340
- },
341
-
342
- /**
343
- * 导出图片
344
- */
345
- exportImage(fileType = 'png', quality = 1) {
346
- return new Promise((resolve, reject) => {
347
- // #ifdef MP-WEIXIN
348
- // 微信小程序中需要先完成绘制,然后导出图片
349
- setTimeout(() => {
350
- uni.canvasToTempFilePath({
351
- x: 0,
352
- y: 0,
353
- width: this.actualWidth,
354
- height: this.actualHeight,
355
- destWidth: this.actualWidth * 2, // 使用双倍尺寸以提高清晰度
356
- destHeight: this.actualHeight * 2,
357
- canvas: canvasNode, // 2d必须
358
- canvasId: this.canvasId,
359
- fileType: fileType,
360
- quality: quality,
361
- success: (res) => {
362
- resolve(res.tempFilePath);
363
- },
364
- fail: (err) => {
365
- console.error('导出图片失败:', err);
366
- reject(err);
367
- }
368
- }, this);
369
- }, 50); // 等待50毫秒确保绘制完成
370
- // #endif
371
-
372
- // #ifndef MP-WEIXIN
373
- uni.canvasToTempFilePath({
374
- canvas: canvasNode, // 2d必须
375
- canvasId: this.canvasId,
376
- fileType: fileType,
377
- quality: quality,
378
- success: (res) => {
379
- resolve(res.tempFilePath);
380
- },
381
- fail: (err) => {
382
- console.error('导出图片失败:', err);
383
- reject(err);
384
- }
385
- }, this);
386
- // #endif
387
- });
388
- },
389
-
390
- /**
391
- * 使用根节点宽高 设置新的size
392
- * @return {Promise<void>}
393
- */
394
- async setNewSize(){
395
- const rootNode = await this.getCanvasNode(this.rootId, false);
396
- const { width , height } = rootNode;
397
- this.widthLocal = height;
398
- this.heightLocal = width;
399
- }
400
- },
401
-
402
- async mounted() {
403
- // 如果使用根节点的宽高 则 重新设置 size
404
- if(this.useRootHeightAndWidth){
405
- await this.setNewSize();
406
- }
407
-
408
- // 初始化Canvas
409
- await this.initCanvas();
410
- }
411
- };
412
- </script>
413
-
414
- <style lang="scss" scoped>
415
- .u-canvas {
416
- position: relative;
417
- overflow: hidden;
418
- }
419
-
420
- .u-canvas__canvas {
421
- display: block;
422
- }
423
- </style>
1
+ <template>
2
+ <view
3
+ class="u-canvas"
4
+ :id="rootId"
5
+ :style="{
6
+ width: useRootHeightAndWidth ? '100%' : actualWidth + unit,
7
+ height: useRootHeightAndWidth ? '100%' : actualHeight + unit
8
+ }"
9
+ >
10
+ <!-- #ifdef MP || H5 -->
11
+ <canvas
12
+ class="u-canvas__canvas"
13
+ :id="canvasId"
14
+ :canvas-id="canvasId"
15
+ type="2d"
16
+ :disable-scroll="disableScroll"
17
+ :style="{ width: actualWidth + unit, height: actualHeight + unit }"
18
+ @touchstart="onTouchStart"
19
+ @touchmove="onTouchMove"
20
+ @touchend="onTouchEnd"
21
+ />
22
+ <!-- #endif -->
23
+
24
+ <!-- #ifdef APP-PLUS -->
25
+ <!-- #ifndef APP-NVUE -->
26
+ <canvas
27
+ class="u-canvas__canvas"
28
+ :id="canvasId"
29
+ :canvas-id="canvasId"
30
+ :disable-scroll="disableScroll"
31
+ :style="{ width: actualWidth + unit, height: actualHeight + unit }"
32
+ @touchstart="onTouchStart"
33
+ @touchmove="onTouchMove"
34
+ @touchend="onTouchEnd"
35
+ />
36
+ <!-- #endif -->
37
+ <!-- #endif -->
38
+
39
+ <!-- #ifdef APP-NVUE -->
40
+ <web-view
41
+ class="u-canvas__canvas"
42
+ ref="web"
43
+ src="/static/app-plus/up-canvas/local.html"
44
+ :style="{ width: actualWidth + unit, height: actualHeight + unit }"
45
+ @onPostMessage="onWebViewMessage"
46
+ />
47
+ <!-- #endif -->
48
+ </view>
49
+ </template>
50
+
51
+ <script>
52
+ // #ifdef APP-NVUE
53
+ import {
54
+ createWebViewCanvasBridge,
55
+ createWebViewCanvasContext
56
+ } from '../../libs/util/app-nvue-webview-canvas.js';
57
+ // #endif
58
+
59
+ export default {
60
+ name: 'u-canvas',
61
+ emits: ['ready', 'touchstart', 'touchmove', 'touchend'],
62
+ props: {
63
+ canvasId: {
64
+ type: String,
65
+ default: () => `u-canvas${Math.floor(Math.random() * 1000000)}`
66
+ },
67
+ width: {
68
+ type: [String, Number],
69
+ default: 300
70
+ },
71
+ height: {
72
+ type: [String, Number],
73
+ default: 300
74
+ },
75
+ unit: {
76
+ type: String,
77
+ default: 'px'
78
+ },
79
+ useRootHeightAndWidth: {
80
+ type: Boolean,
81
+ default: false
82
+ },
83
+ bgColor: {
84
+ type: String,
85
+ default: '#ffffff'
86
+ },
87
+ disableScroll: {
88
+ type: Boolean,
89
+ default: false
90
+ }
91
+ },
92
+ data() {
93
+ return {
94
+ rootId: `u-canvas-root-${Math.floor(Math.random() * 1000000)}`,
95
+ ctx: null,
96
+ widthLocal: this.parseSize(this.width),
97
+ heightLocal: this.parseSize(this.height),
98
+ fontSize: 12,
99
+ fontFamily: 'sans-serif',
100
+ fontWeight: 'normal',
101
+ dpr: 1
102
+ };
103
+ },
104
+ computed: {
105
+ actualWidth() {
106
+ return this.useRootHeightAndWidth ? this.widthLocal : this.parseSize(this.width);
107
+ },
108
+ actualHeight() {
109
+ return this.useRootHeightAndWidth ? this.heightLocal : this.parseSize(this.height);
110
+ }
111
+ },
112
+ watch: {
113
+ width() {
114
+ this.refresh();
115
+ },
116
+ height() {
117
+ this.refresh();
118
+ },
119
+ bgColor() {
120
+ this.clearCanvas();
121
+ }
122
+ },
123
+ created() {
124
+ this._canvasNode = null;
125
+ this._selectorResult = null;
126
+ this._canvasElement = null;
127
+ this._imageCache = Object.create(null);
128
+ this._imageDataCache = Object.create(null);
129
+ this._isNvue = false;
130
+ this._initPromise = null;
131
+ this._webViewBridge = null;
132
+ this._webViewContext = null;
133
+ this._webViewInitSignature = null;
134
+ this._webViewSessionId = null;
135
+ },
136
+ mounted() {
137
+ this.$nextTick(() => {
138
+ this.initCanvas();
139
+ });
140
+ },
141
+ beforeUnmount() {
142
+ if (this._webViewBridge) {
143
+ this._webViewBridge.destroy();
144
+ }
145
+ this._webViewBridge = null;
146
+ this._webViewContext = null;
147
+ this.ctx = null;
148
+ },
149
+ methods: {
150
+ parseSize(value) {
151
+ if (typeof value === 'number') {
152
+ return value;
153
+ }
154
+ if (typeof value !== 'string') {
155
+ return 0;
156
+ }
157
+ if (value.endsWith('rpx') || value.endsWith('upx')) {
158
+ return uni.upx2px(parseFloat(value));
159
+ }
160
+ if (value.endsWith('px')) {
161
+ return parseFloat(value) || 0;
162
+ }
163
+ return parseFloat(value) || 0;
164
+ },
165
+ onTouchStart(event) {
166
+ this.$emit('touchstart', event);
167
+ },
168
+ onTouchMove(event) {
169
+ this.$emit('touchmove', event);
170
+ },
171
+ onTouchEnd(event) {
172
+ this.$emit('touchend', event);
173
+ },
174
+ onWebViewMessage(event) {
175
+ if (this._webViewBridge) {
176
+ this._webViewBridge.handleMessage(event);
177
+ }
178
+ },
179
+ onWebViewTouch(message) {
180
+ const eventType = message.eventType;
181
+ if (!['touchstart', 'touchmove', 'touchend'].includes(eventType)) return;
182
+ this.$emit(eventType, {
183
+ type: eventType,
184
+ detail: {
185
+ x: message.x,
186
+ y: message.y
187
+ },
188
+ touches: message.touches || [],
189
+ changedTouches: message.changedTouches || [],
190
+ canvasWidth: message.canvasWidth,
191
+ canvasHeight: message.canvasHeight
192
+ });
193
+ },
194
+ async getCanvasNode(id = this.canvasId, isCanvas = true) {
195
+ return new Promise((resolve) => {
196
+ try {
197
+ uni.createSelectorQuery()
198
+ .in(this)
199
+ .select(`#${id}`)
200
+ .fields(
201
+ {
202
+ node: isCanvas,
203
+ size: true
204
+ },
205
+ (res) => {
206
+ // #ifdef APP-PLUS
207
+ resolve(res || {
208
+ width: this.actualWidth,
209
+ height: this.actualHeight
210
+ });
211
+ return;
212
+ // #endif
213
+
214
+ resolve(res || false);
215
+ }
216
+ )
217
+ .exec();
218
+ } catch (error) {
219
+ console.error('获取画布节点失败:', error);
220
+ resolve(false);
221
+ }
222
+ });
223
+ },
224
+ getCanvasElement() {
225
+ return this._canvasElement || null;
226
+ },
227
+ getRawContext() {
228
+ return this.ctx;
229
+ },
230
+ getCanvasContext() {
231
+ // #ifdef APP-NVUE
232
+ return this._webViewContext;
233
+ // #endif
234
+
235
+ // #ifdef APP-PLUS
236
+ return uni.createCanvasContext(this.canvasId, this);
237
+ // #endif
238
+
239
+ // #ifdef MP || H5
240
+ return this._canvasElement && typeof this._canvasElement.getContext === 'function'
241
+ ? this._canvasElement.getContext('2d')
242
+ : null;
243
+ // #endif
244
+ },
245
+ async setNewSize() {
246
+ const rootNode = await this.getCanvasNode(this.rootId, false);
247
+ if (!rootNode) {
248
+ return;
249
+ }
250
+ if (rootNode.width) {
251
+ this.widthLocal = rootNode.width;
252
+ }
253
+ if (rootNode.height) {
254
+ this.heightLocal = rootNode.height;
255
+ }
256
+ },
257
+ async initCanvas(force = false) {
258
+ if (this._initPromise) {
259
+ return this._initPromise;
260
+ }
261
+
262
+ const initPromise = this._initializeCanvas(force);
263
+ this._initPromise = initPromise;
264
+
265
+ try {
266
+ return await initPromise;
267
+ } finally {
268
+ if (this._initPromise === initPromise) {
269
+ this._initPromise = null;
270
+ }
271
+ }
272
+ },
273
+ async _initializeCanvas(force = false) {
274
+ try {
275
+ if (this.useRootHeightAndWidth) {
276
+ await this.setNewSize();
277
+ }
278
+ // #ifdef APP-NVUE
279
+ if (typeof createWebViewCanvasBridge === 'function') {
280
+ return await this._initializeWebViewCanvas(force);
281
+ }
282
+ // #endif
283
+
284
+ if (this.ctx && !force) {
285
+ this.$emit('ready', {
286
+ width: this.actualWidth,
287
+ height: this.actualHeight
288
+ });
289
+ return true;
290
+ }
291
+
292
+ this._canvasNode = await this.getCanvasNode(this.canvasId);
293
+ if (!this._canvasNode) {
294
+ return false;
295
+ }
296
+
297
+ this._selectorResult = this._canvasNode;
298
+ this._canvasElement = this._isNvue
299
+ ? this._canvasNode
300
+ : (this._canvasNode.node || null);
301
+ const systemInfo = uni.getSystemInfoSync() || {};
302
+ const pixelRatio = Number(systemInfo.pixelRatio) || 1;
303
+ // 微信开发者工具的模拟器预览通常按高密度屏幕缩放,至少使用 2 倍 backing store。
304
+ this.dpr = systemInfo.platform === 'devtools'
305
+ ? Math.max(pixelRatio, 2)
306
+ : pixelRatio;
307
+
308
+ // #ifdef MP
309
+ if (this._canvasElement) {
310
+ this._canvasElement.width = Math.ceil(this.actualWidth * this.dpr);
311
+ this._canvasElement.height = Math.ceil(this.actualHeight * this.dpr);
312
+ }
313
+ // #endif
314
+
315
+ this.ctx = this.getCanvasContext();
316
+ if (!this.ctx) {
317
+ return false;
318
+ }
319
+
320
+ // #ifdef MP
321
+ if (typeof this.ctx.setTransform === 'function') {
322
+ this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
323
+ } else if (typeof this.ctx.scale === 'function') {
324
+ this.ctx.scale(this.dpr, this.dpr);
325
+ }
326
+ // #endif
327
+
328
+ this.applyFont();
329
+ this.clearCanvas();
330
+ this.$emit('ready', {
331
+ width: this.actualWidth,
332
+ height: this.actualHeight
333
+ });
334
+ return true;
335
+ } catch (error) {
336
+ console.error('初始化Canvas失败:', error);
337
+ return false;
338
+ }
339
+ },
340
+ async _initializeWebViewCanvas(force = false) {
341
+ this._isNvue = true;
342
+ const systemInfo = uni.getSystemInfoSync() || {};
343
+ this.dpr = Math.max(1, Number(systemInfo.pixelRatio) || 1);
344
+
345
+ if (!this._webViewBridge) {
346
+ this._webViewBridge = createWebViewCanvasBridge({
347
+ getWebView: () => this.$refs.web,
348
+ onTouch: message => this.onWebViewTouch(message),
349
+ onReady: (message) => {
350
+ if (
351
+ this._webViewSessionId &&
352
+ message.sessionId &&
353
+ this._webViewSessionId !== message.sessionId
354
+ ) {
355
+ this._webViewInitSignature = null;
356
+ }
357
+ this._webViewSessionId = message.sessionId || null;
358
+ },
359
+ onError: error => console.error('Canvas WebView错误:', error)
360
+ });
361
+ this._webViewContext = createWebViewCanvasContext({
362
+ bridge: this._webViewBridge,
363
+ resolveImage: source => this.resolveNvueImageSource(source),
364
+ measureText: (text, state) => {
365
+ const matched = String(state.font || '').match(/(\d+(?:\.\d+)?)px/);
366
+ const fontSize = matched ? Number(matched[1]) : this.fontSize;
367
+ return { width: String(text).length * fontSize * 0.6 };
368
+ }
369
+ });
370
+ this.ctx = this._webViewContext;
371
+ }
372
+
373
+ await this._webViewBridge.ready();
374
+ const signature = [
375
+ this.actualWidth,
376
+ this.actualHeight,
377
+ this.dpr,
378
+ this.disableScroll ? 1 : 0
379
+ ].join(':');
380
+ if (force || this._webViewInitSignature !== signature) {
381
+ await this._webViewBridge.request('init', {
382
+ width: this.actualWidth,
383
+ height: this.actualHeight,
384
+ dpr: this.dpr,
385
+ disableScroll: this.disableScroll
386
+ });
387
+ this._webViewInitSignature = signature;
388
+ this.applyFont();
389
+ await this.clearCanvas();
390
+ }
391
+
392
+ this.$emit('ready', {
393
+ width: this.actualWidth,
394
+ height: this.actualHeight
395
+ });
396
+ return true;
397
+ },
398
+ refresh() {
399
+ return this.initCanvas(true);
400
+ },
401
+ getWidth() {
402
+ return this.actualWidth;
403
+ },
404
+ getHeight() {
405
+ return this.actualHeight;
406
+ },
407
+ clearCanvas() {
408
+ if (!this.ctx) return;
409
+ this.clearRect(0, 0, this.actualWidth, this.actualHeight);
410
+ if (this.bgColor && this.bgColor !== 'transparent') {
411
+ this.beginPath();
412
+ this.rect(0, 0, this.actualWidth, this.actualHeight);
413
+ this.setFillStyle(this.bgColor);
414
+ this.fill();
415
+ }
416
+ return this.draw();
417
+ },
418
+ callContext(method, ...args) {
419
+ if (this.ctx && typeof this.ctx[method] === 'function') {
420
+ return this.ctx[method](...args);
421
+ }
422
+ return undefined;
423
+ },
424
+ rect(x, y, width, height) {
425
+ return this.callContext('rect', x, y, width, height);
426
+ },
427
+ clearRect(x, y, width, height) {
428
+ return this.callContext('clearRect', x, y, width, height);
429
+ },
430
+ fillRect(x, y, width, height) {
431
+ return this.callContext('fillRect', x, y, width, height);
432
+ },
433
+ strokeRect(x, y, width, height) {
434
+ return this.callContext('strokeRect', x, y, width, height);
435
+ },
436
+ fill() {
437
+ return this.callContext('fill');
438
+ },
439
+ stroke() {
440
+ return this.callContext('stroke');
441
+ },
442
+ beginPath() {
443
+ return this.callContext('beginPath');
444
+ },
445
+ closePath() {
446
+ return this.callContext('closePath');
447
+ },
448
+ moveTo(x, y) {
449
+ return this.callContext('moveTo', x, y);
450
+ },
451
+ lineTo(x, y) {
452
+ return this.callContext('lineTo', x, y);
453
+ },
454
+ arc(x, y, radius, startAngle, endAngle, anticlockwise = false) {
455
+ return this.callContext('arc', x, y, radius, startAngle, endAngle, anticlockwise);
456
+ },
457
+ arcTo(x1, y1, x2, y2, radius) {
458
+ return this.callContext('arcTo', x1, y1, x2, y2, radius);
459
+ },
460
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
461
+ return this.callContext('bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y);
462
+ },
463
+ quadraticCurveTo(cpx, cpy, x, y) {
464
+ return this.callContext('quadraticCurveTo', cpx, cpy, x, y);
465
+ },
466
+ ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise = false) {
467
+ return this.callContext(
468
+ 'ellipse',
469
+ x,
470
+ y,
471
+ radiusX,
472
+ radiusY,
473
+ rotation,
474
+ startAngle,
475
+ endAngle,
476
+ anticlockwise
477
+ );
478
+ },
479
+ clip() {
480
+ return this.callContext('clip');
481
+ },
482
+ save() {
483
+ return this.callContext('save');
484
+ },
485
+ restore() {
486
+ return this.callContext('restore');
487
+ },
488
+ translate(x, y) {
489
+ return this.callContext('translate', x, y);
490
+ },
491
+ rotate(angle) {
492
+ return this.callContext('rotate', angle);
493
+ },
494
+ scale(x, y) {
495
+ return this.callContext('scale', x, y);
496
+ },
497
+ setTransform(a, b, c, d, e, f) {
498
+ return this.callContext('setTransform', a, b, c, d, e, f);
499
+ },
500
+ transform(a, b, c, d, e, f) {
501
+ return this.callContext('transform', a, b, c, d, e, f);
502
+ },
503
+ resetTransform() {
504
+ return this.callContext('resetTransform');
505
+ },
506
+ setFillStyle(color) {
507
+ if (!this.ctx) return;
508
+ if (typeof this.ctx.setFillStyle === 'function') {
509
+ this.ctx.setFillStyle(color);
510
+ } else {
511
+ this.ctx.fillStyle = color;
512
+ }
513
+ },
514
+ setStrokeStyle(color) {
515
+ if (!this.ctx) return;
516
+ if (typeof this.ctx.setStrokeStyle === 'function') {
517
+ this.ctx.setStrokeStyle(color);
518
+ } else {
519
+ this.ctx.strokeStyle = color;
520
+ }
521
+ },
522
+ setLineWidth(width) {
523
+ if (!this.ctx) return;
524
+ if (typeof this.ctx.setLineWidth === 'function') {
525
+ this.ctx.setLineWidth(width);
526
+ } else {
527
+ this.ctx.lineWidth = width;
528
+ }
529
+ },
530
+ setLineCap(lineCap = 'round') {
531
+ if (!this.ctx) return;
532
+ if (typeof this.ctx.setLineCap === 'function') {
533
+ this.ctx.setLineCap(lineCap);
534
+ } else {
535
+ this.ctx.lineCap = lineCap;
536
+ }
537
+ },
538
+ setLineJoin(lineJoin = 'round') {
539
+ if (!this.ctx) return;
540
+ if (typeof this.ctx.setLineJoin === 'function') {
541
+ this.ctx.setLineJoin(lineJoin);
542
+ } else {
543
+ this.ctx.lineJoin = lineJoin;
544
+ }
545
+ },
546
+ setMiterLimit(miterLimit) {
547
+ if (!this.ctx) return;
548
+ if (typeof this.ctx.setMiterLimit === 'function') {
549
+ this.ctx.setMiterLimit(miterLimit);
550
+ } else {
551
+ this.ctx.miterLimit = miterLimit;
552
+ }
553
+ },
554
+ setLineDash(segments = []) {
555
+ return this.callContext('setLineDash', segments);
556
+ },
557
+ getLineDash() {
558
+ const result = this.callContext('getLineDash');
559
+ return Array.isArray(result) ? result : [];
560
+ },
561
+ setTextAlign(align = 'left') {
562
+ if (!this.ctx) return;
563
+ if (typeof this.ctx.setTextAlign === 'function') {
564
+ this.ctx.setTextAlign(align);
565
+ } else {
566
+ this.ctx.textAlign = align;
567
+ }
568
+ },
569
+ setTextBaseline(baseline = 'alphabetic') {
570
+ if (!this.ctx) return;
571
+ if (typeof this.ctx.setTextBaseline === 'function') {
572
+ this.ctx.setTextBaseline(baseline);
573
+ } else {
574
+ this.ctx.textBaseline = baseline;
575
+ }
576
+ },
577
+ setFontSize(fontSize) {
578
+ this.fontSize = Number(fontSize) || this.fontSize;
579
+ if (this.ctx && typeof this.ctx.setFontSize === 'function') {
580
+ this.ctx.setFontSize(this.fontSize);
581
+ }
582
+ this.applyFont();
583
+ },
584
+ setFont(font) {
585
+ if (!this.ctx) return;
586
+ if ('font' in this.ctx) {
587
+ this.ctx.font = font;
588
+ return;
589
+ }
590
+ const matched = String(font).match(/(\d+(?:\.\d+)?)px/);
591
+ if (matched) {
592
+ this.fontSize = Number(matched[1]);
593
+ }
594
+ this.applyFont();
595
+ },
596
+ setGlobalAlpha(alpha) {
597
+ if (!this.ctx) return;
598
+ if (typeof this.ctx.setGlobalAlpha === 'function') {
599
+ this.ctx.setGlobalAlpha(alpha);
600
+ } else {
601
+ this.ctx.globalAlpha = alpha;
602
+ }
603
+ },
604
+ setGlobalCompositeOperation(operation) {
605
+ if (!this.ctx) return;
606
+ if (typeof this.ctx.setGlobalCompositeOperation === 'function') {
607
+ this.ctx.setGlobalCompositeOperation(operation);
608
+ } else {
609
+ this.ctx.globalCompositeOperation = operation;
610
+ }
611
+ },
612
+ setShadow(offsetX = 0, offsetY = 0, blur = 0, color = 'rgba(0,0,0,0)') {
613
+ if (!this.ctx) return;
614
+ if (typeof this.ctx.setShadow === 'function') {
615
+ this.ctx.setShadow(offsetX, offsetY, blur, color);
616
+ return;
617
+ }
618
+ this.ctx.shadowOffsetX = offsetX;
619
+ this.ctx.shadowOffsetY = offsetY;
620
+ this.ctx.shadowBlur = blur;
621
+ this.ctx.shadowColor = color;
622
+ },
623
+ setLineStyle(lineColor, lineWidth) {
624
+ this.setLineCap('round');
625
+ this.setLineJoin('round');
626
+ this.setStrokeStyle(lineColor);
627
+ this.setLineWidth(lineWidth);
628
+ },
629
+ applyFont() {
630
+ if (!this.ctx) return;
631
+ const font = `${this.fontWeight === 'normal' ? '' : `${this.fontWeight} `}${this.fontSize}px ${this.fontFamily}`.trim();
632
+ if ('font' in this.ctx) {
633
+ this.ctx.font = font;
634
+ } else if (typeof this.ctx.setFont === 'function') {
635
+ this.ctx.setFont(font);
636
+ } else if (typeof this.ctx.setFontSize === 'function') {
637
+ this.ctx.setFontSize(this.fontSize);
638
+ }
639
+ },
640
+ fillText(text, x, y) {
641
+ return this.callContext('fillText', String(text), x, y);
642
+ },
643
+ strokeText(text, x, y, maxWidth) {
644
+ if (maxWidth === undefined) {
645
+ return this.callContext('strokeText', String(text), x, y);
646
+ }
647
+ return this.callContext('strokeText', String(text), x, y, maxWidth);
648
+ },
649
+ measureText(text) {
650
+ if (this.ctx && typeof this.ctx.measureText === 'function') {
651
+ return this.ctx.measureText(String(text));
652
+ }
653
+ return {
654
+ width: String(text).length * this.fontSize * 0.6
655
+ };
656
+ },
657
+ measureTextAsync(text) {
658
+ if (this.ctx && typeof this.ctx.measureTextAsync === 'function') {
659
+ return this.ctx.measureTextAsync(String(text));
660
+ }
661
+ return Promise.resolve(this.measureText(text));
662
+ },
663
+ createLinearGradient(x0, y0, x1, y1) {
664
+ if (this.ctx && typeof this.ctx.createLinearGradient === 'function') {
665
+ return this.ctx.createLinearGradient(x0, y0, x1, y1);
666
+ }
667
+ return null;
668
+ },
669
+ createRadialGradient(x0, y0, r0, x1, y1, r1) {
670
+ if (this.ctx && typeof this.ctx.createRadialGradient === 'function') {
671
+ return this.ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
672
+ }
673
+ return null;
674
+ },
675
+ createPattern(image, repetition = 'repeat') {
676
+ if (this.ctx && typeof this.ctx.createPattern === 'function') {
677
+ return this.ctx.createPattern(image, repetition);
678
+ }
679
+ return null;
680
+ },
681
+ async resolveNvueImageSource(src) {
682
+ if (typeof src !== 'string' || src.indexOf('data:image/') === 0) {
683
+ return src;
684
+ }
685
+ if (this._imageDataCache[src]) {
686
+ return this._imageDataCache[src];
687
+ }
688
+
689
+ const task = (async () => {
690
+ const localPath = await new Promise((resolve, reject) => {
691
+ uni.getImageInfo({
692
+ src,
693
+ success: res => resolve(res.path || res.tempFilePath),
694
+ fail: reject
695
+ });
696
+ });
697
+ if (!localPath) {
698
+ throw new Error(`Canvas图片未返回本地路径: ${src}`);
699
+ }
700
+ return this.readNvueFileAsDataURL(localPath);
701
+ })();
702
+ this._imageDataCache[src] = task;
703
+ try {
704
+ const dataUrl = await task;
705
+ this._imageDataCache[src] = dataUrl;
706
+ return dataUrl;
707
+ } catch (error) {
708
+ delete this._imageDataCache[src];
709
+ throw error;
710
+ }
711
+ },
712
+ readNvueFileAsDataURL(path) {
713
+ return new Promise((resolve, reject) => {
714
+ // #ifdef APP-NVUE
715
+ if (typeof plus === 'undefined' || !plus.io) {
716
+ reject(new Error('HTML5+ FileReader不可用'));
717
+ return;
718
+ }
719
+ plus.io.resolveLocalFileSystemURL(path, (entry) => {
720
+ entry.file((file) => {
721
+ const reader = new plus.io.FileReader();
722
+ reader.onloadend = event => resolve(event.target.result);
723
+ reader.onerror = reject;
724
+ reader.readAsDataURL(file);
725
+ }, reject);
726
+ }, reject);
727
+ return;
728
+ // #endif
729
+
730
+ resolve(path);
731
+ });
732
+ },
733
+ loadImage(src) {
734
+ // #ifdef APP-NVUE
735
+ return this.resolveNvueImageSource(src);
736
+ // #endif
737
+
738
+ if (this._imageCache[src]) {
739
+ return Promise.resolve(this._imageCache[src]);
740
+ }
741
+ return new Promise((resolve, reject) => {
742
+ let image = null;
743
+
744
+ // #ifdef MP
745
+ const canvas = this.getCanvasElement();
746
+ if (canvas && typeof canvas.createImage === 'function') {
747
+ image = canvas.createImage();
748
+ }
749
+ // #endif
750
+
751
+ // #ifdef H5
752
+ image = new Image();
753
+ image.crossOrigin = 'anonymous';
754
+ // #endif
755
+
756
+ if (!image) {
757
+ resolve(src);
758
+ return;
759
+ }
760
+
761
+ image.onload = () => {
762
+ this._imageCache[src] = image;
763
+ resolve(image);
764
+ };
765
+ image.onerror = reject;
766
+ image.src = src;
767
+ });
768
+ },
769
+ async drawImage(source, ...args) {
770
+ if (!this.ctx || typeof this.ctx.drawImage !== 'function') {
771
+ return false;
772
+ }
773
+ if (typeof source !== 'string') {
774
+ this.ctx.drawImage(source, ...args);
775
+ return true;
776
+ }
777
+ if (typeof this.ctx.setFillStyle === 'function' && !this._isNvue) {
778
+ this.ctx.drawImage(source, ...args);
779
+ return true;
780
+ }
781
+ const image = await this.loadImage(source);
782
+ this.ctx.drawImage(image, ...args);
783
+ return true;
784
+ },
785
+ draw(isLastDraw = false, callback) {
786
+ if (this.ctx && typeof this.ctx.draw === 'function') {
787
+ return this.ctx.draw(isLastDraw, callback);
788
+ }
789
+ if (typeof callback === 'function') {
790
+ setTimeout(callback, 0);
791
+ }
792
+ return undefined;
793
+ },
794
+ toTempFilePath(options = {}) {
795
+ return new Promise((resolve, reject) => {
796
+ const width = options.width || this.actualWidth;
797
+ const height = options.height || this.actualHeight;
798
+ const hasDestWidth = options.destWidth !== undefined && options.destWidth !== null;
799
+ const hasDestHeight = options.destHeight !== undefined && options.destHeight !== null;
800
+ let destWidth = hasDestWidth ? options.destWidth : width;
801
+ let destHeight = hasDestHeight ? options.destHeight : height;
802
+
803
+ // MP、H5 和 APP-PLUS 保持逻辑绘制坐标,只提升默认导出像素。
804
+ // #ifdef MP || H5 || APP-PLUS
805
+ if (!hasDestWidth) {
806
+ destWidth = Math.round(width * this.dpr);
807
+ }
808
+ if (!hasDestHeight) {
809
+ destHeight = Math.round(height * this.dpr);
810
+ }
811
+ // #endif
812
+
813
+ const defaultFileType = 'png';
814
+ const request = {
815
+ x: options.x || 0,
816
+ y: options.y || 0,
817
+ width,
818
+ height,
819
+ destWidth,
820
+ destHeight,
821
+ fileType: options.fileType === undefined || options.fileType === null
822
+ ? defaultFileType
823
+ : options.fileType,
824
+ quality: options.quality === undefined ? 1 : options.quality
825
+ };
826
+ const success = (res) => {
827
+ if (typeof options.success === 'function') options.success(res);
828
+ resolve(res);
829
+ };
830
+ const fail = (err) => {
831
+ if (typeof options.fail === 'function') options.fail(err);
832
+ reject(err);
833
+ };
834
+ const complete = (res) => {
835
+ if (typeof options.complete === 'function') options.complete(res);
836
+ };
837
+
838
+ // #ifdef H5
839
+ const canvas = this.getCanvasElement() || (this.ctx && this.ctx.canvas);
840
+ if (canvas && typeof canvas.toDataURL === 'function') {
841
+ try {
842
+ let exportCanvas = canvas;
843
+ if (
844
+ request.x !== 0 ||
845
+ request.y !== 0 ||
846
+ request.width !== this.actualWidth ||
847
+ request.height !== this.actualHeight ||
848
+ request.destWidth !== request.width ||
849
+ request.destHeight !== request.height
850
+ ) {
851
+ exportCanvas = document.createElement('canvas');
852
+ exportCanvas.width = request.destWidth;
853
+ exportCanvas.height = request.destHeight;
854
+ const exportCtx = exportCanvas.getContext('2d');
855
+ exportCtx.drawImage(
856
+ canvas,
857
+ request.x * this.dpr,
858
+ request.y * this.dpr,
859
+ request.width * this.dpr,
860
+ request.height * this.dpr,
861
+ 0,
862
+ 0,
863
+ request.destWidth,
864
+ request.destHeight
865
+ );
866
+ }
867
+ const mime = request.fileType === 'jpg' || request.fileType === 'jpeg'
868
+ ? 'image/jpeg'
869
+ : 'image/png';
870
+ const res = {
871
+ tempFilePath: exportCanvas.toDataURL(mime, request.quality)
872
+ };
873
+ success(res);
874
+ complete(res);
875
+ return;
876
+ } catch (error) {
877
+ fail(error);
878
+ complete(error);
879
+ return;
880
+ }
881
+ }
882
+ // #endif
883
+
884
+ // #ifdef APP-NVUE
885
+ if (this.ctx && typeof this.ctx.toTempFilePath === 'function') {
886
+ Promise.resolve(this.ctx.toTempFilePath(request)).then(
887
+ (res) => {
888
+ success(res);
889
+ complete(res);
890
+ },
891
+ (error) => {
892
+ fail(error);
893
+ complete(error);
894
+ }
895
+ );
896
+ return;
897
+ }
898
+ // #endif
899
+
900
+ const canvasNode = this.getCanvasElement();
901
+ const uniOptions = {
902
+ ...request,
903
+ canvasId: this.canvasId,
904
+ success,
905
+ fail,
906
+ complete
907
+ };
908
+ if (canvasNode) {
909
+ uniOptions.canvas = canvasNode;
910
+ }
911
+ uni.canvasToTempFilePath(uniOptions, this);
912
+ });
913
+ },
914
+ async exportImage(fileType = 'png', quality = 1) {
915
+ const res = await this.toTempFilePath({
916
+ fileType,
917
+ quality,
918
+ width: this.actualWidth,
919
+ height: this.actualHeight
920
+ });
921
+ return res.tempFilePath || res.apFilePath;
922
+ },
923
+ getImageData(options = {}) {
924
+ return new Promise((resolve, reject) => {
925
+ const success = (res) => {
926
+ if (typeof options.success === 'function') options.success(res);
927
+ resolve(res);
928
+ };
929
+ const fail = (err) => {
930
+ if (typeof options.fail === 'function') options.fail(err);
931
+ reject(err);
932
+ };
933
+ const complete = (res) => {
934
+ if (typeof options.complete === 'function') options.complete(res);
935
+ };
936
+ // #ifdef APP-NVUE
937
+ if (this.ctx && typeof this.ctx.getImageData === 'function') {
938
+ Promise.resolve(this.ctx.getImageData({
939
+ x: options.x || 0,
940
+ y: options.y || 0,
941
+ width: options.width || this.actualWidth,
942
+ height: options.height || this.actualHeight
943
+ })).then((res) => {
944
+ success(res);
945
+ complete(res);
946
+ }, (error) => {
947
+ fail(error);
948
+ complete(error);
949
+ });
950
+ return;
951
+ }
952
+ // #endif
953
+
954
+ const request = {
955
+ canvasId: this.canvasId,
956
+ x: options.x || 0,
957
+ y: options.y || 0,
958
+ width: options.width || this.actualWidth,
959
+ height: options.height || this.actualHeight,
960
+ success,
961
+ fail,
962
+ complete
963
+ };
964
+ uni.canvasGetImageData(request, this);
965
+ });
966
+ },
967
+ putImageData(options = {}) {
968
+ return new Promise((resolve, reject) => {
969
+ const success = (res) => {
970
+ if (typeof options.success === 'function') options.success(res);
971
+ resolve(res);
972
+ };
973
+ const fail = (err) => {
974
+ if (typeof options.fail === 'function') options.fail(err);
975
+ reject(err);
976
+ };
977
+ const complete = (res) => {
978
+ if (typeof options.complete === 'function') options.complete(res);
979
+ };
980
+ // #ifdef APP-NVUE
981
+ if (this.ctx && typeof this.ctx.putImageData === 'function') {
982
+ Promise.resolve(this.ctx.putImageData({
983
+ x: options.x || 0,
984
+ y: options.y || 0,
985
+ width: options.width || (options.data && options.data.width) || this.actualWidth,
986
+ height: options.height || (options.data && options.data.height) || this.actualHeight,
987
+ data: options.data
988
+ })).then((res) => {
989
+ success(res);
990
+ complete(res);
991
+ }, (error) => {
992
+ fail(error);
993
+ complete(error);
994
+ });
995
+ return;
996
+ }
997
+ // #endif
998
+
999
+ const request = {
1000
+ canvasId: this.canvasId,
1001
+ x: options.x || 0,
1002
+ y: options.y || 0,
1003
+ width: options.width || (options.data && options.data.width) || this.actualWidth,
1004
+ height: options.height || (options.data && options.data.height) || this.actualHeight,
1005
+ data: options.data,
1006
+ success,
1007
+ fail,
1008
+ complete
1009
+ };
1010
+ uni.canvasPutImageData(request, this);
1011
+ });
1012
+ }
1013
+ }
1014
+ };
1015
+ </script>
1016
+
1017
+ <style lang="scss" scoped>
1018
+ .u-canvas {
1019
+ position: relative;
1020
+ overflow: hidden;
1021
+ }
1022
+
1023
+ .u-canvas__canvas {
1024
+ display: block;
1025
+ width: 100%;
1026
+ height: 100%;
1027
+ }
1028
+ </style>