uview-plus 3.8.113 → 3.8.119

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.
@@ -92,14 +92,16 @@
92
92
  return {
93
93
  columnList: [[]], // 存储每列的数据
94
94
  children: [],
95
- // 用于标记是否已经初始化
96
- initialized: false,
97
- windowWidth: 375,
98
- windowHeight: 0,
99
- distributionQueue: [],
100
- distributionRunning: false,
101
- distributionPromise: null,
102
- distributionGeneration: 0
95
+ // 用于标记是否已经初始化
96
+ initialized: false,
97
+ windowWidth: 375,
98
+ windowHeight: 0,
99
+ distributionQueue: [],
100
+ distributionRunning: false,
101
+ distributionPromise: null,
102
+ distributionGeneration: 0,
103
+ // 每个分配循环持有独立令牌:锁被强制重置后新循环接管,旧循环恢复时只能安静退出
104
+ distributionRunToken: 0
103
105
  }
104
106
  },
105
107
  watch: {
@@ -111,27 +113,27 @@
111
113
  if (this.columnList.length == 1) {
112
114
  this.initColumnList()
113
115
  }
114
- if (!this.isPureAppend(nVal, oVal)) {
115
- this.redistributeData(nVal);
116
- return;
117
- }
118
- // 取差值,即这一次数组变化新增的部分
119
- const startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0;
120
- // 纯追加时只将新增数据加入共享分配队列
121
- this.handleData(nVal.slice(startIndex));
116
+ if (!this.isPureAppend(nVal, oVal)) {
117
+ this.redistributeData(nVal);
118
+ return;
119
+ }
120
+ // 取差值,即这一次数组变化新增的部分
121
+ const startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0;
122
+ // 纯追加时只将新增数据加入共享分配队列
123
+ this.handleData(nVal.slice(startIndex));
122
124
  }
123
125
  },
124
126
  immediate: true
125
127
  },
126
- columns: {
127
- handler() {
128
- // 重新分配数据
129
- if (this.copyFlowList.length > 0) {
130
- this.redistributeData(this.copyFlowList);
131
- } else {
132
- this.clear(false);
133
- }
134
- },
128
+ columns: {
129
+ handler() {
130
+ // 重新分配数据
131
+ if (this.copyFlowList.length > 0) {
132
+ this.redistributeData(this.copyFlowList);
133
+ } else {
134
+ this.clear(false);
135
+ }
136
+ },
135
137
  immediate: false
136
138
  }
137
139
  },
@@ -219,144 +221,161 @@
219
221
  const newColumnsCount = this.getColumnsCount();
220
222
  const oldColumnsCount = this.columnList.length;
221
223
 
222
- // 只有列数发生变化时才重新分配数据
223
- if (newColumnsCount !== oldColumnsCount) {
224
- this.redistributeData(this.copyFlowList);
225
- }
226
- }, 300);
227
- },
228
-
229
- // 重新分配所有数据
230
- redistributeData(data = this.copyFlowList) {
231
- this.clear(false);
232
- // 保存所有数据
233
- const allData = this.cloneData(data || []);
234
- // 重新分配数据
235
- return this.handleData(allData);
236
- },
237
-
238
- // 判断新数据是否只在原数组末尾追加
239
- isPureAppend(newData, oldData) {
240
- if (!Array.isArray(oldData) || oldData.length === 0) return true;
241
- if (!Array.isArray(newData) || newData.length < oldData.length) return false;
242
- return oldData.every((item, index) => {
243
- return JSON.stringify(item) === JSON.stringify(newData[index]);
244
- });
245
- },
246
-
247
- // 将新增数据加入共享队列,确保只有一个分配循环运行
248
- handleData(newData) {
249
- if (!newData || newData.length === 0) {
250
- return this.distributionPromise || Promise.resolve();
251
- }
252
- this.distributionQueue.push({
253
- generation: this.distributionGeneration,
254
- data: this.cloneData(newData)
255
- });
256
- if (!this.distributionRunning) {
257
- this.distributionPromise = this.runDistributionQueue();
258
- }
259
- return this.distributionPromise;
260
- },
261
-
262
- // 串行消费所有待分配数据
263
- async runDistributionQueue() {
264
- if (this.distributionRunning) return;
265
- this.distributionRunning = true;
266
- try {
267
- while (this.distributionQueue.length > 0) {
268
- const task = this.distributionQueue.shift();
269
- if (task.generation !== this.distributionGeneration) continue;
270
- await this.distributeData(task.data, task.generation);
271
- }
272
- } finally {
273
- this.distributionRunning = false;
274
- this.distributionPromise = null;
275
- if (this.distributionQueue.length > 0) {
276
- this.distributionPromise = this.runDistributionQueue();
277
- }
278
- }
279
- },
280
-
281
- // 逐项测量并分配数据,代数变化时立即退出
282
- async distributeData(newData, generation) {
283
- let columnHeights = new Array(this.columnList.length).fill(0);
284
- for (const item of newData) {
285
- if (generation !== this.distributionGeneration) return;
286
- columnHeights = await this.getColumnHeights();
287
- if (generation !== this.distributionGeneration) return;
288
-
289
- const minHeightIndex = this.getMinHeightColumnIndex(columnHeights);
290
- this.columnList[minHeightIndex].push(item);
291
-
292
- await sleep(this.addTime);
293
- if (generation !== this.distributionGeneration) return;
294
- await this.$nextTick();
295
- if (generation !== this.distributionGeneration) return;
296
- try {
297
- const rect = await this.$uGetRect(`#u-column-${minHeightIndex}`);
298
- if (generation !== this.distributionGeneration) return;
299
- if (rect.height) {
300
- columnHeights[minHeightIndex] = rect.height;
301
- this.$emit('after-add-one', {
302
- ...item,
303
- height: rect.height
304
- });
305
- }
306
- } catch (e) {
307
- // 获取不到列高时,下一个元素会重新测量所有列
308
- }
309
- }
310
- if (generation !== this.distributionGeneration) return;
311
- this.$emit('after-add-all', {
312
- columnHeights: columnHeights,
313
- newData: newData
314
- });
315
- },
316
-
317
- // 获取当前所有列的真实高度
318
- async getColumnHeights() {
319
- const columnHeights = new Array(this.columnList.length).fill(0);
320
- for (let i = 0; i < this.columnList.length; i++) {
321
- try {
322
- const rect = await this.$uGetRect(`#u-column-${i}`);
323
- columnHeights[i] = rect.height || 0;
324
- } catch (e) {
325
- columnHeights[i] = 0;
326
- }
327
- }
328
- return columnHeights;
329
- },
330
-
331
- // 获取最短列;高度相同或暂不可测时按列数据量打散,避免全部落到第一列
332
- getMinHeightColumnIndex(columnHeights) {
333
- let minIndex = 0;
334
- for (let i = 1; i < columnHeights.length; i++) {
335
- const currentHeight = Number(columnHeights[i]) || 0;
336
- const minHeight = Number(columnHeights[minIndex]) || 0;
337
- if (currentHeight < minHeight) {
338
- minIndex = i;
339
- } else if (currentHeight === minHeight) {
340
- const currentLength = this.columnList[i] ? this.columnList[i].length : 0;
341
- const minLength = this.columnList[minIndex] ? this.columnList[minIndex].length : 0;
342
- if (currentLength < minLength) {
343
- minIndex = i;
344
- }
345
- }
346
- }
347
- return minIndex;
348
- },
349
-
350
- // 复制而不是引用对象和数组
351
- cloneData(data) {
352
- return JSON.parse(JSON.stringify(data));
224
+ // 只有列数发生变化时才重新分配数据
225
+ if (newColumnsCount !== oldColumnsCount) {
226
+ this.redistributeData(this.copyFlowList);
227
+ }
228
+ }, 300);
229
+ },
230
+
231
+ // 重新分配所有数据
232
+ redistributeData(data = this.copyFlowList) {
233
+ // 强制重置锁状态,确保能重新开始分配
234
+ this.distributionRunning = false;
235
+ this.distributionPromise = null;
236
+ this.clear(false);
237
+ // 保存所有数据
238
+ const allData = this.cloneData(data || []);
239
+ // 重新分配数据
240
+ return this.handleData(allData);
241
+ },
242
+
243
+ // 判断新数据是否只在原数组末尾追加
244
+ isPureAppend(newData, oldData) {
245
+ if (!Array.isArray(oldData) || oldData.length === 0) return true;
246
+ if (!Array.isArray(newData) || newData.length < oldData.length) return false;
247
+ return oldData.every((item, index) => {
248
+ return JSON.stringify(item) === JSON.stringify(newData[index]);
249
+ });
250
+ },
251
+
252
+ // 将新增数据加入共享队列,确保只有一个分配循环运行
253
+ handleData(newData) {
254
+ if (!newData || newData.length === 0) {
255
+ return this.distributionPromise || Promise.resolve();
256
+ }
257
+ this.distributionQueue.push({
258
+ generation: this.distributionGeneration,
259
+ data: this.cloneData(newData)
260
+ });
261
+ if (!this.distributionRunning) {
262
+ this.distributionPromise = this.runDistributionQueue();
263
+ }
264
+ return this.distributionPromise;
265
+ },
266
+
267
+ // 串行消费所有待分配数据
268
+ async runDistributionQueue() {
269
+ if (this.distributionRunning) return;
270
+ this.distributionRunning = true;
271
+ const runToken = ++this.distributionRunToken;
272
+ try {
273
+ while (this.distributionQueue.length > 0) {
274
+ if (runToken !== this.distributionRunToken) return;
275
+ const task = this.distributionQueue.shift();
276
+ if (task.generation !== this.distributionGeneration) continue;
277
+ await this.distributeData(task.data, task.generation, runToken);
278
+ }
279
+ } finally {
280
+ // 仅当自己仍是当前循环时才释放锁,否则会清掉接管者的运行状态
281
+ if (runToken === this.distributionRunToken) {
282
+ this.distributionRunning = false;
283
+ this.distributionPromise = null;
284
+ if (this.distributionQueue.length > 0) {
285
+ this.distributionPromise = this.runDistributionQueue();
286
+ }
287
+ }
288
+ }
289
+ },
290
+
291
+ // 数据被重置或分配循环被接管时,当前循环应立即停止写入
292
+ isStaleDistribution(generation, runToken) {
293
+ return generation !== this.distributionGeneration
294
+ || runToken !== this.distributionRunToken;
295
+ },
296
+
297
+ // 逐项测量并分配数据,代数或令牌变化时立即退出
298
+ async distributeData(newData, generation, runToken) {
299
+ let columnHeights = new Array(this.columnList.length).fill(0);
300
+ for (const item of newData) {
301
+ if (this.isStaleDistribution(generation, runToken)) return;
302
+ columnHeights = await this.getColumnHeights();
303
+ if (this.isStaleDistribution(generation, runToken)) return;
304
+
305
+ const minHeightIndex = this.getMinHeightColumnIndex(columnHeights);
306
+ this.columnList[minHeightIndex].push(item);
307
+
308
+ await sleep(this.addTime);
309
+ if (this.isStaleDistribution(generation, runToken)) return;
310
+ await this.$nextTick();
311
+ if (this.isStaleDistribution(generation, runToken)) return;
312
+ try {
313
+ const rect = await this.$uGetRect(`#u-column-${minHeightIndex}`);
314
+ if (this.isStaleDistribution(generation, runToken)) return;
315
+ if (rect.height) {
316
+ columnHeights[minHeightIndex] = rect.height;
317
+ this.$emit('after-add-one', {
318
+ ...item,
319
+ height: rect.height
320
+ });
321
+ }
322
+ } catch (e) {
323
+ // 获取不到列高时,下一个元素会重新测量所有列
324
+ }
325
+ }
326
+ if (this.isStaleDistribution(generation, runToken)) return;
327
+ this.$emit('after-add-all', {
328
+ columnHeights: columnHeights,
329
+ newData: newData
330
+ });
331
+ },
332
+
333
+ // 获取当前所有列的真实高度
334
+ async getColumnHeights() {
335
+ const columnHeights = new Array(this.columnList.length).fill(0);
336
+ for (let i = 0; i < this.columnList.length; i++) {
337
+ try {
338
+ const rect = await this.$uGetRect(`#u-column-${i}`);
339
+ columnHeights[i] = rect.height || 0;
340
+ } catch (e) {
341
+ columnHeights[i] = 0;
342
+ }
343
+ }
344
+ return columnHeights;
345
+ },
346
+
347
+ // 获取最短列;高度相同或暂不可测时按列数据量打散,避免全部落到第一列
348
+ getMinHeightColumnIndex(columnHeights) {
349
+ let minIndex = 0;
350
+ for (let i = 1; i < columnHeights.length; i++) {
351
+ const currentHeight = Number(columnHeights[i]) || 0;
352
+ const minHeight = Number(columnHeights[minIndex]) || 0;
353
+ if (currentHeight < minHeight) {
354
+ minIndex = i;
355
+ } else if (currentHeight === minHeight) {
356
+ const currentLength = this.columnList[i] ? this.columnList[i].length : 0;
357
+ const minLength = this.columnList[minIndex] ? this.columnList[minIndex].length : 0;
358
+ if (currentLength < minLength) {
359
+ minIndex = i;
360
+ }
361
+ }
362
+ }
363
+ return minIndex;
364
+ },
365
+
366
+ // 复制而不是引用对象和数组
367
+ cloneData(data) {
368
+ return JSON.parse(JSON.stringify(data));
353
369
  },
354
370
 
355
- // 清空数据列表
356
- clear(bak = true) {
357
- this.distributionGeneration += 1;
358
- this.distributionQueue.splice(0);
359
- this.initColumnList();
371
+ // 清空数据列表
372
+ clear(bak = true) {
373
+ this.distributionGeneration += 1;
374
+ this.distributionQueue.splice(0);
375
+ // 强制重置分配锁状态,避免页面隐藏导致的永久锁死
376
+ this.distributionRunning = false;
377
+ this.distributionPromise = null;
378
+ this.initColumnList();
360
379
  // 同时清除父组件列表中的数据
361
380
  if (bak) {
362
381
  // #ifdef VUE2
@@ -465,4 +484,4 @@
465
484
  max-width: 100%;
466
485
  /* #endif */
467
486
  }
468
- </style>
487
+ </style>
@@ -17,6 +17,20 @@ function emptyNodeInfo() {
17
17
  }
18
18
  }
19
19
 
20
+ // 组件卸载后仍把它交给原生查询,APP 端会拿已失效的 nodeId 去查视图层映射表,
21
+ // 读到 undefined 再取 .$ 就抛 Cannot read properties of undefined (reading '$')
22
+ function isCompUnmounted(comp) {
23
+ if (!comp) {
24
+ return false
25
+ }
26
+ // 公共 mixin 的 beforeUnmount 里同步置位
27
+ if (comp.__upUnmounted) {
28
+ return true
29
+ }
30
+ // 未应用公共 mixin 时退回 Vue 自身标记:公开实例挂在 $ 上,内部实例直接持有
31
+ return !!(comp.$ ? comp.$.isUnmounted : comp.isUnmounted)
32
+ }
33
+
20
34
  // #ifdef APP-NVUE
21
35
  const dom = (typeof uni !== 'undefined' && uni && typeof uni.requireNativePlugin === 'function')
22
36
  ? uni.requireNativePlugin('dom')
@@ -37,6 +51,10 @@ function getNvueRect(ref) {
37
51
 
38
52
  // 查询节点信息
39
53
  export function upGetRect(selector, all = false, comp = null) {
54
+ // 判定必须放在条件编译之外,否则 app-harmony 上整段会被裁掉
55
+ if (isCompUnmounted(comp)) {
56
+ return Promise.resolve(all ? [] : emptyNodeInfo())
57
+ }
40
58
  return new Promise((resolve) => {
41
59
  // #ifndef APP-NVUE
42
60
  const query = uni.createSelectorQuery()
@@ -70,6 +88,17 @@ export function upGetRect(selector, all = false, comp = null) {
70
88
  })
71
89
  }
72
90
 
91
+ // 卸载后替代真实观察器,保持链式调用不报错
92
+ function noopIntersectionObserver() {
93
+ const observer = {
94
+ relativeTo: () => observer,
95
+ relativeToViewport: () => observer,
96
+ observe: () => {},
97
+ disconnect: () => {}
98
+ }
99
+ return observer
100
+ }
101
+
73
102
  // 创建交叉观察器
74
103
  // 小程序端 uni.createIntersectionObserver(vm) 会把 Vue 实例代理直接透传给原生 API,
75
104
  // 原生内部的 Object.keys(vm) 会触发告警:
@@ -77,6 +106,10 @@ export function upGetRect(selector, all = false, comp = null) {
77
106
  // (uni-app 只给 createSelectorQuery 做了 $scope 解包,没给 createIntersectionObserver 做)
78
107
  // 组件实例上的同名方法内部使用 $scope(原生组件实例),不会把代理外泄,因此优先使用
79
108
  export function upCreateIntersectionObserver(comp, options) {
109
+ // 和 upGetRect 同理:APP 端 addIntersectionObserver 也走 window.__$__(id).$ 查表
110
+ if (isCompUnmounted(comp)) {
111
+ return noopIntersectionObserver()
112
+ }
80
113
  // 各家小程序与 H5 都在组件实例上暴露了该方法,内部已完成实例解包
81
114
  if (comp && typeof comp.createIntersectionObserver === 'function') {
82
115
  return options ? comp.createIntersectionObserver(options) : comp.createIntersectionObserver()
@@ -251,6 +251,9 @@ export const mixin = defineMixin({
251
251
  uni.$emit('uOnReachBottom')
252
252
  },
253
253
  beforeUnmount() {
254
+ // mixin 的钩子先于组件自身钩子同步执行,比 Vue 内部的 $.isUnmounted
255
+ // (在 post-render 队列里异步置位)更早,异步回调据此拦截节点查询
256
+ this.__upUnmounted = true
254
257
  // 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
255
258
  // 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
256
259
  if (this.parent && test.array(this.parent.children)) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-plus",
3
3
  "name": "uview-plus",
4
4
  "displayName": "零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙跨端移动组件库,组件丰富维护更新稳定。",
5
- "version": "3.8.113",
5
+ "version": "3.8.119",
6
6
  "description": "零云®uview-plus已兼容vue3支持多语言,120+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名、Markdown等。",
7
7
  "keywords": [
8
8
  "uview",
@@ -11,7 +11,7 @@
11
11
  "echarts",
12
12
  "ui",
13
13
  "可视化"
14
- ],
14
+ ],
15
15
  "main": "index.js",
16
16
  "repository": "https://github.com/ijry/uview-plus",
17
17
  "engines": {