@dolphinweex/weex-harmony 0.1.28 → 0.1.29

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dolphinweex/weex-harmony",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "weex harmony adapter",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -8,7 +8,7 @@
8
8
  <div class="grid-items-wrapper">
9
9
  <transition-group name="grid-fade" tag="div" class="grid-inner-wrapper">
10
10
  <div
11
- v-for="(item, index) in processedList"
11
+ v-for="(item, index) in data.list"
12
12
  :key="item.itemView.id"
13
13
  class="grid-item"
14
14
  :class="{
@@ -117,34 +117,34 @@
117
117
  <div v-if="isDragClone" class="drag-clone" :style="dragCloneStyle">
118
118
  <div
119
119
  class="grid-box"
120
- :style="applyNormalAttribute(processedList[draggingIndex].itemView)"
120
+ :style="applyNormalAttribute(data.list[draggingIndex].itemView)"
121
121
  >
122
122
  <div class="flexBox">
123
123
  <img
124
- :src="processedList[draggingIndex].iconImage.url"
124
+ :src="data.list[draggingIndex].iconImage.url"
125
125
  :style="
126
- applyNormalAttribute(processedList[draggingIndex].iconImage)
126
+ applyNormalAttribute(data.list[draggingIndex].iconImage)
127
127
  "
128
128
  />
129
129
  <div @click.stop.prevent>
130
130
  <img
131
131
  class="isSelected"
132
132
  :src="
133
- processedList[draggingIndex].itemView.isSelected
134
- ? processedList[draggingIndex].selectIcon.selectedUrl
135
- : processedList[draggingIndex].selectIcon.url
133
+ data.list[draggingIndex].itemView.isSelected
134
+ ? data.list[draggingIndex].selectIcon.selectedUrl
135
+ : data.list[draggingIndex].selectIcon.url
136
136
  "
137
137
  :style="
138
- applyNormalAttribute(processedList[draggingIndex].selectIcon)
138
+ applyNormalAttribute(data.list[draggingIndex].selectIcon)
139
139
  "
140
140
  />
141
141
  </div>
142
142
  </div>
143
143
  <div
144
144
  class="itemText"
145
- :style="[applyNormalAttribute(processedList[draggingIndex].text)]"
145
+ :style="[applyNormalAttribute(data.list[draggingIndex].text)]"
146
146
  >
147
- {{ processedList[draggingIndex].text.text }}
147
+ {{ data.list[draggingIndex].text.text }}
148
148
  </div>
149
149
  </div>
150
150
  </div>
@@ -173,7 +173,6 @@ export default {
173
173
  return {
174
174
  targetTouchX: 0,
175
175
  targetTouchY: 0,
176
- processedList: [],
177
176
  selected: null,
178
177
  longPressTimer: null,
179
178
  longPressDuration: 350,
@@ -198,9 +197,7 @@ export default {
198
197
  fixedItemWidth: 0,
199
198
  fixedItemHeight: 0,
200
199
  animationFrameId: null,
201
- lastBestTarget: null,
202
- targetChangeTime: null,
203
- pendingTarget: null,
200
+ isProcessingData: false,
204
201
  };
205
202
  },
206
203
  computed: {
@@ -208,6 +205,7 @@ export default {
208
205
  return {
209
206
  '--grid-cols': this.data.spanCount || this.cols,
210
207
  '--grid-gap': `${this.data.spaceSize || 8}px`,
208
+ '--grid-margin': `${(this.data.layoutConfig.marginStart ) + (this.data.layoutConfig.marginEnd )}px`,
211
209
  marginTop: this.data.layoutConfig.marginTop * scla,
212
210
  marginBottom: this.data.layoutConfig.marginBottom * scla,
213
211
  marginLeft: this.data.layoutConfig.marginStart * scla,
@@ -220,7 +218,7 @@ export default {
220
218
  return { display: 'none' };
221
219
  }
222
220
 
223
- const item = this.processedList[this.draggingIndex];
221
+ const item = this.data.list[this.draggingIndex];
224
222
  let itemView = {};
225
223
  if (item) {
226
224
  itemView = this.applyNormalAttribute(item.itemView);
@@ -245,10 +243,7 @@ export default {
245
243
  updateListItem(data) {
246
244
  console.log('updateListItem收到数据:', JSON.stringify(data));
247
245
  const id = data.itemView.id;
248
- const index = this.processedList.findIndex(
249
- (item) => item.itemView.id == id
250
- );
251
- const dataIndex = this.data.list.findIndex(
246
+ const index = this.data.list.findIndex(
252
247
  (item) => item.itemView.id == id
253
248
  );
254
249
 
@@ -257,89 +252,98 @@ export default {
257
252
  }
258
253
 
259
254
  // 检查是否有动画URL的变化
260
- const oldBgAnimUrl = this.processedList[index].bgAnimView.animUrl || '';
261
- const oldLoadingAnimUrl =
262
- this.processedList[index].loadingAnimView.animUrl || '';
263
- const oldFrontAnimUrl =
264
- this.processedList[index].frontAnimView.animUrl || '';
265
-
255
+ const oldBgAnimUrl = this.data.list[index].bgAnimView.animUrl || '';
256
+ const oldLoadingAnimUrl = this.data.list[index].loadingAnimView.animUrl || '';
257
+ const oldFrontAnimUrl = this.data.list[index].frontAnimView.animUrl || '';
266
258
  const newBgAnimUrl = data.bgAnimView.animUrl || '';
267
259
  const newLoadingAnimUrl = data.loadingAnimView.animUrl || '';
268
260
  const newFrontAnimUrl = data.frontAnimView.animUrl || '';
269
- // 同时更新processedList和原始data.list数据
270
- this.processedList[index] = data;
271
- this.processedList = [...this.processedList];
272
-
273
- // 更新原始数据以确保watch能触发
274
- if (dataIndex !== -1) {
275
- // 使用Vue的$set确保响应式更新
276
- this.$set(this.data.list, dataIndex, JSON.parse(JSON.stringify(data)));
277
- }
278
261
 
279
- // 如果动画URL有变化,重新初始化相关的Lottie动画
280
- console.log('检测到动画URL变化,重新初始化动画');
262
+ // 合并全局数据到新数据
263
+ const mergedData = this.mergeItemData(data);
264
+
265
+ // 更新数据
266
+ this.$set(this.data.list, index, mergedData);
281
267
 
282
- // 等待下一帧DOM更新完成后再操作
283
268
  this.$nextTick(() => {
284
- // 清理旧的动画实例
285
269
  const itemId = String(data.itemView.id);
286
270
 
287
- // 清除所有相关的动画实例
288
- const animKeys = [
289
- `bg-${itemId}`,
290
- `loading-${itemId}`,
291
- `front-${itemId}`,
292
- ];
293
- animKeys.forEach((key) => {
294
- if (this.lottieAnimations[key]) {
295
- console.log(`销毁动画实例: ${key}`);
296
- const anim = this.lottieAnimations[key];
297
- try {
298
- anim.removeEventListener('complete');
299
- } catch (e) {
300
- console.warn(`移除事件监听器失败: ${e.message}`);
301
- }
302
- anim.destroy();
303
- delete this.lottieAnimations[key];
304
- }
305
- });
271
+ // 处理背景动画
272
+ if (oldBgAnimUrl !== newBgAnimUrl && newBgAnimUrl) {
273
+ this.createLottieAnimation('bg', itemId, mergedData.bgAnimView, mergedData);
274
+ }
306
275
 
307
- console.log('清理完成,开始重新初始化动画');
276
+ // 处理加载动画
277
+ if (oldLoadingAnimUrl !== newLoadingAnimUrl && newLoadingAnimUrl) {
278
+ this.createLottieAnimation('loading', itemId, mergedData.loadingAnimView, mergedData);
279
+ }
308
280
 
309
- // 延迟一点时间确保DOM完全更新
310
- setTimeout(() => {
311
- // 重新初始化背景动画
312
- if (oldBgAnimUrl !== newBgAnimUrl) {
313
- this.loadLottieAnimation(
314
- data.bgAnimView,
315
- `bgAnim-${itemId}`,
316
- `bg-${itemId}`,
317
- data
318
- );
319
- }
281
+ // 处理前景动画
282
+ if (oldFrontAnimUrl !== newFrontAnimUrl && newFrontAnimUrl) {
283
+ this.createLottieAnimation('front', itemId, mergedData.frontAnimView, mergedData);
284
+ }
285
+ });
286
+ },
320
287
 
321
- if (oldLoadingAnimUrl !== newLoadingAnimUrl) {
322
- this.loadLottieAnimation(
323
- data.loadingAnimView,
324
- `loadingAnim-${itemId}`,
325
- `loading-${itemId}`,
326
- data
327
- );
328
- }
288
+ // 简化的动画创建方法
289
+ createLottieAnimation(type, itemId, animView, itemData) {
290
+ const animKey = `${type}-${itemId}`;
291
+ const refName = `${type}Anim-${itemId}`;
292
+
293
+ // 销毁旧动画
294
+ if (this.lottieAnimations[animKey]) {
295
+ this.lottieAnimations[animKey].destroy();
296
+ delete this.lottieAnimations[animKey];
297
+ }
329
298
 
330
- if (oldFrontAnimUrl !== newFrontAnimUrl) {
331
- this.loadLottieAnimation(
332
- data.frontAnimView,
333
- `frontAnim-${itemId}`,
334
- `front-${itemId}`,
335
- data
336
- );
337
- }
338
- }, 100); // 增加延迟确保DOM已更新和旧动画已完全销毁
339
- });
299
+ // 检查DOM元素是否存在
300
+ const animEl = this.$refs[refName];
301
+ if (!animEl || !animEl[0] || !animView.animUrl) {
302
+ return;
303
+ }
304
+
305
+ // 检查可见性
306
+ if (typeof animView.visibility === 'number' && animView.visibility === 0) {
307
+ return;
308
+ }
309
+
310
+ try {
311
+ const anim = lottie.loadAnimation({
312
+ container: animEl[0],
313
+ renderer: 'svg',
314
+ loop: Boolean(itemData.animRepeatCount) || false,
315
+ autoplay: true,
316
+ path: animView.animUrl,
317
+ rendererSettings: {
318
+ preserveAspectRatio: 'xMidYMid slice',
319
+ progressiveLoad: true,
320
+ hideOnTransparent: true,
321
+ },
322
+ });
323
+
324
+ this.lottieAnimations[animKey] = anim;
325
+
326
+ // 非循环动画完成后清理
327
+ if (!itemData.animRepeatCount) {
328
+ anim.addEventListener('complete', () => {
329
+ if (this.lottieAnimations[animKey] === anim) {
330
+ anim.destroy();
331
+ delete this.lottieAnimations[animKey];
332
+
333
+ // 清空对应的animUrl数据
334
+ const index = this.data.list.findIndex(i => i.itemView.id == itemId);
335
+ if (index !== -1) {
336
+ this.$set(this.data.list[index][`${type}AnimView`], 'animUrl', '');
337
+ }
338
+ }
339
+ });
340
+ }
341
+ } catch (error) {
342
+ console.error(`创建${type}动画失败:`, error);
343
+ }
340
344
  },
341
345
  getListData(callback) {
342
- let data = Array.from(this.processedList);
346
+ let data = Array.from(this.data.list);
343
347
  data = data.map((item) => ({
344
348
  ...item,
345
349
  itemView: {
@@ -364,7 +368,7 @@ export default {
364
368
  },
365
369
  selectIconClick(e, item, index) {
366
370
  e.preventDefault();
367
- e.stopPropagation(); // 阻止事件冒泡到itemViewClick
371
+ e.stopPropagation();
368
372
  if (item.selectIcon.visibility) {
369
373
  item.itemView.isSelected = !item.itemView.isSelected;
370
374
  }
@@ -380,16 +384,10 @@ export default {
380
384
  },
381
385
 
382
386
  itemViewClick(e, item, index) {
383
- // 如果事件已经被处理或被阻止默认行为,则不继续执行
384
387
  if (e.defaultPrevented) {
385
388
  return;
386
389
  }
387
- const target = {
388
- ...item.itemView,
389
- id: String(item.itemView.id),
390
- index,
391
- ...e,
392
- };
390
+
393
391
  console.log('itemViewClick');
394
392
  // 确保取消任何长按计时器
395
393
  this.cancelLongPress();
@@ -401,10 +399,13 @@ export default {
401
399
  }
402
400
  e.preventDefault();
403
401
  e.stopPropagation();
404
-
402
+ const target = {
403
+ ...item.itemView,
404
+ id: String(item.itemView.id),
405
+ index,
406
+ ...e,
407
+ };
405
408
  this.$emit('onClickItem', target);
406
- this.data.isEditing&&item.selectIcon.visibility&&this.$emit('onSelectItems', target);
407
-
408
409
  },
409
410
 
410
411
  actionClick(e, item, index) {
@@ -425,14 +426,12 @@ export default {
425
426
  return false;
426
427
  },
427
428
 
428
- // 新增方法:清理除指定项目外的所有动画实例
429
429
  cleanupOtherAnimations(currentItemId) {
430
430
  console.log('清理其他项目的动画实例,当前项目ID:', currentItemId);
431
431
 
432
432
  Object.keys(this.lottieAnimations).forEach((key) => {
433
433
  if (!key.includes(`-${currentItemId}`)) {
434
434
  if (this.lottieAnimations[key]) {
435
- console.log(`销毁其他项目的动画实例: ${key}`);
436
435
  const anim = this.lottieAnimations[key];
437
436
 
438
437
  // 不要立即销毁,而是先停止动画
@@ -477,37 +476,55 @@ export default {
477
476
  });
478
477
  },
479
478
 
480
- // 生成处理后的数据列表
481
479
  generateDataList() {
482
480
  if (!this.data || !this.data.list || !this.data.globalData) {
483
- return [];
481
+ return;
484
482
  }
485
483
 
486
- return this.data.list.map((item) => {
487
- const processedItem = {};
488
-
489
- // 获取所有可能的类型
490
- const types = [
491
- 'itemView',
492
- 'text',
493
- 'actionButton',
494
- 'rightIcon',
495
- 'leftTopIcon',
496
- 'iconImage',
497
- 'selectIcon',
498
- 'loadingAnimView',
499
- 'bgAnimView',
500
- 'frontAnimView',
501
- ];
484
+ // 防止死循环
485
+ if (this.isProcessingData) {
486
+ return;
487
+ }
488
+
489
+ this.isProcessingData = true;
502
490
 
503
- // 对每种类型进行处理
504
- types.forEach((type) => {
505
- // 使用mergeDate合并全局数据和项目数据
506
- processedItem[type] = this.mergeDate(item[type] || {}, type);
491
+ try {
492
+ // 直接处理 this.data.list,为每个项目合并全局数据
493
+ this.data.list.forEach((item, index) => {
494
+ const processedItem = this.mergeItemData(item);
495
+ this.$set(this.data.list, index, processedItem);
496
+ });
497
+ } finally {
498
+ // 确保标志位被重置
499
+ this.$nextTick(() => {
500
+ this.isProcessingData = false;
507
501
  });
508
- return processedItem;
502
+ }
503
+ },
504
+
505
+ mergeItemData(item) {
506
+ const processedItem = {};
507
+ const types = [
508
+ 'itemView',
509
+ 'text',
510
+ 'actionButton',
511
+ 'rightIcon',
512
+ 'leftTopIcon',
513
+ 'iconImage',
514
+ 'selectIcon',
515
+ 'loadingAnimView',
516
+ 'bgAnimView',
517
+ 'frontAnimView',
518
+ ];
519
+
520
+ types.forEach((type) => {
521
+ // 使用mergeDate合并全局数据和项目数据
522
+ processedItem[type] = this.mergeDate(item[type] || {}, type);
509
523
  });
524
+
525
+ return processedItem;
510
526
  },
527
+
511
528
  getItemStyle(item, type) {
512
529
  const result = this.mergeDate(item[type] || {}, type);
513
530
  console.log(type, this.applyNormalAttribute(result), 'ggg');
@@ -538,18 +555,14 @@ export default {
538
555
  borderRadius: `${(data.cornerRadius || 0) * scla}px`,
539
556
  opacity: data.alpha || 1,
540
557
  };
541
- // 特殊处理visibility和display
542
558
  if (typeof data.visibility === 'number') {
543
559
  if (data.visibility === 0) {
544
- // 如果visibility为0,则完全隐藏元素
545
560
  style.display = 'none';
546
561
  } else {
547
- // 否则保持原有display属性,并设置visibility
548
562
  style.display = data.display || '';
549
563
  style.visibility = data.visibility === 1 ? 'visible' : 'hidden';
550
564
  }
551
565
  } else {
552
- // 如果未设置visibility,保持原有display
553
566
  style.display = data.display || '';
554
567
  }
555
568
 
@@ -582,18 +595,6 @@ export default {
582
595
  this.$emit('onDragEventStart', e);
583
596
 
584
597
  // 只在编辑模式下阻止事件冒泡和默认行为
585
- if (this.data.isEditing) {
586
- if (e.oriEvent) {
587
- // e.oriEvent.preventDefault();
588
- // e.oriEvent.stopPropagation();
589
- // e.oriEvent.stopImmediatePropagation();
590
- }
591
- // e.preventDefault && e.preventDefault();
592
- // e.stopPropagation && e.stopPropagation();
593
- // e.stopImmediatePropagation && e.stopImmediatePropagation();
594
- }
595
-
596
- // 清除任何现有计时器
597
598
  this.cancelLongPress();
598
599
  // 记录起始触摸位置和索引
599
600
  const touch = e.changedTouches[0];
@@ -603,11 +604,6 @@ export default {
603
604
  this.longPressStartIndex = index;
604
605
  this.isScrolling = false; // 重置滚动状态
605
606
 
606
- // 添加临时事件监听器,在长按期间阻止滚动
607
- document.addEventListener('touchmove', this.onTouchMove, {
608
- passive: false // 设置为false以允许阻止默认行为
609
- });
610
-
611
607
  // 设置新的长按计时器
612
608
  this.longPressTimer = setTimeout(() => {
613
609
  // 只有在没有滚动的情况下才激活拖拽模式
@@ -671,13 +667,6 @@ export default {
671
667
  this.draggingIndex = index;
672
668
  this.isTouchDragging = true;
673
669
 
674
- // 此时添加阻止默认行为的事件监听,但只影响拖拽状态下的行为
675
- document.addEventListener('touchmove', this.onTouchMove, {
676
- passive: false, // 只有在拖拽模式下才阻止默认行为
677
- });
678
- document.addEventListener('touchend', this.onTouchEnd, {
679
- passive: true,
680
- });
681
670
 
682
671
  // 使用存储的固定宽高
683
672
  this.draggedItemRect = {
@@ -696,11 +685,8 @@ export default {
696
685
  // 设置克隆元素的初始位置
697
686
  this.dragCloneX = rect.left;
698
687
  this.dragCloneY = rect.top;
699
-
700
- // 显示拖拽克隆
701
688
  this.isDragClone = true;
702
689
 
703
- // 立即执行一次定位以确保初始位置准确
704
690
  requestAnimationFrame(() => {
705
691
  this.dragCloneX = this.touchStartX - this.touchOffsetX;
706
692
  this.dragCloneY = this.touchStartY - this.touchOffsetY;
@@ -732,7 +718,7 @@ export default {
732
718
  if (e.oriEvent) {
733
719
  e.oriEvent.preventDefault();
734
720
  e.oriEvent.stopPropagation();
735
- e.oriEvent.stopImmediatePropagation(); // 添加这行以确保事件不会继续传播
721
+ e.oriEvent.stopImmediatePropagation();
736
722
  }
737
723
  e.preventDefault && e.preventDefault();
738
724
  e.stopPropagation && e.stopPropagation();
@@ -750,122 +736,72 @@ export default {
750
736
  this.animationFrameId = requestAnimationFrame(this.updateDragPosition);
751
737
  }
752
738
 
753
- console.log('开始移动ddddddd', touch, touch.pageY, touch.pageX);
754
- const touchX = touch.pageX;
755
- const touchY = touch.pageY;
739
+ // 简化的交换逻辑
740
+ this.handleSwapLogic(touch.pageX, touch.pageY);
741
+ },
742
+
743
+ // 简化的交换处理逻辑
744
+ handleSwapLogic(touchX, touchY) {
745
+ // 控制交换频率,避免过于频繁的交换
746
+ const now = Date.now();
747
+ if (now - this.lastSwapTime < 300) return;
756
748
 
757
749
  // 获取所有网格元素
758
750
  const gridItems = Array.from(document.querySelectorAll('.grid-item'));
751
+ let targetIndex = null;
759
752
 
760
- let bestTarget = null;
761
- let maxCenterScore = 0; // 用于记录最佳的中心区域得分
762
-
763
- // ✅ 优化:精确坐标检测,偏向中心区域的元素
753
+ // 寻找触摸点所在的目标元素
764
754
  for (const item of gridItems) {
765
755
  const itemIndex = parseInt(item.getAttribute('data-index'), 10);
766
756
  if (itemIndex === this.draggingIndex) continue;
767
757
 
768
758
  const rect = item.getBoundingClientRect();
769
-
770
- // 计算触摸点在元素内的深度,用于判断是否足够深入(放宽到整个元素区域)
771
- const insetThresholdX = rect.width;
772
- const insetThresholdY = rect.height;
773
-
774
- // 判断触摸点是否足够深入元素内部(只要在元素内部即可)
775
- const isDeepEnough =
776
- touchX >= rect.left &&
777
- touchX <= rect.right &&
778
- touchY >= rect.top &&
779
- touchY <= rect.bottom;
780
-
781
- // 只有当触摸点足够深入时才考虑此元素作为交换目标
782
- if (isDeepEnough) {
783
- // 计算触摸点到元素中心的距离比例(越接近中心,值越大)
784
- const centerX = (rect.left + rect.right) / 2;
785
- const centerY = (rect.top + rect.bottom) / 2;
786
- const distanceX = Math.abs(touchX - centerX) / (rect.width / 2);
787
- const distanceY = Math.abs(touchY - centerY) / (rect.height / 2);
788
-
789
- // 0表示在中心,1表示在边缘
790
- const distanceFromCenter = Math.max(
791
- 0,
792
- Math.min(1, (distanceX + distanceY) / 2)
793
- );
794
- const centerScore = 1 - distanceFromCenter;
795
-
796
- // 只有当分数更高且高于阈值时才更新bestTarget
797
- // 提高阈值到0.35,确保更稳定的交换判断
798
- if (centerScore > maxCenterScore && centerScore > 0.35) {
799
- maxCenterScore = centerScore;
800
- bestTarget = itemIndex;
801
- }
802
- }
803
- }
804
-
805
- // 设置悬停高亮,仅当有合格的目标时才高亮
806
- this.hoverIndex = bestTarget;
807
-
808
- // 增加交换防抖动处理
809
- // 如果bestTarget变化但不等于上次的目标,先记录一下
810
- if (this.lastBestTarget !== bestTarget && bestTarget !== null) {
811
- if (!this.targetChangeTime) {
812
- this.targetChangeTime = Date.now();
813
- this.pendingTarget = bestTarget;
814
- } else if (Date.now() - this.targetChangeTime > 100) {
815
- // 只有当目标持续100ms稳定不变时,才认为是有效目标
816
- this.lastBestTarget = this.pendingTarget;
817
- this.targetChangeTime = null;
818
- this.pendingTarget = null;
759
+
760
+ // 触摸点是否在元素的中心区域内
761
+ const centerX = (rect.left + rect.right) / 2;
762
+ const centerY = (rect.top + rect.bottom) / 2;
763
+ const halfWidth = rect.width * 0.4; // 缩小判断区域到40%,避免边缘误触
764
+ const halfHeight = rect.height * 0.4;
765
+
766
+ // 检查触摸点是否在元素的中心区域内
767
+ if (
768
+ touchX >= centerX - halfWidth &&
769
+ touchX <= centerX + halfWidth &&
770
+ touchY >= centerY - halfHeight &&
771
+ touchY <= centerY + halfHeight
772
+ ) {
773
+ targetIndex = itemIndex;
774
+ break; // 找到第一个符合条件的就退出
819
775
  }
820
- } else if (bestTarget === null) {
821
- // 如果没有目标,清除防抖状态
822
- this.targetChangeTime = null;
823
- this.pendingTarget = null;
824
776
  }
825
777
 
826
- // 使用已稳定的目标进行交换
827
- bestTarget = this.lastBestTarget;
778
+ // 设置悬停高亮
779
+ this.hoverIndex = targetIndex;
828
780
 
829
- // 控制交换频率,改为250ms,大幅增加稳定性
830
- const now = Date.now();
831
- if (now - this.lastSwapTime < 250 || bestTarget === null) return;
832
-
833
- // 执行交换逻辑
834
- if (bestTarget !== this.draggingIndex) {
835
- console.log(`交换元素: ${this.draggingIndex} 和 ${bestTarget}`);
836
-
837
- // 使用ES6解构直接交换数组元素
781
+ // 执行交换
782
+ if (targetIndex !== null && targetIndex !== this.draggingIndex) {
838
783
  [
839
- this.processedList[this.draggingIndex],
840
- this.processedList[bestTarget],
784
+ this.data.list[this.draggingIndex],
785
+ this.data.list[targetIndex],
841
786
  ] = [
842
- this.processedList[bestTarget],
843
- this.processedList[this.draggingIndex],
844
- ];
845
-
846
- // 同时更新原始数据,保持同步
847
- [this.data.list[this.draggingIndex], this.data.list[bestTarget]] = [
848
- this.data.list[bestTarget],
787
+ this.data.list[targetIndex],
849
788
  this.data.list[this.draggingIndex],
850
789
  ];
790
+ console.log(`交换: ${this.draggingIndex} ↔ ${targetIndex}`);
851
791
 
852
792
  // 更新拖拽索引
853
- this.draggingIndex = bestTarget;
793
+ this.draggingIndex = targetIndex;
854
794
  this.lastSwapTime = now;
855
795
  }
856
796
  },
857
797
 
858
- // 新增:平滑更新拖拽位置的方法
798
+ // 平滑更新拖拽位置的方法
859
799
  updateDragPosition() {
860
- // 计算当前位置到目标位置的平滑过渡
861
800
  const easing = 0.6; // 增加缓动系数,让拖拽更跟手
862
-
863
801
  this.dragCloneX +=
864
802
  (this.targetTouchX - this.touchOffsetX - this.dragCloneX) * easing;
865
803
  this.dragCloneY +=
866
804
  (this.targetTouchY - this.touchOffsetY - this.dragCloneY) * easing;
867
-
868
- // 如果距离目标很近,直接设为目标位置以避免微小抖动
869
805
  if (
870
806
  Math.abs(this.dragCloneX - (this.targetTouchX - this.touchOffsetX)) <
871
807
  0.5
@@ -876,8 +812,6 @@ export default {
876
812
  0.5
877
813
  )
878
814
  this.dragCloneY = this.targetTouchY - this.touchOffsetY;
879
-
880
- // 继续下一帧动画
881
815
  this.animationFrameId = requestAnimationFrame(this.updateDragPosition);
882
816
  },
883
817
 
@@ -887,17 +821,8 @@ export default {
887
821
  this.$emit('onDragEventEnd', e);
888
822
  // 如果只是短按(还在计时),就取消长按
889
823
  this.cancelLongPress();
890
-
891
824
  // 如果正在拖拽中,确保清理状态
892
825
  if (this.isTouchDragging) {
893
- // 移除全局事件监听
894
- document.removeEventListener('touchmove', this.onTouchMove, {
895
- passive: false,
896
- });
897
- document.removeEventListener('touchend', this.onTouchEnd, {
898
- passive: false,
899
- });
900
-
901
826
  // 取消动画帧
902
827
  if (this.animationFrameId) {
903
828
  cancelAnimationFrame(this.animationFrameId);
@@ -908,28 +833,12 @@ export default {
908
833
  this.isDragClone = false;
909
834
  this.hoverIndex = null;
910
835
  this.draggingIndex = null;
911
- this.lastBestTarget = null;
912
- this.targetChangeTime = null;
913
- this.pendingTarget = null;
914
- this.touchStartX = 0;
915
- this.touchStartY = 0;
916
- this.targetTouchX = 0;
917
- this.targetTouchY = 0;
918
836
  }
919
837
  },
920
838
 
921
839
  // 触摸结束处理 - 全局事件
922
840
  onTouchEnd(e) {
923
841
  console.log('全局触摸结束');
924
-
925
- // 移除全局事件监听
926
- document.removeEventListener('touchmove', this.onTouchMove, {
927
- passive: false,
928
- });
929
- document.removeEventListener('touchend', this.onTouchEnd, {
930
- passive: false,
931
- });
932
-
933
842
  // 取消动画帧
934
843
  if (this.animationFrameId) {
935
844
  cancelAnimationFrame(this.animationFrameId);
@@ -985,168 +894,27 @@ export default {
985
894
 
986
895
  console.log(
987
896
  '开始初始化Lottie动画,列表项数量:',
988
- this.processedList.length
897
+ this.data.list.length
989
898
  );
990
899
 
991
900
  // 延迟一点时间确保DOM完全更新并且之前的动画已被销毁
992
901
  setTimeout(() => {
993
902
  // 为每个列表项初始化动画
994
- this.processedList.forEach((item) => {
903
+ this.data.list.forEach((item) => {
995
904
  const itemId = String(item.itemView.id);
996
- console.log('处理项目ID:', itemId);
997
- console.log('loadingAnimView:', item.loadingAnimView);
998
- console.log('bgAnimView:', item.bgAnimView);
999
- console.log('frontAnimView:', item.frontAnimView);
1000
-
1001
905
  // 加载背景动画
1002
- this.loadLottieAnimation(
1003
- item.bgAnimView,
1004
- `bgAnim-${itemId}`,
1005
- `bg-${itemId}`,
1006
- item
1007
- );
906
+ this.createLottieAnimation('bg', itemId, item.bgAnimView, item);
1008
907
 
1009
908
  // 加载加载中动画
1010
- this.loadLottieAnimation(
1011
- item.loadingAnimView,
1012
- `loadingAnim-${itemId}`,
1013
- `loading-${itemId}`,
1014
- item
1015
- );
909
+ this.createLottieAnimation('loading', itemId, item.loadingAnimView, item);
1016
910
 
1017
911
  // 加载前景动画
1018
- this.loadLottieAnimation(
1019
- item.frontAnimView,
1020
- `frontAnim-${itemId}`,
1021
- `front-${itemId}`,
1022
- item
1023
- );
912
+ this.createLottieAnimation('front', itemId, item.frontAnimView, item);
1024
913
  });
1025
914
  }, 50); // 小延迟确保DOM已更新和旧动画已销毁
1026
915
  });
1027
916
  },
1028
917
 
1029
- // 抽取公共的loadLottieAnimation方法
1030
- loadLottieAnimation(animView, refName, animKey, item) {
1031
- if (!animView || !animView.animUrl) {
1032
- console.log(`[${animKey}] 跳过加载:animUrl不存在`);
1033
- return;
1034
- }
1035
-
1036
- console.log(
1037
- `[${animKey}] 尝试初始化${refName}, animUrl:`,
1038
- animView.animUrl
1039
- );
1040
-
1041
- const animEl = this.$refs[refName];
1042
- if (!animEl || !animEl[0]) {
1043
- console.warn(`[${animKey}] 未找到${refName}的DOM引用`);
1044
- return;
1045
- }
1046
-
1047
- try {
1048
- // 如果已经有实例,先暂停它
1049
- if (this.lottieAnimations[animKey]) {
1050
- this.lottieAnimations[animKey].pause();
1051
- }
1052
-
1053
- // 确保容器可见且透明度重置
1054
- animEl[0].style.display = '';
1055
- animEl[0].style.opacity = '1';
1056
- animEl[0].style.transition = 'opacity 0.2s';
1057
-
1058
- if (
1059
- typeof animView.visibility !== 'number' ||
1060
- animView.visibility !== 0
1061
- ) {
1062
- // 延迟一帧再销毁旧实例
1063
- requestAnimationFrame(() => {
1064
- if (this.lottieAnimations[animKey]) {
1065
- this.lottieAnimations[animKey].destroy();
1066
- delete this.lottieAnimations[animKey];
1067
- }
1068
-
1069
- // 创建新的动画实例
1070
- console.log(`[${animKey}] 创建新的动画实例`);
1071
- const anim = lottie.loadAnimation({
1072
- container: animEl[0],
1073
- renderer: 'svg',
1074
- loop: Boolean(item.animRepeatCount) || false,
1075
- autoplay: true,
1076
- path: animView.animUrl,
1077
- rendererSettings: {
1078
- preserveAspectRatio: 'xMidYMid slice',
1079
- progressiveLoad: true, // 启用渐进式加载
1080
- hideOnTransparent: true, // 优化透明度处理
1081
- },
1082
- });
1083
-
1084
- this.lottieAnimations[animKey] = anim;
1085
- anim.addEventListener('complete', () => {
1086
- // 检查是否需要销毁动画(非循环动画完成后销毁)
1087
- if (!item.animRepeatCount) {
1088
- // 销毁前再次检查实例是否存在
1089
- if (this.lottieAnimations[animKey] === anim) {
1090
- anim.removeEventListener('complete');
1091
- anim.destroy();
1092
- delete this.lottieAnimations[animKey];
1093
- // 将对应数据中的animUrl清空
1094
- const itemId = String(item.itemView.id);
1095
- const index = this.processedList.findIndex(
1096
- (i) => i.itemView.id == itemId
1097
- );
1098
- if (index !== -1) {
1099
- // 根据动画类型设置对应的animUrl为空
1100
- if (animKey.startsWith('bg-')) {
1101
- this.$set(
1102
- this.processedList[index].bgAnimView,
1103
- 'animUrl',
1104
- ''
1105
- );
1106
- this.$set(
1107
- this.data.list[index].bgAnimView,
1108
- 'animUrl',
1109
- ''
1110
- );
1111
- } else if (animKey.startsWith('loading-')) {
1112
- this.$set(
1113
- this.processedList[index].loadingAnimView,
1114
- 'animUrl',
1115
- ''
1116
- );
1117
- this.$set(
1118
- this.data.list[index].loadingAnimView,
1119
- 'animUrl',
1120
- ''
1121
- );
1122
- } else if (animKey.startsWith('front-')) {
1123
- this.$set(
1124
- this.processedList[index].frontAnimView,
1125
- 'animUrl',
1126
- ''
1127
- );
1128
- this.$set(
1129
- this.data.list[index].frontAnimView,
1130
- 'animUrl',
1131
- ''
1132
- );
1133
- }
1134
- console.log(`[${animKey}] 已清空动画URL数据`);
1135
- item.addEventListener('touchstart', (e) => this.startLongPress(e, index), { passive: false });
1136
- item.addEventListener('touchmove', this.checkLongPressMove, { passive: false });
1137
- item.addEventListener('touchend', this.endTouch, { passive: false });
1138
- item.addEventListener('touchcancel', this.endTouch, { passive: false });
1139
- }
1140
- }
1141
- }
1142
- });
1143
- });
1144
- }
1145
- } catch (error) {
1146
- console.error(`[${animKey}] 初始化失败:`, error);
1147
- }
1148
- },
1149
-
1150
918
  // 添加新方法用于获取和存储固定宽高
1151
919
  updateFixedItemSize() {
1152
920
  this.$nextTick(() => {
@@ -1168,48 +936,30 @@ export default {
1168
936
  },
1169
937
  },
1170
938
  watch: {
1171
- // 监听初始数据变化
1172
- initialItems: {
1173
- handler(newItems) {
1174
- if (!this.isTouchDragging) {
1175
- this.items = [...newItems];
1176
- }
1177
- },
1178
- deep: true,
939
+ 'data.isEditing':{
940
+ handler(newVal){
941
+ if(!newVal){
942
+ this.$set(this.data, 'list', this.data.list);
943
+ }
944
+ },
945
+ deep:true
1179
946
  },
1180
947
  // 监听列表数据变化,自动更新处理后的数据
1181
948
  'data.list': {
1182
949
  handler(newList, oldList) {
1183
- this.processedList = this.generateDataList();
950
+ // 防止死循环
951
+ if (this.isProcessingData) {
952
+ return;
953
+ }
954
+
955
+ console.log('检测到data.list变化');
956
+ this.generateDataList();
1184
957
  this.$nextTick(() => {
1185
- // 再次使用 nextTick 确保 DOM 完全更新
1186
- this.$nextTick(() => {
1187
- const gridItems = document.querySelectorAll('.grid-item');
1188
- gridItems.forEach((item, index) => {
1189
- const hasEvents = item._vei;
1190
- if (!hasEvents) {
1191
- item.addEventListener('touchstart', (e) => this.startLongPress(e, index), { passive: false });
1192
- item.addEventListener('touchmove', this.checkLongPressMove, { passive: false });
1193
- item.addEventListener('touchend', this.endTouch, { passive: false });
1194
- item.addEventListener('touchcancel', this.endTouch, { passive: false });
1195
- }
1196
- });
1197
- this.initLottieAnimations();
1198
- });
958
+ this.initLottieAnimations();
1199
959
  });
1200
960
  },
1201
961
  deep: true,
1202
962
  },
1203
- // 监听所有动画URL变化,确保能及时更新动画
1204
- processedList: {
1205
- handler(newList, oldList) {
1206
- if (!newList || !newList.length || this.isTouchDragging) return;
1207
- // 避免在拖拽过程中重新初始化动画
1208
- if (this.draggingIndex !== null) return;
1209
- // 检查每个项目的动画URL是否有变化
1210
- },
1211
- deep: true,
1212
- },
1213
963
  },
1214
964
  mounted() {
1215
965
  console.log(this.data, 'djdjdjdjdjdjdj');
@@ -1217,61 +967,21 @@ export default {
1217
967
  this.updateGridBoxWidth();
1218
968
  // 获取并存储固定宽高
1219
969
  this.updateFixedItemSize();
1220
-
1221
- // 监听窗口大小变化,更新grid-box宽度
1222
- window.addEventListener('resize', () => {
1223
- this.updateGridBoxWidth();
1224
- this.updateFixedItemSize();
1225
- });
1226
-
1227
970
  // 初始化Lottie动画
1228
971
  this.initLottieAnimations();
1229
972
  },
1230
973
  beforeDestroy() {
1231
974
  // 确保清理所有事件监听器
1232
975
  this.cancelLongPress();
1233
- document.removeEventListener('touchmove', this.onTouchMove, {
1234
- passive: false,
1235
- });
1236
- document.removeEventListener('touchend', this.onTouchEnd, {
1237
- passive: false,
1238
- });
1239
- window.removeEventListener('resize', () => {
1240
- this.updateGridBoxWidth();
1241
- this.updateFixedItemSize();
1242
- });
1243
-
1244
976
  // 清理动画帧
1245
977
  if (this.animationFrameId) {
1246
978
  cancelAnimationFrame(this.animationFrameId);
1247
979
  this.animationFrameId = null;
1248
980
  }
1249
-
1250
- // 清理所有Lottie动画实例
1251
- Object.keys(this.lottieAnimations).forEach((key) => {
1252
- const anim = this.lottieAnimations[key];
1253
- if (anim && typeof anim.destroy === 'function') {
1254
- console.log(`组件销毁时清理动画: ${key}`);
1255
- try {
1256
- anim.removeEventListener('complete');
1257
- anim.removeEventListener('DOMLoaded');
1258
- anim.removeEventListener('data_ready');
1259
- anim.removeEventListener('loaded_images');
1260
- anim.removeEventListener('loopComplete');
1261
- } catch (e) {
1262
- console.warn(`移除事件监听器失败: ${e.message}`);
1263
- }
1264
- anim.destroy();
1265
- }
1266
- delete this.lottieAnimations[key];
1267
- });
1268
-
1269
- // 确保动画实例对象被清空
1270
- this.lottieAnimations = {};
1271
981
  },
1272
982
  created() {
1273
983
  // 初始化处理后的数据列表
1274
- this.processedList = this.generateDataList();
984
+ this.generateDataList();
1275
985
  },
1276
986
  };
1277
987
  </script>
@@ -1302,7 +1012,7 @@ button {
1302
1012
  display: grid;
1303
1013
  grid-template-columns: repeat(var(--grid-cols), 1fr);
1304
1014
  grid-gap: var(--grid-gap);
1305
- width: calc(100% - 74px);
1015
+ width: calc(100% - var(--grid-margin));
1306
1016
  }
1307
1017
 
1308
1018
  /* 优化排序过渡效果 */