alipay-emulator/pages/balance/fast-entrance-management/fast-entrance-management.vue

272 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<NavBar class="nav-bar" title="快捷入口" :bgColor="data.navBar.bgColor" isRightButton @right-click="onRightClick">
</NavBar>
<view class="content">
<view class="grid-card">
<view class="grid-list">
<!-- 遍历显示网格项绑定触摸事件以支持拖拽 -->
<view class="grid-item" v-for="(item, index) in data.list" :key="item.id"
:class="{ 'dragging-placeholder': draggingItem && draggingItem.id === item.id }"
@touchstart="handleTouchStart($event, item, index)" @touchmove.stop.prevent="handleTouchMove"
@touchend="handleTouchEnd">
<view class="icon-box">
<image class="icon" :src="`/static/image/balance/menu-icon/${item.label}.png`"
mode="aspectFit"></image>
</view>
<text class="name">{{ item.name }}</text>
</view>
</view>
<view class="footer-tip">*长按可以排序哟</view>
</view>
</view>
<!-- 拖拽时显示的浮动项(跟随手指) -->
<view v-if="draggingItem" class="grid-item floating-item" :style="dragStyle">
<view class="icon-box">
<image class="icon" :src="`/static/image/balance/menu-icon/${draggingItem.label}.png`" mode="aspectFit">
</image>
</view>
<text class="name">{{ draggingItem.name }}</text>
</view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import { fastEntranceList } from '@/static/json/initial.json'
import { onShow } from '@dcloudio/uni-app'
import { storage } from '@/utils/storage'
import {
reactive,
toRefs,
ref,
nextTick
} from 'vue'
const data = reactive({
navBar: {
bgColor: "#F5F5F5"
},
list: []
})
let { list } = toRefs(data)
// 拖拽相关状态
const draggingItem = ref(null) // 当前正在拖拽的项
const dragStyle = ref({ top: '0px', left: '0px' }) // 拖拽项的样式(位置)
let startX = 0 // 触摸开始X坐标
let startY = 0 // 触摸开始Y坐标
let longPressTimer = null // 长按定时器
let itemRects = [] // 所有网格项的位置信息
let containerTop = 0 // 容器顶部偏移
onShow(() => {
// 优先从缓存读取
const cachedList = storage.get('fastEntranceList')
if (cachedList && cachedList.length > 0) {
data.list = cachedList
} else {
// 深拷贝数据防止直接修改原引用的json数据允许页面内重新排序
data.list = JSON.parse(JSON.stringify(fastEntranceList))
}
// 渲染完成后测量位置
nextTick(() => {
measureItems()
})
})
/**
* 测量所有网格项的位置,用于后续碰撞检测
*/
const measureItems = () => {
uni.createSelectorQuery().select('.grid-list').boundingClientRect(res => {
if (res) {
containerTop = res.top
}
}).exec()
uni.createSelectorQuery().selectAll('.grid-item').boundingClientRect(rects => {
itemRects = rects
}).exec()
}
/**
* 触摸开始:启动长按定时器
*/
const handleTouchStart = (e, item, index) => {
if (e.touches.length !== 1) return
startX = e.touches[0].clientX
startY = e.touches[0].clientY
longPressTimer = setTimeout(() => {
// 长按触发拖拽
draggingItem.value = item
// 更新拖拽项位置使其出现在手指中心
updateDragPosition(startX, startY)
// 震动反馈
uni.vibrateShort()
}, 500) // 500ms 长按触发
}
/**
* 触摸移动:更新拖拽位置并检测排序
*/
const handleTouchMove = (e) => {
if (!draggingItem.value) {
// 如果还没触发拖拽,检测手指移动距离,如果移动过多则取消长按定时器(由点击变为滑动)
const moveX = e.touches[0].clientX
const moveY = e.touches[0].clientY
if (Math.abs(moveX - startX) > 10 || Math.abs(moveY - startY) > 10) {
clearTimeout(longPressTimer)
}
return
}
const x = e.touches[0].clientX
const y = e.touches[0].clientY
updateDragPosition(x, y)
checkReorder(x, y)
}
/**
* 触摸结束:重置状态
*/
const handleTouchEnd = () => {
clearTimeout(longPressTimer)
draggingItem.value = null
}
/**
* 更新拖拽浮层的位置
*/
const updateDragPosition = (x, y) => {
// 将项目中心定位到手指位置
// 假设项目宽度大约占屏幕宽度的20% (例如75px) -> 中心偏移约37px
// 仅用于视觉更新
dragStyle.value = {
top: (y - 40) + 'px',
left: (x - 37) + 'px',
position: 'fixed',
zIndex: 999,
opacity: 0.8,
width: itemRects[0] ? itemRects[0].width + 'px' : '20%' // 匹配实际宽度
}
}
/**
* 检测是否需要重新排序
*/
const checkReorder = (x, y) => {
// 查找当前手指位置所在的网格项索引
const targetIndex = itemRects.findIndex(rect =>
x >= rect.left && x <= rect.right &&
y >= rect.top && y <= rect.bottom
)
if (targetIndex !== -1) {
const draggedIndex = data.list.findIndex(item => item.id === draggingItem.value.id)
if (draggedIndex !== -1 && draggedIndex !== targetIndex) {
// 交换逻辑:将原位置删掉,插入新位置
const item = data.list.splice(draggedIndex, 1)[0]
data.list.splice(targetIndex, 0, item)
}
}
}
const onRightClick = () => {
// 保存到缓存
storage.set('fastEntranceList', data.list)
uni.showToast({
title: '保存成功',
icon: 'success',
duration: 500
})
setTimeout(() => {
uni.navigateBack()
}, 500)
}
</script>
<style>
page {
background-color: #F5F5F5;
}
</style>
<style lang="less" scoped>
.container {
min-height: 100vh;
}
.content {
padding: 12px;
}
.grid-card {
background-color: #FFFFFF;
border-radius: 12px;
padding: 20px 0;
padding-bottom: 12px;
.footer-tip {
text-align: right;
font-size: 20rpx;
color: #767676;
padding: 0 16rpx;
}
}
.grid-list {
display: flex;
flex-wrap: wrap;
}
.grid-item {
width: 20%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 24px;
}
.icon-box {
width: 36px;
height: 36px;
border: 1px dashed #cccccc;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.icon {
width: 20px;
/* Adjusted based on visual ratio in screenshot inside the box */
height: 20px;
}
.name {
font-size: 12px;
color: #333333;
}
.dragging-placeholder {
opacity: 0;
}
.floating-item {
pointer-events: none;
/* Let touches pass through to underlying elements for detection */
}
</style>