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,24 +1,31 @@
1
1
  <template>
2
2
  <view class="up-poster">
3
3
  <!-- canvas用于绘制海报 -->
4
- <canvas
5
- v-if="showCanvas"
6
- class="up-poster__hidden-canvas"
7
- :canvas-id="canvasId"
8
- :id="canvasId"
9
- :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
10
- </canvas>
4
+ <view v-if="showCanvas" class="up-poster__renderer">
5
+ <up-canvas
6
+ ref="posterCanvas"
7
+ class="up-poster__hidden-canvas"
8
+ :canvas-id="canvasId"
9
+ :width="canvasWidth"
10
+ :height="canvasHeight"
11
+ bg-color="transparent"
12
+ :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
13
+ </up-canvas>
14
+ </view>
11
15
  <!-- 隐藏的二维码组件,用于生成二维码图片 -->
12
- <up-qrcode
13
- ref="qrCode"
14
- :val="qrCodeValue"
15
- :size="qrCodeSize"
16
- :margin="0"
17
- :loadMake="false"
18
- background="#ffffff"
19
- foreground="#000000"
20
- :class="['up-poster__hidden-qrcode', qrCodeShow ? '' : 'up-poster__hidden-qrcode--hidden']"
21
- />
16
+ <view class="up-poster__renderer">
17
+ <up-qrcode
18
+ ref="qrCode"
19
+ :val="qrCodeValue"
20
+ :size="qrCodeSize"
21
+ :margin="0"
22
+ :loadMake="false"
23
+ :onval="false"
24
+ background="#ffffff"
25
+ foreground="#000000"
26
+ :class="['up-poster__hidden-qrcode', qrCodeShow ? '' : 'up-poster__hidden-qrcode--hidden']"
27
+ />
28
+ </view>
22
29
  </view>
23
30
  </template>
24
31
 
@@ -86,80 +93,89 @@ export default {
86
93
  * @author jry ijry@qq.com
87
94
  */
88
95
  async exportImage() {
89
- return new Promise(async(resolve, reject) => {
90
- try {
91
- // 获取海报尺寸信息
92
- const posterSize = this.json.css;
93
- // 将rpx转换为px
94
- const width = this.convertRpxToPx(posterSize.width || '750rpx');
95
- const height = this.convertRpxToPx(posterSize.height || '1114rpx');
96
-
97
- // 设置canvas尺寸
98
- this.canvasWidth = width;
99
- this.canvasHeight = height;
100
- this.showCanvas = true;
101
-
102
- // 等待DOM更新
103
- await this.$nextTick();
104
-
105
- // 创建canvas上下文
106
- const ctx = uni.createCanvasContext(this.canvasId, this);
107
-
108
- // 绘制背景
109
- if (posterSize.background) {
110
- // 支持渐变背景色
96
+ let timeoutId = null;
97
+ try {
98
+ const timeoutPromise = new Promise((resolve, reject) => {
99
+ timeoutId = setTimeout(() => reject(new Error('导出图片超时')), 20000);
100
+ });
101
+ const exportPromise = (async () => {
102
+ // 获取海报尺寸信息
103
+ const posterSize = this.json.css || {};
104
+ const width = this.convertRpxToPx(posterSize.width || '750rpx');
105
+ const height = this.convertRpxToPx(posterSize.height || '1114rpx');
106
+
107
+ this.canvasWidth = width;
108
+ this.canvasHeight = height;
109
+ this.showCanvas = true;
110
+ await this.$nextTick();
111
+
112
+ const posterCanvas = await this.getPosterCanvas();
113
+ const ctx = posterCanvas;
114
+ if (posterSize.background) {
111
115
  if (posterSize.background.includes('linear-gradient') || posterSize.background.includes('radial-gradient')) {
112
116
  this.drawGradientBackground(ctx, posterSize, 0, 0, width, height);
113
117
  } else {
114
118
  ctx.setFillStyle(posterSize.background);
115
- ctx.fillRect(0, 0, width, height);
119
+ ctx.fillRect(0, 0, width, height);
116
120
  }
117
- }
118
-
119
- // 绘制所有元素
120
- for (const item of this.json.views) {
121
- await this.drawItem(ctx, item, width, height);
122
- }
123
-
124
- // 绘制到canvas
125
- ctx.draw(false, () => {
126
- // 等待绘制完成
127
- setTimeout(() => {
128
- // 导出图片
129
- uni.canvasToTempFilePath({
130
- canvasId: this.canvasId,
131
- success: (res) => {
132
- // 隐藏canvas
133
- this.showCanvas = false;
134
- // 返回图片路径
135
- resolve({
136
- width: width,
137
- height: height,
138
- path: res.tempFilePath,
139
- // H5下添加blob格式
140
- blob: this.dataURLToBlob(res.tempFilePath)
141
- });
142
- },
143
- fail: (err) => {
144
- // 隐藏canvas
145
- this.showCanvas = false;
146
- reject(new Error('导出图片失败: ' + JSON.stringify(err)));
147
- }
148
- }, this);
149
- }, 300);
150
- });
151
-
152
- // 超时处理
153
- setTimeout(() => {
154
- this.showCanvas = false;
155
- reject(new Error('导出图片超时'));
156
- }, 10000);
157
- } catch (error) {
158
- this.showCanvas = false;
159
- reject(error);
160
- }
121
+ }
122
+
123
+ for (const item of this.json.views || []) {
124
+ await this.drawItem(ctx, item, width, height);
125
+ }
126
+
127
+ await this.flushPosterCanvas(posterCanvas);
128
+ const res = await posterCanvas.toTempFilePath({ width, height });
129
+ const path = res && (res.tempFilePath || res.apFilePath);
130
+ if (!path) {
131
+ throw new Error('导出图片未返回文件路径');
132
+ }
133
+ return {
134
+ width,
135
+ height,
136
+ path,
137
+ blob: this.dataURLToBlob(path)
138
+ };
139
+ })();
140
+ return await Promise.race([exportPromise, timeoutPromise]);
141
+ } finally {
142
+ if (timeoutId) clearTimeout(timeoutId);
143
+ this.showCanvas = false;
144
+ }
145
+ },
146
+
147
+ flushPosterCanvas(posterCanvas) {
148
+ return new Promise((resolve, reject) => {
149
+ let settled = false;
150
+ const finish = (error) => {
151
+ if (settled) return;
152
+ settled = true;
153
+ if (error) reject(error);
154
+ else resolve();
155
+ };
156
+ try {
157
+ const result = posterCanvas.draw(false, () => finish());
158
+ if (result && typeof result.then === 'function') {
159
+ result.then(() => finish(), finish);
160
+ }
161
+ } catch (error) {
162
+ finish(error);
163
+ }
161
164
  });
162
165
  },
166
+
167
+ async getPosterCanvas() {
168
+ await this.$nextTick();
169
+ const posterCanvas = this.$refs.posterCanvas;
170
+ if (!posterCanvas) {
171
+ throw new Error('无法获取海报画布实例');
172
+ }
173
+ const initialized = await posterCanvas.initCanvas();
174
+ if (!initialized || !posterCanvas.getRawContext()) {
175
+ throw new Error('无法初始化海报画布');
176
+ }
177
+ return posterCanvas;
178
+ },
163
179
 
164
180
  /**
165
181
  * 绘制单个元素
@@ -200,7 +216,7 @@ export default {
200
216
 
201
217
  case 'text':
202
218
  // 设置文本样式
203
- if (css.color) ctx.setFillStyle(css.color);
219
+ ctx.setFillStyle(css.color || '#000000');
204
220
  if (css.fontSize) {
205
221
  const fontSize = this.convertRpxToPx(css.fontSize);
206
222
  ctx.setFontSize(fontSize);
@@ -211,7 +227,7 @@ export default {
211
227
 
212
228
  // 处理文本换行
213
229
  if (css.lineClamp) {
214
- this.drawTextWithLineClamp(ctx, item.text, left, top, width, css);
230
+ await this.drawTextWithLineClamp(ctx, item.text, left, top, width, css);
215
231
  } else {
216
232
  // 修复:文本垂直居中对齐问题
217
233
  const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
@@ -221,28 +237,27 @@ export default {
221
237
 
222
238
  case 'image':
223
239
  // 绘制图片
224
- return new Promise((resolve) => {
240
+ return new Promise((resolve, reject) => {
225
241
  uni.getImageInfo({
226
242
  src: item.src,
227
- success: (res) => {
228
- // console.log('图片加载成功: ' + item.src, res);
229
- // 处理圆角
230
- if (css.radius) {
231
- const radius = this.convertRpxToPx(css.radius);
232
- this.clipRoundRect(ctx, left, top, width, height, radius);
243
+ success: async (res) => {
244
+ let clipped = false;
245
+ try {
246
+ if (css.radius) {
247
+ const radius = this.convertRpxToPx(css.radius);
248
+ this.clipRoundRect(ctx, left, top, width, height, radius);
249
+ clipped = true;
250
+ }
251
+ await ctx.drawImage(res.path, left, top, width, height);
252
+ resolve();
253
+ } catch (error) {
254
+ reject(new Error(`海报图片绘制失败: ${error && error.message ? error.message : error}`));
255
+ } finally {
256
+ if (clipped) ctx.restore();
233
257
  }
234
- // 不能用item.src,要用res.path。
235
- ctx.drawImage(res.path, left, top, width, height);
236
- // 恢复剪切区域
237
- ctx.restore();
238
- resolve();
239
258
  },
240
259
  fail: (e) => {
241
- // 图片加载失败时绘制占位符
242
- ctx.setFillStyle('#f5f5f5');
243
- ctx.fillRect(left, top, width, height);
244
- console.log('图片加载失败: ' + item.src, e);
245
- resolve();
260
+ reject(new Error(`海报图片加载失败: ${e && e.errMsg ? e.errMsg : e}`));
246
261
  }
247
262
  });
248
263
  });
@@ -252,26 +267,10 @@ export default {
252
267
  if (item.text) {
253
268
  // 使用u-qrcode生成二维码图片
254
269
  const qrCodeImageUrl = await this.generateQRCode(item.text, width, height);
255
- return new Promise((resolve) => {
256
- uni.getImageInfo({
257
- src: qrCodeImageUrl,
258
- success: (res) => {
259
- ctx.drawImage(res.path, left, top, width, height);
260
- resolve();
261
- },
262
- fail: () => {
263
- // 二维码加载失败时绘制占位符
264
- ctx.setFillStyle('#f5f5f5');
265
- ctx.fillRect(left, top, width, height);
266
- ctx.setFillStyle('#999');
267
- ctx.setFontSize(12);
268
- ctx.setTextAlign('center');
269
- ctx.fillText('QR', left + width/2, top + height/2);
270
- ctx.setTextAlign('left');
271
- resolve();
272
- }
273
- });
274
- });
270
+ const drawn = await ctx.drawImage(qrCodeImageUrl, left, top, width, height);
271
+ if (drawn === false) {
272
+ throw new Error('二维码图片无法绘制到海报画布');
273
+ }
275
274
  } else {
276
275
  // 绘制二维码占位符
277
276
  ctx.setFillStyle('#f5f5f5');
@@ -354,51 +353,71 @@ export default {
354
353
  * @param {Object} css 样式配置
355
354
  * @author jry ijry@qq.com
356
355
  */
357
- drawTextWithLineClamp(ctx, text, x, y, maxWidth, css) {
358
- const lineClamp = parseInt(css.lineClamp) || 1;
359
- const lineHeight = css.lineHeight ? this.convertRpxToPx(css.lineHeight) : 20;
360
- const lines = [];
361
- let currentLine = '';
362
- const ellipsis = '...';
363
- const appendEllipsis = (line) => {
364
- let fitLine = line;
365
- while (ctx.measureText(fitLine + ellipsis).width > maxWidth && fitLine.length > 0) {
366
- fitLine = fitLine.substring(0, fitLine.length - 1);
367
- }
368
- return fitLine + ellipsis;
369
- };
370
-
371
- for (let i = 0; i < text.length; i++) {
372
- const char = text[i];
373
- const testLine = currentLine + char;
374
- const metrics = ctx.measureText(testLine);
375
-
376
- if (metrics.width > maxWidth && currentLine !== '') {
377
- lines.push(currentLine);
378
-
379
- // 如果已达最大行数,添加省略号并结束
380
- if (lines.length === lineClamp) {
381
- lines[lines.length - 1] = appendEllipsis(currentLine);
382
- break;
383
- }
384
-
385
- currentLine = char;
386
- } else {
387
- currentLine = testLine;
388
- }
389
-
390
- // 处理最后一行
391
- if (i === text.length - 1 && lines.length < lineClamp) {
392
- lines.push(currentLine);
393
- }
394
- }
395
-
396
- // 绘制每一行
397
- for (let i = 0; i < lines.length; i++) {
398
- // 修复:正确计算文本垂直位置
399
- const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
400
- ctx.fillText(lines[i], x, y + (i * lineHeight) + textBaseLine);
401
- }
356
+ async drawTextWithLineClamp(ctx, text, x, y, maxWidth, css) {
357
+ const lineClamp = Math.max(1, parseInt(css.lineClamp, 10) || 1);
358
+ const lineHeight = css.lineHeight ? this.convertRpxToPx(css.lineHeight) : 20;
359
+ const characters = Array.from(String(text || ''));
360
+ const lines = [];
361
+ const ellipsis = '...';
362
+ const measurementCache = new Map();
363
+ const measureTextWidth = async (value) => {
364
+ if (measurementCache.has(value)) {
365
+ return measurementCache.get(value);
366
+ }
367
+ let metrics;
368
+ if (ctx && typeof ctx.measureTextAsync === 'function') {
369
+ metrics = await ctx.measureTextAsync(value);
370
+ } else {
371
+ metrics = ctx.measureText(value);
372
+ }
373
+ const measuredWidth = Number(metrics && metrics.width) || 0;
374
+ measurementCache.set(value, measuredWidth);
375
+ return measuredWidth;
376
+ };
377
+ const findTextFitLength = async (values, suffix = '') => {
378
+ let low = 0;
379
+ let high = values.length;
380
+ while (low < high) {
381
+ const middle = Math.ceil((low + high) / 2);
382
+ const candidate = values.slice(0, middle).join('') + suffix;
383
+ if (await measureTextWidth(candidate) <= maxWidth) {
384
+ low = middle;
385
+ } else {
386
+ high = middle - 1;
387
+ }
388
+ }
389
+ return low;
390
+ };
391
+
392
+ let offset = 0;
393
+ while (offset < characters.length && lines.length < lineClamp) {
394
+ const remaining = characters.slice(offset);
395
+ const remainingText = remaining.join('');
396
+ if (await measureTextWidth(remainingText) <= maxWidth) {
397
+ lines.push(remainingText);
398
+ break;
399
+ }
400
+
401
+ const isLastLine = lines.length === lineClamp - 1;
402
+ if (isLastLine) {
403
+ let suffix = ellipsis;
404
+ while (suffix && await measureTextWidth(suffix) > maxWidth) {
405
+ suffix = suffix.slice(0, -1);
406
+ }
407
+ const fitLength = await findTextFitLength(remaining, suffix);
408
+ lines.push(remaining.slice(0, fitLength).join('') + suffix);
409
+ break;
410
+ }
411
+
412
+ const fitLength = Math.max(1, await findTextFitLength(remaining));
413
+ lines.push(remaining.slice(0, fitLength).join(''));
414
+ offset += fitLength;
415
+ }
416
+
417
+ const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
418
+ for (let i = 0; i < lines.length; i++) {
419
+ ctx.fillText(lines[i], x, y + (i * lineHeight) + textBaseLine);
420
+ }
402
421
  },
403
422
 
404
423
  /**
@@ -410,54 +429,41 @@ export default {
410
429
  * @returns {Promise<String>} 二维码图片URL
411
430
  * @author jry ijry@qq.com
412
431
  */
413
- generateQRCode(text, width, height) {
414
- return new Promise((resolve) => {
415
- // 为每个二维码生成唯一标识
416
- const qrCodeKey = `${text}_${width}_${height}`;
417
-
418
- // 检查是否已经生成过该二维码
419
- if (this.qrCodeMap.has(qrCodeKey)) {
420
- resolve(this.qrCodeMap.get(qrCodeKey));
421
- return;
432
+ async generateQRCode(text, width, height) {
433
+ const qrCodeKey = `${text}_${width}_${height}`;
434
+ if (this.qrCodeMap.has(qrCodeKey)) {
435
+ return this.qrCodeMap.get(qrCodeKey);
436
+ }
437
+
438
+ this.qrCodeValue = text;
439
+ this.qrCodeSize = Math.max(width, height);
440
+ this.qrCodeShow = true;
441
+
442
+ try {
443
+ await this.$nextTick();
444
+ const qrCode = this.$refs.qrCode;
445
+ if (!qrCode) {
446
+ throw new Error('无法获取二维码组件实例');
422
447
  }
423
-
424
- // 使用 u-qrcode 组件生成二维码
425
- try {
426
- // 设置二维码参数
427
- this.qrCodeValue = text;
428
- this.qrCodeSize = Math.max(width, height);
429
- this.qrCodeShow = true;
430
-
431
- // 等待DOM更新
432
- this.$nextTick(() => {
433
- // 获取二维码组件实例并导出图片
434
- if (this.$refs.qrCode) {
435
- // 延迟一点时间确保二维码渲染完成
436
- setTimeout(() => {
437
- // 调用 u-qrcode 的 toTempFilePath 方法导出图片
438
- this.$refs.qrCode.toTempFilePath({
439
- success: (res) => {
440
- // 缓存二维码图片路径
441
- this.qrCodeMap.set(qrCodeKey, res.tempFilePath);
442
- this.qrCodeShow = false;
443
- resolve(res.tempFilePath);
444
- },
445
- fail: (err) => {
446
- console.error('二维码生成失败:', err);
447
- this.qrCodeShow = false;
448
- }
449
- });
450
- }, 300);
451
- } else {
452
- // 如果没有 u-qrcode 组件,返回占位符
453
- this.qrCodeShow = false;
454
- }
455
- });
456
- } catch (error) {
457
- console.error('生成二维码出错:', error);
458
- this.qrCodeShow = false;
448
+ if (typeof qrCode.refreshCanvas === 'function') {
449
+ await qrCode.refreshCanvas();
459
450
  }
460
- });
451
+ const generatedPath = await qrCode._makeCode();
452
+ let tempFilePath = typeof generatedPath === 'string' ? generatedPath : '';
453
+ if (!tempFilePath) {
454
+ const res = await qrCode.toTempFilePath();
455
+ tempFilePath = res && (res.tempFilePath || res.apFilePath);
456
+ }
457
+ if (!tempFilePath) {
458
+ throw new Error('二维码组件未返回图片路径');
459
+ }
460
+ this.qrCodeMap.set(qrCodeKey, tempFilePath);
461
+ return tempFilePath;
462
+ } catch (error) {
463
+ throw new Error(`二维码生成失败: ${error && error.message ? error.message : error}`);
464
+ } finally {
465
+ this.qrCodeShow = false;
466
+ }
461
467
  },
462
468
 
463
469
  /**
@@ -610,6 +616,31 @@ export default {
610
616
  overflow: hidden;
611
617
  }
612
618
 
619
+ /* #ifdef APP-NVUE */
620
+ &__renderer {
621
+ position: fixed;
622
+ top: -1px;
623
+ left: -1px;
624
+ width: 1px;
625
+ height: 1px;
626
+ overflow: hidden;
627
+ z-index: -1;
628
+ opacity: 0.01;
629
+ pointer-events: none;
630
+ }
631
+ /* #endif */
632
+
633
+ /* #ifndef APP-NVUE */
634
+ &__renderer {
635
+ position: fixed;
636
+ top: -10000px;
637
+ left: -10000px;
638
+ width: 1px;
639
+ height: 1px;
640
+ overflow: hidden;
641
+ z-index: -1;
642
+ }
643
+
613
644
  &__hidden-canvas {
614
645
  position: fixed;
615
646
  top: -10000px;
@@ -627,5 +658,6 @@ export default {
627
658
  display: none;
628
659
  }
629
660
  }
661
+ /* #endif */
630
662
  }
631
663
  </style>