uview-plus 3.8.86 → 3.8.108

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 (61) hide show
  1. package/changelog.md +173 -3
  2. package/components/u-action-sheet/u-action-sheet.vue +21 -2
  3. package/components/u-barcode/u-barcode.vue +0 -2
  4. package/components/u-button/u-button.vue +2 -5
  5. package/components/u-canvas/u-canvas.vue +430 -77
  6. package/components/u-cell/u-cell.vue +8 -0
  7. package/components/u-datetime-picker/u-datetime-picker.vue +53 -4
  8. package/components/u-dragsort/u-dragsort.vue +55 -31
  9. package/components/u-icon/u-icon.vue +12 -3
  10. package/components/u-icon/util.js +81 -10
  11. package/components/u-index-item/u-index-item.vue +1 -1
  12. package/components/u-index-list/u-index-list.vue +1 -1
  13. package/components/u-novel-reader/content-normalizer.js +80 -0
  14. package/components/u-novel-reader/layout-engine.js +225 -0
  15. package/components/u-novel-reader/measure-adapter.js +69 -0
  16. package/components/u-novel-reader/novelReader.js +35 -0
  17. package/components/u-novel-reader/persistence.js +144 -0
  18. package/components/u-novel-reader/props.js +100 -0
  19. package/components/u-novel-reader/reader-catalog.vue +234 -0
  20. package/components/u-novel-reader/reader-content.vue +226 -0
  21. package/components/u-novel-reader/reader-core.js +151 -0
  22. package/components/u-novel-reader/reader-settings.vue +325 -0
  23. package/components/u-novel-reader/reader-toolbar.vue +313 -0
  24. package/components/u-novel-reader/theme-vars.scss +49 -0
  25. package/components/u-novel-reader/u-novel-reader.vue +836 -0
  26. package/components/u-poster/u-poster.vue +272 -222
  27. package/components/u-qrcode/qrcode.js +56 -49
  28. package/components/u-qrcode/u-qrcode.vue +159 -88
  29. package/components/u-scroll-list/scrollWxs.wxs +1 -1
  30. package/components/u-slider/u-slider.vue +1 -1
  31. package/components/u-swipe-action-item/index.wxs +43 -25
  32. package/components/u-swipe-action-item/nvue.js +9 -0
  33. package/components/u-swipe-action-item/other.js +32 -21
  34. package/components/u-swipe-action-item/props.js +5 -0
  35. package/components/u-swipe-action-item/swipeActionItem.js +1 -0
  36. package/components/u-swipe-action-item/u-swipe-action-item.vue +28 -4
  37. package/components/u-switch/u-switch.vue +1 -1
  38. package/components/u-tabbar/u-tabbar.vue +2 -1
  39. package/components/u-tabbar-item/u-tabbar-item.vue +64 -21
  40. package/components/u-tabs/u-tabs.vue +11 -8
  41. package/components/u-tabs-pro/u-tabs-pro.vue +212 -0
  42. package/components/u-text/text.js +1 -1
  43. package/components/u-text/u-text.vue +18 -6
  44. package/components/u-upload/u-upload.vue +1 -1
  45. package/components/u-waterfall/u-waterfall.vue +118 -65
  46. package/libs/config/config.js +1 -1
  47. package/libs/config/props.js +1 -0
  48. package/libs/function/index.js +66 -0
  49. package/libs/mixin/mixin.js +4 -50
  50. package/libs/root/index.js +78 -5
  51. package/libs/root/page.js +7 -7
  52. package/libs/root/root.js +73 -3
  53. package/libs/util/app-nvue-webview-canvas.js +486 -0
  54. package/libs/util/gcanvas/bridge/bridge-weex.js +0 -2
  55. package/libs/util/gcanvas/index.js +3 -3
  56. package/package.json +2 -2
  57. package/types/comps/swipeActionItem.d.ts +13 -0
  58. package/types/comps/tabs.d.ts +2 -1
  59. package/types/comps/text.d.ts +1 -1
  60. package/types/func.d.ts +23 -0
  61. package/types/index.d.ts +1 -0
@@ -90,6 +90,11 @@
90
90
  }
91
91
  },
92
92
  mixins: [mpMixin, mixin, props],
93
+ inject: {
94
+ uActionSheet: {
95
+ default: null
96
+ }
97
+ },
93
98
  computed: {
94
99
  titleTextStyle() {
95
100
  return addStyle(this.titleStyle)
@@ -123,6 +128,9 @@
123
128
  this.$emit('click', {
124
129
  name: this.name
125
130
  })
131
+ if (this.stop && typeof this.uActionSheet?.slotClickHandler === 'function') {
132
+ this.uActionSheet.slotClickHandler()
133
+ }
126
134
  // 如果配置了url(此props参数通过mixin引入)参数,跳转页面
127
135
  this.openPage()
128
136
  // 是否阻止事件传播
@@ -108,7 +108,10 @@
108
108
  showByClickInput: false, // 是否在hasInput模式下显示日期选择弹唱
109
109
  columns: [],
110
110
  innerDefaultIndex: [],
111
- innerFormatter: (type, value) => value
111
+ innerFormatter: (type, value) => value,
112
+ // 记录最近一次通过change上抛给外部的值,用于去重,
113
+ // 避免边界(min/max)变化导致程序化重建列时再次触发change
114
+ lastEmitValue: null
112
115
  }
113
116
  },
114
117
  watch: {
@@ -124,6 +127,8 @@
124
127
  this.innerValue = this.correctValue(this.value)
125
128
  // #endif
126
129
  this.updateColumnValue(this.innerValue)
130
+ // 打开时以外部值为基准,重置去重基准
131
+ this.lastEmitValue = this.innerValue
127
132
  }
128
133
  },
129
134
  // #ifdef VUE3
@@ -138,14 +143,20 @@
138
143
  // this.getInputValue(newValue)
139
144
  },
140
145
  // #endif
141
- propsChange() {
146
+ // mode变化会改变已选值的语义(时间串<->时间戳),需要完整重新初始化
147
+ mode() {
142
148
  this.init()
149
+ },
150
+ propsChange() {
151
+ // 边界(min/max、filter)变化时,保留当前已选值,仅重新校正并重建各列,
152
+ // 不回退到外部 modelValue,避免未确认前丢失用户已滚动的值
153
+ this.reInitColumns()
143
154
  }
144
155
  },
145
156
  computed: {
146
- // 如果以下这些变量发生了变化,意味着需要重新初始化各列的值
157
+ // 如果以下这些变量发生了变化,意味着需要重新计算各列范围,但应保留当前已选值
147
158
  propsChange() {
148
- return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.minSecond, this.maxSecond, this.filter, this.modelValue]
159
+ return [this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.minSecond, this.maxSecond, this.filter]
149
160
  },
150
161
  resolvedMaskStyle() {
151
162
  if (this.upHasProp('maskStyle')) {
@@ -249,6 +260,38 @@
249
260
 
250
261
  // 初始化hasInput展示
251
262
  this.getInputValue(this.innerValue)
263
+ // 以外部值为基准,重置去重基准
264
+ this.lastEmitValue = this.innerValue
265
+ },
266
+ // 边界(minMinute等)变化时调用:以当前已选值(innerValue)为基准,
267
+ // 按新的边界重新校正并重建各列,而不是回退到外部modelValue。
268
+ // 这样在未点击确认前也不会丢失/重置用户已经滚动选择的值。
269
+ reInitColumns() {
270
+ let base = this.innerValue
271
+ if (base === undefined || base === null || base === '') {
272
+ // #ifdef VUE3
273
+ base = this.modelValue
274
+ // #endif
275
+ // #ifdef VUE2
276
+ base = this.value
277
+ // #endif
278
+ }
279
+ // correctValue会把小于新minMinute(或大于maxMinute)的值夹取到合法范围内,
280
+ // 例如原选中15:30、minMinute变为51时会被夹取为15:51
281
+ const corrected = this.correctValue(base)
282
+ const changed = corrected !== this.innerValue
283
+ this.innerValue = corrected
284
+ this.updateColumnValue(corrected)
285
+ this.getInputValue(corrected)
286
+ // 仅当已选值确实因边界收紧而被夹取变化时,才补发一次change通知外部;
287
+ // 值未变化则不上抛,避免边界变化引起的重复change
288
+ if (changed && this.lastEmitValue !== corrected) {
289
+ this.lastEmitValue = corrected
290
+ this.$emit('change', {
291
+ value: corrected,
292
+ mode: this.mode
293
+ })
294
+ }
252
295
  },
253
296
  // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
254
297
  setFormatter(e) {
@@ -378,8 +421,14 @@
378
421
  }
379
422
  // 取出准确的合法值,防止超越边界的情况
380
423
  selectValue = this.correctValue(selectValue)
424
+ // 边界变化会程序化重建各列并回填索引,picker-view在部分原生端会再次派发change,
425
+ // 若此时算出的值与上一次已上抛的值一致,说明并非用户真实操作,直接忽略,避免重复触发change
426
+ if (selectValue === this.lastEmitValue && selectValue === this.innerValue) {
427
+ return
428
+ }
381
429
  this.innerValue = selectValue
382
430
  this.updateColumnValue(selectValue)
431
+ this.lastEmitValue = selectValue
383
432
  // 发出change时间,value为当前选中的时间戳
384
433
  this.$emit('change', {
385
434
  value: selectValue,
@@ -4,21 +4,21 @@
4
4
  :style="movableAreaStyle"
5
5
  >
6
6
  <movable-area class="u-dragsort-area">
7
- <movable-view v-for="(item, index) in list" :key="item.id" :id="`u-dragsort-item-${index}`"
8
- class="u-dragsort-item" :class="{ 'dragging': dragIndex === index, disabled: !draggable || item.draggable === false }"
7
+ <movable-view v-for="(item, index) in list" :key="item.id" :id="`u-dragsort-item-${instanceId}-${item.id}`"
8
+ class="u-dragsort-item" :class="{ 'dragging': dragItemId === item.id, disabled: !draggable || item.draggable === false }"
9
9
  :direction="direction === 'all' ? 'all' : direction" :x="item.x" :y="item.y" :inertia="false"
10
- :disabled="!draggable || dragIndex === -1 || item.draggable === false" @change="onChange(index, $event)"
11
- @touchstart="onTouchStart(index, $event)" @touchend="onTouchEnd" @touchcancel="onTouchEnd" @touchmove="onTouchMove">
10
+ :disabled="!draggable || dragItemId === null || item.draggable === false" @change="onChange(item.id, $event)"
11
+ @touchstart="onTouchStart(item.id, $event)" @touchend="onTouchEnd" @touchcancel="onTouchEnd" @touchmove="onTouchMove">
12
12
  <view class="u-dragsort-item-content">
13
13
  <view
14
14
  class="ui-dragSort-item-handler"
15
- v-if="$slots.handler"
15
+ v-if="hasHandler"
16
16
  data-action="handler"
17
- @touchstart="onTouchStart(index, $event)"
17
+ @touchstart="onTouchStart(item.id, $event)"
18
18
  >
19
- <slot name="handler" :item="item" :index="index"></slot>
19
+ <slot name="handler" :item="item" :index="getItemIndex(item.id)"></slot>
20
20
  </view>
21
- <slot :item="item" :index="index">
21
+ <slot :item="item" :index="getItemIndex(item.id)">
22
22
  {{ item.label }}
23
23
  </slot>
24
24
  </view>
@@ -30,7 +30,7 @@
30
30
  <script>
31
31
  import { mpMixin } from '../../libs/mixin/mpMixin';
32
32
  import { mixin } from '../../libs/mixin/mixin';
33
- import { sleep } from '../../libs/function/index';
33
+ import { guid, sleep } from '../../libs/function/index';
34
34
  export default {
35
35
  name: 'u-dragsort',
36
36
  // #ifdef MP
@@ -67,7 +67,9 @@ export default {
67
67
  data() {
68
68
  return {
69
69
  list: [],
70
- dragIndex: -1,
70
+ orderIds: [],
71
+ instanceId: guid(8),
72
+ dragItemId: null,
71
73
  sortChanged: false,
72
74
  itemHeight: 0,
73
75
  itemWidth: 0,
@@ -99,6 +101,9 @@ export default {
99
101
  width: '100%'
100
102
  }
101
103
  }
104
+ },
105
+ hasHandler() {
106
+ return !!(this.$slots.handler || this.$slots.$handler)
102
107
  }
103
108
  },
104
109
  emits: ['drag-end'],
@@ -110,8 +115,17 @@ export default {
110
115
  },
111
116
  methods: {
112
117
  initList() {
118
+ this.orderIds = this.initialList.map(item => item.id)
119
+ const currentItems = new Map(this.list.map(item => [item.id, item]))
120
+ const initialItems = new Map(this.initialList.map(item => [item.id, item]))
121
+ const renderIds = [
122
+ ...this.list.map(item => item.id).filter(itemId => initialItems.has(itemId)),
123
+ ...this.orderIds.filter(itemId => !currentItems.has(itemId))
124
+ ]
113
125
  // 初始化列表项的位置
114
- this.list = this.initialList.map((item, index) => {
126
+ this.list = renderIds.map(itemId => {
127
+ const item = initialItems.get(itemId)
128
+ const index = this.getItemIndex(itemId)
115
129
  let x
116
130
  let y
117
131
 
@@ -136,6 +150,9 @@ export default {
136
150
  }
137
151
  })
138
152
  },
153
+ getItemIndex(itemId) {
154
+ return this.orderIds.indexOf(itemId)
155
+ },
139
156
  async calculateItemSize() {
140
157
  // 计算项目尺寸
141
158
  await sleep(30);
@@ -175,12 +192,14 @@ export default {
175
192
  },
176
193
  updatePositions(isDragging) {
177
194
  // 更新所有项目的位置
178
- this.list = this.list.map((item, index) => {
195
+ this.list = this.list.map(item => {
179
196
  // 当前正在拖动的项目保持拖动位置不动,避免抖动
180
- if (isDragging && this.dragIndex === index) {
197
+ if (isDragging && this.dragItemId === item.id) {
181
198
  return item
182
199
  }
183
200
 
201
+ const index = this.getItemIndex(item.id)
202
+
184
203
  if (this.direction === 'vertical') {
185
204
  return {
186
205
  ...item,
@@ -208,26 +227,31 @@ export default {
208
227
  }
209
228
  })
210
229
  },
211
- onTouchStart(index, e) {
212
- if (this.$slots.handler && e.currentTarget.dataset.action !== 'handler') {
230
+ onTouchStart(itemId, e) {
231
+ if (this.hasHandler && e.currentTarget.dataset.action !== 'handler') {
213
232
  return
214
233
  }
234
+ const index = this.list.findIndex(item => item.id === itemId)
235
+ if (index === -1) return
215
236
  // 全局禁用或单项禁用时,不进入拖拽态,避免 Android 上小范围漂浮/阴影
216
237
  if (!this.draggable || this.list[index]?.draggable === false) return;
217
238
  if (this.timer) clearTimeout(this.timer);
218
239
  this.sortChanged = false;
219
- this.dragIndex = index;
240
+ this.dragItemId = itemId;
220
241
  },
221
242
  onTouchMove(e) {
222
- if (this.dragIndex !== -1) {
243
+ if (this.dragItemId !== null) {
223
244
  // 目前只对H5生效, 如果该组件放置在开启了下拉刷新的scroll-view中, 向下拖动item还是会触发下拉刷新
224
245
  e.stopPropagation()
225
246
  e.preventDefault()
226
247
  }
227
248
  },
228
- onChange(index, event) {
249
+ onChange(itemId, event) {
229
250
  if (!event.detail.source || event.detail.source !== 'touch') return;
230
251
 
252
+ const index = this.getItemIndex(itemId)
253
+ if (index === -1) return;
254
+
231
255
  this.currentPosition.x = event.detail.x;
232
256
  this.currentPosition.y = event.detail.y;
233
257
 
@@ -274,11 +298,9 @@ export default {
274
298
  }
275
299
  },
276
300
  reorderItems(fromIndex, toIndex) {
277
- const movedItem = this.list.splice(fromIndex, 1)[0];
278
- this.list.splice(toIndex, 0, movedItem);
301
+ const movedItemId = this.orderIds.splice(fromIndex, 1)[0];
302
+ this.orderIds.splice(toIndex, 0, movedItemId);
279
303
 
280
- // 更新当前拖拽项目的新索引
281
- this.dragIndex = toIndex;
282
304
  this.sortChanged = true;
283
305
 
284
306
  // 更新所有项目的位置
@@ -291,25 +313,29 @@ export default {
291
313
  },
292
314
  onTouchEnd() {
293
315
  // 未发生位移
294
- if (this.dragIndex === -1) return
316
+ if (this.dragItemId === null) return
317
+
318
+ const dragItem = this.list.find(item => item.id === this.dragItemId)
319
+ if (!dragItem) return
295
320
 
296
321
  // 0.001是为了解决拖动过快等某些极限场景下位置还原不生效问题
297
322
  if (this.direction === 'horizontal') {
298
- this.list[this.dragIndex].x = this.currentPosition.x + 0.001;
323
+ dragItem.x = this.currentPosition.x + 0.001;
299
324
  } else if (this.direction === 'vertical' || this.direction === 'all') {
300
- this.list[this.dragIndex].y = this.currentPosition.y + 0.001;
301
- this.list[this.dragIndex].x = this.currentPosition.x + 0.001;
325
+ dragItem.y = this.currentPosition.y + 0.001;
326
+ dragItem.x = this.currentPosition.x + 0.001;
302
327
  }
303
328
 
304
329
  // 重置到位置,需要延迟触发动,否则无效。
305
330
  sleep(50).then(() => {
306
331
  this.updatePositions();
307
332
  if (this.sortChanged) {
308
- this.$emit('drag-end', [...this.list]);
333
+ const sortedList = this.orderIds.map(itemId => this.list.find(item => item.id === itemId));
334
+ this.$emit('drag-end', sortedList);
309
335
  this.sortChanged = false;
310
336
  }
311
337
  this.timer = setTimeout(() => {
312
- this.dragIndex = -1
338
+ this.dragItemId = null
313
339
  }, 600)
314
340
  });
315
341
  }
@@ -319,6 +345,7 @@ export default {
319
345
  handler() {
320
346
  this.$nextTick(() => {
321
347
  this.initList();
348
+ this.updatePositions();
322
349
  });
323
350
  },
324
351
  // deep: true
@@ -372,9 +399,6 @@ export default {
372
399
  position: relative;
373
400
  padding: 0;
374
401
  box-sizing: border-box;
375
- border: 1px solid var(--up-border-color, rgba(125, 126, 128, 0.35));
376
- border-radius: 8px;
377
- background-color: var(--up-card-bg-color, #ffffff);
378
402
  }
379
403
  }
380
404
 
@@ -72,10 +72,19 @@
72
72
  export default {
73
73
  name: 'u-icon',
74
74
  beforeCreate() {
75
- if (!fontUtil.params.loaded) {
75
+ // #ifndef APP-VUE
76
+ if (!fontUtil.isLoaded()) {
76
77
  fontUtil.loadFont();
77
78
  }
79
+ // #endif
78
80
  },
81
+ mounted() {
82
+ // #ifdef APP-VUE
83
+ if (!fontUtil.isLoaded()) {
84
+ fontUtil.loadFont();
85
+ }
86
+ // #endif
87
+ },
79
88
  data() {
80
89
  return {
81
90
  }
@@ -161,8 +170,8 @@
161
170
  $u-icon-error: $u-error !default;
162
171
  $u-icon-label-line-height:1 !default;
163
172
 
164
- /* #ifdef MP-QQ || MP-TOUTIAO || MP-BAIDU || MP-KUAISHOU || MP-XHS */
165
- // App端通过uni.loadFontFace加载包内本地字体,避免远程字体阻塞页面渲染。
173
+ /* #ifdef APP || MP-QQ || MP-TOUTIAO || MP-BAIDU || MP-KUAISHOU || MP-XHS */
174
+ // 默认保留App端CSS字体,兼容未启用UniUpRoot的老项目;启用UniUpRoot后构建插件会移除App条件并改用本地字体。
166
175
  @font-face {
167
176
  font-family: 'uicon-iconfont';
168
177
  src: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype');
@@ -1,28 +1,74 @@
1
1
  import config from '../../libs/config/config';
2
- // #ifdef APP || APP-NVUE
3
- import iconFontUrl from './upicon.ttf?url';
4
- // #endif
5
2
 
6
3
  const iconFontFamily = 'uicon-iconfont';
4
+ const appIconFontUrl = '_www/static/app-plus/uview-plus/upicon.ttf';
5
+ const useAppStaticIconFont = false;
7
6
 
8
7
  let params = {
9
- loaded: false
8
+ loaded: false,
9
+ loading: false
10
+ };
11
+
12
+ // #ifdef APP-VUE
13
+ const appVueLoadedPages = new WeakSet();
14
+ const appVueLoadingPages = new WeakSet();
15
+ // #endif
16
+
17
+ const getCurrentAppVuePage = () => {
18
+ // #ifdef APP-VUE
19
+ try {
20
+ if (typeof getCurrentPages !== 'function') return null;
21
+ const pages = getCurrentPages();
22
+ if (!Array.isArray(pages) || pages.length === 0) return null;
23
+ const page = pages[pages.length - 1];
24
+ return page && (typeof page === 'object' || typeof page === 'function') ? page : null;
25
+ } catch (e) {
26
+ return null;
27
+ }
28
+ // #endif
29
+ return null;
30
+ };
31
+
32
+ const isLoaded = () => {
33
+ // #ifdef APP-VUE
34
+ const page = getCurrentAppVuePage();
35
+ return !!page && appVueLoadedPages.has(page);
36
+ // #endif
37
+ return params.loaded;
38
+ };
39
+
40
+ const getAppIconUrl = () => {
41
+ // #ifdef APP || APP-NVUE
42
+ if (!useAppStaticIconFont) {
43
+ return config.iconUrl;
44
+ }
45
+ if (typeof plus !== 'undefined' && plus.io && typeof plus.io.convertLocalFileSystemURL === 'function') {
46
+ const iconFontPath = plus.io.convertLocalFileSystemURL(appIconFontUrl);
47
+ if (iconFontPath) return iconFontPath;
48
+ }
49
+ return appIconFontUrl;
50
+ // #endif
51
+ return '';
10
52
  };
11
53
 
12
54
  const getIconUrl = () => {
13
55
  // #ifdef APP || APP-NVUE
14
- return iconFontUrl;
56
+ return getAppIconUrl();
15
57
  // #endif
16
58
  return config.iconUrl;
17
59
  };
18
60
 
19
61
  const markFontLoaded = () => {
20
- // App端使用包内本地字体,重复注册没有收益且会放大多图标页面开销。
21
- // #ifdef APP || APP-NVUE
62
+ // App Vue的字体注册只对当前页面WebView生效,由页面级状态在成功回调中记录。
63
+ // #ifdef APP-VUE
64
+ return;
65
+ // #endif
66
+ params.loading = false;
67
+ // #ifdef APP-NVUE
22
68
  params.loaded = true;
23
69
  return;
24
70
  // #endif
25
- // 全局加载不稳定,默认关闭,需要开启可以配置loadFontOnce
71
+ // 非App Vue平台由loadFontOnce决定成功后是否复用本次加载结果。
26
72
  if (config.loadFontOnce) {
27
73
  params.loaded = true;
28
74
  }
@@ -30,8 +76,20 @@ const markFontLoaded = () => {
30
76
 
31
77
  // 加载字体方法
32
78
  const loadFont = () => {
79
+ // #ifdef APP-VUE
80
+ const appVuePage = getCurrentAppVuePage();
81
+ if (!appVuePage || appVueLoadedPages.has(appVuePage) || appVueLoadingPages.has(appVuePage)) {
82
+ return false;
83
+ }
84
+ appVueLoadingPages.add(appVuePage);
85
+ // #endif
86
+ // #ifndef APP-VUE
87
+ if (params.loaded || params.loading) {
88
+ return false;
89
+ }
90
+ params.loading = true;
91
+ // #endif
33
92
  const iconUrl = getIconUrl();
34
- markFontLoaded();
35
93
  // #ifdef APP-NVUE
36
94
  // nvue通过weex的dom模块引入字体,相关文档地址如下:
37
95
  // https://weex.apache.org/zh/docs/modules/dom.html#addrule
@@ -46,16 +104,28 @@ const loadFont = () => {
46
104
  'src': `url('${config.customIcon.url}')`
47
105
  });
48
106
  }
107
+ markFontLoaded();
49
108
  // #endif
50
- // #ifdef APP || H5 || MP-WEIXIN || MP-ALIPAY
109
+ // #ifdef APP-VUE || H5 || MP-WEIXIN || MP-ALIPAY
51
110
  uni.loadFontFace({
52
111
  global: true, // 是否全局生效。微信小程序 '2.10.0'起支持全局生效,需在 app.vue 中调用。
53
112
  family: iconFontFamily,
54
113
  source: 'url("' + iconUrl + '")',
55
114
  success() {
115
+ // #ifdef APP-VUE
116
+ appVueLoadingPages.delete(appVuePage);
117
+ appVueLoadedPages.add(appVuePage);
118
+ // #endif
119
+ markFontLoaded();
56
120
  // console.log('内置字体图标加载成功');
57
121
  },
58
122
  fail() {
123
+ // #ifdef APP-VUE
124
+ appVueLoadingPages.delete(appVuePage);
125
+ // #endif
126
+ // #ifndef APP-VUE
127
+ params.loading = false;
128
+ // #endif
59
129
  // console.error('内置字体图标加载出错');
60
130
  }
61
131
  });
@@ -86,5 +156,6 @@ const loadFont = () => {
86
156
 
87
157
  export default {
88
158
  params: params,
159
+ isLoaded,
89
160
  loadFont
90
161
  }
@@ -20,7 +20,7 @@
20
20
  import { mixin } from '../../libs/mixin/mixin';
21
21
  import { sleep, error } from '../../libs/function/index';
22
22
  // #ifdef APP-NVUE
23
- // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
23
+ // 由于weex不支持百分比单位,这里需要通过dom查询组件的宽度
24
24
  const dom = uni.requireNativePlugin('dom')
25
25
  // #endif
26
26
  /**
@@ -108,7 +108,7 @@
108
108
  import { addUnit, getWindowInfo, sleep, getPx } from '../../libs/function/index';
109
109
 
110
110
  // #ifdef APP-NVUE
111
- // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
111
+ // 由于weex不支持百分比单位,这里需要通过dom查询组件的宽度
112
112
  const dom = uni.requireNativePlugin('dom')
113
113
  // #endif
114
114
  /**
@@ -0,0 +1,80 @@
1
+ const LINE_BREAK_PATTERN = /\r\n|\r|\n/
2
+
3
+ function normalizeParagraphs(content) {
4
+ const values = Array.isArray(content) ? content : [content == null ? '' : content]
5
+ return values.reduce((paragraphs, value) => {
6
+ String(value == null ? '' : value)
7
+ .split(LINE_BREAK_PATTERN)
8
+ .forEach((text) => paragraphs.push(text))
9
+ return paragraphs
10
+ }, [])
11
+ }
12
+
13
+ export function normalizeContent(content) {
14
+ const sourceParagraphs = normalizeParagraphs(content)
15
+ const hasContent = sourceParagraphs.some((text) => text.length > 0)
16
+ if (!hasContent) {
17
+ return {
18
+ paragraphs: [],
19
+ text: '',
20
+ length: 0
21
+ }
22
+ }
23
+
24
+ let offset = 0
25
+ const paragraphs = sourceParagraphs.map((text, index) => {
26
+ const startOffset = offset
27
+ const endOffset = startOffset + text.length
28
+ offset = endOffset + 1
29
+ return {
30
+ index,
31
+ text,
32
+ startOffset,
33
+ endOffset
34
+ }
35
+ })
36
+
37
+ const text = paragraphs.map((paragraph) => paragraph.text).join('\n')
38
+ return {
39
+ paragraphs,
40
+ text,
41
+ length: text.length
42
+ }
43
+ }
44
+
45
+ export function normalizeProgress(progress, chapter) {
46
+ const normalizedContent = normalizeContent(chapter && chapter.content)
47
+ const contentLength = normalizedContent.length
48
+ const requestedOffset = Number(progress && progress.charOffset)
49
+ const charOffset = Math.min(
50
+ contentLength,
51
+ Math.max(0, Number.isFinite(requestedOffset) ? requestedOffset : 0)
52
+ )
53
+ const requestedPageIndex = Number(progress && progress.pageIndex)
54
+ const chapterIndex = Number(chapter && chapter.index)
55
+
56
+ return {
57
+ chapterId: progress && progress.chapterId != null
58
+ ? progress.chapterId
59
+ : chapter && chapter.id != null
60
+ ? chapter.id
61
+ : '',
62
+ chapterIndex: Number.isFinite(chapterIndex)
63
+ ? chapterIndex
64
+ : Number(progress && progress.chapterIndex) || 0,
65
+ pageIndex: Math.max(0, Number.isFinite(requestedPageIndex) ? requestedPageIndex : 0),
66
+ pageCount: Math.max(0, Number(progress && progress.pageCount) || 0),
67
+ charOffset,
68
+ chapterProgress: contentLength
69
+ ? Math.min(1, Math.max(0, charOffset / contentLength))
70
+ : 0,
71
+ totalProgress: Math.min(1, Math.max(0, Number(progress && progress.totalProgress) || 0)),
72
+ scrollTop: Math.max(0, Number(progress && progress.scrollTop) || 0),
73
+ updatedAt: Number(progress && progress.updatedAt) || 0
74
+ }
75
+ }
76
+
77
+ export default {
78
+ normalizeContent,
79
+ normalizeProgress
80
+ }