@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
|
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
|
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
|
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
|
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
|
100
|
-
: item
|
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
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
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
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
303
|
-
|
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
|
-
|
615
|
+
// 确保数据结构完整性
|
616
|
+
this.ensureItemStructure(data);
|
368
617
|
|
369
|
-
|
618
|
+
// 直接更新数据,不再需要合并
|
619
|
+
this.$set(this.data.list, index, data);
|
370
620
|
this.$nextTick(() => {
|
371
621
|
const itemId = String(data.itemView.id);
|
372
|
-
|
373
|
-
|
374
|
-
|
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
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
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
|
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
|
-
|
820
|
-
this.
|
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
|
-
|
829
|
-
|
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.
|
1031
|
+
this.animationFrameId = requestAnimationFrame(this.optimizedUpdateDragPosition);
|
876
1032
|
}
|
877
1033
|
|
878
|
-
//
|
879
|
-
this.
|
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 <
|
1049
|
+
if (now - this.lastSwapTime < 150) return; // 增加频率控制到150ms
|
887
1050
|
|
888
|
-
//
|
889
|
-
const
|
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.
|
903
|
-
const halfHeight = rect.height * 0.
|
1103
|
+
const halfWidth = rect.width * 0.3; // 缩小判断区域到30%,减少误触
|
1104
|
+
const halfHeight = rect.height * 0.3;
|
904
1105
|
|
905
|
-
//
|
1106
|
+
// 检查有效触摸点是否在元素的中心区域内
|
906
1107
|
if (
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
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
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
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
|
-
},
|
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
|
-
|
1006
|
-
|
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
|
-
|
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
|
-
|
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 (
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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>
|