@dolphinweex/weex-harmony 0.1.49 → 0.1.51

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.
@@ -27,64 +27,56 @@
27
27
  @touchcancel="endTouch"
28
28
  >
29
29
  <div
30
- v-if="
31
- item.bgAnimView && item.bgAnimView.animUrl && !data.isEditing
32
- "
30
+ v-if="hasAnimation(item, 'bgAnimView')"
33
31
  class="lottie-container bg-anim"
34
- :style="applyNormalAttribute(item.bgAnimView)"
32
+ :style="applyNormalAttribute(item.bgAnimView, 'bgAnimView')"
35
33
  :ref="`bgAnim-${item.itemView.id}`"
36
34
  ></div>
37
35
  <div
38
- v-if="
39
- item.frontAnimView &&
40
- item.frontAnimView.animUrl &&
41
- !data.isEditing
42
- "
36
+ v-if="hasAnimation(item, 'frontAnimView')"
43
37
  class="lottie-container front-anim"
44
- :style="applyNormalAttribute(item.frontAnimView)"
38
+ :style="applyNormalAttribute(item.frontAnimView, 'frontAnimView')"
45
39
  :ref="`frontAnim-${item.itemView.id}`"
46
40
  ></div>
47
41
  <div
48
- :style="applyNormalAttribute(item.itemView)"
42
+ :style="applyNormalAttribute(item.itemView, 'itemView')"
49
43
  class="grid-box"
50
44
  :class="{ shaking: data.isEditing }"
51
45
  @click.stop.prevent="(e) => itemViewClick(e, item, index)"
52
46
  >
53
47
  <div class="flexBox">
54
48
  <img
55
- :src="item.iconImage.url"
56
- :style="applyNormalAttribute(item.iconImage)"
49
+ :src="getUrl(item, 'iconImage')"
50
+ :style="applyNormalAttribute(item.iconImage, 'iconImage')"
57
51
  />
58
52
  <div>
59
53
  <div @click.stop.prevent v-if="!data.isEditing">
60
54
  <button
61
55
  class="actionButton"
62
56
  @click.stop.prevent="(e) => actionClick(e, item, index)"
63
- :style="applyNormalAttribute(item.actionButton)"
57
+ :style="applyNormalAttribute(item.actionButton, 'actionButton')"
64
58
  >
65
- {{ item.actionButton.text }}
59
+ {{ getText(item, 'actionButton') }}
66
60
  </button>
67
61
  <div
68
- v-if="
69
- item.loadingAnimView && item.loadingAnimView.animUrl
70
- "
62
+ v-if="getUrl(item, 'loadingAnimView', 'animUrl')"
71
63
  class="loading-anim"
72
- :style="applyNormalAttribute(item.loadingAnimView)"
64
+ :style="applyNormalAttribute(item.loadingAnimView, 'loadingAnimView')"
73
65
  :ref="`loadingAnim-${item.itemView.id}`"
74
66
  ></div>
75
67
  </div>
76
68
  <div @click.stop.prevent v-if="!data.isEditing">
77
69
  <img
78
- :src="item.rightIcon.url"
79
- :style="applyNormalAttribute(item.rightIcon)"
70
+ :src="getUrl(item, 'rightIcon')"
71
+ :style="applyNormalAttribute(item.rightIcon, 'rightIcon')"
80
72
  @click.stop.prevent="(e) => rightIconClick(e)"
81
73
  />
82
74
  </div>
83
75
  <div @click.stop.prevent v-if="!data.isEditing">
84
76
  <img
85
77
  class="leftTopIcon"
86
- :src="item.leftTopIcon.url"
87
- :style="applyNormalAttribute(item.leftTopIcon)"
78
+ :src="getUrl(item, 'leftTopIcon')"
79
+ :style="applyNormalAttribute(item.leftTopIcon, 'leftTopIcon')"
88
80
  @click.stop.prevent
89
81
  />
90
82
  </div>
@@ -96,16 +88,16 @@
96
88
  "
97
89
  :src="
98
90
  item.itemView.isSelected
99
- ? item.selectIcon.selectedUrl
100
- : item.selectIcon.url
91
+ ? getUrl(item, 'selectIcon', 'selectedUrl')
92
+ : getUrl(item, 'selectIcon')
101
93
  "
102
- :style="applyNormalAttribute(item.selectIcon)"
94
+ :style="applyNormalAttribute(item.selectIcon, 'selectIcon')"
103
95
  />
104
96
  </div>
105
97
  </div>
106
98
  </div>
107
- <div class="itemText" :style="[applyNormalAttribute(item.text)]">
108
- {{ item.text.text }}
99
+ <div class="itemText" :style="[applyNormalAttribute(item.text, 'text')]">
100
+ {{ getText(item, 'text') }}
109
101
  </div>
110
102
  </div>
111
103
  </div>
@@ -120,6 +112,27 @@
120
112
  <script>
121
113
  import lottie from 'lottie-web';
122
114
  const scla = 2;
115
+
116
+ // 常量配置
117
+ const REQUIRED_TYPES = [
118
+ 'itemView',
119
+ 'text',
120
+ 'actionButton',
121
+ 'rightIcon',
122
+ 'leftTopIcon',
123
+ 'iconImage',
124
+ 'selectIcon',
125
+ 'loadingAnimView',
126
+ 'bgAnimView',
127
+ 'frontAnimView',
128
+ ];
129
+
130
+ const ANIMATION_TYPES = [
131
+ { type: 'bg', view: 'bgAnimView' },
132
+ { type: 'loading', view: 'loadingAnimView' },
133
+ { type: 'front', view: 'frontAnimView' }
134
+ ];
135
+
123
136
  export default {
124
137
  name: 'EditableGrid',
125
138
  props: {
@@ -147,6 +160,10 @@ export default {
147
160
  isTouchDragging: false,
148
161
  lastSwapTime: 0,
149
162
  isAnimating: false,
163
+ lastSwapPair: null, // 记录上次交换的元素对
164
+ stablePositionTime: 0, // 稳定位置时间
165
+ lastTouchX: 0, // 上次触摸X坐标
166
+ lastTouchY: 0, // 上次触摸Y坐标
150
167
  touchStartX: 0,
151
168
  touchStartY: 0,
152
169
  draggedItemRect: null,
@@ -163,6 +180,15 @@ export default {
163
180
  fixedItemHeight: 0,
164
181
  animationFrameId: null,
165
182
  dragCloneElement: null, // 拖拽克隆DOM元素
183
+ // 性能优化缓存
184
+ gridItemsCache: [],
185
+ lastCacheTime: 0,
186
+ cacheValidDuration: 1000, // 缓存有效期1秒
187
+ listChangeDebounced: null,
188
+ updatePositionThrottled: null,
189
+ // 避免频繁DOM查询
190
+ containerElement: null,
191
+ isUpdatingPosition: false,
166
192
  };
167
193
  },
168
194
  computed: {
@@ -181,6 +207,280 @@ export default {
181
207
  },
182
208
  },
183
209
  methods: {
210
+ // 添加工具方法
211
+ isNil(value) {
212
+ return value === null || value === undefined;
213
+ },
214
+
215
+ // 获取属性值,支持回退到全局数据和默认值
216
+ getValue(itemData, globalData, property, defaultValue = null) {
217
+ // 安全地获取属性值,避免访问undefined对象的属性
218
+ let itemValue;
219
+ let globalValue;
220
+
221
+ try {
222
+ itemValue = itemData && typeof itemData === 'object' ? itemData[property] : undefined;
223
+ } catch (e) {
224
+ itemValue = undefined;
225
+ }
226
+
227
+ try {
228
+ globalValue = globalData && typeof globalData === 'object' ? globalData[property] : undefined;
229
+ } catch (e) {
230
+ globalValue = undefined;
231
+ }
232
+
233
+ if (!this.isNil(itemValue)) {
234
+ return itemValue;
235
+ }
236
+ if (!this.isNil(globalValue)) {
237
+ return globalValue;
238
+ }
239
+ return defaultValue;
240
+ },
241
+
242
+ // 简化的URL获取方法
243
+ getUrl(item, type, property = 'url') {
244
+ return this.getValue(item[type], this.data.globalData[type], property, '');
245
+ },
246
+
247
+ // 简化的文本获取方法
248
+ getText(item, type, property = 'text') {
249
+ return this.getValue(item[type], this.data.globalData[type], property, '');
250
+ },
251
+
252
+ // 简化的动画URL检查方法
253
+ hasAnimation(item, type) {
254
+ return this.getValue(item[type], this.data.globalData[type], 'animUrl', '') && !this.data.isEditing;
255
+ },
256
+
257
+ // 性能优化工具方法
258
+ debounce(func, wait) {
259
+ let timeout;
260
+ return function executedFunction(...args) {
261
+ const later = () => {
262
+ clearTimeout(timeout);
263
+ func(...args);
264
+ };
265
+ clearTimeout(timeout);
266
+ timeout = setTimeout(later, wait);
267
+ };
268
+ },
269
+
270
+ throttle(func, limit) {
271
+ let inThrottle;
272
+ return function executedFunction(...args) {
273
+ if (!inThrottle) {
274
+ func.apply(this, args);
275
+ inThrottle = true;
276
+ setTimeout(() => inThrottle = false, limit);
277
+ }
278
+ };
279
+ },
280
+
281
+ // 缓存DOM查询结果
282
+ getCachedGridItems() {
283
+ const now = Date.now();
284
+ if (now - this.lastCacheTime > this.cacheValidDuration || this.gridItemsCache.length === 0) {
285
+ this.gridItemsCache = Array.from(document.querySelectorAll('.grid-item'));
286
+ this.lastCacheTime = now;
287
+ }
288
+ return this.gridItemsCache;
289
+ },
290
+
291
+ // 清除DOM缓存
292
+ clearGridItemsCache() {
293
+ this.gridItemsCache = [];
294
+ this.lastCacheTime = 0;
295
+ },
296
+
297
+ // 优化的位置更新方法
298
+ optimizedUpdateDragPosition() {
299
+ if (this.isUpdatingPosition) return;
300
+
301
+ this.isUpdatingPosition = true;
302
+
303
+ const easing = 0.6;
304
+
305
+ // 获取outer-container的边界
306
+ const containerRect = this.containerElement.getBoundingClientRect();
307
+
308
+ // 计算相对于容器的目标位置
309
+ let targetX = this.targetTouchX - this.touchOffsetX - containerRect.left;
310
+ let targetY = this.targetTouchY - this.touchOffsetY - containerRect.top;
311
+
312
+ // 限制拖拽范围在容器内
313
+ const cloneWidth = this.fixedItemWidth;
314
+ const cloneHeight = this.fixedItemHeight;
315
+ const maxX = containerRect.width - cloneWidth;
316
+ const maxY = containerRect.height - cloneHeight;
317
+
318
+ // 限制X坐标
319
+ targetX = Math.max(0, Math.min(targetX, maxX));
320
+
321
+ // 限制Y坐标
322
+ targetY = Math.max(0, Math.min(targetY, maxY));
323
+
324
+ this.dragCloneX += (targetX - this.dragCloneX) * easing;
325
+ this.dragCloneY += (targetY - this.dragCloneY) * easing;
326
+
327
+ if (Math.abs(this.dragCloneX - targetX) < 0.5) {
328
+ this.dragCloneX = targetX;
329
+ }
330
+ if (Math.abs(this.dragCloneY - targetY) < 0.5) {
331
+ this.dragCloneY = targetY;
332
+ }
333
+
334
+ this.updateDragClonePosition();
335
+
336
+ this.isUpdatingPosition = false;
337
+
338
+ if (this.isTouchDragging) {
339
+ this.animationFrameId = requestAnimationFrame(() => this.optimizedUpdateDragPosition());
340
+ }
341
+ },
342
+
343
+ // 批量更新样式,减少重绘
344
+ batchUpdateStyles(elements, styles) {
345
+ if (!elements || elements.length === 0) return;
346
+
347
+ // 使用documentFragment来减少DOM操作
348
+ elements.forEach(element => {
349
+ Object.keys(styles).forEach(property => {
350
+ element.style[property] = styles[property];
351
+ });
352
+ });
353
+ },
354
+
355
+ // 确保项目数据结构完整性
356
+ ensureItemStructure(item) {
357
+ REQUIRED_TYPES.forEach((type) => {
358
+ if (!item[type] || typeof item[type] !== 'object') {
359
+ item[type] = {};
360
+ }
361
+ });
362
+
363
+ return item;
364
+ },
365
+
366
+ // 抽取的动画初始化公共方法
367
+ initItemAnimations(item, itemData = null) {
368
+ const itemId = String(item.itemView.id);
369
+ const dataSource = itemData || item; // 支持传入不同的数据源
370
+
371
+ ANIMATION_TYPES.forEach(({ type, view }) => {
372
+ const animUrl = this.getValue(
373
+ dataSource[view],
374
+ this.data.globalData[view],
375
+ 'animUrl',
376
+ ''
377
+ );
378
+ if (animUrl) {
379
+ this.createLottieAnimation(type, itemId, dataSource[view], dataSource);
380
+ }
381
+ });
382
+ },
383
+
384
+ // 统一的样式应用方法,支持缩放控制
385
+ applyOptimizedAttribute(itemData = {}, type = '', useScale = true) {
386
+ const globalData = this.data.globalData[type] || {};
387
+ const scale = useScale ? scla : 1; // 根据参数决定是否应用缩放
388
+
389
+ const style = {
390
+ color: this.getValue(itemData, globalData, 'textColor', '#000000'),
391
+ fontWeight: this.getValue(itemData, globalData, 'fontWeight', 'normal'),
392
+ fontFamily: this.getValue(itemData, globalData, 'fontFamilyPath', 'inherit'),
393
+ fontSize: `${(this.getValue(itemData, globalData, 'textSize', 16)) * scale}px`,
394
+ // 布局相关样式
395
+ backgroundColor: this.getValue(itemData, globalData, 'backgroundColor', 'transparent'),
396
+ borderRadius: `${(this.getValue(itemData, globalData, 'cornerRadius', 0)) * scale}px`,
397
+ opacity: this.getValue(itemData, globalData, 'alpha', 1),
398
+ };
399
+
400
+ // 处理width
401
+ const width = this.getValue(itemData, globalData, 'width');
402
+ if (typeof width === 'number' && width > 0) {
403
+ style.width = `${width * scale}px`;
404
+ } else if (width === -1) {
405
+ style.width = '100%';
406
+ } else if (width && typeof width === 'string') {
407
+ style.width = width;
408
+ }
409
+
410
+ // 处理height
411
+ const height = this.getValue(itemData, globalData, 'height');
412
+ if (typeof height === 'number' && height > 0) {
413
+ style.height = `${height * scale}px`;
414
+ } else if (height && typeof height === 'string') {
415
+ style.height = height;
416
+ }
417
+
418
+ // 处理visibility和display
419
+ const visibility = this.getValue(itemData, globalData, 'visibility');
420
+ if (typeof visibility === 'number') {
421
+ if (visibility === 0) {
422
+ style.display = 'none';
423
+ } else {
424
+ style.display = this.getValue(itemData, globalData, 'display', '');
425
+ style.visibility = visibility === 1 ? 'visible' : 'hidden';
426
+ }
427
+ } else {
428
+ style.display = this.getValue(itemData, globalData, 'display', '');
429
+ }
430
+
431
+ // 处理边框
432
+ const borderColor = this.data.isEditing
433
+ ? this.getValue(itemData, globalData, 'selectedBorderColor', '')
434
+ : this.getValue(itemData, globalData, 'normalBorderColor', '');
435
+ const borderWidth = this.data.isEditing
436
+ ? this.getValue(itemData, globalData, 'selectedBorderWidth', 0)
437
+ : this.getValue(itemData, globalData, 'normalBorderWidth', 0);
438
+
439
+ style.borderColor = borderColor;
440
+ style.borderWidth = `${borderWidth * scale}px`;
441
+
442
+ // 处理padding
443
+ const padding = this.getValue(itemData, globalData, 'padding');
444
+ if (padding && Array.isArray(padding)) {
445
+ style.paddingTop = `${(padding[1] || 0) * scale}px`;
446
+ style.paddingLeft = `${(padding[0] || 0) * scale}px`;
447
+ style.paddingBottom = `${(padding[3] || 0) * scale}px`;
448
+ style.paddingRight = `${(padding[2] || 0) * scale}px`;
449
+ }
450
+
451
+ // 处理margin
452
+ const margin = this.getValue(itemData, globalData, 'margin');
453
+ if (margin && Array.isArray(margin)) {
454
+ style.marginTop = `${(margin[1] || 0) * scale}px`;
455
+ style.marginLeft = `${(margin[0] || 0) * scale}px`;
456
+ style.marginBottom = `${(margin[3] || 0) * scale}px`;
457
+ style.marginRight = `${(margin[2] || 0) * scale}px`;
458
+ }
459
+
460
+ return style;
461
+ },
462
+
463
+
464
+
465
+
466
+
467
+ // 优化内存使用
468
+ optimizeMemoryUsage() {
469
+ // 清理无用的动画实例
470
+ Object.keys(this.lottieAnimations).forEach(key => {
471
+ const anim = this.lottieAnimations[key];
472
+ if (anim && anim.isLoaded === false) {
473
+ anim.destroy();
474
+ delete this.lottieAnimations[key];
475
+ }
476
+ });
477
+
478
+ // 清理DOM缓存
479
+ if (this.gridItemsCache.length > 50) { // 如果缓存过多,清理
480
+ this.clearGridItemsCache();
481
+ }
482
+ },
483
+
184
484
  // 获取元素在页面中的绝对位置(考虑所有transform影响)
185
485
  getAbsolutePosition(element) {
186
486
  let x = 0;
@@ -216,32 +516,32 @@ export default {
216
516
  this.dragCloneElement = document.createElement('div');
217
517
  this.dragCloneElement.className = 'drag-clone-dynamic';
218
518
 
219
- // 设置基本样式(使用不带缩放的样式方法)
220
- const itemViewStyle = this.applyDragCloneAttribute(item.itemView);
221
- Object.assign(this.dragCloneElement.style, {
222
- position: 'absolute',
223
- zIndex: '9999',
224
- userSelect: 'none',
225
- boxSizing: 'border-box',
226
- flexDirection: 'column',
227
- pointerEvents: 'none',
228
- transition: 'none',
229
- willChange: 'transform',
230
- left: '0',
231
- top: '0',
232
- // 覆盖itemViewStyle中可能的宽高设置,确保使用固定尺寸
233
- ...itemViewStyle,
234
- width: `${this.fixedItemWidth }px`,
235
- height: `${this.fixedItemHeight }px`,
236
- display: 'flex',
237
- justifyContent: 'space-between',
238
- });
239
-
240
- // 创建内容HTML(使用不带缩放的样式方法)
241
- const iconImageStyle = this.applyDragCloneAttribute(item.iconImage);
242
- const selectIconStyle = this.applyDragCloneAttribute(item.selectIcon);
243
- const textStyle = this.applyDragCloneAttribute(item.text);
244
- const actionButtonStyle = this.applyDragCloneAttribute(item.actionButton);
519
+ // 设置基本样式(使用不带缩放的样式方法)
520
+ const itemViewStyle = this.applyOptimizedAttribute(item.itemView, 'itemView', false);
521
+ Object.assign(this.dragCloneElement.style, {
522
+ position: 'absolute',
523
+ zIndex: '9999',
524
+ userSelect: 'none',
525
+ boxSizing: 'border-box',
526
+ flexDirection: 'column',
527
+ pointerEvents: 'none',
528
+ transition: 'none',
529
+ willChange: 'transform',
530
+ left: '0',
531
+ top: '0',
532
+ // 覆盖itemViewStyle中可能的宽高设置,确保使用固定尺寸
533
+ ...itemViewStyle,
534
+ width: `${this.fixedItemWidth}px`,
535
+ height: `${this.fixedItemHeight}px`,
536
+ display: 'flex',
537
+ justifyContent: 'space-between',
538
+ });
539
+
540
+ // 创建内容HTML(不使用缩放)
541
+ const iconImageStyle = this.applyOptimizedAttribute(item.iconImage, 'iconImage', false);
542
+ const selectIconStyle = this.applyOptimizedAttribute(item.selectIcon, 'selectIcon', false);
543
+ const textStyle = this.applyOptimizedAttribute(item.text, 'text', false);
544
+ const actionButtonStyle = this.applyOptimizedAttribute(item.actionButton, 'actionButton', false);
245
545
 
246
546
  // 将样式对象转换为CSS字符串
247
547
  const stylesToString = (styleObj) => {
@@ -250,33 +550,35 @@ export default {
250
550
  .join('; ');
251
551
  };
252
552
 
253
- // 确保内部grid-box也使用固定尺寸(使用不带缩放的样式方法)
254
- const gridBoxStyle = {
255
- ...this.applyDragCloneAttribute(item.itemView),
256
- width: `${this.fixedItemWidth }px`,
257
- height: `${this.fixedItemHeight }px`
258
- };
553
+ // 确保内部grid-box也使用固定尺寸(使用不带缩放的样式方法)
554
+ const gridBoxStyle = {
555
+ ...this.applyOptimizedAttribute(item.itemView, 'itemView', false),
556
+ width: `${this.fixedItemWidth}px`,
557
+ height: `${this.fixedItemHeight}px`
558
+ };
259
559
 
260
- this.dragCloneElement.innerHTML = `
261
- <div class="grid-box" style="${stylesToString(gridBoxStyle)} ;display: flex; justify-content: space-between;flex-direction: column">
262
- <div class="flexBox" style="display: flex; justify-content: space-between; width: 100%; position: relative; flex-direction: row;">
263
- <img src="${item.iconImage.url}" style="${stylesToString(iconImageStyle)}" />
264
- <div style="pointer-events: none;">
265
- <img
266
- class="isSelected"
267
- src="${item.itemView.isSelected ? item.selectIcon.selectedUrl : item.selectIcon.url}"
268
- style="${stylesToString(selectIconStyle)}"
269
- />
270
- </div>
271
- </div>
272
- <div class="itemText" style="${stylesToString(textStyle)}; display: block; width: 80%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
273
- ${item.text.text}
274
- </div>
275
- </div>
276
- `;
277
-
278
- // 挂载到body
279
- document.body.appendChild(this.dragCloneElement);
560
+ this.dragCloneElement.innerHTML = `
561
+ <div class="grid-box" style="${stylesToString(gridBoxStyle)} ;display: flex; justify-content: space-between;flex-direction: column">
562
+ <div class="flexBox" style="display: flex; justify-content: space-between; width: 100%; position: relative; flex-direction: row;">
563
+ <img src="${this.getUrl(item, 'iconImage')}" style="${stylesToString(iconImageStyle)}" />
564
+ <div style="pointer-events: none;">
565
+ <img
566
+ class="isSelected"
567
+ src="${item.itemView.isSelected
568
+ ? this.getUrl(item, 'selectIcon', 'selectedUrl')
569
+ : this.getUrl(item, 'selectIcon')}"
570
+ style="${stylesToString(selectIconStyle)}"
571
+ />
572
+ </div>
573
+ </div>
574
+ <div class="itemText" style="${stylesToString(textStyle)}; display: block; width: 80%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
575
+ ${this.getText(item, 'text')}
576
+ </div>
577
+ </div>
578
+ `;
579
+
580
+ // 挂载到outer-container而不是body,这样可以更好地控制拖拽范围
581
+ this.containerElement.appendChild(this.dragCloneElement);
280
582
 
281
583
  return this.dragCloneElement;
282
584
  },
@@ -298,64 +600,11 @@ export default {
298
600
  this.dragCloneElement.style.transform = `translate(${this.dragCloneX}px, ${this.dragCloneY}px)`;
299
601
  },
300
602
 
301
- // 为拖拽克隆元素应用样式(不应用缩放因子)
302
- applyDragCloneAttribute(data = {}) {
303
- let style = {
304
- color: data.textColor || '#000000',
305
- fontWeight: data.fontWeight || 'normal',
306
- fontFamily: data.fontFamilyPath || 'inherit',
307
- fontSize: `${data.textSize || 16}px`, // 不乘以scla
308
- // 布局相关样式
309
- width:
310
- typeof data.width === 'number' && data.width > 0
311
- ? `${data.width}px` // 不乘以scla
312
- : data.width === -1
313
- ? '%'
314
- : data.width || '',
315
- height:
316
- typeof data.height === 'number'
317
- ? `${data.height}px` // 不乘以scla
318
- : data.height || '',
319
- backgroundColor: data.backgroundColor || 'transparent',
320
- borderRadius: `${data.cornerRadius || 0}px`, // 不乘以scla
321
- opacity: data.alpha || 1,
322
- };
323
-
324
- if (typeof data.visibility === 'number') {
325
- if (data.visibility === 0) {
326
- style.display = 'none';
327
- } else {
328
- style.display = data.display || '';
329
- style.visibility = data.visibility === 1 ? 'visible' : 'hidden';
330
- }
331
- } else {
332
- style.display = data.display || '';
333
- }
334
-
335
- style.borderColor = this.data.isEditing
336
- ? data.selectedBorderColor || ''
337
- : data.normalBorderColor || '';
338
- style.borderWidth = this.data.isEditing
339
- ? `${data.selectedBorderWidth || 0}px` // 不乘以scla
340
- : `${data.normalBorderWidth || 0}px`; // 不乘以scla
341
-
342
- if (data.padding) {
343
- style.paddingTop = `${data.padding[1] || 0}px`; // 不乘以scla
344
- style.paddingLeft = `${data.padding[0] || 0}px`; // 不乘以scla
345
- style.paddingBottom = `${data.padding[3] || 0}px`; // 不乘以scla
346
- style.paddingRight = `${data.padding[2] || 0}px`; // 不乘以scla
347
- }
348
- if (data.margin) {
349
- style.marginTop = `${data.margin[1] || 0}px`; // 不乘以scla
350
- style.marginLeft = `${data.margin[0] || 0}px`; // 不乘以scla
351
- style.marginBottom = `${data.margin[3] || 0}px`; // 不乘以scla
352
- style.marginRight = `${data.margin[2] || 0}px`; // 不乘以scla
353
- }
354
-
355
- return style;
603
+ // 普通渲染的样式应用方法(使用缩放)
604
+ applyNormalAttribute(itemData = {}, type = '') {
605
+ return this.applyOptimizedAttribute(itemData, type, true);
356
606
  },
357
607
  updateListItem(data) {
358
- console.log('updateListItem收到数据:', JSON.stringify(data));
359
608
  const id = data.itemView.id;
360
609
  const index = this.data.list.findIndex((item) => item.itemView.id == id);
361
610
 
@@ -363,38 +612,16 @@ export default {
363
612
  return;
364
613
  }
365
614
 
366
- // 合并全局数据到新数据
367
- const mergedData = this.mergeItemData(data);
615
+ // 确保数据结构完整性
616
+ this.ensureItemStructure(data);
368
617
 
369
- this.$set(this.data.list, index, mergedData);
618
+ // 直接更新数据,不再需要合并
619
+ this.$set(this.data.list, index, data);
370
620
  this.$nextTick(() => {
371
621
  const itemId = String(data.itemView.id);
372
- if (mergedData.bgAnimView.animUrl) {
373
- this.createLottieAnimation(
374
- 'bg',
375
- itemId,
376
- mergedData.bgAnimView,
377
- mergedData
378
- );
379
- }
380
-
381
- if (mergedData.loadingAnimView.animUrl) {
382
- this.createLottieAnimation(
383
- 'loading',
384
- itemId,
385
- mergedData.loadingAnimView,
386
- mergedData
387
- );
388
- }
389
-
390
- if (mergedData.frontAnimView.animUrl) {
391
- this.createLottieAnimation(
392
- 'front',
393
- itemId,
394
- mergedData.frontAnimView,
395
- mergedData
396
- );
397
- }
622
+
623
+ // 使用抽取的公共方法初始化动画
624
+ this.initItemAnimations(data, data);
398
625
  });
399
626
  },
400
627
  createLottieAnimation(type, itemId, animView, itemData) {
@@ -488,17 +715,31 @@ export default {
488
715
  },
489
716
  getListData(callback) {
490
717
  let data = Array.from(this.data.list);
491
- data = data.map((item) => ({
492
- ...item,
493
- itemView: {
494
- ...item.itemView,
495
- id: String(item.itemView.id),
496
- },
497
- }));
718
+ data = data.map((item) => {
719
+ // 确保每个项目都有完整的数据结构
720
+ const completeItem = { ...item };
721
+ this.ensureItemStructure(completeItem);
722
+
723
+ // 为了外部使用,合并全局数据到每个类型
724
+ REQUIRED_TYPES.forEach((type) => {
725
+ const globalData = this.data.globalData && this.data.globalData[type] || {};
726
+ completeItem[type] = {
727
+ ...globalData,
728
+ ...completeItem[type]
729
+ };
730
+ });
731
+
732
+ return {
733
+ ...completeItem,
734
+ itemView: {
735
+ ...completeItem.itemView,
736
+ id: String(completeItem.itemView.id),
737
+ },
738
+ };
739
+ });
498
740
  callback({ listData: data });
499
741
  },
500
742
  rightIconClick(e) {
501
- console.log('rightIconClick执行,阻止事件冒泡');
502
743
  e.preventDefault();
503
744
  e.stopPropagation();
504
745
  this.$emit('onClickRightIcon', e);
@@ -554,7 +795,6 @@ export default {
554
795
  },
555
796
 
556
797
  actionClick(e, item, index) {
557
- console.log('actionClick执行,阻止事件冒泡');
558
798
  e.preventDefault();
559
799
  e.stopPropagation(); // 阻止事件冒泡到itemViewClick
560
800
 
@@ -572,8 +812,6 @@ export default {
572
812
  },
573
813
 
574
814
  cleanupOtherAnimations(currentItemId) {
575
- console.log('清理其他项目的动画实例,当前项目ID:', currentItemId);
576
-
577
815
  Object.keys(this.lottieAnimations).forEach((key) => {
578
816
  if (!key.includes(`-${currentItemId}`)) {
579
817
  if (this.lottieAnimations[key]) {
@@ -621,103 +859,10 @@ export default {
621
859
  });
622
860
  },
623
861
 
624
- generateDataList() {
625
- if (!this.data || !this.data.list || !this.data.globalData) {
626
- return;
627
- }
628
- // 直接处理 this.data.list,为每个项目合并全局数据
629
- this.data.list.forEach((item, index) => {
630
- const processedItem = this.mergeItemData(item);
631
- this.$set(this.data.list, index, processedItem);
632
- });
633
862
 
634
- },
635
863
 
636
- mergeItemData(item) {
637
- const processedItem = {};
638
- const types = [
639
- 'itemView',
640
- 'text',
641
- 'actionButton',
642
- 'rightIcon',
643
- 'leftTopIcon',
644
- 'iconImage',
645
- 'selectIcon',
646
- 'loadingAnimView',
647
- 'bgAnimView',
648
- 'frontAnimView',
649
- ];
650
-
651
- types.forEach((type) => {
652
- // 使用mergeDate合并全局数据和项目数据
653
- processedItem[type] = this.mergeDate(item[type] || {}, type);
654
- });
655
864
 
656
- return processedItem;
657
- },
658
865
 
659
- getItemStyle(item, type) {
660
- const result = this.mergeDate(item[type] || {}, type);
661
- console.log(type, this.applyNormalAttribute(result), 'ggg');
662
- return this.applyNormalAttribute(result);
663
- },
664
- mergeDate(target, type) {
665
- const globalData = this.data.globalData[type] || {};
666
- return Object.assign({}, globalData, target);
667
- },
668
- applyNormalAttribute(data = {}, type) {
669
- let style = {
670
- color: data.textColor || '#000000',
671
- fontWeight: data.fontWeight || 'normal',
672
- fontFamily: data.fontFamilyPath || 'inherit',
673
- fontSize: `${(data.textSize || 16) * scla}px`, // 默认16px
674
- // 布局相关样式
675
- width:
676
- typeof data.width === 'number' && data.width > 0
677
- ? `${data.width * scla}px`
678
- : data.width === -1
679
- ? '%'
680
- : data.width || '',
681
- height:
682
- typeof data.height === 'number'
683
- ? `${data.height * scla}px`
684
- : data.height || '',
685
- backgroundColor: data.backgroundColor || 'transparent',
686
- borderRadius: `${(data.cornerRadius || 0) * scla}px`,
687
- opacity: data.alpha || 1,
688
- };
689
- if (typeof data.visibility === 'number') {
690
- if (data.visibility === 0) {
691
- style.display = 'none';
692
- } else {
693
- style.display = data.display || '';
694
- style.visibility = data.visibility === 1 ? 'visible' : 'hidden';
695
- }
696
- } else {
697
- style.display = data.display || '';
698
- }
699
-
700
- style.borderColor = this.data.isEditing
701
- ? data.selectedBorderColor || ''
702
- : data.normalBorderColor || '';
703
- style.borderWidth = this.data.isEditing
704
- ? `${(data.selectedBorderWidth || 0) * scla}px`
705
- : `${(data.normalBorderWidth || 0) * scla}px`;
706
- if (data.padding) {
707
- style.paddingTop = `${(data.padding[1] || 0) * scla}px`;
708
- style.paddingLeft = `${(data.padding[0] || 0) * scla}px`;
709
- style.paddingBottom = `${(data.padding[3] || 0) * scla}px`;
710
- style.paddingRight = `${(data.padding[2] || 0) * scla}px`;
711
- }
712
- if (data.margin) {
713
- style.marginTop = `${(data.margin[1] || 0) * scla}px`;
714
- style.marginLeft = `${(data.margin[0] || 0) * scla}px`;
715
- style.marginBottom = `${(data.margin[3] || 0) * scla}px`;
716
- style.marginRight = `${(data.margin[2] || 0) * scla}px`;
717
- }
718
-
719
- return style;
720
- },
721
866
  // 开始长按
722
867
  startLongPress(e, index) {
723
868
  if (!this.data.isEditable) {
@@ -730,7 +875,6 @@ export default {
730
875
  const touch = e.changedTouches[0];
731
876
  this.touchStartX = touch.pageX;
732
877
  this.touchStartY = touch.pageY;
733
- console.log('开始长按ddddddddd', touch, touch.pageY, touch.pageX);
734
878
  this.isScrolling = false; // 重置滚动状态
735
879
 
736
880
  // 设置新的长按计时器
@@ -788,10 +932,14 @@ export default {
788
932
 
789
933
  // 激活拖拽模式
790
934
  activateDragMode(index) {
791
- console.log('长按触发,进入编辑模式');
792
- // 进入编辑模式
793
-
794
935
  if (this.data.isEditing) {
936
+ // 重置交换状态
937
+ this.lastSwapPair = null;
938
+ this.stablePositionTime = 0;
939
+ this.isAnimating = false;
940
+ this.lastTouchX = this.touchStartX;
941
+ this.lastTouchY = this.touchStartY;
942
+
795
943
  // 设置当前拖拽索引
796
944
  this.draggingIndex = index;
797
945
  this.isTouchDragging = true;
@@ -802,8 +950,9 @@ export default {
802
950
  height: this.fixedItemHeight,
803
951
  };
804
952
 
805
- // 获取当前元素位置用于计算偏移
806
- const element = document.querySelectorAll('.grid-item')[index];
953
+ // 获取当前元素位置用于计算偏移 - 使用缓存的元素
954
+ const gridItems = this.getCachedGridItems();
955
+ const element = gridItems[index];
807
956
  if (element) {
808
957
  // 使用getBoundingClientRect获取视口中的位置
809
958
  const rect = element.getBoundingClientRect();
@@ -815,22 +964,29 @@ export default {
815
964
  // 创建拖拽克隆元素
816
965
  this.createDragClone(index);
817
966
 
818
- // 设置克隆元素的初始位置(直接使用视口坐标加滚动偏移)
819
- this.dragCloneX = rect.left + window.pageXOffset;
820
- this.dragCloneY = rect.top + window.pageYOffset;
967
+ // 计算相对于outer-container的位置
968
+ const containerRect = this.containerElement.getBoundingClientRect();
969
+ this.dragCloneX = rect.left - containerRect.left;
970
+ this.dragCloneY = rect.top - containerRect.top;
821
971
  this.isDragClone = true;
822
972
 
823
973
  // 立即更新位置
824
974
  this.updateDragClonePosition();
825
975
 
826
976
  requestAnimationFrame(() => {
827
- // 使用触摸点减去偏移来计算正确位置
828
- this.dragCloneX = this.touchStartX - this.touchOffsetX + window.pageXOffset;
829
- this.dragCloneY = this.touchStartY - this.touchOffsetY + window.pageYOffset;
977
+ // 使用触摸点减去偏移来计算正确位置,相对于容器
978
+ const targetX = this.touchStartX - this.touchOffsetX - containerRect.left;
979
+ const targetY = this.touchStartY - this.touchOffsetY - containerRect.top;
980
+
981
+ // 限制在容器范围内
982
+ const maxX = containerRect.width - this.fixedItemWidth;
983
+ const maxY = containerRect.height - this.fixedItemHeight;
984
+
985
+ this.dragCloneX = Math.max(0, Math.min(targetX, maxX));
986
+ this.dragCloneY = Math.max(0, Math.min(targetY, maxY));
830
987
  this.updateDragClonePosition();
831
988
  });
832
989
 
833
- console.log('拖拽克隆已创建');
834
990
  }
835
991
  }
836
992
  if(!this.data.isEditing){
@@ -872,42 +1028,87 @@ export default {
872
1028
 
873
1029
  // 启动平滑动画(如果尚未启动)
874
1030
  if (!this.animationFrameId) {
875
- this.animationFrameId = requestAnimationFrame(this.updateDragPosition);
1031
+ this.animationFrameId = requestAnimationFrame(this.optimizedUpdateDragPosition);
876
1032
  }
877
1033
 
878
- // 简化的交换逻辑
879
- this.handleSwapLogic(touch.pageX, touch.pageY);
1034
+ // 使用节流优化交换逻辑,增加节流时间减少频繁调用
1035
+ if (!this.updatePositionThrottled) {
1036
+ this.updatePositionThrottled = this.throttle(this.handleSwapLogic.bind(this), 500);
1037
+ }
1038
+
1039
+ // 在动画过程中不执行交换逻辑
1040
+ if (!this.isAnimating) {
1041
+ this.updatePositionThrottled(touch.pageX, touch.pageY);
1042
+ }
880
1043
  },
881
1044
 
882
1045
  // 简化的交换处理逻辑
883
1046
  handleSwapLogic(touchX, touchY) {
884
1047
  // 控制交换频率,避免过于频繁的交换
885
1048
  const now = Date.now();
886
- if (now - this.lastSwapTime < 300) return;
1049
+ if (now - this.lastSwapTime < 150) return; // 增加频率控制到150ms
887
1050
 
888
- // 获取所有网格元素
889
- const gridItems = Array.from(document.querySelectorAll('.grid-item'));
1051
+ // 检查位置稳定性 - 如果触摸点基本没有移动,认为是稳定状态
1052
+ const moveDistance = Math.sqrt(
1053
+ Math.pow(touchX - this.lastTouchX, 2) + Math.pow(touchY - this.lastTouchY, 2)
1054
+ );
1055
+
1056
+ if (moveDistance < 10) { // 移动距离小于10px认为是稳定的
1057
+ if (this.stablePositionTime === 0) {
1058
+ this.stablePositionTime = now;
1059
+ } else if (now - this.stablePositionTime > 300) {
1060
+ // 稳定超过300ms,跳过交换检测
1061
+ return;
1062
+ }
1063
+ } else {
1064
+ // 位置发生明显移动,重置稳定时间
1065
+ this.stablePositionTime = 0;
1066
+ }
1067
+
1068
+ // 更新上次触摸位置
1069
+ this.lastTouchX = touchX;
1070
+ this.lastTouchY = touchY;
1071
+
1072
+ // 获取容器边界
1073
+ const containerRect = this.containerElement.getBoundingClientRect();
1074
+
1075
+ // 计算用于交换检测的有效触摸点
1076
+ // 如果触摸点在容器外,使用拖拽克隆元素的中心位置
1077
+ let effectiveTouchX = touchX;
1078
+ let effectiveTouchY = touchY;
1079
+
1080
+ if (touchX < containerRect.left || touchX > containerRect.right ||
1081
+ touchY < containerRect.top || touchY > containerRect.bottom) {
1082
+ // 触摸点在容器外,使用拖拽克隆元素的中心位置
1083
+ const cloneCenterX = this.dragCloneX + this.fixedItemWidth / 2;
1084
+ const cloneCenterY = this.dragCloneY + this.fixedItemHeight / 2;
1085
+ effectiveTouchX = containerRect.left + cloneCenterX;
1086
+ effectiveTouchY = containerRect.top + cloneCenterY;
1087
+ }
1088
+
1089
+ // 使用缓存的网格元素
1090
+ const gridItems = this.getCachedGridItems();
890
1091
  let targetIndex = null;
891
1092
 
892
- // 寻找触摸点所在的目标元素
1093
+ // 寻找有效触摸点所在的目标元素
893
1094
  for (const item of gridItems) {
894
1095
  const itemIndex = parseInt(item.getAttribute('data-index'), 10);
895
1096
  if (itemIndex === this.draggingIndex) continue;
896
1097
 
897
1098
  const rect = item.getBoundingClientRect();
898
1099
 
899
- // 触摸点是否在元素的中心区域内
1100
+ // 有效触摸点是否在元素的中心区域内
900
1101
  const centerX = (rect.left + rect.right) / 2;
901
1102
  const centerY = (rect.top + rect.bottom) / 2;
902
- const halfWidth = rect.width * 0.4; // 缩小判断区域到40%,避免边缘误触
903
- const halfHeight = rect.height * 0.4;
1103
+ const halfWidth = rect.width * 0.3; // 缩小判断区域到30%,减少误触
1104
+ const halfHeight = rect.height * 0.3;
904
1105
 
905
- // 检查触摸点是否在元素的中心区域内
1106
+ // 检查有效触摸点是否在元素的中心区域内
906
1107
  if (
907
- touchX >= centerX - halfWidth &&
908
- touchX <= centerX + halfWidth &&
909
- touchY >= centerY - halfHeight &&
910
- touchY <= centerY + halfHeight
1108
+ effectiveTouchX >= centerX - halfWidth &&
1109
+ effectiveTouchX <= centerX + halfWidth &&
1110
+ effectiveTouchY >= centerY - halfHeight &&
1111
+ effectiveTouchY <= centerY + halfHeight
911
1112
  ) {
912
1113
  targetIndex = itemIndex;
913
1114
  break; // 找到第一个符合条件的就退出
@@ -919,46 +1120,46 @@ export default {
919
1120
 
920
1121
  // 执行交换
921
1122
  if (targetIndex !== null && targetIndex !== this.draggingIndex) {
922
- [this.data.list[this.draggingIndex], this.data.list[targetIndex]] = [
923
- this.data.list[targetIndex],
924
- this.data.list[this.draggingIndex],
925
- ];
926
- console.log(`交换: ${this.draggingIndex} ↔ ${targetIndex}`);
927
-
928
- // 更新拖拽索引
1123
+ // 防止在动画过程中频繁交换
1124
+ if (this.isAnimating) return;
1125
+
1126
+ // 创建当前交换对的标识符
1127
+ const currentSwapPair = [this.draggingIndex, targetIndex].sort().join('-');
1128
+
1129
+ // 如果这是同一对元素的重复交换,需要更严格的时间控制
1130
+ if (this.lastSwapPair === currentSwapPair) {
1131
+ if (now - this.lastSwapTime < 1000) return; // 同一对元素需要等待1秒
1132
+ }
1133
+
1134
+ this.isAnimating = true;
1135
+
1136
+ // 使用Vue的$set来避免触发不必要的重新渲染
1137
+ const temp = this.data.list[this.draggingIndex];
1138
+ this.$set(this.data.list, this.draggingIndex, this.data.list[targetIndex]);
1139
+ this.$set(this.data.list, targetIndex, temp);
1140
+
1141
+ // 更新拖拽索引和交换记录
929
1142
  this.draggingIndex = targetIndex;
930
1143
  this.lastSwapTime = now;
1144
+ this.lastSwapPair = currentSwapPair;
1145
+
1146
+ // 重置稳定位置时间,因为刚刚发生了交换
1147
+ this.stablePositionTime = 0;
1148
+
1149
+ // 延迟清理缓存,等待DOM更新完成
1150
+ this.$nextTick(() => {
1151
+ setTimeout(() => {
1152
+ this.clearGridItemsCache();
1153
+ this.isAnimating = false;
1154
+ }, 100); // 增加延迟时间到100ms
1155
+ });
931
1156
  }
932
1157
  },
933
1158
 
934
- // 平滑更新拖拽位置的方法
935
- updateDragPosition() {
936
- const easing = 0.6;
937
- // 计算目标位置(考虑页面滚动位置)
938
- const targetX = this.targetTouchX - this.touchOffsetX + window.pageXOffset;
939
- const targetY = this.targetTouchY - this.touchOffsetY + window.pageYOffset;
940
-
941
- // 平滑插值
942
- this.dragCloneX += (targetX - this.dragCloneX) * easing;
943
- this.dragCloneY += (targetY - this.dragCloneY) * easing;
944
-
945
- // 当接近目标位置时直接设置为目标位置
946
- if (Math.abs(this.dragCloneX - targetX) < 0.5) {
947
- this.dragCloneX = targetX;
948
- }
949
- if (Math.abs(this.dragCloneY - targetY) < 0.5) {
950
- this.dragCloneY = targetY;
951
- }
952
-
953
- // 更新拖拽克隆元素位置
954
- this.updateDragClonePosition();
955
-
956
- this.animationFrameId = requestAnimationFrame(this.updateDragPosition);
957
- },
1159
+
958
1160
 
959
1161
  // 元素本身的触摸结束
960
1162
  endTouch(e) {
961
- console.log('触摸结束');
962
1163
  this.$emit('onDragEventEnd', e);
963
1164
  // 如果只是短按(还在计时),就取消长按
964
1165
  this.cancelLongPress();
@@ -973,20 +1174,32 @@ export default {
973
1174
  this.isTouchDragging = false;
974
1175
  this.isDragClone = false;
975
1176
  this.hoverIndex = null;
1177
+ this.isUpdatingPosition = false;
1178
+
1179
+ // 重置交换状态
1180
+ this.lastSwapPair = null;
1181
+ this.stablePositionTime = 0;
1182
+ this.lastTouchX = 0;
1183
+ this.lastTouchY = 0;
976
1184
 
977
1185
  // 销毁拖拽克隆元素
978
1186
  this.destroyDragClone();
1187
+
1188
+ // 等待可能的交换动画完成后再清理
1189
+ setTimeout(() => {
1190
+ this.clearGridItemsCache();
1191
+ this.isAnimating = false; // 重置动画状态
1192
+ }, 100);
979
1193
 
980
1194
  // 使用延时来重置拖拽索引,避免与点击事件冲突
981
1195
  setTimeout(() => {
982
1196
  this.draggingIndex = null;
983
- }, 100);
1197
+ }, 150);
984
1198
  }
985
1199
  },
986
1200
 
987
1201
  // 触摸结束处理 - 全局事件
988
1202
  onTouchEnd(e) {
989
- console.log('全局触摸结束');
990
1203
  // 取消动画帧
991
1204
  if (this.animationFrameId) {
992
1205
  cancelAnimationFrame(this.animationFrameId);
@@ -997,13 +1210,23 @@ export default {
997
1210
  this.isTouchDragging = false;
998
1211
  this.isDragClone = false;
999
1212
  this.hoverIndex = null;
1213
+ this.isUpdatingPosition = false;
1214
+
1215
+ // 重置交换状态
1216
+ this.lastSwapPair = null;
1217
+ this.stablePositionTime = 0;
1218
+ this.lastTouchX = 0;
1219
+ this.lastTouchY = 0;
1000
1220
 
1001
1221
  // 销毁拖拽克隆元素
1002
1222
  this.destroyDragClone();
1003
-
1004
- // 立即清除拖拽索引,避免状态卡住
1005
- this.draggingIndex = null;
1006
- this.isAnimating = false;
1223
+
1224
+ // 延迟清理,确保所有动画和状态更新完成
1225
+ setTimeout(() => {
1226
+ this.clearGridItemsCache();
1227
+ this.isAnimating = false;
1228
+ this.draggingIndex = null;
1229
+ }, 100);
1007
1230
  },
1008
1231
  // 获取grid-box宽度的方法
1009
1232
  updateGridBoxWidth() {
@@ -1012,7 +1235,6 @@ export default {
1012
1235
  if (gridBoxes && gridBoxes.length > 0) {
1013
1236
  const box = gridBoxes[0];
1014
1237
  this.gridBoxWidth = box.offsetWidth;
1015
- console.log('Grid box宽度:', this.gridBoxWidth);
1016
1238
 
1017
1239
  // 更新text样式,确保不超出
1018
1240
  const textElements = document.querySelectorAll('.itemText');
@@ -1029,7 +1251,6 @@ export default {
1029
1251
  Object.keys(this.lottieAnimations).forEach((key) => {
1030
1252
  const anim = this.lottieAnimations[key];
1031
1253
  if (anim && typeof anim.destroy === 'function') {
1032
- console.log(`销毁旧动画实例: ${key}`);
1033
1254
  try {
1034
1255
  anim.removeEventListener('complete');
1035
1256
  } catch (e) {
@@ -1043,31 +1264,11 @@ export default {
1043
1264
  // 重置动画实例对象
1044
1265
  this.lottieAnimations = {};
1045
1266
 
1046
- console.log('开始初始化Lottie动画,列表项数量:', this.data.list.length);
1047
-
1048
1267
  // 延迟一点时间确保DOM完全更新并且之前的动画已被销毁
1049
1268
  setTimeout(() => {
1050
1269
  // 为每个列表项初始化动画
1051
1270
  this.data.list.forEach((item) => {
1052
- const itemId = String(item.itemView.id);
1053
- // 加载背景动画
1054
- this.createLottieAnimation('bg', itemId, item.bgAnimView, item);
1055
-
1056
- // 加载加载中动画
1057
- this.createLottieAnimation(
1058
- 'loading',
1059
- itemId,
1060
- item.loadingAnimView,
1061
- item
1062
- );
1063
-
1064
- // 加载前景动画
1065
- this.createLottieAnimation(
1066
- 'front',
1067
- itemId,
1068
- item.frontAnimView,
1069
- item
1070
- );
1271
+ this.initItemAnimations(item);
1071
1272
  });
1072
1273
  }, 50); // 小延迟确保DOM已更新和旧动画已销毁
1073
1274
  });
@@ -1094,39 +1295,66 @@ export default {
1094
1295
  },
1095
1296
  },
1096
1297
  watch: {
1097
- 'data.isEditing': {
1098
- handler(newVal) {
1099
- this.generateDataList();
1100
- this.$nextTick(() => {
1101
- this.initLottieAnimations();
1102
- });
1103
- },
1104
- deep: true,
1105
- },
1106
- // 监听列表数据变化,自动更新处理后的数据
1298
+ // 监听列表数据变化,自动更新缓存
1107
1299
  'data.list': {
1108
1300
  handler(newList, oldList) {
1109
- // 防止死循环
1110
- if (newList.length !== oldList.length) {
1111
- console.log('检测到data.list变化');
1112
- this.generateDataList();
1113
- this.$nextTick(() => {
1114
- this.initLottieAnimations();
1115
- });
1301
+ // 跳过拖拽期间的更新,提高性能
1302
+ if (this.isTouchDragging) {
1303
+ return;
1304
+ }
1305
+ // 更精确的变化检测
1306
+ const hasLengthChange = newList.length !== oldList.length;
1307
+ if (hasLengthChange) {
1308
+
1309
+ // 清除DOM缓存
1310
+ this.clearGridItemsCache();
1311
+
1312
+ // 使用防抖处理列表变化
1313
+ if (!this.listChangeDebounced) {
1314
+ this.listChangeDebounced = this.debounce(() => {
1315
+ this.$nextTick(() => {
1316
+ // 优化内存使用
1317
+ // 确保每个项目都有完整的数据结构
1318
+ if (newList && Array.isArray(newList)) {
1319
+ newList.forEach((item, index) => {
1320
+ this.ensureItemStructure(item);
1321
+ });
1322
+ }
1323
+
1324
+ this.optimizeMemoryUsage();
1325
+ });
1326
+ }, 100);
1327
+ }
1328
+ this.listChangeDebounced();
1116
1329
  }
1117
1330
  },
1118
1331
  deep: true,
1119
1332
  },
1120
1333
  },
1121
-
1334
+ updated(){
1335
+ if(this.isTouchDragging){return}
1336
+ },
1122
1337
  mounted() {
1123
- console.log(this.data, 'djdjdjdjdjdjdj');
1338
+
1339
+ // 确保初始数据结构完整性
1340
+ if (this.data && this.data.list && Array.isArray(this.data.list)) {
1341
+ this.data.list.forEach((item, index) => {
1342
+ this.ensureItemStructure(item);
1343
+ });
1344
+ }
1345
+
1346
+ // 缓存容器元素
1347
+ this.containerElement = this.$el;
1348
+
1124
1349
  // 初始获取grid-box宽度
1125
1350
  this.updateGridBoxWidth();
1126
1351
  // 获取并存储固定宽高
1127
1352
  this.updateFixedItemSize();
1128
1353
  // 初始化Lottie动画
1129
1354
  this.initLottieAnimations();
1355
+
1356
+ // 初始化节流函数
1357
+ this.updatePositionThrottled = this.throttle(this.handleSwapLogic.bind(this), 100);
1130
1358
  },
1131
1359
  beforeDestroy() {
1132
1360
  // 确保清理所有事件监听器
@@ -1138,10 +1366,19 @@ export default {
1138
1366
  }
1139
1367
  // 清理拖拽克隆元素
1140
1368
  this.destroyDragClone();
1369
+
1370
+ // 清理缓存
1371
+ this.clearGridItemsCache();
1372
+
1373
+ // 清理节流和防抖函数
1374
+ this.updatePositionThrottled = null;
1375
+ this.listChangeDebounced = null;
1376
+
1377
+ // 清理容器引用
1378
+ this.containerElement = null;
1141
1379
  },
1142
1380
  created() {
1143
- // 初始化处理后的数据列表
1144
- this.generateDataList();
1381
+ // 组件创建时无需预处理数据,使用时按需获取全局数据
1145
1382
  },
1146
1383
  };
1147
1384
  </script>
@@ -1160,6 +1397,8 @@ button {
1160
1397
  width: 100%;
1161
1398
  position: relative;
1162
1399
  z-index: 1;
1400
+ /* 确保容器可以作为拖拽克隆元素的定位父元素 */
1401
+ overflow: visible;
1163
1402
  }
1164
1403
 
1165
1404
  .items-container {
@@ -1176,21 +1415,28 @@ button {
1176
1415
  grid-template-columns: repeat(var(--grid-cols), 1fr);
1177
1416
  grid-gap: var(--grid-gap);
1178
1417
  width: calc(100% - var(--grid-margin));
1418
+ /* 性能优化 */
1419
+ contain: layout style paint;
1420
+ transform: translateZ(0);
1179
1421
  }
1180
1422
 
1181
1423
  /* 优化排序过渡效果 */
1182
1424
  .grid-fade-move {
1183
- transition: transform 0.2s cubic-bezier(0.2, 0, 0.2, 1);
1425
+ transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
1184
1426
  will-change: transform;
1185
1427
  }
1186
1428
 
1187
1429
  .grid-item {
1188
1430
  position: relative;
1189
1431
  box-sizing: border-box;
1190
- transition: all 0.15s cubic-bezier(0.2, 0, 0.2, 1);
1432
+ transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
1191
1433
  will-change: transform, opacity, box-shadow;
1192
1434
  overflow: hidden;
1193
1435
  z-index: 1;
1436
+ /* 性能优化 */
1437
+ transform: translateZ(0);
1438
+ backface-visibility: hidden;
1439
+ perspective: 1000px;
1194
1440
  }
1195
1441
 
1196
1442
  .grid-box {
@@ -1252,7 +1498,7 @@ button {
1252
1498
  .hover-target {
1253
1499
  z-index: 50;
1254
1500
  transform: scale(1.05);
1255
- transition: all 0.1s ease-out;
1501
+ transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
1256
1502
  position: relative;
1257
1503
  }
1258
1504
 
@@ -1270,6 +1516,18 @@ button {
1270
1516
  }
1271
1517
 
1272
1518
  /* 拖拽克隆元素样式(现在通过JavaScript动态应用) */
1519
+ .drag-clone-dynamic {
1520
+ position: absolute;
1521
+ z-index: 9999;
1522
+ pointer-events: none;
1523
+ user-select: none;
1524
+ box-sizing: border-box;
1525
+ transform-origin: center center;
1526
+ /* 确保拖拽克隆元素不会超出容器边界 */
1527
+ max-width: 100%;
1528
+ max-height: 100%;
1529
+ overflow: hidden;
1530
+ }
1273
1531
 
1274
1532
  .flexBox {
1275
1533
  display: flex;
@@ -1314,4 +1572,6 @@ button {
1314
1572
  transform: rotateZ(2deg);
1315
1573
  }
1316
1574
  }
1575
+
1576
+
1317
1577
  </style>