@dolphinweex/weex-harmony 0.1.19 → 0.1.21

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.
@@ -1,109 +1,1607 @@
1
1
  <template>
2
- <BaseSameLayer :hosSameLayerArgs="hosSameLayerArgs" embedType="native/midea-scene-card-drag-list-view" ref="sceneCardRed"></BaseSameLayer>
2
+ <div
3
+ class="outer-container"
4
+ :class="{ editMode: data.isEditing }"
5
+ :style="gridItemStyle"
6
+ >
7
+ <div class="grid-items-wrapper">
8
+ <transition-group name="grid-fade" tag="div" class="grid-inner-wrapper">
9
+ <div
10
+ v-for="(item, index) in processedList"
11
+ :key="item.itemView.id"
12
+ class="grid-item"
13
+ :class="{
14
+ 'being-dragged': draggingIndex === index,
15
+ hidden: isDragClone && draggingIndex === index,
16
+ 'hover-target': hoverIndex === index && draggingIndex !== index,
17
+ 'disabled-item': item.itemView.enable === false,
18
+ }"
19
+ :style="{
20
+ overflow: data.isEditing ? 'visible' : 'hidden',
21
+ }"
22
+ :data-index="index"
23
+ @touchstart="(event) => startLongPress(event, index)"
24
+ @touchmove.stop="checkLongPressMove"
25
+ @touchend="endTouch"
26
+ @touchcancel="endTouch"
27
+ >
28
+ <div
29
+ v-if="
30
+ item.bgAnimView && item.bgAnimView.animUrl && !data.isEditing
31
+ "
32
+ class="lottie-container bg-anim"
33
+ :style="applyNormalAttribute(item.bgAnimView)"
34
+ :ref="`bgAnim-${item.itemView.id}`"
35
+ ></div>
36
+ <div
37
+ v-if="
38
+ item.frontAnimView &&
39
+ item.frontAnimView.animUrl &&
40
+ !data.isEditing
41
+ "
42
+ class="lottie-container front-anim"
43
+ :style="applyNormalAttribute(item.frontAnimView)"
44
+ :ref="`frontAnim-${item.itemView.id}`"
45
+ ></div>
46
+ <div
47
+ :style="applyNormalAttribute(item.itemView)"
48
+ class="grid-box"
49
+ :class="{ shaking: data.isEditing }"
50
+ @click.stop="(e) => itemViewClick(e, item, index)"
51
+ >
52
+ <div class="flexBox">
53
+ <img
54
+ :src="item.iconImage.url"
55
+ :style="applyNormalAttribute(item.iconImage)"
56
+ />
57
+ <div>
58
+ <div @click.stop.prevent v-if="!data.isEditing">
59
+ <button
60
+ class="actionButton"
61
+ @click.stop.prevent="(e) => actionClick(e, item, index)"
62
+ :style="applyNormalAttribute(item.actionButton)"
63
+ >
64
+ {{ item.actionButton.text }}
65
+ </button>
66
+ <div
67
+ v-if="
68
+ item.loadingAnimView && item.loadingAnimView.animUrl
69
+ "
70
+ class="loading-anim"
71
+ :style="applyNormalAttribute(item.loadingAnimView)"
72
+ :ref="`loadingAnim-${item.itemView.id}`"
73
+ ></div>
74
+ </div>
75
+ <div @click.stop.prevent v-if="!data.isEditing">
76
+ <img
77
+ :src="item.rightIcon.url"
78
+ :style="applyNormalAttribute(item.rightIcon)"
79
+ @click.stop.prevent="(e) => rightIconClick(e)"
80
+ />
81
+ </div>
82
+ <div @click.stop.prevent v-if="!data.isEditing">
83
+ <img
84
+ class="leftTopIcon"
85
+ :src="item.leftTopIcon.url"
86
+ :style="applyNormalAttribute(item.leftTopIcon)"
87
+ @click.stop.prevent
88
+ />
89
+ </div>
90
+ <div @click.stop.prevent v-if="data.isEditing">
91
+ <img
92
+ class="isSelected"
93
+ @click.stop.prevent="
94
+ (e) => selectIconClick(e, item, index)
95
+ "
96
+ :src="
97
+ item.itemView.isSelected
98
+ ? item.selectIcon.selectedUrl
99
+ : item.selectIcon.url
100
+ "
101
+ :style="applyNormalAttribute(item.selectIcon)"
102
+ />
103
+ </div>
104
+ </div>
105
+ </div>
106
+ <div class="itemText" :style="[applyNormalAttribute(item.text)]">
107
+ {{ item.text.text }}
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </transition-group>
112
+ </div>
113
+
114
+
115
+ <!-- 拖拽克隆元素 -->
116
+ <div v-if="isDragClone" class="drag-clone" :style="dragCloneStyle">
117
+ <div
118
+ class="grid-box"
119
+ :style="applyNormalAttribute(processedList[draggingIndex].itemView)"
120
+ >
121
+ <div class="flexBox">
122
+ <img
123
+ :src="processedList[draggingIndex].iconImage.url"
124
+ :style="
125
+ applyNormalAttribute(processedList[draggingIndex].iconImage)
126
+ "
127
+ />
128
+ <div @click.stop.prevent>
129
+ <img
130
+ class="isSelected"
131
+ :src="
132
+ processedList[draggingIndex].itemView.isSelected
133
+ ? processedList[draggingIndex].selectIcon.selectedUrl
134
+ : processedList[draggingIndex].selectIcon.url
135
+ "
136
+ :style="
137
+ applyNormalAttribute(processedList[draggingIndex].selectIcon)
138
+ "
139
+ />
140
+ </div>
141
+ </div>
142
+ <div
143
+ class="itemText"
144
+ :style="[applyNormalAttribute(processedList[draggingIndex].text)]"
145
+ >
146
+ {{ processedList[draggingIndex].text.text }}
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
3
151
  </template>
4
152
 
5
153
  <script>
6
- import BaseSameLayer from "./baseSameLayer.vue";
7
- const weexModule = weex.requireModule('weexModule');
8
-
154
+ import lottie from 'lottie-web';
155
+ const scla = 2;
9
156
  export default {
10
- name: "mideaSceneCardDragListView",
11
- data() {
12
- return {
13
- width: 0,
14
- height: 0
15
- }
16
- },
17
- components: {
18
- BaseSameLayer,
19
- },
157
+ name: 'EditableGrid',
20
158
  props: {
21
- hosUniqueProps: {
22
- type: Object,
23
- default() {
24
- return {};
25
- },
26
- },
27
159
  data: {
28
160
  type: Object,
29
- default() {
30
- return {
31
- embedId: ''
32
- };
33
- },
161
+ default: () => ({}),
34
162
  },
163
+ onClickRightIcon: { type: Function, default: () => {} },
164
+ onClickActionButton: { type: Function, default: () => {} },
165
+ onEditStateChanged: { type: Function, default: () => {} },
166
+ onClickItem: { type: Function, default: () => {} },
167
+ onSelectItems: { type: Function, default: () => {} },
168
+ onDragEventStart: { type: Function, default: () => {} },
169
+ onDragEventEnd: { type: Function, default: () => {} },
170
+ },
171
+ data() {
172
+ return {
173
+ targetTouchX: 0,
174
+ targetTouchY: 0,
175
+ processedList: [],
176
+ selected: null,
177
+ longPressTimer: null,
178
+ longPressDuration: 350,
179
+ longPressStartIndex: null,
180
+ draggingIndex: null,
181
+ hoverIndex: null,
182
+ isTouchDragging: false,
183
+ lastSwapTime: 0,
184
+ isAnimating: false,
185
+ touchStartX: 0,
186
+ touchStartY: 0,
187
+ draggedItemRect: null,
188
+ isDragClone: false,
189
+ dragCloneX: 0,
190
+ dragCloneY: 0,
191
+ touchOffsetX: 0,
192
+ touchOffsetY: 0,
193
+ gridBoxWidth: 0,
194
+ lottieAnimations: {},
195
+ isScrolling: false,
196
+ scrollThreshold: 5,
197
+ fixedItemWidth: 0,
198
+ fixedItemHeight: 0,
199
+ animationFrameId: null,
200
+ lastBestTarget: null,
201
+ targetChangeTime: null,
202
+ pendingTarget: null,
203
+ };
35
204
  },
36
205
  computed: {
37
- hosSameLayerArgs() {
206
+ gridItemStyle() {
38
207
  return {
39
- ...this.hosUniqueProps, // 鸿蒙原生组件独有属性
40
- width: this.width,
41
- height: this.height,
42
- data: this.data,
43
- updateListItem: this.updateListItem,
44
- getListData: this.getListData,
45
- onClickRightIcon: this.onClickRightIcon,
46
- onClickActionButton: this.onClickActionButton,
47
- onEditStateChanged: this.onEditStateChanged,
48
- onClickItem: this.onClickItem,
49
- onSelectItems: this.onSelectItems,
50
- onDragEventStart: this.onDragEventStart,
51
- onDragEventEnd: this.onDragEventEnd,
208
+ '--grid-cols': this.data.spanCount || this.cols,
209
+ '--grid-gap': `${this.data.spaceSize || 8}px`,
210
+ '--grid-margin': `${(this.data.layoutConfig.marginStart ) + (this.data.layoutConfig.marginEnd )}px`,
211
+ marginTop: this.data.layoutConfig.marginTop * scla,
212
+ marginBottom: this.data.layoutConfig.marginBottom * scla,
213
+ marginLeft: this.data.layoutConfig.marginStart * scla,
214
+ marginRight: this.data.layoutConfig.marginEnd * scla,
52
215
  };
53
216
  },
54
- },
55
- mounted() {
56
- this.width = this.$el.clientWidth;
57
- this.height = this.$el.clientHeight;
58
- this.embedId = this.$refs.sceneCardRed.embedId;
59
- },
217
+ // 拖拽克隆元素的样式
218
+ dragCloneStyle() {
219
+ if (this.draggingIndex === null) {
220
+ return { display: 'none' };
221
+ }
60
222
 
223
+ const item = this.processedList[this.draggingIndex];
224
+ let itemView = {};
225
+ if (item) {
226
+ itemView = this.applyNormalAttribute(item.itemView);
227
+ }
228
+
229
+ return {
230
+ transform: `translate(${this.dragCloneX * scla}px, ${
231
+ this.dragCloneY * scla
232
+ }px)`,
233
+ ...itemView, //里面有width
234
+ height: `${this.fixedItemHeight * scla}px`,
235
+ width: `${this.fixedItemWidth * scla}px`,
236
+ willChange: 'transform',
237
+ transitionProperty: 'transform',
238
+ transitionDuration: '0.08s', // 减少过渡时间,让拖拽更跟手
239
+ position: 'fixed',
240
+ zIndex: 1000,
241
+ };
242
+ },
243
+ },
61
244
  methods: {
62
- getListData(callback) {
63
- weexModule.callNative(
64
- 'sceneCardListHandle',
65
- {
66
- method: 'getListData',
67
- name: this.embedId,
68
- },
69
- callback,
245
+ updateListItem(data) {
246
+ console.log('updateListItem收到数据:', JSON.stringify(data));
247
+ const id = data.itemView.id;
248
+ const index = this.processedList.findIndex(
249
+ (item) => item.itemView.id === id
70
250
  );
251
+ const dataIndex = this.data.list.findIndex(
252
+ (item) => item.itemView.id === id
253
+ );
254
+
255
+ if (index === -1) {
256
+ return;
257
+ }
258
+
259
+ // 保存更新前的可见性状态用于比较
260
+ const oldVisibility = this.processedList[index].actionButton.visibility;
261
+ const newVisibility = data.actionButton.visibility;
262
+
263
+ if (typeof newVisibility === 'number') {
264
+ console.log(`按钮可见性从 ${oldVisibility} 变更为 ${newVisibility}`);
265
+ }
266
+
267
+ // 检查是否有动画URL的变化
268
+ const oldBgAnimUrl = this.processedList[index].bgAnimView.animUrl || '';
269
+ const oldLoadingAnimUrl =
270
+ this.processedList[index].loadingAnimView.animUrl || '';
271
+ const oldFrontAnimUrl =
272
+ this.processedList[index].frontAnimView.animUrl || '';
273
+
274
+ const newBgAnimUrl = data.bgAnimView.animUrl || '';
275
+ const newLoadingAnimUrl = data.loadingAnimView.animUrl || '';
276
+ const newFrontAnimUrl = data.frontAnimView.animUrl || '';
277
+
278
+ const hasAnimUrlChanged =
279
+ oldBgAnimUrl !== newBgAnimUrl ||
280
+ oldLoadingAnimUrl !== newLoadingAnimUrl ||
281
+ oldFrontAnimUrl !== newFrontAnimUrl;
282
+
283
+ // 同时更新processedList和原始data.list数据
284
+ this.processedList[index] = data;
285
+ this.processedList = [...this.processedList];
286
+
287
+ // 更新原始数据以确保watch能触发
288
+ if (dataIndex !== -1) {
289
+ // 使用Vue的$set确保响应式更新
290
+ this.$set(this.data.list, dataIndex, JSON.parse(JSON.stringify(data)));
291
+ }
292
+
293
+ // 如果动画URL有变化,重新初始化相关的Lottie动画
294
+ if (hasAnimUrlChanged) {
295
+ console.log('检测到动画URL变化,重新初始化动画');
296
+
297
+ // 等待下一帧DOM更新完成后再操作
298
+ this.$nextTick(() => {
299
+ // 清理旧的动画实例
300
+ const itemId = data.itemView.id;
301
+
302
+ // 清除所有相关的动画实例
303
+ const animKeys = [
304
+ `bg-${itemId}`,
305
+ `loading-${itemId}`,
306
+ `front-${itemId}`,
307
+ ];
308
+ animKeys.forEach((key) => {
309
+ if (this.lottieAnimations[key]) {
310
+ console.log(`销毁动画实例: ${key}`);
311
+ const anim = this.lottieAnimations[key];
312
+ try {
313
+ anim.removeEventListener('complete');
314
+ anim.removeEventListener('DOMLoaded');
315
+ anim.removeEventListener('data_ready');
316
+ anim.removeEventListener('loaded_images');
317
+ anim.removeEventListener('loopComplete');
318
+ } catch (e) {
319
+ console.warn(`移除事件监听器失败: ${e.message}`);
320
+ }
321
+ anim.destroy();
322
+ delete this.lottieAnimations[key];
323
+ }
324
+ });
325
+
326
+ console.log('清理完成,开始重新初始化动画');
327
+
328
+ // 延迟一点时间确保DOM完全更新
329
+ setTimeout(() => {
330
+ // 重新初始化背景动画
331
+ if (newBgAnimUrl) {
332
+ this.loadLottieAnimation(
333
+ data.bgAnimView,
334
+ `bgAnim-${itemId}`,
335
+ `bg-${itemId}`,
336
+ data
337
+ );
338
+ }
339
+
340
+ // 重新初始化加载动画
341
+ if (newLoadingAnimUrl) {
342
+ this.loadLottieAnimation(
343
+ data.loadingAnimView,
344
+ `loadingAnim-${itemId}`,
345
+ `loading-${itemId}`,
346
+ data
347
+ );
348
+ }
349
+
350
+ // 重新初始化前景动画
351
+ if (newFrontAnimUrl) {
352
+ this.loadLottieAnimation(
353
+ data.frontAnimView,
354
+ `frontAnim-${itemId}`,
355
+ `front-${itemId}`,
356
+ data
357
+ );
358
+ }
359
+ }, 100); // 增加延迟确保DOM已更新和旧动画已完全销毁
360
+ });
361
+ }
362
+ },
363
+ getListData(callback) {
364
+ const data = Array.from(this.processedList);
365
+ callback({ listData: data });
366
+ },
367
+ rightIconClick(e) {
368
+ console.log('rightIconClick执行,阻止事件冒泡');
369
+ e.preventDefault();
370
+ e.stopPropagation();
371
+ this.$emit('onClickRightIcon', e);
372
+ return false;
373
+ },
374
+ onSelectItems() {
375
+ const selectedIds = this.data.list
376
+ .filter((item) => item.itemView.isSelected)
377
+ .map((item) => item.itemView.id);
378
+ return selectedIds;
379
+ },
380
+ selectIconClick(e, item, index) {
381
+ e.preventDefault();
382
+ e.stopPropagation(); // 阻止事件冒泡到itemViewClick
383
+ item.itemView.isSelected = !item.itemView.isSelected;
384
+ const target = {
385
+ ...item.itemView,
386
+ id: String(item.itemView.id),
387
+ index,
388
+ ...e,
389
+ };
390
+ this.$emit('onClickSelectIcon', target);
391
+ return false;
392
+ },
393
+
394
+ itemViewClick(e, item, index) {
395
+ // 如果事件已经被处理或被阻止默认行为,则不继续执行
396
+ if (e.defaultPrevented) {
397
+ return;
398
+ }
399
+
400
+ console.log('itemViewClick');
401
+ // 确保取消任何长按计时器
402
+ this.cancelLongPress();
403
+ if (this.data.isEditing) {
404
+ item.itemView.isSelected = !item.itemView.isSelected;
405
+ return;
406
+ }
407
+ e.preventDefault();
408
+ e.stopPropagation();
409
+ const target = {
410
+ ...item.itemView,
411
+ id: String(item.itemView.id),
412
+ index,
413
+ ...e,
414
+ };
415
+ this.$emit('onClickItem', target);
416
+ },
417
+
418
+ actionClick(e, item, index) {
419
+ console.log('actionClick执行,阻止事件冒泡');
420
+ e.preventDefault();
421
+ e.stopPropagation(); // 阻止事件冒泡到itemViewClick
422
+
423
+ // 在触发事件前,先清理所有其他项目的动画实例
424
+ this.cleanupOtherAnimations(item.itemView.id);
425
+
426
+ const target = {
427
+ ...item.itemView,
428
+ id: String(item.itemView.id),
429
+ index,
430
+ ...e,
431
+ };
432
+ this.$emit('onClickActionButton', target);
433
+ return false;
434
+ },
435
+
436
+ // 新增方法:清理除指定项目外的所有动画实例
437
+ cleanupOtherAnimations(currentItemId) {
438
+ console.log('清理其他项目的动画实例,当前项目ID:', currentItemId);
439
+
440
+ // 遍历所有动画实例
441
+ Object.keys(this.lottieAnimations).forEach((key) => {
442
+ // 检查是否为其他项目的动画
443
+ if (!key.includes(`-${currentItemId}`)) {
444
+ if (this.lottieAnimations[key]) {
445
+ console.log(`销毁其他项目的动画实例: ${key}`);
446
+ const anim = this.lottieAnimations[key];
447
+
448
+ // 移除所有事件监听器
449
+ try {
450
+ anim.removeEventListener('complete');
451
+ anim.removeEventListener('DOMLoaded');
452
+ anim.removeEventListener('data_ready');
453
+ anim.removeEventListener('loaded_images');
454
+ anim.removeEventListener('loopComplete');
455
+ } catch (e) {
456
+ console.warn(`移除事件监听器失败: ${e.message}`);
457
+ }
458
+
459
+ anim.destroy();
460
+ delete this.lottieAnimations[key];
461
+
462
+ // 提取项目ID和动画类型
463
+ const parts = key.split('-');
464
+ if (parts.length === 2) {
465
+ const animType = parts[0]; // bg, loading, front
466
+ const itemId = parts[1];
467
+
468
+ // 隐藏对应的动画容器
469
+ this.$nextTick(() => {
470
+ const refName = `${animType}Anim-${itemId}`;
471
+ const animContainer =
472
+ this.$refs[refName] && this.$refs[refName][0];
473
+ if (animContainer) {
474
+ console.log(`隐藏动画容器: ${refName}`);
475
+ animContainer.style.display = 'none';
476
+ }
477
+ });
478
+ }
479
+ }
480
+ }
481
+ });
482
+ },
483
+
484
+ // 生成处理后的数据列表
485
+ generateDataList() {
486
+ if (!this.data || !this.data.list || !this.data.globalData) {
487
+ return [];
488
+ }
489
+
490
+ return this.data.list.map((item) => {
491
+ const processedItem = {};
492
+
493
+ // 获取所有可能的类型
494
+ const types = [
495
+ 'itemView',
496
+ 'text',
497
+ 'actionButton',
498
+ 'rightIcon',
499
+ 'leftTopIcon',
500
+ 'iconImage',
501
+ 'selectIcon',
502
+ 'loadingAnimView',
503
+ 'bgAnimView',
504
+ 'frontAnimView',
505
+ ];
506
+
507
+ // 对每种类型进行处理
508
+ types.forEach((type) => {
509
+ // 使用mergeDate合并全局数据和项目数据
510
+ processedItem[type] = this.mergeDate(item[type] || {}, type);
511
+ });
512
+
513
+ return processedItem;
514
+ });
515
+ },
516
+ getItemStyle(item, type) {
517
+ const result = this.mergeDate(item[type] || {}, type);
518
+ console.log(type, this.applyNormalAttribute(result), 'ggg');
519
+ return this.applyNormalAttribute(result);
520
+ },
521
+ mergeDate(target, type) {
522
+ const globalData = this.data.globalData[type] || {};
523
+ return Object.assign({}, globalData, target);
524
+ },
525
+ applyNormalAttribute(data = {}, type) {
526
+ let style = {
527
+ color: data.textColor || '#000000',
528
+ fontWeight: data.fontWeight || 'normal',
529
+ fontFamily: data.fontFamilyPath || 'inherit',
530
+ fontSize: `${(data.textSize || 16) * scla}px`, // 默认16px
531
+ // 布局相关样式
532
+ width:
533
+ typeof data.width === 'number' && data.width > 0
534
+ ? `${data.width * scla}px`
535
+ : data.width === -1
536
+ ? '%'
537
+ : data.width || '',
538
+ height:
539
+ typeof data.height === 'number'
540
+ ? `${data.height * scla}px`
541
+ : data.height || '',
542
+ backgroundColor: data.backgroundColor || 'transparent',
543
+ borderRadius: `${(data.cornerRadius || 0) * scla}px`,
544
+ opacity: data.alpha || 1,
545
+ };
546
+ // 特殊处理visibility和display
547
+ if (typeof data.visibility === 'number') {
548
+ if (data.visibility === 0) {
549
+ // 如果visibility为0,则完全隐藏元素
550
+ style.display = 'none';
551
+ } else {
552
+ // 否则保持原有display属性,并设置visibility
553
+ style.display = data.display || '';
554
+ style.visibility = data.visibility === 1 ? 'visible' : 'hidden';
555
+ }
556
+ } else {
557
+ // 如果未设置visibility,保持原有display
558
+ style.display = data.display || '';
559
+ }
560
+
561
+ style.borderColor = this.data.isEditing
562
+ ? data.selectedBorderColor || ''
563
+ : data.normalBorderColor || '';
564
+ style.borderWidth = this.data.isEditing
565
+ ? `${(data.selectedBorderWidth || 0) * scla}px`
566
+ : `${(data.normalBorderWidth || 0) * scla}px`;
567
+ if (data.padding) {
568
+ style.paddingTop = `${(data.padding[1] || 0) * scla}px`;
569
+ style.paddingLeft = `${(data.padding[0] || 0) * scla}px`;
570
+ style.paddingBottom = `${(data.padding[3] || 0) * scla}px`;
571
+ style.paddingRight = `${(data.padding[2] || 0) * scla}px`;
572
+ }
573
+ if (data.margin) {
574
+ style.marginTop = `${(data.margin[1] || 0) * scla}px`;
575
+ style.marginLeft = `${(data.margin[0] || 0) * scla}px`;
576
+ style.marginBottom = `${(data.margin[3] || 0) * scla}px`;
577
+ style.marginRight = `${(data.margin[2] || 0) * scla}px`;
578
+ }
579
+
580
+ return style;
71
581
  },
72
- updateListItem(item) {
73
- weexModule.callNative(
74
- 'sceneCardListHandle',
75
- {
76
- method: 'updateListItem',
77
- name: this.embedId,
78
- item
79
- },
582
+ // 开始长按
583
+ startLongPress(e, index) {
584
+ if (!this.data.isEditable) {
585
+ return;
586
+ }
587
+ this.$emit('onDragEventStart', e);
588
+ // 清除任何现有计时器
589
+ this.cancelLongPress();
590
+ // 记录起始触摸位置和索引
591
+ const touch = e.changedTouches[0];
592
+ this.touchStartX = touch.pageX;
593
+ this.touchStartY = touch.pageY;
594
+ console.log('开始长按ddddddddd', touch, touch.pageY, touch.pageX);
595
+ this.longPressStartIndex = index;
596
+ this.isScrolling = false; // 重置滚动状态
597
+
598
+ // 注意:不在这里阻止默认行为,允许滚动
599
+ document.removeEventListener('touchmove', this.onTouchMove, {
600
+ passive: true, // 设置为passive:true允许浏览器进行滚动优化
601
+ });
602
+ document.removeEventListener('touchend', this.onTouchEnd, {
603
+ passive: true,
604
+ });
605
+
606
+ if (this.isTouchDragging) {
607
+ this.$emit('drag-end', {
608
+ items: [...this.itemsList],
609
+ });
610
+ }
611
+ console.log(
612
+ `准备长按: index=${index}, 触摸坐标=(${touch.pageX}, ${touch.pageY})`
80
613
  );
614
+
615
+ // 设置新的长按计时器
616
+ this.longPressTimer = setTimeout(() => {
617
+ // 只有在没有滚动的情况下才激活拖拽模式
618
+ if (!this.isScrolling) {
619
+ this.activateDragMode(index);
620
+ }
621
+ }, this.longPressDuration);
622
+ // 不阻止默认行为 - 允许滚动
623
+ },
624
+
625
+ // 检查长按过程中的移动
626
+ checkLongPressMove(e) {
627
+ if (!this.data.isEditable) {
628
+ return;
629
+ }
630
+ const touch = e.changedTouches[0];
631
+ // 如果已经在拖拽模式,交给onTouchMove处理
632
+ if (this.isTouchDragging) {
633
+ this.onTouchMove(e);
634
+ return;
635
+ }
636
+
637
+ const moveX = Math.abs(touch.pageX - this.touchStartX);
638
+ const moveY = Math.abs(touch.pageY - this.touchStartY);
639
+
640
+ // 检测是否在滚动
641
+ if (moveY > this.scrollThreshold * 3) {
642
+ // 增加滚动阈值以更容易识别滚动意图
643
+ this.isScrolling = true;
644
+ this.cancelLongPress();
645
+ return;
646
+ }
647
+
648
+ // 如果移动超过阈值,取消长按
649
+ if (moveX > this.scrollThreshold || moveY > this.scrollThreshold) {
650
+ this.cancelLongPress();
651
+ }
652
+
653
+ // 不阻止默认行为,允许滚动
654
+ },
655
+
656
+ // 激活拖拽模式
657
+ activateDragMode(index) {
658
+ console.log('长按触发,进入编辑模式');
659
+ // 进入编辑模式
660
+ this.data.isEditing = true;
661
+ this.$emit('onEditStateChanged', true);
662
+ // 设置当前拖拽索引
663
+ this.draggingIndex = index;
664
+ this.isTouchDragging = true;
665
+
666
+ // 此时添加阻止默认行为的事件监听,但只影响拖拽状态下的行为
667
+ document.addEventListener('touchmove', this.onTouchMove, {
668
+ passive: false, // 只有在拖拽模式下才阻止默认行为
669
+ });
670
+ document.addEventListener('touchend', this.onTouchEnd, {
671
+ passive: true,
672
+ });
673
+
674
+ // 使用存储的固定宽高
675
+ this.draggedItemRect = {
676
+ width: this.fixedItemWidth,
677
+ height: this.fixedItemHeight,
678
+ };
679
+
680
+ // 获取当前元素位置用于计算偏移
681
+ const element = document.querySelectorAll('.grid-item')[index];
682
+ if (element) {
683
+ const rect = element.getBoundingClientRect();
684
+ // 计算触摸点相对于元素左上角的偏移
685
+ this.touchOffsetX = this.touchStartX - rect.left;
686
+ this.touchOffsetY = this.touchStartY - rect.top;
687
+
688
+ // 设置克隆元素的初始位置
689
+ this.dragCloneX = rect.left;
690
+ this.dragCloneY = rect.top;
691
+
692
+ // 显示拖拽克隆
693
+ this.isDragClone = true;
694
+
695
+ // 立即执行一次定位以确保初始位置准确
696
+ requestAnimationFrame(() => {
697
+ this.dragCloneX = this.touchStartX - this.touchOffsetX;
698
+ this.dragCloneY = this.touchStartY - this.touchOffsetY;
699
+ });
700
+
701
+ console.log('拖拽克隆已创建');
702
+ }
703
+
704
+ this.longPressTimer = null;
705
+ },
706
+
707
+ // 取消长按计时器
708
+ cancelLongPress() {
709
+ if (this.longPressTimer) {
710
+ clearTimeout(this.longPressTimer);
711
+ this.longPressTimer = null;
712
+ }
713
+ this.longPressStartIndex = null;
81
714
  },
82
- onClickRightIcon(event) { //点击右侧icon
83
- this.$emit('onClickRightIcon', event)
715
+
716
+ // 触摸移动处理
717
+ onTouchMove(e) {
718
+ if (!this.isTouchDragging) return;
719
+
720
+ // 只有在进入拖拽模式后才阻止默认滚动行为
721
+ if (this.isTouchDragging) {
722
+ e.preventDefault(); // 阻止页面滚动
723
+ e.stopPropagation();
724
+ }
725
+
726
+ const touch = e.changedTouches[0];
727
+
728
+ // 更新目标位置而非直接设置
729
+ this.targetTouchX = touch.pageX;
730
+ this.targetTouchY = touch.pageY;
731
+
732
+ // 启动平滑动画(如果尚未启动)
733
+ if (!this.animationFrameId) {
734
+ this.animationFrameId = requestAnimationFrame(this.updateDragPosition);
735
+ }
736
+
737
+ console.log('开始移动ddddddd', touch, touch.pageY, touch.pageX);
738
+ const touchX = touch.pageX;
739
+ const touchY = touch.pageY;
740
+
741
+ // 获取所有网格元素
742
+ const gridItems = Array.from(document.querySelectorAll('.grid-item'));
743
+
744
+ let bestTarget = null;
745
+ let maxCenterScore = 0; // 用于记录最佳的中心区域得分
746
+
747
+ // ✅ 优化:精确坐标检测,偏向中心区域的元素
748
+ for (const item of gridItems) {
749
+ const itemIndex = parseInt(item.getAttribute('data-index'), 10);
750
+ if (itemIndex === this.draggingIndex) continue;
751
+
752
+ const rect = item.getBoundingClientRect();
753
+
754
+ // 计算触摸点在元素内的深度,用于判断是否足够深入(放宽到整个元素区域)
755
+ const insetThresholdX = rect.width;
756
+ const insetThresholdY = rect.height;
757
+
758
+ // 判断触摸点是否足够深入元素内部(只要在元素内部即可)
759
+ const isDeepEnough =
760
+ touchX >= rect.left &&
761
+ touchX <= rect.right &&
762
+ touchY >= rect.top &&
763
+ touchY <= rect.bottom;
764
+
765
+ // 只有当触摸点足够深入时才考虑此元素作为交换目标
766
+ if (isDeepEnough) {
767
+ // 计算触摸点到元素中心的距离比例(越接近中心,值越大)
768
+ const centerX = (rect.left + rect.right) / 2;
769
+ const centerY = (rect.top + rect.bottom) / 2;
770
+ const distanceX = Math.abs(touchX - centerX) / (rect.width / 2);
771
+ const distanceY = Math.abs(touchY - centerY) / (rect.height / 2);
772
+
773
+ // 0表示在中心,1表示在边缘
774
+ const distanceFromCenter = Math.max(
775
+ 0,
776
+ Math.min(1, (distanceX + distanceY) / 2)
777
+ );
778
+ const centerScore = 1 - distanceFromCenter;
779
+
780
+ // 只有当分数更高且高于阈值时才更新bestTarget
781
+ // 提高阈值到0.35,确保更稳定的交换判断
782
+ if (centerScore > maxCenterScore && centerScore > 0.35) {
783
+ maxCenterScore = centerScore;
784
+ bestTarget = itemIndex;
785
+ }
786
+ }
787
+ }
788
+
789
+ // 设置悬停高亮,仅当有合格的目标时才高亮
790
+ this.hoverIndex = bestTarget;
791
+
792
+ // 增加交换防抖动处理
793
+ // 如果bestTarget变化但不等于上次的目标,先记录一下
794
+ if (this.lastBestTarget !== bestTarget && bestTarget !== null) {
795
+ if (!this.targetChangeTime) {
796
+ this.targetChangeTime = Date.now();
797
+ this.pendingTarget = bestTarget;
798
+ } else if (Date.now() - this.targetChangeTime > 100) {
799
+ // 只有当目标持续100ms稳定不变时,才认为是有效目标
800
+ this.lastBestTarget = this.pendingTarget;
801
+ this.targetChangeTime = null;
802
+ this.pendingTarget = null;
803
+ }
804
+ } else if (bestTarget === null) {
805
+ // 如果没有目标,清除防抖状态
806
+ this.targetChangeTime = null;
807
+ this.pendingTarget = null;
808
+ }
809
+
810
+ // 使用已稳定的目标进行交换
811
+ bestTarget = this.lastBestTarget;
812
+
813
+ // 控制交换频率,改为250ms,大幅增加稳定性
814
+ const now = Date.now();
815
+ if (now - this.lastSwapTime < 250 || bestTarget === null) return;
816
+
817
+ // 执行交换逻辑
818
+ if (bestTarget !== this.draggingIndex) {
819
+ console.log(`交换元素: ${this.draggingIndex} 和 ${bestTarget}`);
820
+
821
+ // 使用ES6解构直接交换数组元素
822
+ [
823
+ this.processedList[this.draggingIndex],
824
+ this.processedList[bestTarget],
825
+ ] = [
826
+ this.processedList[bestTarget],
827
+ this.processedList[this.draggingIndex],
828
+ ];
829
+
830
+ // 同时更新原始数据,保持同步
831
+ [this.data.list[this.draggingIndex], this.data.list[bestTarget]] = [
832
+ this.data.list[bestTarget],
833
+ this.data.list[this.draggingIndex],
834
+ ];
835
+
836
+ // 更新拖拽索引
837
+ this.draggingIndex = bestTarget;
838
+ this.lastSwapTime = now;
839
+ }
840
+ },
841
+
842
+ // 新增:平滑更新拖拽位置的方法
843
+ updateDragPosition() {
844
+ // 计算当前位置到目标位置的平滑过渡
845
+ const easing = 0.6; // 增加缓动系数,让拖拽更跟手
846
+
847
+ this.dragCloneX +=
848
+ (this.targetTouchX - this.touchOffsetX - this.dragCloneX) * easing;
849
+ this.dragCloneY +=
850
+ (this.targetTouchY - this.touchOffsetY - this.dragCloneY) * easing;
851
+
852
+ // 如果距离目标很近,直接设为目标位置以避免微小抖动
853
+ if (
854
+ Math.abs(this.dragCloneX - (this.targetTouchX - this.touchOffsetX)) <
855
+ 0.5
856
+ )
857
+ this.dragCloneX = this.targetTouchX - this.touchOffsetX;
858
+ if (
859
+ Math.abs(this.dragCloneY - (this.targetTouchY - this.touchOffsetY)) <
860
+ 0.5
861
+ )
862
+ this.dragCloneY = this.targetTouchY - this.touchOffsetY;
863
+
864
+ // 继续下一帧动画
865
+ this.animationFrameId = requestAnimationFrame(this.updateDragPosition);
866
+ },
867
+
868
+ // 元素本身的触摸结束
869
+ endTouch(e) {
870
+ console.log('触摸结束');
871
+ this.$emit('onDragEventEnd', e);
872
+ // 如果只是短按(还在计时),就取消长按
873
+ this.cancelLongPress();
874
+
875
+ // 如果正在拖拽中,确保清理状态
876
+ if (this.isTouchDragging) {
877
+ // 移除全局事件监听
878
+ document.removeEventListener('touchmove', this.onTouchMove, {
879
+ passive: false,
880
+ });
881
+ document.removeEventListener('touchend', this.onTouchEnd, {
882
+ passive: false,
883
+ });
884
+
885
+ // 取消动画帧
886
+ if (this.animationFrameId) {
887
+ cancelAnimationFrame(this.animationFrameId);
888
+ this.animationFrameId = null;
889
+ }
890
+
891
+ this.isTouchDragging = false;
892
+ this.isDragClone = false;
893
+ this.hoverIndex = null;
894
+ this.draggingIndex = null;
895
+ }
896
+ },
897
+
898
+ // 触摸结束处理 - 全局事件
899
+ onTouchEnd(e) {
900
+ console.log('全局触摸结束');
901
+
902
+ // 移除全局事件监听
903
+ document.removeEventListener('touchmove', this.onTouchMove, {
904
+ passive: false,
905
+ });
906
+ document.removeEventListener('touchend', this.onTouchEnd, {
907
+ passive: false,
908
+ });
909
+
910
+ // 取消动画帧
911
+ if (this.animationFrameId) {
912
+ cancelAnimationFrame(this.animationFrameId);
913
+ this.animationFrameId = null;
914
+ }
915
+
916
+ // 重置拖拽状态
917
+ this.isTouchDragging = false;
918
+ this.isDragClone = false;
919
+ this.hoverIndex = null;
920
+
921
+ // 立即清除拖拽索引,避免状态卡住
922
+ this.draggingIndex = null;
923
+ this.isAnimating = false;
924
+ },
925
+ // 获取grid-box宽度的方法
926
+ updateGridBoxWidth() {
927
+ this.$nextTick(() => {
928
+ const gridBoxes = document.querySelectorAll('.grid-item');
929
+ if (gridBoxes && gridBoxes.length > 0) {
930
+ const box = gridBoxes[0];
931
+ this.gridBoxWidth = box.offsetWidth;
932
+ console.log('Grid box宽度:', this.gridBoxWidth);
933
+
934
+ // 更新text样式,确保不超出
935
+ const textElements = document.querySelectorAll('.itemText');
936
+ textElements.forEach((el) => {
937
+ el.style.maxWidth = `${this.gridBoxWidth}px`;
938
+ });
939
+ }
940
+ });
941
+ },
942
+
943
+ initLottieAnimations() {
944
+ this.$nextTick(() => {
945
+ // 清理之前的动画实例
946
+ Object.keys(this.lottieAnimations).forEach((key) => {
947
+ const anim = this.lottieAnimations[key];
948
+ if (anim && typeof anim.destroy === 'function') {
949
+ console.log(`销毁旧动画实例: ${key}`);
950
+ try {
951
+ anim.removeEventListener('complete');
952
+ anim.removeEventListener('DOMLoaded');
953
+ anim.removeEventListener('data_ready');
954
+ anim.removeEventListener('loaded_images');
955
+ anim.removeEventListener('loopComplete');
956
+ } catch (e) {
957
+ console.warn(`移除事件监听器失败: ${e.message}`);
958
+ }
959
+ anim.destroy();
960
+ }
961
+ delete this.lottieAnimations[key];
962
+ });
963
+
964
+ // 重置动画实例对象
965
+ this.lottieAnimations = {};
966
+
967
+ console.log(
968
+ '开始初始化Lottie动画,列表项数量:',
969
+ this.processedList.length
970
+ );
971
+
972
+ // 延迟一点时间确保DOM完全更新并且之前的动画已被销毁
973
+ setTimeout(() => {
974
+ // 为每个列表项初始化动画
975
+ this.processedList.forEach((item) => {
976
+ const itemId = item.itemView.id;
977
+
978
+ console.log('处理项目ID:', itemId);
979
+ console.log('loadingAnimView:', item.loadingAnimView);
980
+ console.log('bgAnimView:', item.bgAnimView);
981
+ console.log('frontAnimView:', item.frontAnimView);
982
+
983
+ // 加载背景动画
984
+ this.loadLottieAnimation(
985
+ item.bgAnimView,
986
+ `bgAnim-${itemId}`,
987
+ `bg-${itemId}`,
988
+ item
989
+ );
990
+
991
+ // 加载加载中动画
992
+ this.loadLottieAnimation(
993
+ item.loadingAnimView,
994
+ `loadingAnim-${itemId}`,
995
+ `loading-${itemId}`,
996
+ item
997
+ );
998
+
999
+ // 加载前景动画
1000
+ this.loadLottieAnimation(
1001
+ item.frontAnimView,
1002
+ `frontAnim-${itemId}`,
1003
+ `front-${itemId}`,
1004
+ item
1005
+ );
1006
+ });
1007
+ }, 50); // 小延迟确保DOM已更新和旧动画已销毁
1008
+ });
84
1009
  },
85
1010
 
86
- onClickActionButton(event) { //点击执行按钮
87
- this.$emit('onClickActionButton', event)
1011
+ // 抽取公共的loadLottieAnimation方法
1012
+ loadLottieAnimation(animView, refName, animKey, item) {
1013
+ if (!animView || !animView.animUrl) {
1014
+ console.log(`[${animKey}] 跳过加载:animUrl不存在`);
1015
+ return;
1016
+ }
1017
+
1018
+ console.log(
1019
+ `[${animKey}] 尝试初始化${refName}, animUrl:`,
1020
+ animView.animUrl,
1021
+ '可见性:',
1022
+ animView.visibility
1023
+ );
1024
+
1025
+ const animEl = this.$refs[refName];
1026
+ if (!animEl || !animEl[0]) {
1027
+ console.warn(`[${animKey}] 未找到${refName}的DOM引用,请检查DOM结构`);
1028
+ return;
1029
+ }
1030
+
1031
+ try {
1032
+ // 如果已经有实例,先销毁它
1033
+ if (this.lottieAnimations[animKey]) {
1034
+ console.log(`[${animKey}] 销毁现有的动画实例`);
1035
+ this.lottieAnimations[animKey].destroy();
1036
+ delete this.lottieAnimations[animKey];
1037
+ }
1038
+
1039
+ // 确保animView.visibility不为0才初始化动画
1040
+ if (
1041
+ typeof animView.visibility !== 'number' ||
1042
+ animView.visibility !== 0
1043
+ ) {
1044
+ // 使用fetch方式获取动画数据
1045
+ console.log(`[${animKey}] 开始获取动画数据:`, animView.animUrl);
1046
+ fetch(animView.animUrl)
1047
+ .then((response) => {
1048
+ if (!response.ok) {
1049
+ console.error(
1050
+ `[${animKey}] 获取动画数据失败,HTTP状态码:`,
1051
+ response.status
1052
+ );
1053
+ throw new Error('Network response was not ok');
1054
+ }
1055
+ console.log(`[${animKey}] 网络请求成功,正在解析JSON`);
1056
+ return response.json();
1057
+ })
1058
+ .then((animationData) => {
1059
+ console.log(
1060
+ `[${animKey}] 动画数据已加载,数据大小:`,
1061
+ JSON.stringify(animationData).length
1062
+ );
1063
+ // 再次检查是否已有实例(可能在fetch期间创建了)
1064
+ if (this.lottieAnimations[animKey]) {
1065
+ console.log(`[${animKey}] 销毁fetch期间创建的动画实例`);
1066
+ this.lottieAnimations[animKey].destroy();
1067
+ delete this.lottieAnimations[animKey];
1068
+ }
1069
+
1070
+ // 创建新的动画实例
1071
+ console.log(`[${animKey}] 创建新的动画实例`);
1072
+ const anim = lottie.loadAnimation({
1073
+ container: animEl[0],
1074
+ renderer: 'svg',
1075
+ loop: Boolean(item.animRepeatCount) || false,
1076
+ autoplay: true,
1077
+ animationData: animationData,
1078
+ rendererSettings: {
1079
+ preserveAspectRatio: 'xMidYMid slice',
1080
+ },
1081
+ });
1082
+
1083
+ // 保存动画实例
1084
+ this.lottieAnimations[animKey] = anim;
1085
+
1086
+ // 添加动画事件监听
1087
+ anim.addEventListener('DOMLoaded', () => {
1088
+ console.log(`[${animKey}] 动画DOM已加载完成`);
1089
+ });
1090
+
1091
+ anim.addEventListener('complete', () => {
1092
+ console.log(`[${animKey}] 动画播放完成一次`);
1093
+ // 检查是否需要销毁动画(非循环动画完成后销毁)
1094
+ if (!item.animRepeatCount) {
1095
+ console.log(`[${animKey}] 非循环动画播放完成,执行销毁`);
1096
+ // 销毁前再次检查实例是否存在
1097
+ if (this.lottieAnimations[animKey] === anim) {
1098
+ anim.removeEventListener('complete');
1099
+ anim.removeEventListener('DOMLoaded');
1100
+ anim.removeEventListener('data_ready');
1101
+ anim.removeEventListener('loaded_images');
1102
+ anim.removeEventListener('loopComplete');
1103
+ anim.destroy();
1104
+ delete this.lottieAnimations[animKey];
1105
+ console.log(`[${animKey}] 动画实例已销毁`);
1106
+
1107
+ // 查找并隐藏对应的动画容器
1108
+ this.$nextTick(() => {
1109
+ const animContainer =
1110
+ this.$refs[refName] && this.$refs[refName][0];
1111
+ if (animContainer) {
1112
+ animContainer.style.display = 'none';
1113
+ }
1114
+
1115
+ // 将对应数据中的animUrl清空
1116
+ const itemId = item.itemView.id;
1117
+ const index = this.processedList.findIndex(
1118
+ (i) => i.itemView.id === itemId
1119
+ );
1120
+ if (index !== -1) {
1121
+ // 根据动画类型设置对应的animUrl为空
1122
+ if (animKey.startsWith('bg-')) {
1123
+ this.$set(
1124
+ this.processedList[index].bgAnimView,
1125
+ 'animUrl',
1126
+ ''
1127
+ );
1128
+ // 同步更新原始数据
1129
+ const dataIndex = this.data.list.findIndex(
1130
+ (i) => i.itemView.id === itemId
1131
+ );
1132
+ if (dataIndex !== -1) {
1133
+ this.$set(
1134
+ this.data.list[dataIndex].bgAnimView,
1135
+ 'animUrl',
1136
+ ''
1137
+ );
1138
+ }
1139
+ } else if (animKey.startsWith('loading-')) {
1140
+ this.$set(
1141
+ this.processedList[index].loadingAnimView,
1142
+ 'animUrl',
1143
+ ''
1144
+ );
1145
+ // 同步更新原始数据
1146
+ const dataIndex = this.data.list.findIndex(
1147
+ (i) => i.itemView.id === itemId
1148
+ );
1149
+ if (dataIndex !== -1) {
1150
+ this.$set(
1151
+ this.data.list[dataIndex].loadingAnimView,
1152
+ 'animUrl',
1153
+ ''
1154
+ );
1155
+ }
1156
+ } else if (animKey.startsWith('front-')) {
1157
+ this.$set(
1158
+ this.processedList[index].frontAnimView,
1159
+ 'animUrl',
1160
+ ''
1161
+ );
1162
+ // 同步更新原始数据
1163
+ const dataIndex = this.data.list.findIndex(
1164
+ (i) => i.itemView.id === itemId
1165
+ );
1166
+ if (dataIndex !== -1) {
1167
+ this.$set(
1168
+ this.data.list[dataIndex].frontAnimView,
1169
+ 'animUrl',
1170
+ ''
1171
+ );
1172
+ }
1173
+ }
1174
+ console.log(`[${animKey}] 已清空动画URL数据`);
1175
+ }
1176
+ });
1177
+ }
1178
+ }
1179
+ });
1180
+
1181
+ console.log(`[${animKey}] ${refName}初始化成功`);
1182
+ })
1183
+ .catch((error) => {
1184
+ console.error(`[${animKey}] 加载${refName}动画数据失败:`, error);
1185
+ });
1186
+ } else {
1187
+ console.log(`[${animKey}] ${refName}可见性为0,跳过初始化`);
1188
+ }
1189
+ } catch (error) {
1190
+ console.error(`[${animKey}] ${refName}初始化失败,详细错误:`, error);
1191
+ }
88
1192
  },
89
1193
 
90
- onEditStateChanged(event) { //切换编辑状态
91
- this.$emit('onEditStateChanged', event)
1194
+ // 添加新方法用于获取和存储固定宽高
1195
+ updateFixedItemSize() {
1196
+ this.$nextTick(() => {
1197
+ const gridItem = document.querySelector('.grid-item');
1198
+ if (gridItem) {
1199
+ const rect = gridItem.getBoundingClientRect();
1200
+ this.fixedItemWidth = rect.width;
1201
+ this.fixedItemHeight = rect.height;
1202
+ console.log(
1203
+ '固定宽高已更新:',
1204
+ this.fixedItemWidth,
1205
+ this.fixedItemHeight
1206
+ );
1207
+ }
1208
+ });
92
1209
  },
93
1210
 
94
- onClickItem(event) { //点击item
95
- this.$emit('onClickItem', event)
1211
+ // 暂未使用,但保留用于将来扩展
1212
+ leftTopIconClick(e) {
1213
+ console.log('leftTopIconClick执行,阻止事件冒泡');
1214
+ e.preventDefault();
1215
+ e.stopPropagation();
1216
+ // 这里可以添加将来的处理逻辑
1217
+ return false;
1218
+ },
1219
+ },
1220
+ watch: {
1221
+ // 监听初始数据变化
1222
+ initialItems: {
1223
+ handler(newItems) {
1224
+ if (!this.isTouchDragging) {
1225
+ this.items = [...newItems];
1226
+ }
1227
+ },
1228
+ deep: true,
96
1229
  },
1230
+ // 监听列表数据变化,自动更新处理后的数据
1231
+ 'data.list': {
1232
+ handler(newList, oldList) {
1233
+ console.log('检测到data.list变化');
1234
+ this.processedList = this.generateDataList();
1235
+ this.$nextTick(() => {
1236
+ // 日志输出当前列表中所有按钮的可见性
1237
+ if (this.processedList && this.processedList.length > 0) {
1238
+ console.log('更新后的按钮可见性状态:');
1239
+ this.processedList.forEach((item, idx) => {
1240
+ if (item.actionButton) {
1241
+ console.log(
1242
+ `项目${idx} (ID:${item.itemView.id}) 按钮可见性: ${item.actionButton.visibility}`
1243
+ );
1244
+ }
1245
+ });
1246
+ }
97
1247
 
98
- onSelectItems(event) { //是编辑状态下,选中的 item的 id 数组 返回的是ids":["21675327"]
99
- this.$emit('onSelectItems', event)
1248
+ this.updateGridBoxWidth();
1249
+ this.updateFixedItemSize();
1250
+ this.initLottieAnimations();
1251
+ });
1252
+ },
1253
+ deep: true,
100
1254
  },
101
- onDragEventStart(event) {
102
- this.$emit('onDragEventStart', event)
1255
+ // 监听所有动画URL变化,确保能及时更新动画
1256
+ processedList: {
1257
+ handler(newList, oldList) {
1258
+ if (!newList || !newList.length || this.isTouchDragging) return;
1259
+
1260
+ // 避免在拖拽过程中重新初始化动画
1261
+ if (this.draggingIndex !== null) return;
1262
+
1263
+ // 检查每个项目的动画URL是否有变化
1264
+ newList.forEach((item, index) => {
1265
+ // 如果是新增的项目,直接更新
1266
+ if (!oldList || !oldList[index]) {
1267
+ this.$nextTick(() => {
1268
+ const itemId = item.itemView.id;
1269
+ this.loadLottieAnimation(
1270
+ item.bgAnimView,
1271
+ `bgAnim-${itemId}`,
1272
+ `bg-${itemId}`,
1273
+ item
1274
+ );
1275
+ this.loadLottieAnimation(
1276
+ item.loadingAnimView,
1277
+ `loadingAnim-${itemId}`,
1278
+ `loading-${itemId}`,
1279
+ item
1280
+ );
1281
+ this.loadLottieAnimation(
1282
+ item.frontAnimView,
1283
+ `frontAnim-${itemId}`,
1284
+ `front-${itemId}`,
1285
+ item
1286
+ );
1287
+ });
1288
+ return;
1289
+ }
1290
+
1291
+ // 检查bgAnimView.animUrl是否变化
1292
+ if (
1293
+ item.bgAnimView &&
1294
+ oldList[index].bgAnimView &&
1295
+ item.bgAnimView.animUrl !== oldList[index].bgAnimView.animUrl
1296
+ ) {
1297
+ console.log(
1298
+ '检测到bgAnimView.animUrl变化:',
1299
+ item.bgAnimView.animUrl
1300
+ );
1301
+ this.$nextTick(() => {
1302
+ const itemId = item.itemView.id;
1303
+ if (this.lottieAnimations[`bg-${itemId}`]) {
1304
+ this.lottieAnimations[`bg-${itemId}`].destroy();
1305
+ delete this.lottieAnimations[`bg-${itemId}`];
1306
+ }
1307
+ this.loadLottieAnimation(
1308
+ item.bgAnimView,
1309
+ `bgAnim-${itemId}`,
1310
+ `bg-${itemId}`,
1311
+ item
1312
+ );
1313
+ });
1314
+ }
1315
+
1316
+ // 检查loadingAnimView.animUrl是否变化
1317
+ if (
1318
+ item.loadingAnimView &&
1319
+ oldList[index].loadingAnimView &&
1320
+ item.loadingAnimView.animUrl !==
1321
+ oldList[index].loadingAnimView.animUrl
1322
+ ) {
1323
+ console.log(
1324
+ '检测到loadingAnimView.animUrl变化:',
1325
+ item.loadingAnimView.animUrl
1326
+ );
1327
+ this.$nextTick(() => {
1328
+ const itemId = item.itemView.id;
1329
+ if (this.lottieAnimations[`loading-${itemId}`]) {
1330
+ this.lottieAnimations[`loading-${itemId}`].destroy();
1331
+ delete this.lottieAnimations[`loading-${itemId}`];
1332
+ }
1333
+ this.loadLottieAnimation(
1334
+ item.loadingAnimView,
1335
+ `loadingAnim-${itemId}`,
1336
+ `loading-${itemId}`,
1337
+ item
1338
+ );
1339
+ });
1340
+ }
1341
+
1342
+ // 检查frontAnimView.animUrl是否变化
1343
+ if (
1344
+ item.frontAnimView &&
1345
+ oldList[index].frontAnimView &&
1346
+ item.frontAnimView.animUrl !== oldList[index].frontAnimView.animUrl
1347
+ ) {
1348
+ console.log(
1349
+ '检测到frontAnimView.animUrl变化:',
1350
+ item.frontAnimView.animUrl
1351
+ );
1352
+ this.$nextTick(() => {
1353
+ const itemId = item.itemView.id;
1354
+ if (this.lottieAnimations[`front-${itemId}`]) {
1355
+ this.lottieAnimations[`front-${itemId}`].destroy();
1356
+ delete this.lottieAnimations[`front-${itemId}`];
1357
+ }
1358
+ this.loadLottieAnimation(
1359
+ item.frontAnimView,
1360
+ `frontAnim-${itemId}`,
1361
+ `front-${itemId}`,
1362
+ item
1363
+ );
1364
+ });
1365
+ }
1366
+ });
1367
+ },
1368
+ deep: true,
103
1369
  },
104
- onDragEventEnd(event) {
105
- this.$emit('onDragEventEnd', event)
1370
+ },
1371
+ mounted() {
1372
+ console.log(this.data, 'djdjdjdjdjdjdj');
1373
+ // 初始获取grid-box宽度
1374
+ this.updateGridBoxWidth();
1375
+ // 获取并存储固定宽高
1376
+ this.updateFixedItemSize();
1377
+
1378
+ // 监听窗口大小变化,更新grid-box宽度
1379
+ window.addEventListener('resize', () => {
1380
+ this.updateGridBoxWidth();
1381
+ this.updateFixedItemSize();
1382
+ });
1383
+
1384
+ // 初始化Lottie动画
1385
+ this.initLottieAnimations();
1386
+ },
1387
+ beforeDestroy() {
1388
+ // 确保清理所有事件监听器
1389
+ this.cancelLongPress();
1390
+ document.removeEventListener('touchmove', this.onTouchMove, {
1391
+ passive: false,
1392
+ });
1393
+ document.removeEventListener('touchend', this.onTouchEnd, {
1394
+ passive: false,
1395
+ });
1396
+ window.removeEventListener('resize', () => {
1397
+ this.updateGridBoxWidth();
1398
+ this.updateFixedItemSize();
1399
+ });
1400
+
1401
+ // 清理动画帧
1402
+ if (this.animationFrameId) {
1403
+ cancelAnimationFrame(this.animationFrameId);
1404
+ this.animationFrameId = null;
106
1405
  }
1406
+
1407
+ // 清理所有Lottie动画实例
1408
+ Object.keys(this.lottieAnimations).forEach((key) => {
1409
+ const anim = this.lottieAnimations[key];
1410
+ if (anim && typeof anim.destroy === 'function') {
1411
+ console.log(`组件销毁时清理动画: ${key}`);
1412
+ try {
1413
+ anim.removeEventListener('complete');
1414
+ anim.removeEventListener('DOMLoaded');
1415
+ anim.removeEventListener('data_ready');
1416
+ anim.removeEventListener('loaded_images');
1417
+ anim.removeEventListener('loopComplete');
1418
+ } catch (e) {
1419
+ console.warn(`移除事件监听器失败: ${e.message}`);
1420
+ }
1421
+ anim.destroy();
1422
+ }
1423
+ delete this.lottieAnimations[key];
1424
+ });
1425
+
1426
+ // 确保动画实例对象被清空
1427
+ this.lottieAnimations = {};
1428
+ },
1429
+ created() {
1430
+ // 初始化处理后的数据列表
1431
+ this.processedList = this.generateDataList();
1432
+
1433
+ // 添加测试拖拽模式的方法
1434
+ window.testDragMode = (index = 0) => {
1435
+ this.testDragMode(index);
1436
+ };
107
1437
  },
108
1438
  };
109
1439
  </script>
1440
+
1441
+ <style scoped>
1442
+ button {
1443
+ border: none;
1444
+ }
1445
+ .disabled-item {
1446
+ opacity: 0.6;
1447
+ pointer-events: none;
1448
+ }
1449
+ .outer-container {
1450
+ width: 100%;
1451
+ position: relative;
1452
+ }
1453
+
1454
+ .grid-items-wrapper {
1455
+ width: 100%;
1456
+ position: relative;
1457
+ }
1458
+
1459
+ .grid-inner-wrapper {
1460
+ display: grid;
1461
+ grid-template-columns: repeat(var(--grid-cols), 1fr);
1462
+ grid-gap: var(--grid-gap);
1463
+ width: calc(100% - var(--grid-margin));
1464
+ }
1465
+
1466
+ /* 优化排序过渡效果 */
1467
+ .grid-fade-move {
1468
+ transition: transform 0.2s cubic-bezier(0.2, 0, 0.2, 1);
1469
+ will-change: transform;
1470
+ }
1471
+
1472
+ .grid-item {
1473
+ position: relative;
1474
+ box-sizing: border-box;
1475
+ transition: all 0.15s cubic-bezier(0.2, 0, 0.2, 1);
1476
+ will-change: transform, opacity, box-shadow;
1477
+ overflow: hidden;
1478
+ }
1479
+ .grid-box {
1480
+ position: relative;
1481
+ display: flex;
1482
+ justify-content: space-evenly;
1483
+ flex-direction: column;
1484
+ width: 100%;
1485
+ box-sizing: border-box;
1486
+ overflow: hidden;
1487
+ height: auto;
1488
+ }
1489
+ .leftTopIcon {
1490
+ position: fixed;
1491
+ left: 0;
1492
+ top: 0;
1493
+ }
1494
+ .actionButton {
1495
+ display: flex;
1496
+ justify-content: center;
1497
+ align-items: center;
1498
+ }
1499
+ /* Lottie容器样式 */
1500
+ .lottie-container {
1501
+ position: absolute;
1502
+ overflow: hidden;
1503
+ width: 100%;
1504
+ height: 100%;
1505
+ left: 0;
1506
+ top: 0;
1507
+ pointer-events: none;
1508
+ }
1509
+
1510
+ .bg-anim,
1511
+ .front-anim {
1512
+ z-index: 1;
1513
+ width: 100%;
1514
+ height: 100%;
1515
+ }
1516
+ .bg-anim,
1517
+ .front-anim {
1518
+ position: absolute;
1519
+ left: 0;
1520
+ top: 0;
1521
+ right: 0;
1522
+ bottom: 0;
1523
+ }
1524
+
1525
+ /* 当进入编辑模式时给所有元素添加淡淡的阴影 */
1526
+ /* .editMode .grid-item:not(.being-dragged):not(.hidden) {
1527
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
1528
+ } */
1529
+
1530
+ /* 悬停目标的样式 */
1531
+ .hover-target {
1532
+ z-index: 5;
1533
+ transform: scale(1.05);
1534
+ transition: all 0.1s ease-out;
1535
+ }
1536
+
1537
+ /* 正在拖拽的元素样式 */
1538
+ .being-dragged {
1539
+ z-index: 10;
1540
+ opacity: 0.3; /* 降低原位置的不透明度 */
1541
+ }
1542
+
1543
+ /* 被拖拽时原位置隐藏 */
1544
+ .grid-item.hidden {
1545
+ opacity: 0 !important;
1546
+ visibility: hidden;
1547
+ }
1548
+
1549
+ /* 拖拽克隆元素 */
1550
+ .drag-clone {
1551
+ position: absolute;
1552
+ z-index: 100;
1553
+ user-select: none;
1554
+ box-sizing: border-box;
1555
+ display: flex;
1556
+ justify-content: space-between;
1557
+ flex-direction: column;
1558
+ pointer-events: none;
1559
+ transition: none;
1560
+ will-change: transform;
1561
+ left: 0;
1562
+ top: 0;
1563
+ }
1564
+ .flexBox {
1565
+ display: flex;
1566
+ justify-content: space-between;
1567
+ width: 100%;
1568
+ position: relative;
1569
+ flex-direction: row;
1570
+ }
1571
+ .itemText {
1572
+ display: block;
1573
+ width: 80%;
1574
+ white-space: nowrap;
1575
+ overflow: hidden;
1576
+ text-overflow: ellipsis;
1577
+ }
1578
+
1579
+ .shaking {
1580
+ animation: shake 0.3s infinite;
1581
+ transform-origin: center center;
1582
+ }
1583
+
1584
+ /* 抖动关键帧 */
1585
+ @keyframes shake {
1586
+ 0% {
1587
+ transform: rotateZ(2deg);
1588
+ }
1589
+ 25% {
1590
+ transform: rotateZ(-2deg);
1591
+ }
1592
+ 50% {
1593
+ transform: rotateZ(2deg);
1594
+ }
1595
+ 75% {
1596
+ transform: rotateZ(-2deg);
1597
+ }
1598
+ 100% {
1599
+ transform: rotateZ(2deg);
1600
+ }
1601
+ }
1602
+
1603
+ .grid-item.shaking {
1604
+ animation: seesaw-rotate 0.6s linear infinite;
1605
+ transform-origin: center center;
1606
+ }
1607
+ </style>