cxc-szcx-uniapp/uni_modules/lime-drag/components/l-drag/l-drag.vue

533 lines
16 KiB
Vue
Raw Normal View History

2025-01-12 10:49:20 +00:00
<template>
<view class="l-drag l-class" :style="[areaStyles]" ref="dragRef" @touchstart="setDisabled">
<movable-area class="l-drag__inner" v-if="isReset" :style="[innerStyles]">
<slot></slot>
<movable-view class="l-drag__ghost" v-if="isDrag && props.ghost" :animation="true" :style="[viewStyles]" direction="all" :x="ghostEl.x" :y="ghostEl.y" key="l-drag-clone">
<slot name="ghost"></slot>
</movable-view>
<movable-view v-if="props.before" class="l-drag__before" disabled :animation="false" :style="[viewStyles]" :x="beforeEl.x" :y="beforeEl.y">
<slot name="before"></slot>
</movable-view>
<movable-view
v-for="(item, oindex) in cloneList" :key="item.id"
direction="all"
:data-oindex="oindex"
:style="[viewStyles]"
class="l-drag__view"
:class="[{'l-is-active': oindex == active, 'l-is-hidden': !item.show}, item.class]"
:x="item.x"
:y="item.y"
:friction="friction"
:damping="damping"
:animation="animation"
:disabled="isDisabled || props.disabled"
@touchstart="touchStart"
@change="touchMove"
@touchend="touchEnd"
@touchcancel="touchEnd"
@longpress="setDisabled"
>
<!-- <view v-if="props.remove" class="l-drag__remove" :style="removeStyle" data-remove="true">
<slot name="remove" :oindex="oindex" data-remove="true" />
</view> -->
<!-- <view v-if="props.handle" class="l-drag__handle" :style="handleStyle" data-handle="true">
<slot name="handle" :oindex="oindex" :active="!isDisabled && !isDisabled && oindex == active" />
</view> -->
<slot name="grid" :oindex="oindex" :index="item.index" :oldindex="item.oldindex" :content="item.content" :active="!isDisabled && !isDisabled && oindex == active" />
<view class="mask" v-if="!(isDisabled || props.disabled) && props.longpress"></view>
</movable-view>
<movable-view v-if="props.after" class="l-drag__after" disabled :animation="true" direction="all" :style="[viewStyles]" :x="afterEl.x" :y="afterEl.y">
<slot name="after"></slot>
</movable-view>
</movable-area>
</view>
</template>
<script lang="ts">
// @ts-nocheck
import { computed, onMounted, ref, getCurrentInstance, watch, nextTick, reactive , triggerRef, onUnmounted, defineComponent} from "./vue";
import DragProps from './props';
import type {GridRect, Grid, Position} from './type'
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
// #endif
export default defineComponent({
name: 'l-drag',
externalClasses: ['l-class'],
options: {
addGlobalClass: true,
virtualHost: true,
},
props: DragProps,
emits: ['change'],
setup(props, {emit, expose}) {
const res = wx.getSystemInfoSync();
const statusHeight = res.statusBarHeight; //状态栏高度
const cusnavbarheight = (statusHeight + 44) + "px";
// #ifdef APP-NVUE
const dragRef = ref(null)
// #endif
const app = getCurrentInstance()
const isDrag = ref(false)
const isInit = ref(false)
const isReset = ref(true)
const colmunId = ref(-1)
/** 选中项原始下标 */
const active = ref(-1)
const maxIndex = ref(-1)
const animation = ref(true)
const isDisabled = ref(props.handle || props.longpress ? true: false)
const dragEl = reactive({
content: null,
/** 当前视图下标*/
index: 0,
/** 旧视图下标 */
oldindex: -1,
/** 上次原始下标 */
lastindex: -1
})
const ghostEl = reactive({
content: null,
x: 0,
y: 0
})
const beforeEl = reactive({
x: 0,
y: 0
})
const afterEl = reactive({
x: 0,
y: 0
})
let gridRects = [] //ref<GridRect[]>([])
const areaWidth = ref(0)
const cloneList = ref<Grid[]>([])
// 删除项时可能会减少行数影响到删除过渡动画,故增加此值在删除时保持高度不变,等动画完成后再归零
const leaveRow = ref(0)
const extra = computed(() => (props.before ? 1 :0) + (props.after ? 1 : 0))
const rows = computed(() => Math.ceil( ((isInit.value ? cloneList.value.length : props.list.length) + extra.value) / props.column ))
const gridHeight = computed(() => props.aspectRatio ? girdWidth.value / props.aspectRatio : (/rpx$/.test(`${props.gridHeight}`) ? uni.upx2px(parseInt(`${props.gridHeight}`)) : parseInt(`${props.gridHeight}`)))
const girdWidth = computed(() => areaWidth.value / props.column)
const viewStyles = computed(() => ({width: girdWidth.value + 'px',height: gridHeight.value + 'px'}))
const areaStyles = computed(() => ({height: (rows.value + leaveRow.value ) * gridHeight.value + 'px'}))
const innerStyles = computed(() => ({
// #ifdef APP-NVUE
width: areaWidth.value + 'px',
// #endif
height: (rows.value + props.extraRow + leaveRow.value) * gridHeight.value + 'px'}))
const sleep = (cb: Function, time = 1000/60) => setTimeout(cb, time)
const createGrid = (content: any, position?:Position|null): Grid => {
colmunId.value++
maxIndex.value++
const index = maxIndex.value
const colmun = gridRects[index]
let x = 0
let y = 0
if(colmun) {
if(props.after) {
let nxet = gridRects[index + 1]
if(!nxet) {
nxet = createGridRect(gridRects.length + (props.before ? 1 : 0))
gridRects.push(nxet)
}
setReset(() => setAfter(nxet))
} else {
setReset()
}
x = colmun.x
y = colmun.y
} else {
const nxet = createGridRect(gridRects.length + (props.before ? 1 : 0))
gridRects.push(nxet)
setReset()
x = nxet.x
y = nxet.y
}
if(position) {
x = position.x
y = position.y
}
return {id: `l-drag-item-${colmunId.value}`, index, oldindex: index, content, x, y, class: '', show: true}
}
const setReset = (cb?: any) => {
// const newRow = (cloneList.value.length + extra.value) % (props.column)
if(isInit.value) {
cb&&sleep(cb)
}
}
const setAfter = ({x, y} = {x: 0, y: 0}) => {
if(props.after) {
afterEl.x = x
afterEl.y = y
}
}
const setDisabled = (e: any, flag?: boolean= false) => {
// e?.preventDefault()
const type = `${e.type}`.toLowerCase()
const {handle = props.touchHandle} = e.target.dataset
if(props.handle && !handle) {
isDisabled.value = true
} else if(props.handle && handle && !props.longpress) {
isDisabled.value = flag
} else if(props.handle && handle && props.longpress && type.includes('longpress')) {
isDisabled.value = false
} else if(props.longpress && type.includes('longpress') && !props.handle) {
isDisabled.value = false
}
if(type.includes('touchend') && props.longpress) {
isDisabled.value = true
}
}
const createGridRect = (i: number, last?: GridRect): GridRect => {
let { row } = last || gridRects[gridRects.length - 1] || { row: 0 }
const col = i % (props.column)
const grid = (row: number, x: number, y: number):GridRect => {
return {row, x, y, x1: x + girdWidth.value, y1: y + gridHeight.value}
}
if(col == 0 && i != 0) {row++}
return grid(row, col * girdWidth.value, row * gridHeight.value)
}
const createGridRects = () => {
let rects: GridRect[] = []
const length = rows.value * props.column + extra.value
gridRects = []
for (var i = 0; i < length; i++) {
const item = createGridRect(i, rects[rects.length - 1])
rects.push(item)
}
if(props.before) {
const {x, y} = rects.shift()
beforeEl.x = x
beforeEl.y = y
}
setAfter(rects[props.list.length])
gridRects = rects as GridRect[]
}
const updateList = (v: any[]) => {
cloneList.value = v.map((content) => createGrid(content))
}
const touchStart = (e: any) => {
if(e.target.dataset.remove) return
// 选中项原始下标
const {oindex} = e.currentTarget?.dataset || e.target?.dataset || {}
if(typeof oindex !== 'number') return
const target = cloneList.value[oindex]
isDrag.value = true
// 选中项原始下标
active.value = oindex
// 选中项的当前下标
dragEl.index = dragEl.oldindex = target.index
ghostEl.x = target.x||0
ghostEl.y = target.y||0
dragEl.content = ghostEl.content = target.content
}
const touchEnd = (e: any) => {
setTimeout(() => {
if(e.target.dataset.remove || active.value==-1) return
setDisabled(e, true)
isDrag.value = false
const isEmit = dragEl.index !== dragEl.oldindex && dragEl.oldindex > -1 // active.value !== dragEl.index
dragEl.lastindex = active.value
dragEl.oldindex = active.value = -1
const last = cloneList.value[dragEl.lastindex]
const position = gridRects[dragEl.index]
nextTick(() => {
last.x = position.x + 0.001
last.y = position.y + 0.001
sleep(() => {
last.x = position.x
last.y = position.y
isEmit && emitting()
})
})
},80)
}
const emitting = () => {
const clone = [...cloneList.value].sort((a, b) => a.index - b.index)//.map(item => ref(item.content))
emit('change', clone)
}
const touchMove = (e: any) => {
if(!isDrag.value) return
// #ifndef APP-NVUE
let {oindex} = e.currentTarget.dataset
// #endif
// #ifdef APP-NVUE
oindex = e.currentTarget.dataset['-oindex']
// #endif
if(oindex != active.value) return
const {x, y} = e.detail
const centerX = x + girdWidth.value / 2
const centerY = y + gridHeight.value / 2
for (let i = 0; i < cloneList.value.length; i++) {
const item = gridRects[i]
if(centerX > item.x && centerX < item.x1 && centerY > item.y && centerY < item.y1) {
ghostEl.x = item.x
ghostEl.y = item.y
if(dragEl.index != i) {
_move(active.value, i)
}
break;
}
}
}
const getDragEl = (oindex: number) => {
if(isDrag.value) {return dragEl}
return cloneList.value[oindex]
}
/**
* 把原始数据中排序为index的项 移动到 toIndex
* @param oindex 原始数据的下标
* @param toIndex 视图中的下标
* @param position 指定坐标
*/
const _move = (oindex: number, toIndex: number, position?: Position|null, emit: boolean = true) => {
const length = cloneList.value.length - 1
if(toIndex > length || toIndex < 0) return
// 获取oIdnex在视图中的项目
const dragEl = getDragEl(oindex)
let speed = 0
let start = dragEl.index
// 比较开始index和终点index设置方向
if(start < toIndex) {speed = 1}
if(start > toIndex) {speed = -1}
if(!speed) return
// 距离
let distance = start - toIndex
// 找到区间所有的项
while(distance) {
distance += speed
// 目标
const target = isDrag.value ? (dragEl.index += speed) : (start += speed)
let targetOindex = cloneList.value.findIndex(item => item.index == target && item.content != dragEl.content)
if (targetOindex == oindex) return
if (targetOindex < 0) {targetOindex = cloneList.value.length - 1}
let targetEl = cloneList.value[targetOindex]
if(!targetEl) return;
// 上一个index
const lastIndex = target - speed
const activeEl = cloneList.value[oindex]
const rect = gridRects[lastIndex]
targetEl.x = rect.x
targetEl.y = rect.y
targetEl.oldindex = targetEl.index
targetEl.index = lastIndex
activeEl.oldindex = activeEl.index //oIndex
activeEl.index = toIndex
// 到达终点,如果是拖拽则不处理
if(!distance && !isDrag.value) {
const rect = gridRects[toIndex]
const {x, y} = position||rect
activeEl.x = dragEl.x = x
activeEl.y = dragEl.y = y
// triggerRef(cloneList)
if(emit) {
emitting()
}
}
}
}
/**
* 为区分是主动调用还是内部方法
*/
const move = (oindex: number, toIndex: number) => {
active.value = -1
isDrag.value = false
_move(oindex, toIndex)
}
// 临时处理 待有空再完善
const REMOVE_TIME = 400
let removeTimer = null
const remove = (oindex: number) => {
active.value = -1
isDrag.value = false
clearTimeout(removeTimer)
const item = cloneList.value[oindex]
if(props.disabled || !item) return
item.show = false
const after = cloneList.value.length - 1
_move(oindex, after, item, false)
setAfter(gridRects[after])
maxIndex.value--
const _remove = (_index = oindex) => {
// 小程序 删除会闪一下 所以先关闭动画再开启
// animation.value = false
const row = Math.ceil((cloneList.value.length - 1 + extra.value) / props.column)
if( row < rows.value) {
leaveRow.value = (rows.value - row)
}
cloneList.value.splice(_index, 1)[0]
emitting()
removeTimer = setTimeout(() => {
leaveRow.value = 0
}, REMOVE_TIME)
}
_remove()
}
const push = (...args: any) => {
if(props.disabled) return
if(Array.isArray(args)) {
Promise.all(args.map(async item => await add(item, true))).then(emitting)
}
}
const add = (content: any, after: boolean) => {
return new Promise((resolve) => {
const item = createGrid(content, after ? null : {x: -100, y:0})
item.class = 'l-drag-enter'
cloneList.value.push(item)
const length = cloneList.value.length - 1
nextTick(() => {
sleep(() => {
item.class = 'l-drag-leave'
_move(length, (after ? length : 0), null, false)
triggerRef(cloneList)
resolve(true)
})
})
})
}
const unshift = (...args: any) => {
if(props.disabled) return
if(Array.isArray(args)) {
Promise.all(args.map(async (item) => await add(item))).then(emitting)
}
}
// 暂时先简单处理,待有空再完善
const shift = () => {
if(!cloneList.value.length) return
remove(cloneList.value.findIndex(item => item.index == 0) || 0)
}
const pop = () => {
const length = cloneList.value.length-1
if(length < 0 ) return
remove(cloneList.value.findIndex(item => item.index == length) || length)
}
// const splice = (start, count, ...context) => {
// // 暂未实现
// }
const clear = () => {
isInit.value = isDrag.value = false
maxIndex.value = colmunId.value = active.value = -1
cloneList.value = []
gridRects = []
}
const init = () => {
clear()
createGridRects()
nextTick(() => {
updateList(props.list)
isInit.value = true
})
}
let count = 0
const getRect = () => {
count++
// #ifndef APP-NVUE
uni.createSelectorQuery().in(app.proxy).select('.l-drag').boundingClientRect((res: UniNamespace.NodeInfo) => {
if(res) {
areaWidth.value = res.width || 0
// 小程序居然无法响应式?
init()
}
}).exec()
// #endif
// #ifdef APP-NVUE
sleep(() => {
nextTick(() => {
dom.getComponentRect(dragRef.value, (res) => {
if(!res.size.width && count < 5) {
getRect()
} else {
areaWidth.value = res.size.width || 0
init()
}
})
})
})
// #endif
}
onMounted(getRect)
onUnmounted(clear)
watch(() => props.list, init)
// #ifdef VUE3
expose({
remove,
// add,
move,
push,
unshift,
shift,
pop
})
// #endif
return {
// #ifdef APP-NVUE
dragRef,
// #endif
cloneList,
areaStyles,
innerStyles,
viewStyles,
setDisabled,
isDisabled,
isReset,
isDrag,
active,
animation,
afterEl,
ghostEl,
beforeEl,
touchStart,
touchMove,
touchEnd,
remove,
// add,
move,
push,
unshift,
// shift,
// pop,
props
// isDelete: props.delete,
// ...toRefs(props)
}
}
})
</script>
<style lang="scss">
.l-drag{
margin-top: v-bind(cusnavbarheight);
}
@import './index';
</style>